Linux -- 进程控制

进程控制相关的头文件一般都是 <unistd.h>

进程创建

进程创建简单的来说就是创建一个PCB。使用 fork 库函数。

pid_t fork();
pid_f vfork(void);

fork — 通过复制调用进程也就是父进程的PCB来创建一个新的子进程。fork的返回值是:父进程返回子进程的PID,子进程返回0,若创建失败返回-1.
vfork — 创建子进程,父子进程公用一个虚拟地址空间。
父子进程公用一个虚拟地址空间的作用是为了防止调用栈混乱,具体实现是:使父进程调用vfork之后就一直阻塞,一直阻塞到子进程退出 或者 子进程发生程序替换。返回值和fork一样。
vfork创建的子进程不能用return退出,只能用exit。
因为fork加入了写时拷贝技术之后,vfork创建子进程的高效率的地位被取代,vfork也就慢慢被淘汰。写时拷贝技术提高子进程的效率,避免了无谓的开辟空间。
fork 和 vfork再内核中创建进程都是调用 clone函数实现PCB创建并拷贝数据。
fork 调用失败的原因:
1. 系统中存在太多的进程;
2. 实际用户进程数超过了限制。
通常使用fork创建子进程之后,可以通过返回值的不同使用if分流,分辨出父子进程。
进程终止
进程终止就是退出进程,进程退出分为两种:1.正常退出,结果符合或者不符合预期退出;2.异常退出:常见的异常退出就是程序奔溃。
关于进程退出的方法:

  1. main 函数中的return;
  2. 两种exit函数,通常使用的都是库函数中的exit(), 另一个是系统调用接口中的_exit();
  3. 异常退出:Linux下使用ctrl + c,信号终止。
void exit(int status);   //库函数中的exit
//status是进程终止状态,父进程通过wait来获取该值

return 和 exit() 两种方式都会在退出之前刷新缓冲区,return 通常在main中退出进程,而exit可以在任意地方使用。

void _exit(int status); //系统调用接口中的exit

_exit 在退出之前不会刷新缓冲区,直接就释放全部资源,缓冲区的数据会被丢弃。
库函数是系统调用接口的一层封装,所以可以理解,exit函数底层其实也调用了_exit函数,但是在调用之前做了清理工作和缓冲区数据写入工作。

进程等待

进程等待就是等待子进程退出或者状态改变,获取子进程的退出返回值,并且释放子进程的资源,防止僵尸进程产生。
头文件包含 <sys / wait.h>
有两种等待的接口:

pid_t wait(int* status); //等待任意一个子进程退出
//status中保存的是子进程退出的返回值
//返回值:成功返回子进程的pid,失败返回-1
pid_t waitpid(pid_t pid, int* status, int options); //等待指定进程或者任意进程退出
//pid指定子进程的pid    status 子进程退出的返回值   options选项标志
//如果参数中的pid>0 就是指定进程, pid==-1表示任意一个子进程
//返回值:-1 表示出错    0 表示没有子进程退出    >0表示退出子进程的PID

status内存中,高16位没有使用,低16位的高8位存储子进程的返回值,低8位中的高1位存储core dump标志,低7位表示异常信号值。所以,若低7位中异常信号为0表示程序正常退出,否则异常退出,异常退出时的返回值不具备任何意义。

两个接口的不同:
wait:一直阻塞的等待任意一个子进程退出,子进程退出之后,获取返回值,放入status中,返回退出子进程的PID。
waitpid:一直阻塞等待任意一个或者指定子进程退出,返回退出子进程的PID。
如果子进程一直没有被退出,wait将一直阻塞。如果子进程已经退出,调用这两个函数的时候,会立即返回,并且释放资源。如果不存在该子进程,则立即报错返回。
阻塞和非阻塞:
阻塞:为了完成一个功能发起调用,若当前不具备完成条件,则等待直到条件具备完成功能后返回。
非阻塞:为了完成一个功能发起调用,若当前不具备完成条件,则立即报错返回。
(通常非阻塞需要循环操作,隔段时间过来查看是否具备完成条件)

进程程序替换

程序替换就是替换一个进程正在运行的程序,具体实现如下:
重新加载一个新的程序到物理内存中,修改页表中的代码段映射关系,让程序的代码段经过页表的转换之后,指向新的程序位置。
即:让一个进程pcb通过页表转化映射到物理内存上另一个程序的位置;进程将运行另一个程序,以前的数据和代码全部失效了,因此需要重新初始化页表以及虚拟地址空间中的代码段和数据段。这个操作并不是创建新的进程,这只是将该进程的代码和数据完全被新的程序替换掉,从新程序的启动例程启动,所以替换前后的进程的pid并不改变。
具体的接口有:

extern char** environ;
//1.
int execl(const char* path, const char* arg, ...)
//使用path这个路径的程序,替换当前进程要运行的程序。参数数量不定以 NULL 结束。
//arg是函数运行参数,argv[]是数组形式保存运行参数,envp[]环境变量列表
//2.
int execlp(const char* file, const char* arg, ...)
//使用file这个文件的程序,替换当前进程要运行的程序。参数数量不定以 NULL 结束
//3.
int execle(const char* path, const char* arg, ... ,char* const envp[])
//4.
int execv(const char* path, char* const argv[])
//5.
int execvp(const char* file, char* const argv[])
//6.
int execvpe(const char* file, char* const argv[], char* const envp[])
//7.这是系统调用接口
int execve(const char* path, char* const argv[], char* const envp[])

其中execve是系统调用接口,其他接口都是库函数,底层调用execve接口。
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回;
如果调用出错则返回 -1; 所以exec函数只有出错的返回值,没有成功的返回值。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值