当前位置: 首页 > news >正文

进程相关概念

目录

  • 进程
    • 1> 什么是进程?
    • 2> 进程的组成
    • 3> 进程与程序的区别
  • 父进程与子进程
    • 父进程与子进程关系
      • 1>资源继承
      • 2>独立性
      • 3>写时复制(Copy-on-Write, COW)
  • 僵尸进程
    • 1>什么是僵尸进程?
    • 2> 僵尸进程的特征
    • 3> 僵尸进程的产生原因
    • 4> 僵尸进程的危害
    • 5> 如何避免僵尸进程
    • 6> 僵尸进程与孤儿进程的区别
  • CPU调度策略
  • 进程组
    • 1> 进程组的定义
    • 2> 为什么需要进程组?
    • 3> 进程组的特点
    • 4> 进程组与会话(Session)的关系
    • 5> 进程组的常用操作
      • (1)获取和设置进程组 ID
      • (2)示例代码:创建进程组
  • 守护进程(精灵进程)
    • 如何实现?

进程

1> 什么是进程?

  • 进程(Process) 是计算机操作系统中,正在运行的程序的实例。
  • 它是操作系统资源分配的基本单位,也是程序运行时的一个动态行为。
  • 本质:进程是程序的一次执行活动,包含程序代码、数据、以及为其分配的资源。

2> 进程的组成

一个进程通常由以下几个部分组成:

  1. 程序代码:可执行的指令。
  2. 数据段:程序运行过程中使用的变量和数据。
  3. 进程控制块
进程控制块(PCB-->process contrl block):指的是系统中定义的一个结构体
系统相关的头文件/usr/src/linux-headers-4.10.0-28/include/linux/sched.h
struct task_struct
{
    //当你./程序名运行一个程序的时候,系统里面产生对应名字的进程,并且把该进程在运行时候的状态参数全部存放到一个结构体变量中
    //这个结构体叫做struct task_struct(老外给它取了很好听的名字进程控制块 )
    //存放了跟当前进程有关的一些状态信息(比如:进程运行占用cpu百分比,内存占用情况,打开的文件信息)
    long state //保存进程的运行状态
        -1表示没有运行
        0表示正在运行
        >0表示暂停
        int pid;  //进程的ID号
    long  prority //进程的优先级
    }

3> 进程与程序的区别

💡程序:用编译器编译得到的二进制文件,静态的概念

特性程序进程
本质静态指令集合程序运行的动态实例
是否动态静态动态
是否占用资源不占用资源占用内存、CPU 等资源
存储位置磁盘上内存中

父进程与子进程

父进程与子进程关系

1>资源继承

子进程继承了父进程的大部分资源,包括:

  • 代码段:子进程执行与父进程相同的代码。
  • 数据段:子进程复制父进程的变量值,但变量的后续修改相互独立(写时复制机制,见下)。
  • 文件描述符:子进程继承父进程打开的文件描述符,但文件偏移量等共享。

2>独立性

  • 独立进程:子进程是一个全新的进程,拥有独立的 PID 和资源。
  • 数据独立:子进程修改变量不会影响父进程,反之亦然(写时复制机制)。

3>写时复制(Copy-on-Write, COW)

  • fork 不会立即复制父进程的内存数据,而是通过“写时复制”优化性能。
  • 只有当父或子进程尝试修改内存时,才会真正复制。

僵尸进程

1>什么是僵尸进程?

  • 僵尸进程 是一种已经 终止运行未被父进程回收 的进程。
  • 当一个子进程执行完毕后,它会向父进程发送一个 SIGCHLD 信号,并保留一个 退出状态(exit status)。
  • 父进程需要调用 wait()waitpid() 来读取该退出状态,从而彻底清除子进程的资源。
  • 如果父进程 未调用 waitwaitpid,子进程的进程表项(PCB)不会被清除,导致进程表中出现 僵尸进程

2> 僵尸进程的特征

  • 在进程列表中显示为 (代表已经终止,但未被回收)。
  • 使用命令 ps aux | grep Ztop 可以看到状态为 Z(Zombie)的进程。
  • 僵尸进程 不占用内存和 CPU,但会占用 进程表项(系统资源有限,如果大量僵尸进程存在,会导致系统无法创建新进程)。

3> 僵尸进程的产生原因

  1. 子进程退出,但父进程未回收
    • 父进程没有调用 wait()waitpid() 来获取子进程的退出状态。
  2. 父进程正在忙碌或被阻塞
    • 父进程未及时处理子进程的退出信号(SIGCHLD)。
  3. 父进程设计不合理
    • 父进程根本没有处理子进程的退出状态,导致僵尸进程一直存在。

4> 僵尸进程的危害

  1. 占用进程表项
    • 每个僵尸进程都会占用一个 进程表项,如果大量僵尸进程存在,可能导致系统无法再创建新进程。
  2. 系统资源浪费
    • 虽然僵尸进程不占用 CPU 和内存,但它们占用了有限的 PID 号内核进程表项
  3. 系统稳定性降低
    • 如果父进程未正确处理僵尸进程,可能导致系统资源耗尽。

5> 如何避免僵尸进程

  1. 使用 wait()waitpid() 回收子进程
    • 父进程在子进程结束后调用 wait()waitpid() 来获取子进程的退出状态。

示例代码:正确回收子进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    pid_t pid = fork();

    if (pid == 0) {
        // 子进程
        printf("子进程: PID = %d\n", getpid());
        sleep(2); // 模拟子进程工作
        exit(0); // 子进程正常退出
    } else if (pid > 0) {
        // 父进程回收子进程
        int status;
        wait(&status);
        printf("父进程: 子进程已回收, 状态 = %d\n", WEXITSTATUS(status));
    }

    return 0;
}

6> 僵尸进程与孤儿进程的区别

区别点僵尸进程孤儿进程
定义子进程已结束,父进程未回收父进程已结束,子进程还在运行
状态Z
(Zombie)状态
init
进程接管
资源占用占用进程表项正常资源分配
解决方法父进程调用 wait()
回收
init
进程自动管理

CPU调度策略

在多任务操作系统中(如 Linux),CPU 的调度由 操作系统内核的调度器 决定。fork() 创建的父子进程由调度器交替运行,具体表现可能是父子进程交替打印,或父进程先运行一段时间再切换到子进程,或相反。

那出现父子进程交替运行的原因是什么?

  1. 时间片用完切换
    • 在时间片轮转(Round Robin)或完全公平调度器(CFS)中,每个进程在自己的时间片用完后会被抢占,调度器切换到另一个进程运行。
  2. I/O 或 sleep 调用的切换
    • 当一个进程执行 sleep() 或进行 I/O 操作时,会主动放弃 CPU,调度器会选择另一个进程运行。
  3. 新创建的进程调度优先
    • 在 CFS 中,新创建的子进程有较低的虚拟运行时间(vruntime),通常会优先被调度器选中运行。

实际情况中的调度不确定性

  1. 操作系统的负载
    • 如果系统中有其他进程在运行,父子进程可能会竞争 CPU 时间。
  2. 硬件与核心数
    • 多核 CPU 上,父子进程可能会被分配到不同的核心并真正并行运行。
  3. 随机性
    • 调度器的实现存在一些随机因素,导致进程切换的具体顺序无法预测。

进程组

1> 进程组的定义

  • 每个进程都属于一个进程组
  • 一个进程组有一个唯一的 进程组 ID(PGID),PGID 通常等于进程组的组长进程的 PID
  • 一个进程组可以包含多个进程,而这些进程可能是由同一个父进程派生的,也可能由不同的父进程派生。


2> 为什么需要进程组?

进程组的作用是方便操作系统对一组相关进程进行统一管理。主要用途包括:

  • 信号分发:可以将信号发送给整个进程组,例如使用 kill(-PGID, signal),可以同时终止整个进程组中的所有进程。
  • 终端管理:操作系统通过进程组来管理终端中的前台和后台任务。
  • 作业控制:Shell(如 Bash)利用进程组区分和管理前台任务和后台任务。

3> 进程组的特点

  1. 组长进程(Leader Process)
    • 进程组的第一个进程是组长,其 PID 等于 PGID。
    • 当组长进程退出时,进程组仍然存在,只要组内有其他进程。
  2. 进程组 ID(PGID)
    • PGID 标识一个进程组,组内所有进程的 PGID 相同。
    • PGID 的值等于组长进程的 PID。
  3. 组内的进程可以独立运行
    • 即使同属于一个进程组,组内的进程可以独立地被调度和执行。

4> 进程组与会话(Session)的关系

  • 会话(Session)是更大的概念,一个会话可以包含多个进程组。
  • 一个会话通常由一个 Shell 或终端创建,前台任务和后台任务都属于同一个会话,但分别属于不同的进程组。

5> 进程组的常用操作

(1)获取和设置进程组 ID

  1. 获取当前进程的进程组 ID:
#include <unistd.h>
pid_t getpgid(pid_t pid); // 返回指定进程 pid 的进程组 ID
pid_t getpgrp();          // 返回当前进程的进程组 ID
  1. 设置进程组 ID:
#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid); // 将指定进程 pid 加入指定进程组 pgid
  • pid == 0,表示操作当前进程。
  • pgid == 0,表示将进程加入自身所在的进程组。

(2)示例代码:创建进程组

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

void handler(int sig) {
    printf("进程 %d 收到信号 %d\n", getpid(), sig);
}

int main() {
    pid_t pid1, pid2;

    // 创建子进程1
    if ((pid1 = fork()) == 0) {
        signal(SIGUSR1, handler); // 注册信号处理器
        while (1) pause(); // 等待信号
    }

    // 创建子进程2
    if ((pid2 = fork()) == 0) {
        signal(SIGUSR1, handler); // 注册信号处理器
        while (1) pause(); // 等待信号
    }

    // 父进程
    printf("父进程 ID: %d\n", getpid());
    printf("子进程1 ID: %d\n", pid1);
    printf("子进程2 ID: %d\n", pid2);

    // 将子进程加入同一进程组
    setpgid(pid1, pid1); // 子进程1成为组长
    setpgid(pid2, pid1); // 子进程2加入子进程1的组

    // 给整个进程组发送信号
    kill(-pid1, SIGUSR1);

    sleep(2);
    kill(-pid1, SIGKILL); // 杀死整个进程组

    return 0;
}
父进程 ID: 12345
子进程1 ID: 12346
子进程2 ID: 12347
进程 12346 收到信号 10
进程 12347 收到信号 10

守护进程(精灵进程)

守护进程(Daemon)是一种在后台运行的特殊进程,它通常没有终端控制,并在系统启动后一直运行,为其他进程或服务提供支持。典型的守护进程包括 httpd(Web 服务器)、sshd(SSH 服务)等。

特点:

  1. 后台运行:不依赖用户会话或终端,通常在系统引导时启动。
  2. 独立性:与用户登录状态无关,用户注销后继续运行。
  3. 长期运行:通常是长时间运行的服务进程。
  4. 功能单一:专注于提供某种功能(例如监听端口、处理请求等)。

如何实现?

1.创建子进程并终止父进程

  • 目的:脱离当前父进程,防止守护进程意外受父进程影响。

2.创建新的会话

  • 目的:脱离控制终端,成为会话的首进程,避免受到终端信号(如挂起、退出等)的干扰。

3. 修改工作目录

  • 目的:改变当前工作目录,防止占用原工作目录(可能会阻止挂载点的卸载)。

4. 重设文件权限掩码

  • 目的:重设文件权限掩码为 0,确保守护进程创建的文件具有预期的权限。

5. 关闭文件描述符

  • 目的:关闭从父进程继承的文件描述符,避免干扰。

6.重定向标准输入/输出/错误

  • 目的:将标准输入/输出/错误重定向到 /dev/null,防止守护进程访问终端。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/stat.h>
#include <syslog.h>

int daemon_init(void)
{
	int max_fd;
	int i;
	pid_t pid;
	
	/*1,先忽略终端的挂断信号对这个程序的影响*/
	signal(SIGHUP, SIG_IGN);

	/*2,新建一个子进程,让父进程退出,这个时候子进程就不会接收到控制终端的信号跟内容*/
	pid = fork();
	if(pid > 0)
		exit(0);
	
	/*3,新建一个会话,脱离开原本的会话(也就是这个控制终端)*/
	setsid();

	/*下面的动作都是为了让我们的程序更加的纯净*/

	/*4,再让这个子进程脱离开原本的进程组*/
	setpgrp();
	
	/*5,新建多一个子进程,脱离开会话管理的权限*/
	pid = fork();
	if(pid > 0)
		exit(0);

	/*6,关闭掉原本的所有文件描述符*/
	max_fd = sysconf(_SC_OPEN_MAX);
	for(i=0; i<max_fd; i++)
		close(i);

	/*7,改变工作路径到根目录*/
	chdir("/");

	/*8,改变原有的掩码,成为没有任何权限影响的0*/
	umask(0);
	
	return 0;
}

int main(void)
{
	daemon_init();//调用完这个函数,你的程序便是精灵进程
	int i=0;

	openlog("靓仔", LOG_CONS|LOG_PID, LOG_DAEMON);
	
	while(1)
	{
		sleep(1);
		
		
		syslog(LOG_INFO, "%d号靓仔说话啦\n", i++);
	}
	
	closelog();
		
	return 0;
}

相关文章:

  • 3. HTTP协议
  • 局域网数据同步软件,局域网数据备份的方法
  • Linux驱动开发-①I2C驱动②spi驱动③uart驱动
  • UE中不同摄像机震动的区别Camera Shake
  • 分布式系统
  • P9246 [蓝桥杯 2023 省 B] 砍树-题解(最近公共祖先LCA + 树上差分)
  • Matlab人脸识别考勤系统【PCA(主成分分析)+ SVM(支持向量机)】
  • 知识表示方法之四:语义网络表示法(Semantic Network Representation)
  • 脑疾病分类的疑惑【6】:脑疾病分类比较适合使用具有哪些特点的模型?
  • OpenIPC开源FPV之Adaptive-Link关键RF参数
  • python下载m3u8格式视频
  • 【前端】【React】第三章:深入理解 React 事件处理与性能优化
  • MySQL日期时间函数
  • Redis 源码硬核解析系列专题 - 第五篇:事件驱动模型与网络层
  • AutoCAD Map 3D:CAD与GIS集成工具
  • Lesson 7 Too late
  • ISIS-4 LSP计算
  • 1.3 斐波那契数列模型:LeetCode 746. 使用最小花费爬楼梯
  • LangChian除了load_qa_chain还有什么chain
  • HTTP和HTTPS区别