1. 进程创建

1-1fork函数初识
在 linux 中 fork 函数是⾮常重要的函数,它从已存在进程中创建⼀个新进程。新进程为⼦进程,
⽽原进程为⽗进程
进程调⽤ fork ,当控制转移到内核中的 fork 代码后,内核做:
• 分配新的内存块和内核数据结构给⼦进程
• 将⽗进程部分数据结构内容拷⻉⾄⼦进程
• 添加⼦进程到系统进程列表当中
• fork 返回,开始调度器调度

1-2fork函数返回值
• ⼦进程返回0
• ⽗进程返回的是⼦进程的pid。

1-3写时拷⻉
通常,⽗⼦代码共享,⽗⼦再不写⼊时,数据也是共享的,当任意⼀⽅试图写⼊,便以写时拷⻉的⽅
式各⾃⼀份副本。
这是只有父进程时其数据段的页表权限是可读写的
在这里插入图片描述
当创建子进程后
数据段的页表权限改为只读的,如果不这么设置会影响进程的独立性
这时子进程尝试写入,操作系统就会"报错"
OS要对当前情况进行判断分类:

  1. 真的是野指针?真的错误,终止进程
  2. a. 写时拷贝 b. 更改权限

问题1:为什么创建子进程的之后,直接把数据分开呢?直接拷贝不就完了,为什么要写时拷贝?
本质是"按需获取"

问题2:为什么要拷贝?直接开辟对应的空间不就好了!
子进程可能要以父进程中的数据为基础
在这里插入图片描述
当子进程需要修改数据时,操作系统进行写实拷贝,给子进程分配新的物理内存后,权限改回可读写
在这里插入图片描述
总结
因为有写时拷⻉技术的存在,所以⽗⼦进程得以彻底分离离!完成了进程独⽴性的技术保证!?
写时拷⻉,是⼀种延时申请技术,可以提⾼整机内存的使⽤率

1-4fork常规⽤法
• ⼀个⽗进程希望复制⾃⼰,使⽗⼦进程同时执⾏不同的代码段。例如,⽗进程等待客⼾端请求,
⽣成⼦进程来处理请求。
• ⼀个进程要执⾏⼀个不同的程序。例如⼦进程从fork返回后,调⽤exec函数。

1-5fork调⽤失败的原因
• 系统中有太多的进程
• 实际⽤⼾的进程数超过了限制

2. 进程终⽌

进程终⽌的本质是释放系统资源,就是释放进程申请的相关内核数据结构和对应的数据和代码。
1.引入

#include<stdio.h>
 int main()
{
	int a = 1;
	int b = 2;
	int c = a + b;
	return 0;
}

return 0 会被父进程也就是系统拿到退出码,‘0’表示进程运行成功。
1,2,3……用来表示不同的错误

2-1进程退出场景
• 代码运⾏完毕,结果正确
• 代码运⾏完毕,结果不正确
• 代码异常终⽌

2-2进程常⻅退出⽅法
正常终⽌(可以通过 echo $ 查看上一个进程退出码):

  1. 从main返回
  2. 调⽤exit
#include <unistd.h>
void exit(int status);

exit最后也会调⽤_exit(只有系统调用才能访问内核使进程真正关闭
, 但在调⽤_exit之前,还做了其他⼯作

  1. 执⾏⽤⼾通过atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写⼊
  3. 调⽤_exit
  4. _exit
#include <unistd.h>
void _exit(int status);
参数:status 定义了进程的终⽌状态,⽗进程通过wait来获取该值

问题一:return返回 和 exit返回 有什么区别
return表示函数调用结束,main函数return 表示进程退出
exit()在你的代码任意地方调用都会导致进程退出

问题二:exit 和_exit 的区别
exit是c库函数终止进程会主动刷新缓存区
_exit 系统调用,不会主动刷新缓存区

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

int main() {
    printf("Hello, World!");  // 注意:没有换行符\n,数据会留在用户缓冲区
    
    // 情况1:使用exit()
    // exit(0);  // 会刷新缓冲区,输出"Hello, World!"
    
    // 情况2:使用_exit()
    _exit(0);  // 不会刷新缓冲区,无输出
}

3. 进程等待

3-1进程等待必要性
• 之前讲过,⼦进程退出,⽗进程如果不管不顾,就可能造成‘僵⼫进程’的问题,进⽽造成内存
泄漏。
• 另外,进程⼀旦变成僵⼫状态,那就⼑枪不⼊,“杀⼈不眨眼”的kill-9也⽆能为⼒,因为谁也
没有办法杀死⼀个已经死去的进程。
• 最后,⽗进程派给⼦进程的任务完成的如何,我们需要知道。如,⼦进程运⾏完成,结果对还是
不对,或者是否正常退出。
• ⽗进程通过进程等待的⽅式,回收⼦进程资源,获取⼦进程退出信息

3-2进程等待的⽅法
3-2-1wait⽅法

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取⼦进程退出状态,不关⼼则可以设置成为NULL
 // 等待子进程结束,并获取其退出状态
        pid_t waited_pid = wait(&status);

3-2-2waitpid⽅法

pid_ t waitpid(pid_t pid, int *status, int options);

返回值:
当正常返回的时候waitpid返回收集到的⼦进程的进程ID;
如果设置了选项WNOHANG,⽽调⽤中waitpid发现没有已退出的⼦进程可收集,则返回0;
如果调⽤中出错,则返回-1,这时errno会被设置成相应的值以指⽰错误所在;
参数:
1.pid:
Pid=-1,等待任⼀个⼦进程。与wait等效。
Pid>0.等待其进程ID与pid相等的⼦进程。
2.status: 输出型参数
WIFEXITED(status): 若为正常终⽌⼦进程返回的状态,则为真。(查看进程
是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED⾮零,提取⼦进程退出码。(查看进程
的退出码)
3.options:默认为0,表⽰阻塞等待
WNOHANG: 若pid指定的⼦进程没有结束,则waitpid()函数返回0,不予以等
待。若正常结束,则返回该⼦进程的ID。

在这里插入图片描述
8-15,8个比特位,所以一般而言进程的退出码范围0-255
0-6,属于信号编号,也就是导致进程异常的返回标号
• 代码运⾏完毕,结果正确 返回0, 0
• 代码运⾏完毕,结果不正确 返回0,!0
• 代码异常终⽌ 返回!0
在这里插入图片描述
如何处理waitpid的返回值

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

int main() {
    pid_t pid, wait_ret;
    int status;
    
    // 创建子进程
    pid = fork();
    
    if (pid < 0) {
        perror("fork failed");
        exit(1);
    } else if (pid == 0) {
        // 子进程
        printf("子进程 (PID: %d) 运行中...\n", getpid());
        sleep(2);
        printf("子进程退出\n");
        exit(42);
    } else {
        // 父进程
        printf("父进程等待子进程 (PID: %d) 结束...\n", pid);
        
        // 处理 waitpid 的返回值
        wait_ret = waitpid(pid, &status, 0);
        
        if (wait_ret == -1) {
            // 出错
            perror("waitpid failed");
            exit(1);
        } else if (wait_ret == 0) {
            // 使用 WNOHANG 选项时可能返回 0
            printf("没有子进程退出\n");
        } else {
            // 成功等待到子进程
            printf("成功等待到子进程 (PID: %d)\n", wait_ret);
            
            // 解析退出状态
            if (WIFEXITED(status)) {
                printf("子进程正常退出,退出码: %d\n", WEXITSTATUS(status));//(status>>8)&0xff
            } else if (WIFSIGNALED(status)) {
                printf("子进程被信号终止,信号编号: %d\n", WTERMSIG(status));//status&0x7f
            }
        }
    }
    
    return 0;
}

在这里插入图片描述
我们用两个数字来描述子进程的执行情况
1.进程退出信号 终止原因
2.进程退出码 程序执行结果

在这里插入图片描述

3-2.阻塞与非阻塞等待
在这里插入图片描述
waitpid中options:默认为0,表⽰阻塞等待**
WNOHANG: 若pid指定的⼦进程没有结束,则waitpid()函数返回0,不予以等
待。若正常结束,则返回该⼦进程的ID。
在这里插入图片描述

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐