Linux进程控制

  1. 进程创建
    在Linux中fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程称为子进程,而原进程为父进程
#include<unistd.h>
pid_t fork(void)
返回值:子进程返回0,父进程返回子进程的pid,出错返回-1

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

  • 分配新的内存块和内核数据结构给子进程
  • **将父进程部分数据内容拷贝至子进程:会把父进程PCB拷贝一份,稍加修改,成为子进程的PCB。会把父进程的虚拟地址空间拷贝一份,作为子进程的地址空间。
    **
  • 添加子进程到系统进程列表中
  • fork返回,开始调度器调度。父进程返回子进程的pid,子进程返回0。在fork后面继续往下执行
  • 父子进程执行顺序没有先后关系,全靠调度器来实现

由于大部分的内存空间可能被拷贝,创建进程开销仍然比较高(和线程相比)。
在有些场景下,线程的创建也会被认为开销比较高(和协程相比)。

当一个进程调用fork之后,就会有两个二进制代码相同的进程。而且他们都运行到相同的地方。但每个进程都将可以开始他们的旅程。

  1. fork调用失败的原因
  • 系统中有太多的进程
  • 实际用户的进程数超过了限制
    说简单点,进程太多,内存不够
  1. 进程终止
    进程退出场景:代码运行完毕,结果正确。代码运行完毕,结果不正确。代码异常终止。
    进程常见退出方法:正常终止:1.从main返回(刷新缓冲区) 2.调用exit 3._exit. 异常退出:ctrl+c,信号终止

可以通过echo $? 查看进程退出码
$? 这是bash中的一个特殊变量,表示上一个命令对应的进程的退出码。

_exit函数系统调用

#include<unistd.h>
void _exit(int status);
参数:status 定义了进程的终止状态,父进程通过wait来获取该值
说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1),在终端执行$?发现返回值是255.

exit函数c语言标准库函数,exit本质上也是调用_exit,还做了其他工作:1.exit关闭文件流并刷新缓冲区,显示器行缓冲。2.exit还多调了系统函数(atexit或者on_exit)作为结束函数

#include<unistd.h>
void exit(int status);

只有main函数结束,才表示进程结束

  1. 进程等待
    父进程对子进程进行进程等待。等待是为了读取子进程的运行结果。

    wait方法

#include<sys/wait.h>
pid_t wait(int* status);
返回值:成功返回被等待进程的pid,失败返回-1
	1.参数是输出型参数 表示退出码+正常/异常退出
	2.返回值是子进程的pid
	3.阻塞等待,一直等待到子进程结束。

status是int,但是仅有低8位可以被父进程所用!

waitpid方法

#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid,int* status,int options);
返回值:
	当正常返回的时候waitpid返回收集到子进程的进程PID
	如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0
	如果调用中出错,则返回-1,这是error会被设置成相应的值以指示错误所在
参数:
	pid:
		pid=-1,等待任一个子进程。与wait等效。
		pid>0.等待其进程ID与pid相等的子进程。
	status:
		WIFEXITED(status):若为正常终止了子进程返回的状态,则为真。(查看进程是否正常退出)
		WEXITSTAUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
	options:
		WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获取子进程退出信息。

  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能堵塞。

  • 如果不存在该子进程,则立即出错返回。

  • waitpid(-1,NULL,0)就和wait()一样

  • WNOHANG加上后,waitpid就变成了非堵塞 WNOHANG是个宏
    通常与while连用

    获取子进程status

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。

  • 如果传递NULL,表示不关心该进程退出的状态信息。

  • 否则,操作系统会根据该参数,将子进程的退出信息返回给父进程。

  • status不能简单的当作整形来看待,可以当作位图来看待。
    在这里插入图片描述

  1. 进程程序替换
    替换原理
    用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

    替换函数
    其实有六种以exec开头的函数,统称为exec函数:

#include<unistd.h>
int execl(const char* path,const char *arg,...);
int execlp(const char *file,const char *arg,...);
int execle(const char* path,const char *arg,...,char *const envp[]);
int execv(const char* path,char *const argv[]);
int execvp(const char *file,char *const argv[]);
int execve(const char* path,char *const argv[],char* const envp[]);

函数解释

  • 这些函数如果调用成功则加载新的程序从启动diamond开始执行,不再返回。
  • 如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值

命名立即

  • l(list):表示参数采用列表
  • v(vector):参数用数组
  • p(path):有p自动搜索环境变量PATH
  • e(env):表示自己维护环境变量
    事实上只有execve是真的的系统调用,其他5个函数最终都调用execve。

替换的数据段与代码段!! 从磁盘中读取,堆和栈都不要 从新生成堆和栈
1.程序替换不会创建新进程,也不会销毁进程
2.替换代码和数据(从一个可执行文件中来)
3.原有的堆和栈的数据就全都不要了,根据新的代码的执行过程重新构造堆和栈
类似于双击exe执行一个程序的过程(操作系统的加载器模块)

4.替换虚拟地址空间

23 int main()
 24 {
 25   printf("before execl\n");
 26 
 27   //最后一个参数必须是null ,如果不填程序就是未定义
 28   int ret=execl("/usr/bin/ls","ls","-l","/",NULL);
 29   printf("after execl %d",ret);//替换成功就不会执行这条语句了
 30  //不管后面是啥 ,都没有了
 31  //所以 先fork()复制一份代码,然后子进程替换
 32   return 0;

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值