[ linux-系统 ] 进程控制

发布于:2025-06-10 ⋅ 阅读:(25) ⋅ 点赞:(0)

进程创建fork

fork 之后发生了什么

  1. 将给子进程分配新的内存块和内核数据结构(形成了新的页表映射)
  2. 将父进程部分数据结构内容拷贝至子进程
  3. 添加子进程到系统进程列表当中
  4. fork 返回,开始调度器调度

 父进程创建子进程,代码是共享的,数据不修改时也是共享的,子进程数据修改时发生写时拷贝

写时拷贝

父进程创建子进程之前,先把数据权限改为只读的,子进程将数据修改时,触发系统错误,触发缺页中断,系统检测,系统检测如果代码段发生错误,就杀掉进程,如果是数据段发生错误,系统就判定要发生写时拷贝,系统申请内存,发生拷贝,修改页表,恢复执行,恢复权限为只读

进程终止

错误码

为什么要使用return ? 

return返回的数字表示是否错误 : 0表示成功,非0表示错误

进程中,父进程会关心我的运行情况

main函数的返回值本质:表示进程运行完成时,是否是正确的结果,如果不是,我们可以用不同的数字表示出错的原因

系统给我们提供了一批错误码

C 语言当中有个的 string.h 中有一个 strerror 接口 以及 errno.h 中有一个errno接口

写linux中以下代码可以将所有错误码信息打印出来,一共有133条错误码 

总结:

main函数结束表示进程退出,main函数的返回值表示进程的退出码

进程的退出码可以由系统默认提供,也可以自定义约定

进程终止的方式

1.main函数return  (只有main函数return表示进程结束,其他函数结束表示当前函数返回)

2.exit (在代码的任何地方,表示进程结束)

3._exit

exit和_exit

exit会刷新缓冲区,_exit不会刷新缓冲区

缓冲区 -- 叫做语言级缓冲区 -- 在C/C++内部,不在操作系统中

exit在man 3 手册,属于库函数 , _exit在man 2 手册,属于系统调用

exit和_exit属于上下层关系,exit就是调用的_exit并封装

进程等待

父进程创建子进程,看以下代码以及运行结果和监视窗口

父进程创建子进程后,如果不管他,子进程运行结束就会变成僵尸进程

所以,父进程创建了子进程,父进程就要等待子进程,直到子进程结束。等待的时候,子进程如果不退,父进程就要阻塞在wait函数内部,wait函数等待任意一个子进程

使用一下wait函数,发现子进程结束后,直接被父进程回收了,监视窗口中也看不到子进程变成僵尸进程了,直接被回收 

waitpid

pid > 0 指定一个子进程  ,  pid == -1 任意子进程

*status 帮助父进程获取子进程的退出信息 (输出型参数)

status不能简单的当作整形来看待,可以当作位图来看待,只研究status低16位比特位

根据以上代码,父进程可以拿到子进程的退出信息

可不可以使用全局变量来获得子进程的退出码?不可以,只能通过系统调用来获取退出信息

重谈进程退出:

1.代码跑完,结果对,return0

2.代码跑完,结果不对,return非0

3.进程异常  OS提前使用信号终止进程

status最低7位提取子进程的退出信号

 

阻塞/非阻塞等待

options决定是否阻塞,0 :阻塞等待  WNOHANG :非阻塞等待

写以下代码及运行结果

  1 #include<iostream>
  2 #include<unistd.h>
  3 #include<cstdlib>
  4 #include<string>
  5 #include<string.h>
  6 #include<errno.h>
  7 #include<vector>
  8 #include<sys/types.h>
  9 #include<sys/wait.h>
 10 #include<functional>
 11 #include"task.h"
 12 
 13 using task_t = std::function<void()>;
 14 
 15 
 16 // 非阻塞等待
 17 
 18 void LoadTask(std::vector<task_t> &tasks)
 19 {
 20     tasks.push_back(PrintLog);
 21     tasks.push_back(Download);
 22     tasks.push_back(BackUp);
 23 }
 24 int main()
 25 {
 26     std::vector<task_t> tasks;
 27     LoadTask(tasks);
 28 
 29     pid_t id = fork();
 30     if(id == 0)  // child
 31     {
 32         while(1)
 33         {
 34             printf("我是子进程,pid: %d\n",getpid());
 35             sleep(1);
 36         }
 37         exit(0);
 38     }
 39 
 40     // father
 41     while(1)
 42     {                                                                                                                                                                                                                                                                                                                                                                                 
 43         pid_t rid = waitpid(id,nullptr,WNOHANG);
 44         if(rid > 0)
 45         {
 46             printf("等待子进程%d成功\n",rid);
 47             sleep(1);
 48             break;
 49         }
 50         else if(rid < 0)
 51         {
 52             printf("等待子进程失败\n");
 53             sleep(1);
 54             break;
 55         }
 56         else
 57         {
 58             printf("子进程尚未退出\n");
 59             sleep(1);
 60 
 61             //阻塞期间父进程做自己的事情
 62             for(auto &task : tasks)
 63             {
 64                 task();
 65             }
 66         }
 67     }
 68     return 0;
 69 }

 

可以看到父进程不等待子进程,子进程在完成任务时,父进程也可以做自己的事情

进程程序替换

程序替换是通过特定的接⼝,加载磁盘上的⼀个全新的程序(代码和数据),加载到调⽤进程的地址空间中,看以下代码,我们的myexec程序就执行了ls命令

execl不仅可以替换系统命令,也可以替换我们的可执行程序

看以下代码:验证了程序替换没有创建新进程

 

返回值:成功时没有返回值,失败返回-1

只要返回,就是失败

 可以理解为execl将可执行程序加载到内存

认识全部接口 

execv的使用方法即运行结果 

execlp使用可以不带路径,为什么可以不带路径?

因为execlp会从环境变量PATH中找到路径(有重复的路径就找到第一个)

 

 execvp 

 

对于环境变量:子进程继承父进程的所有环境变量,如果要传递新的环境变量(自己定义,自己传递)    execvpe可以自己导入新的环境变量

 程序替换不影响命令行参数和环境变量

这六个不是系统调用接口,而是C标准库封装的接口。这六个的区别只有传参方式的差别

 真正意义上的系统调用接口是execve