一,进程学习大致分析

1,进程描述符pid
2,进程的产生  fork()和vfork()
3,进程的消亡和释放资源
4,exec函数族
5,用户权限和组权限
6,观摩课:解释器文件
7,system( )函数
8,进程会记
9,进程时间
10,守护进程
11,系统日志文件

二,进程描述符pid

1,进程描述符的类型

  对于进程描述符,这个进程描述符pid的类型是pid_t,从传统定义上来讲,进程描述符的类型是16位整形,转换过来就是3万个进程,但是对于每一个系统里,这个类型是不固定的,是不一样的,所以并不是所有的进程描述符pid的类型都是16位

  3万个进程够不够用,现在目前来说,由于虚拟技术的发展,那么3万个进程偶尔是不够用的,但是对于目前来说是够用的

  进程描述符是顺次向下使用的,并不是优先使用范围内最小的进程描述符

2,ps指令

  对于学习进程,我们需要掌握终端上的ps指令的使用,因为这个是我们用来查询我们写的程序的进程的用的

参数 英文全称 核心功能
a all with tty 显示所有与终端相关的进程(包括其他用户的进程),不包括无终端的守护进程
x without tty 显示无控制终端的进程(如系统服务、守护进程),与a组合可显示所有进程
m threads 以进程为主线,在进程下方显示其所属线程(轻量级进程),清晰区分进程与线程关系
f forest/tree 树形结构展示进程,通过缩进和特殊符号(如├─、└─)直观呈现父子进程层级关系

1. ps axm

整体作用:显示系统中所有进程及其线程,以进程分组、线程缩进的方式呈现,用于分析线程的

2. ps axf

整体作用:以树形结构显示所有进程的父子关系,清晰展示进程创建的层级链,用于分析进程的

3,ps ax -L

整体作用:执行后会多出两个线程专属列,这是核心:PID:进程 ID(所有线程共享同一个 PID)LWP:线程 ID(每个线程唯一的编号)NLWP:当前进程总共有多少个线程

3,获取进程pid函数


getpid是返回当前进程的pid号,getppid返回的是当前进程的父进程的pid号

这些函数总是会成功,所以这个函数就没有任何的报错信息

三,进程的产生

1,fork函数()


进程的产生是通过这个进程来创建子进程的
对于这个函数的返回值:
  成功:成功将返回父进程中返回子进程的pid号,再子进程中返回值为0
  失败:在父进程中返回-1

这个函数是通过复制当前的进程到创建的进程,所以可以理解为用这个fork创建出来的子进程是与父进程是一样的,父进程与子进程一样

但是还是有以下几点不一样的:
1,未决信号与文件锁不继承
2,资源利用量清0
3,ppid和pid不一样,还有fork的返回值不一样

重要知识:init进程为所有进程的祖先进程

2,实例1:试用fork()函数

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

int main(){
    pid_t pid;

    printf("[%d]:begin\n", getpid());

    pid = fork();
    if(pid < 0){
        perror("fork()");
        exit(1);
    }

    if(pid == 0){
        printf("[%d]:child is working\n", getpid());
    }
    else{
        printf("[%d]:parent is working\n", getpid());
    }

    printf("[%d]:end\n", getpid());
    exit(0);
}


这个执行的过程是这样的,这个是ubuntu来执行之后产生的结果,不难看到这里是先调用了父进程,然后再调用了子进程,这个是根据不同的操作系统拥有不同的调度策略来进行的,但是这个程序还有一个问题,会被我们很容易忽视掉

当我们把这个程序重定向输出到文件里面时

不难看到这个是输出了两次的begin,但是我们上面终端显示的是只输出了一个begin,这是为什么呢?

这是因为fork它是直接复制当前进程给进程的,然后输出到文件里面默认是全缓冲模式,然而终端是行缓冲模式,当我们把这个程序的begin的换行符\n给去掉之后,就会产生这个

也会出现两个begin,这是应为终端是行缓冲模式,所以这个程序我们要怎么修改呢?

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

int main(){
    pid_t pid;

    printf("[%d]:begin\n", getpid());

    fflush(NULL);

    pid = fork();
    if(pid < 0){
        perror("fork()");
        exit(1);
    }

    if(pid == 0){
        printf("[%d]:child is working\n", getpid());
    }
    else{
        printf("[%d]:parent is working\n", getpid());
    }

    printf("[%d]:end\n", getpid());
    exit(0);
}


这里我们加上强制刷新全部的缓冲区,这样不难看到这是只出现了一个begin的,而不是出现了两个begin的

所以我们再使用fork的时候,一定要再前面加上fflush进行刷新,因为在使用fork的时候,这个begin还没有来的及写到文件里面就进行fork了,这样就会导致begin出现两次

3,实例2:把质数找出来

当只有一个进程处理的时候,这个程序的书写实例

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

#define LEFT 30000000
#define RIGHT 30000200

int main(){

    pid_t pid;
    printf("[%d]:begin\n", getpid());

    fflush(NULL);

    for(int i = LEFT; i < RIGHT; i++){
            int mask = 1;
        for(int j = 2; j <= i/2; j++){
            if(i % j == 0) // 能够整除,不是质数
            {
                mask = 0;
                break;
            }
        }
        if(mask == 1){
            printf("%d is primer\n", i);
        }
    }

    printf("[%d]:end!\n", getpid());
    exit(0);
}


我们都知道,fork有个特点就是将当前进程复制到它的子进程里面,这样就形成了一个新的进程,那么我们这个外层循环和内层循环都是复制到了它的子进程里面的,那么就是下面这样了
就像这样子,那么就是200的阶层了,那么这样的话,那不就是成为了要消耗很多很多进程,肯定会爆的,这样的话只好重启了,那么我们就知道我们在子进程执行完属于它所需要的任务的时候,要及时地进行释放


这个是消耗地时间,不难发现其实这个时间是存在问题地
我们当前设置地处理器地数量是2个,处理器地内核数量是2个,那么当我们排完这么多地200个进程,为什么是两百个?因为进程pid是按照顺序进行地,所以这里最多时间除以4,也是在0.1s附近,可是这时间居然减少了很多,这个后面会解释这样地现象,因为这个知识点是在后面

4,init进程(孤儿进程)

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

#define MIN 3000000
#define MAX 3000200

// 求MIN到MAX之间的质数
int main()
{
        pid_t pid;

        printf("[%d]: Begin!\n", getpid());

        fflush(NULL);

        int i,j;
        int primer_flag;


        for(i=MIN; i<=MAX; i++)
        {
                pid = fork();
                if(pid > 0) // 父进程
                {
                        continue;
                }
                else if(pid == 0) // 子进程执行判断操作
                {
                        primer_flag = 1;
                        for(j = 2; j <= i/2; j++)
                        {
                                if(i % j ==0) // 能够整除,不是质数
                                {
                                        primer_flag = 0;
                                        break;
                                }
                        }
                        if(primer_flag == 1)
                        {
                                printf("%d is primer\n", i);
                        }
                        sleep(1000); // 子进程最后退出,父进程先退出
                        exit(0); // 子进程执行结束后,exit退出进程,否则也将执行循环语句 
                }
                

        }

        printf("[%d]: End!\n", getpid());


        // 如果父进程sleep子进程执行完后没有sleep,则子进程先执行完,且由于未被父进程回收称为僵尸进程,僵尸进程将占用进程号
        // 如果父进程没有sleep直接执行完,而子进程sleep,则父进程先执行结束退出,子进程由init进程接管,称为s态
        // 如果父子进程都执行sleep,则通过ps axf查看进程情况,是shell进程创建父进程,父进程创建子进程
        exit(0);
}

当我们在子进程里面加上sleep的话,那么就一定要让父进程先结束,子进程更慢

这里不难看到,这都是顶格写,顶格写就意味着这个进程的父进程是init进程
因为我的子进程是要比父进程执行要慢的,那么当父进程都死了,那么我的子进程就没有了,这个时候就是当前的子进程就变成了孤儿进程,那么init祖先进程看到了自己的子子孙孙变成了孤儿进程,就把这些孤儿收了到自己旗下了,所以就产生了这样的现象

5,僵尸进程

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

#define MIN 3000000
#define MAX 3000200

// 求MIN到MAX之间的质数
int main()
{
        pid_t pid;

        printf("[%d]: Begin!\n", getpid());

        fflush(NULL);

        int i,j;
        int primer_flag;


        for(i=MIN; i<=MAX; i++)
        {
                pid = fork();
                if(pid > 0) // 父进程
                {
                        continue;
                }
                else if(pid == 0) // 子进程执行判断操作
                {
                        primer_flag = 1;
                        for(j = 2; j <= i/2; j++)
                        {
                                if(i % j ==0) // 能够整除,不是质数
                                {
                                        primer_flag = 0;
                                        break;
                                }
                        }
                        if(primer_flag == 1)
                        {
                                printf("%d is primer\n", i);
                        }
                        // sleep(1000); // 子进程最后退出,父进程先退出
                        exit(0); // 子进程执行结束后,exit退出进程,否则也将执行循环语句 
                }
                

        }
        sleep(1000);

        printf("[%d]: End!\n", getpid());


        // 如果父进程sleep子进程执行完后没有sleep,则子进程先执行完,且由于未被父进程回收称为僵尸进程,僵尸进程将占用进程号
        // 如果父进程没有sleep直接执行完,而子进程sleep,则父进程先执行结束退出,子进程由init进程接管,称为s态
        // 如果父子进程都执行sleep,则通过ps axf查看进程情况,是shell进程创建父进程,父进程创建子进程
        exit(0);
}


当我们将sleep放到下面的时候,不难看到这些都成为了僵尸进程,这个僵尸进程是怎么产生的呢?因为当子进程先执行完,没有及时释放这些子进程,这些子进程就会变成僵尸进程

有两种情况就是父进程在等所有的子进程全部弄完一键杀除,还有就是等着init来杀除

6,vfork函数

这个函数就是防止一种情况用的
这个是父进程来指向一个逻辑地址,然后这个逻辑地址指向实际的物理地址,这个是fork的示意图,但是为了防止这样浪费内存,就比如我父进程有2G的数据,然后我的子进程就只需要打印一个hello,用不到这个数据,那么就会导致浪费内存,那么就有了vfork

vfork就是将这个指针指向与父进程相同的区域

但是后面加入一个写实拷贝技术到fork函数里面
就是当还是指向同一个地方,但是这个区域是只读的,不可以写,当有一个要写的时候,就会在别的地方开辟一个地方提供相同的数据进行书写,不会玷污之前的数据,所以vfork就慢慢没有用了

四,进程的消亡和释放资源


 

1、wait() 函数

核心作用

阻塞等待任意一个子进程终止,回收其资源,并获取子进程的退出状态。是最基础的子进程回收函数。

函数原型

pid_t wait(int *wstatus);

参数说明

  • wstatus:指向 int 类型的指针,用于存储子进程的退出状态信息。
    • 传入 NULL:表示不关心子进程的退出状态,仅回收资源。
    • 传入有效指针:可通过宏(如 WIFEXITEDWEXITSTATUS)解析出子进程的退出原因和退出码。

返回值

  • 成功:返回已终止子进程的 PID
  • 失败:返回 -1,并设置 errno(常见原因:没有可等待的子进程,或调用被信号中断)。

特点

  • 会阻塞父进程,直到有子进程终止。
  • 只能等待任意子进程,无法指定特定子进程。
  • 等价于 waitpid(-1, &wstatus, 0)

2、waitpid() 函数

核心作用

wait() 更灵活的子进程等待函数:可以指定等待的子进程、支持非阻塞模式,还能监听子进程暂停 / 继续的状态。

函数原型

pid_t waitpid(pid_t pid, int *wstatus, int options);

参数说明

  1. pid:指定要等待的子进程范围

    • pid > 0:等待 PID 等于 pid 的子进程。
    • pid = -1:等待任意子进程(与 wait() 行为一致)。
    • pid = 0:等待与父进程同进程组的任意子进程。
    • pid < -1:等待进程组 ID 等于 |pid| 的任意子进程。
  2. wstatus:与 wait() 完全相同,用于存储子进程状态。

  3. options:控制等待行为的位掩码(可组合使用)

    • WNOHANG:非阻塞模式 —— 若无子进程状态变化,立即返回 0
    • WUNTRACED:同时监听 ** 被暂停(Stopped)** 的子进程。
    • WCONTINUED:同时监听 ** 被继续(Continued)** 的子进程。

返回值

  • 成功
    • 若设置 WNOHANG 且无状态变化:返回 0
    • 否则:返回状态变化子进程的 PID
  • 失败:返回 -1,并设置 errno(如 pid 无效、调用被信号中断)。

特点

  • 可精准指定等待的子进程 / 进程组。
  • 支持非阻塞,避免父进程无限阻塞。
  • wait()功能超集,实际开发中更常用。

3、waitid() 函数

核心作用

更精细的等待接口:支持按 PID / 进程组 / 会话等类型筛选,且通过 siginfo_t 结构体返回更详细的子进程状态信息(如终止信号、退出码等)。

函数原型

int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

参数说明

  1. idtype:指定 id 的类型

    • P_PIDid 表示子进程 PID。
    • P_PGIDid 表示子进程组 ID。
    • P_ALL:等待所有子进程(id 被忽略)。
  2. id:根据 idtype 传入对应的 PID / 进程组 ID 等。

  3. infop:指向 siginfo_t 结构体的指针,用于存储详细状态信息(如 si_pid 子进程 PID、si_status 退出码 / 信号、si_code 状态原因)。

  4. options:必须至少指定一个状态类型,可组合:

    • WEXITED:等待子进程终止
    • WSTOPPED:等待子进程被暂停
    • WCONTINUED:等待子进程被继续
    • WNOHANG:非阻塞模式(同 waitpid())。
    • WNOWAIT:保留子进程状态,可再次等待(不真正回收)。

返回值

  • 成功
    • 若设置 WNOHANG 且无状态变化:返回 0
    • 否则:返回 0(与 wait()/waitpid() 不同,成功始终返回 0)。
  • 失败:返回 -1,并设置 errno

特点

  • 提供最详细的子进程状态信息。
  • 筛选粒度更细(支持会话、进程组等)。
  • 接口更复杂,多用于需要精细控制的场景(如调试、监控)。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define MIN 3000000
#define MAX 3000200

// 求MIN到MAX之间的质数
int main()
{
        pid_t pid;

        printf("[%d]: Begin!\n", getpid());

        fflush(NULL);

        int i,j;
        int primer_flag;


        for(i=MIN; i<=MAX; i++)
        {
                pid = fork();
                if(pid > 0) // 父进程
                {
                        continue;
                }
                else if(pid == 0) // 子进程执行判断操作
                {
                        primer_flag = 1;
                        for(j = 2; j <= i/2; j++)
                        {
                                if(i % j ==0) // 能够整除,不是质数
                                {
                                        primer_flag = 0;
                                        break;
                                }
                        }
                        if(primer_flag == 1)
                        {
                                printf("%d is primer\n", i);
                        }
                        // sleep(1000); // 子进程最后退出,父进程先退出
                        exit(0); // 子进程执行结束后,exit退出进程,否则也将执行循环语句 
                }
                

        }
        // sleep(1000);
         for(i=MIN; i<=MAX; i++)
        {
           wait(NULL);  // 只wait一次,能回收所有子进程吗 不行,wait是有一个子进程状态发生变化就返回,如果有n个子进程结束,就要调用n次wait   
                        // 如果wait时子进程还未结束,则一直阻塞等子进程结束
        }

        printf("[%d]: End!\n", getpid());

        // 如果父进程sleep子进程执行完后没有sleep,则子进程先执行完,且由于未被父进程回收称为僵尸进程,僵尸进程将占用进程号
        // 如果父进程没有sleep直接执行完,而子进程sleep,则父进程先执行结束退出,子进程由init进程接管,称为s态
        // 如果父子进程都执行sleep,则通过ps axf查看进程情况,是shell进程创建父进程,父进程创建子进程
        exit(0);
}

代码示例

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐