fork+父子进程在理解+进程控制

fork+父子进程在理解

fork之后,内核做了什么?

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

fork父子进程共享所有的代码 != 子进程执行的后续代码,子进程执行的代码只能从fork之后开始,cpu读取指令时一般直接向eip寄存器要,然后eip中存储的指令++,指向下一条执行指令,而fork之后eip寄存器此时存储的是下一条指令,子进程拷贝父进程的eip,因此便只能从fork之后的代码执行了

#include<stdio.h>
  2 #include<unistd.h>
  3 int main()
  4 {
  5   printf("我是一个进程:pid: %d\n",getpid());
  6   fork();
  7   printf("我依旧是一个进程:pid:%d\n",getpid());
  8   return 0;
  9                                                                                                                                   
 10 }

在这里插入图片描述
从上述看出,foek之后父子进程共享代码,但也是只从fork之后子进程才会执行代码,由于进程具有独立性,代码和数据都必须独立,所以此时父子进程共享的代码只能读取,那么如果有一个进程非要修改数据呢?
此时父子进程会发生写实拷贝解决问题,刚开始父子进程的代码和数据都是只读的,但是发生写实拷贝之后,代码还是只读,但是此时可以写入数据,写实拷贝只是拷贝被写入的数据,没有写入的数据父子进程照样共享

在这里插入图片描述
为什么要写实拷贝?直接在创建子进程的时候,将数据分开不行吗?

  1. 父进程的数据,子进程不一定全用,即便全用,也不一定全部写入 ----浪费资源
  2. 最理想的情况,只有会被父子修改的数据,进行分离拷贝,不需要修改共享 ------技术上实现复杂(因为代码要被编译才能知道哪些数据是被写入的,哪些数据是没有被写入的)
  3. 如果fork之后就无脑拷贝给子进程,会增加fork的成本

fork调用失败可能原因

  1. 系统中有太多的进程
  2. 实际用户的进程数超过了限制

进程控制

1.进程终止

常见进程退出有三种情况:
1.代码跑完,结果正确
2.代码跑完,结果不正确
3.代码没跑完,程序异常了

在C/C++中在main函数最后都会有return 0;其实无脑return 0是不正确的行为,应该是return X,其中X表示进程退出码表征进程退出信息,进程的退出信息在将来进程退出的时候给父进程读取的,可以参考一下C语言中默认的错误码描述
在这里插入图片描述
可以用echo $?查看进程退出码

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<string.h>
  5 int main()
  6 {
  7 
  8   for(int i = 0; i < 100; i++){
  9     printf("%d: %s\n",i,strerror(i));
 10   }
 11   printf("\n");
 12   return 123;

在这里插入图片描述
echo虽然是一个内置命令不会生成子进程,但还是将其看做一个运行成功的程序,图中第一个echo $?表示的是我们自己的程序,此时退出码是123,第二个echo $?表示的是上一个命令echo本身的退出情况。

进程正常退出时:
1.从main返回
2.调用exit(系统调用)
3_exit

#include <unistd.h>
void _exit(int status);
status:定义了进程的终止状态
其中父进程通过wiat来获取状态
其中,status虽然是int,但是仅仅是有低八位可以被父进程使用,
所以如果exit(-1),在终端执行$?发现返回的是255
 10    printf("测试_exit函数\n");
 11   _exit(321);
 12   return 0;

在这里插入图片描述
还有一个函数与_exit相似,exit

#include <unistd.h>
void exit(int status);
exit最后也会调用_exit,只是在调用—_exit之前还做了其他工作
1.执行用户定义的清理函数
2.关闭所有打开的柳,所有的缓存数据均被写入
3.调用_exit

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
exit终止进程,刷新缓冲区,一秒钟之后消息被打印出来了,但是调用_exit的函数会直接终止进程,不会有任何刷新操作,所以一秒之后消息没有被刷新出来
关于进程终止,内核做了什么?
进程状态是Z的时候,父进程读取退出码信息后,将状态设置成X后释放代码和数据,其中操作系统会自己形成一个链表,将废弃的内核结构放在链表之中,当有新的进程形成内核数据结构时节省了开辟空间所需要的时间,直接将内核数据结构初始化即可。

2.进程等待

什么是进程等待?
进程等待是指一个进程暂停自己的执行,等待另一个进程执行完毕或发送信号给它,然后再继续执行,通常情况下,父进程会等待子进程完成,以确保子进程的结果能够被正确处理
为什么要进行进程等待?

  1. 子进程退出,如果父进程如果没有及时获取退出状态信息,该子进程的文件描述符和其他资源可能一直保留,经常造成内存泄漏
  2. 父进程再给子进程派发任务时,我们需要知道子进程运行完成后结果是对还是不对,是否是正常退出
  3. 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

如何进行进程等待
wait方法

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
参数:输出型参数,获取子进程的退出状态,不关心则可以设置成null
返回值:成功则返回被等待进程的pid,失败返回-1
 16     while(1)
 17     {
 18       
 19       printf("我是子进程,我正在运行,我的pid: %d\n",getpid()    );
 20       sleep(1);
 21       flag++;
 22       if(flag == 5){
 23         exit(0);                                             
 24       }
 25 
 26     }
 27   }
 28   else 
 29   {
 30     printf("我是父进程pid: %d,,准备等待子进程!\n",getpid())    ;
 31    sleep(10);
 32     pid_t ret = wait(NULL);
 33     if(ret < 0) {
 34       printf("等待失败!\n");
 35     }else {
 36       printf("等待成功:result: %d\n",ret);
 37     }
 38     sleep(20);
 39 
 40   }
 41   return 0;
 42 }


在这里插入图片描述在这里插入图片描述

此时等待成功,原本父子进程的状态都是S,然后子进程变成Z,子进程退出之后就被回收,然后父进程接着运行
如果子进程不退出的话,那么父进程的代码会一直堵在wait()函数部分,因为wait默认是阻塞式等待,只要wait没有等到进程退出,就会一直卡在那。
waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);
参数:
	1.pid: 
		1、pid = -1,等待任意一个子进程,与wait等效
		2、pid > 0,等其进程ID与pid相等的子进程
	2.status:
		输出型参数,由操作系统填充
		如果传递NULL表示不关心子进程的退出状态
	3.options:
		1、WNOHANG:若pid指定的子进程没有结束,
		则waitpid()函数返回0,不予等待,
		2、若正常结束,则返回该子进程的id
		(如果加了这个参数就不是阻塞式等待了,就是非阻塞式等待)
返回值:
	1.当正常返回的时候waitpid返回收集到的子进程的进程id
	2.如果设置了选项WNOHANG,而调用中waitpid发现没有退
	出的子进程可以收集,则返回03.如果调用中出错,则返回-1,此时errno会被设置成相应的值以指示错误	
 8 int main()
  9 {
 10 
 11   pid_t id = fork();
 12   if(id == 0)
 13   {
 14     int flag = 1;
 15     //child
 16     while(1)
 17     {
 18       
 19       printf("我是子进程,我正在运行,我的pid: %d\n",getpid()    );
 20       sleep(1);                                              
 21       flag++;
 22       if(flag == 5){
 23         break;
 24       }
 25       
 26     }
 27     exit(12);                                                
 28   }
 29   else 
 30   {
 31     int status = 0;
 32 
 33     printf("我是父进程pid: %d,,准备等待子进程!\n",getpid())    ;
 34    sleep(1);
 35   //   pid_t ret = wait(NULL);
 36       // pid_t ret = waitpid(id,&status,0);
 37       pid_t ret = waitpid(id,&status,WNOHANG);
 38     if(ret < 0) {
 39       printf("等待失败!\n");
 40     }else {
 41       printf("等待成功:result: %d\n",ret);
 42     }
 43    
 44     sleep(2);
 45   }
 46   return 0;
    }


在这里插入图片描述

此时是非阻塞式等待,父进程运行到waitpid()时,子进程还未退出,当父进程运行完自己代码的时候,直接退出,子进程变成了孤儿进程,此时子进程会托管给1号进程也就是bash。

3.获取进程状态Status

先了解一下什么是信号
在Linux中,信号是一种用于进程间通信的机制,信号可以被一个进程发送给另一个进程,用于通知或中断目标进程的执行。
在Linux中kill命令用于向指定进程发送信号,从而控制进程的行为和状态

语法格式:
kill [signal] PID
signal:信号编号
PID:进程的PID
在这里插入图片描述

wait/waitpid都有一个status参数,但是其不能当做一个简单的整形看待,当做位图看待,(只研究status低16比特位的情况);
在这里插入图片描述

 8 int main()
  9 {
 10 
 11   pid_t id = fork();
 12   if(id == 0)
 13   {
 14     int flag = 1;
 15     //child
 16     while(1)
 17     {
 18       
 19       printf("我是子进程,我正在运行,我的pid: %d\n",getpid()    );
 20       sleep(1);                                              
 21       flag++;
 22     /*  if(flag == 5){
 23         break;
 24       }*/
 25 
 26     }
 27     // exit(12);
 28   }
 29   else 
 30   {
 31     int status = 0;
 32 
 33     printf("我是父进程pid: %d,,准备等待子进程!\n",getpid())    ;                                                            
 34     
 35   //   pid_t ret = wait(NULL);
 36       // pid_t ret = waitpid(id,&status,0);
 37       pid_t ret = waitpid(id,&status,0);
 38       if(ret > 0){
 39         printf("wait success,ret : %d,我所等待的子进程的退出>    码 :%d,退出信号是 :%d\n",\
 40             ret,(status>>8)&0xFF,status&0xFF);
 41       }
 49   }
 50   return 0;
 51 }



在这里插入图片描述
上述代码子进程一直循环,父进程阻塞式等待,用kill-9杀死子进程,此时父进程收到的退出码为0,退出信号为9

LInux中还可以用宏来表示status输出型参数
Linux中还也可以用 WEXITSTATUS(status)宏表示进程的退出码
WIFEXITED(status):表示进程是否正常退出
WIFSIGNALED(status):非0表示被信号终止
WTERMSIG(status):获取终止信号的编号

总结:
退出码表示的是有没有跑完,结果对不对
退出信号表示的进程是否异常,进程一旦异常,我们只需要关心退出信号即可,关心退出码没有任何意义

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: Linux中的fork()函数可以创建一个新的进程,这个新进程是原进程的副本,也就是说,它们有相同的代码、数据和堆栈。fork()函数会返回两次,一次是在进程中返回子进程进程ID,另一次是在子进程中返回。进程子进程之间的区别在于它们的进程ID不同,以及它们的进程ID也不同。进程子进程共享文件描述符、信号处理程序和文件锁等资源,但是它们各自拥有自己的地址空间和堆栈。 ### 回答2: Linuxfork()是创建进程的一个系统调用,fork()会复制一份进程的全部资源,包括代码段、数据段、堆栈、打开的文件、进程组信息等都会被复制到子进程中。因此,原来进程有的资源,在子进程中都会有一个副本。fork()调用成功后,会返回两次,一次是在进程中返回子进程的PID,另一次是在子进程中返回0。 在fork()调用完成后,就会出现两个进程进程子进程进程中的所有资源都被完全复制到了子进程中,但子进程具有独立的内存空间和进程ID,因此进程子进程之间的内存空间是互相独立的。在这种情况下,进程子进程执行的代码相同,但是子进程是一个全新的进程,具有自己的内存空间和进程上下文。 如果子进程想和进程之间进行通信,可以使用管道或者共享内存。透过管道或共享内存,子进程可以读取进程中的数据,或者将自己产生的数据发送给进程。另外,还可以使用信号或者消息队列进行通信。 需要特别注意的是,fork()调用成功后,子进程中会复制进程中的所有资源,包括打开的文件描述符等。因此,子进程需要关闭不需要的文件描述符,避免浪费系统资源。此外,子进程也需要确保在调用exec()函数之前,所有需要使用的文件描述符都已经打开了,否则在子进程中打开的文件描述符可能会覆盖进程中已经打开的文件描述符,导致出现错误。 ### 回答3: 在Linux系统中,每个进程都有一个唯一的进程ID(PID),以及一些其他的属性和信息。除了在启动时由init进程创建的特殊进程以外,每个进程都是由另一个进程fork”出来的,即在原有进程的基础上创建一个全新的进程。 在C语言中,可以使用fork()函数来实现这个操作。每次调用fork()函数时,会创建出一个全新的进程,称为“子进程”,并且这个子进程就是由“进程fork出来的。进程子进程在大部分方面都是相同的,例如二者运行相同的程序、拥有相同的内存空间和变量等等,但在某些方面也有一些差异,例如二者的进程ID不同(进程进程ID就是调用fork()函数前的进程ID,而子进程进程ID是新分配的),以及二者对共享资源的访问方式不同(具体取决于程序的实现方式)。 通常情况下,fork()函数的返回值为0,表示子进程;或者返回一个大于0的数值,表示进程,并且这个数值就是子进程进程ID。如果fork()函数返回一个负数,则表示创建子进程失败。 在实际编程中,可以利用fork()函数来实现一些复杂的应用,例如多进程并行计算、进程间通信等等。此外,fork()函数也是Unix/Linux系统中一种重要的机制,可以实现进程的动态创建和销毁,从而增强了系统的灵活性和可扩展性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

每天少点debug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值