Linux-进程控制

进程创建

操作系统允许一个进程创建另一个进程,并且允许子进程继承父进程所拥有的资源,当子进程被终止时,其在父进程处继承的资源应当还给父进程。同时,终止父进程同时也会终止其所有子进程。
注意:Linux操作系统对于终止有子进程的父进程,会把子进程交给1号进程接管。

进程创建:1、命令行启动命令(程序、指令等) 2、通过程序自身,fork出子进程

创建进程的过程:

  1. 操作系统为新进程分配一个唯一的进程标识号,并申请一个空白的PCB,PCB是有限的,若申请失败则创建失败。
  2. 为进程分配资源,此处如果资源不足,进程就会进入等待状态,以等待资源。
  3. 初始化PCB
  4. 如果进程的调度队列能够接纳新进程,那就将进程插入到就绪队列,等待被调度运行。

fock函数

父进程通过调用fork函数创建一个新的运行的子进程。
新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。父进程和新创建的子进程之间的最大区别在于它们有不同的PID

#include <unistd.h>
pid_t fork(void);
//返回值:子进程中返回0,父进程返回子进程id,出错返回-1

进程调用fork,当控制转移到内核中的fork代码后,OS做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器
  1 #include <stdio.h>                                                                                                                                   
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
   
  6     const char *str = "hello world";
  7 
  8     pid_t pid = fork();
  9     //之后才会运行
 10     if(pid == 0){
   
 11         while(1){
   
 12           printf("child: ppid: %d, pid: %d, str: %s\n", getppid(), getpid(), str);
 13           sleep(1);
 14         }
 15     }
 16     else if(pid > 0){
   
 17         while(1){
   
 18           printf("father: ppid: %d, pid: %d, str: %s\n", getppid(), getpid(), str);
 19           sleep(1);
 20         }
 21     }
 22     else{
   
 23         perror("fork");
 24     }
 25     return 0;
 26 }                                                                            

注意:虽然父子进程代码共享,但fork之后才有子进程,所以子进程是执行fork之后的代码。

fork常规用法:
1、一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如:父进程等待客户端请求,生成子进程来处理请求。
2、一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

1.为什么fork有两个返回值?
2.一个变量里面,怎么会有两个不同的值,从而让父子进入不同的业务逻辑。
> fork后父进程返回时,本质是把返回值写入变量pid,而此时子进程已经创建好了,必定发生了写时拷贝。
所以这一个变量名,内容是不同的,而本质是父子页表映射数据到了不同的内存区域。所以接下来父子进程读取pid拿到的值就不一样。

在这里插入图片描述

写时拷贝

通常,父子代码共享,父子在不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。
写时拷贝的过程实现是由OS参与完成的。
在这里插入图片描述

为什么要有写时拷贝(数据的)?
保证父子进程的“独立性”
1.节省资源。父子进程创建时,拷贝不需要写入修改的数据(只读)是没有意义的,如果直接把数据各自拷贝一份,就浪费了内存和系统资源。
2.提高fork创建的效率。fork时创建数据结构,如果还要将数据拷贝一份,fork效率降低
3.减少fork失败的概率。fork本身就是向系统要更多的资源,而要越多的资源就越容易导致fork失败。

进程终止

进程退出的情况分类:

1.代码跑完,结果正确。退出码:0
2.代码跑完,结果不正确。逻辑问题,但是没有导致程序崩溃。退出码:!0
3.代码没有运行完毕,程序崩溃了,退出码没有意义。

进程常见退出方法:

正常终止(可以通过echo $?查看进程退出码):
1.main函数return
2.任何函数exit
异常退出:
ctrl+c,信号终止

main函数中,return的值(退出码)代表进程退出,结果是否运行正确。0代表成功。而return的0是给系统看的,以此确认进程执行结果是否正确。如果我们想看最近一次执行的一个程序运行结束时的退出码,可以用echo $?来查看
在这里插入图片描述
退出码:可以认为定义,也可以使用系统的错误码list
当程序运行失败时,最关心的是失败的原因。而计算机擅长处理整数类型的数据(0, 1, 2, 3…)。 int(整数)-> string(错误码描述)
在这里插入图片描述
父进程一般需要知道子进程退出的结果,即进程的退出码。但父进程也可以不关心子进程的运行结果。
进程非正常结束:野指针、/0、越界等,此时退出码无意义。(此时是由信号来终止的)

main函数return。非main函数的return不是终止进程,而是结束函数。例如:

int show()
{
   
	return 0;
}

int main()
{
   
	show();
	return 0;
}

这里main函数中调用完show,这个进程并不会终止。

exit:在任何函数中exit都表示直接终止进程
在这里插入图片描述在这里插入图片描述exit:在退出时会执行用户定义的资源清理函数,包括刷新缓冲区,关闭流等。
_exit:在退出时不会进行后续资源处理,直接终止进程。
在这里插入图片描述可以看到使用_exit时,退出码照样是11。

站在OS角度,如何理解进程终止?
核心思想:归还资源
1."释放"曾经为了管理进程所维护的所有的数据结构对象。
2."释放"程序代码和数据占用的内存空间。
3.取消曾经该进程的链接关系。

释放:不是真的把数据结构对象销毁,而是设置为不用状态,然后保存起来。如果不用的对象多了,就有了一个"数据结构的池"。
内存池:先申请分配一定大小的空间,在需要使用时再使用内存池中的空间,就不需要每次需要内存时都进行new/malloc申请空间,提高了用户的效率。
释放数据结构对象:当要创建进程时,需要将内存池中拿出一块空间,并将这块空间强转成task_struct*,再进行访问。但如果每次都要强转就太麻烦。当一个pcb没人用时,可以将该pcb取出并链接到数据结构池中,该过程就是释放不用的数据结构对象,而需要用时再从池中取出,就不用进行强转了。这种释放规则叫做Slab分派器。
释放代码:不是将代码和数据结构清空,而是把内存设置为无效即可。
例如我们在下载电影资源时,所需下载拷进电脑的时间很多,删除却很快,说明写入和删的逻辑是不同的。写入时需要开辟空间,而删的本质是标识数据对应在磁盘上无效,一旦标识无效即意味着可以被覆盖,在写入新数据时,将该无效数据被覆盖也就是被清除了。
在这里插入图片描述

进程等待

如果一个父进程终止了,内核会安排init进程成为它的孤儿进程的养父。init进程的PID为1,是在系统启动时由内核创建的,它不会终止,是所有进程的祖先。如果父进程没有回收它的僵死子进程就终止了,那么内核会安排init进程去回收它们。不过,长时间运行的程序,比如shell或者服务器,总是应该回收它们的僵死子进程。即使僵死子进程没有运行,它们依然消耗系统的内存资源。
一个进程可以通过调用waitpid函数来等待它的子进程终止或停止。

等待的必要性:

  1. 回收僵尸,解决内存泄漏。僵尸状态无法被杀死
  2. 父进程需要获取子进程的运行结束状态(不是必须的)
  3. 父进程要尽量晚于子进程退出,可以规范化进行资源回收。(编码相关)

进程等待的方法:
wait/waitpid

  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。

**wait:**等待任意一个子进程。当子进程退出,wait就可以返回。

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int* status);

返回值:成功则返回被等待进程pid,失败返回-1
参数:输出型参数,获取子进程退出状态,不关心则可以设置为NULL
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <sys/types.h>                                                                                                                               
  5 #include <sys/wait.h>
  6 
  7 int main()
  8 {
   
  9     pid_t id = fork();
 10     if(id < 0){
   
 11         perror("fork");
 12         return 1;//自定义
 13     }
 14     else if(id == 0){
   
 15         //child
 16         int count = 5;
 17         while(count){
   
 18             printf("child is running: %d, ppid: %d, pid: %d\n", count--, getppid(), getpid());
 19             sleep(1);
 20         }
 21         printf("child quit...\n");
 22         exit(0);
 23     }
 24     else{
   
 25         printf("father is waiting...\n");
 26         pid_t ret = wait(NULL);
 27         printf("father is 
评论 29
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值