OS进程控制
进程创建
fork()
关于进程的创建主要是使用fork创建子进程,前面链接有讲如何fork创建子进程,这里不在多讲。
https://blog.csdn.net/2301_80820096/article/details/147480284?spm=1001.2014.3001.5501
os底层如何创建
1.使用fork创建子进程后,os会给子进程分配内存数据结构(pcb)。
2.os将父进程的部分数据结构(mm_struct,页表等)拷贝给子进程。
3.添加子进程到系统进程的列表中(就绪)。
4.fork返回开始被调度(调度)。
fork之前父进程独立执行,fork之后,父子各自执行流执行。创建出的子进程会执行父进程之后的代码。
写时拷贝
在创建子进程后,对于父进程的一些代码数据是共享的,但当任意一方试图写入时,便以写时拷贝的方式各自生成一份副本,展示了进程独立性。

原本的代码段时可写的,但当创建子进程之后就变为只读了,当任意一方想要修改,os会报错,然后生成副本供一方使用。(也可以说写时拷贝是基于系统报错完成的)。
写时拷贝的优点
1.减少进程创建的时间。
2.减少内存浪费(父进程每个数据子进程不一定都要更高,内存不一定都要申请)。
fork常规用法
1.父进程创建出子进程,让子进程执行其他的代码和数据。
2.一个进程执行不同程序,eg:子进程返回后调用exec函数。
for调用失败的原因
1.系统中有太多进程,内存严重不足
2.实际用户的进程超过限制。
进程终止
定义:
进程终止的本质就是释放系统资源,释放相关的内核数据结构(pcb)和代码数据。。
进程退出场景
1.代码运行完毕,返回结果正确(return 0)。
2.代码运行完毕,返回结果错误 (return !0)。
3.代码异常终止,(退出码无意义 )。
查看最近进程退出码:echo $?
查看具体的退出码详细信息:sreerror(i);
return errno:直接返回出错 的退出码
exit(c)VS _exit(系统)
exit:语言层面的库函数(exit底层调用_exit),会自动刷新缓冲区。
_exit():系统层面的接口,不会刷新缓冲区。
(可通过打印printf("/")加不加反斜杠看出)
引出:系统层面不会刷新缓冲区证明缓冲区不在系统内部,可能存在库函数和系统调用中间这个地方。
进程等待
为什么要进行进程等待(必要性)
1.父进程等待回收子进程,以免造成"僵尸进程",造成内存泄漏。
2.父进程创建子进程就是想让子进程完成某些任务,完成任务怎么样,要把结果返回给父进程。
总结一句话:父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。
如何进行进程等待
wait()
#include<sys/types.h>#include<sys/wait.h>pid_t wait(int* status);返回值:成功返回被等待进程pid,失败返回-1。参数:输出型参数,获取子进程退出状态,不想关心设置成为NULL//父进程调用wait()时,子进程没有退出时,父进程处于阻塞状态。
案列验证:
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
//验证回收僵尸进程
int main(){pid_t pid=fork();if(pid==0){int cnt=5;while(cnt>0){printf("我是子进程,pid=%d,ppid=%d\n",getpid(),getppid());cnt--;sleep(1);}exit(0);}//父进程回收sleep(10);///可以屏蔽和放开以下代码来验证wait回收了子进程,解决了僵尸进程问题pid_t rid=wait(NULL);if(rid>0){cout<<"wait sucess!!!"<<endl;}sleep(10);return 0;
}
//另开终端执行ps -ajx | grep 子进程pid
waitpid()
pid_ t waitpid(pid_t pid, int *status, int options);返回值:当正常返回的时候waitpid返回收等待的子进程的进程PID;//如果设置了选项WNOHANG,⽽调⽤中waitpid发现没有已退出的⼦进程可收集,则返回0;//如果调⽤中出错,则返回-1,这时errno会被设置成相应的值以指⽰错误所在;参数:pid:Pid=-1,等待任⼀个⼦进程。与wait等效。Pid>0.等待其进程ID与pid相等的子进程。status: 输出型参数WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)WEXITSTATUS(status): 若WIFEXITED⾮零,提取⼦进程退出码。(查看进程的退出码)options:默认为0,表示阻塞等待(等同wait)//等待子进程结束WNOHANG: 非阻塞调用。若指定的pid子进程没有结束,则waitpid()函数返回0,不予以 等待。若正常结束,则返回该⼦进程的ID。
理解status输出型参数
status是wait/waitpid函数中的一个输出型参数,目的是输出子进程的退出信息(主要整数标示)。
int status=0;
&status:拿到子进程退出信息
由下图我们进程正常退出可以看到我们使用echo $?拿到退出进程的退出码就是根据这个原理(status>>8)&0xff:退出码
(status&0x7f):异常终止信号
进程异常被终止:下边的终止信号低7位保存异常退出信号。(core dump标志位解释后边加进来)
对于整形int表示32个比特位,其中高16位不用来表示,第16位的前八位用来描述退出码。
。

如果进程不异常,正常退出,那么低7位为0
如果进程异常退出,那么低7位就不可能为0,这时就不用关系次低8为的退出码了(无意义)。
退出状态和退出码怎么被父进程拿到

解释:子进程退出后把自己的退出状态保存至自己的task_struct(pcb)中,父进程通过系统调用(wait/waitpid)的方法来获得子进程退出信息。
参数option
非阻塞轮询&阻塞等待
非阻塞轮询:用户向os多次发起请求,但遭到拒绝,这时用户继续向操做系统发起请求。
阻塞等待(类似scanf):用户向os发起一次请求,没遭拒绝,但os也不给用户返回服务,这时候称为阻塞。
非阻塞轮询的返回值
大于0 等待结束
等于0调用结束,但子没有退出
小于0调用失败
非阻塞等待的优点
在非阻塞调用,请求方和服务方可以并发在进行其他事情(效率较高)。
进程替换(了解即可)
简单理解:程序替换就是通过特定接口(execl*类似接口),加载磁盘上的一个全新的程序(代码和数据)。
程序替换可以替换我们自己写的程序
以一个程序替换python程序举例:
//c++主程序
int main(){printf("myname is c++\n");execl("/usr/bin/python3","python","python.py",NULL);return 0;
}
//python程序
#!/usr/bin/python3
print("hello python!")
//运行结果
//myname is c++
//hello python!
//程序替换成功
程序替换后并不等于新建一个新的程序
验证代码:
解释:主程序pid和调用程序pid一致说明使用execl进程程序替换时并没有新建程序。
//主程序code.cc
int main(){printf("mypid:%d\n",getpid());execl("./code1","code1",NULL);return 0;
}
//调用程序code1.cc
int main(){cout<<"i am new,my pid:"<<getpid()<<endl;return 0;
}
//运行结果
mypid:901764
i am new,my pid:901764
进程替换中的相关exec*函数介绍
1.int execl(const char *path, const char *arg, ...); //-l 列表的意思
eg:execl("/usr/bin/ls","-a","-l",NULL);
2.int execlp(const char* file,const char*arg,....); //-p 环境变量,默认找路径
eg:execlp("ls","ls","-a","-l")
3.int execv(const char *path, char *const argv[]); // v(vector) 数组存储命令行参数
eg: const char* argv[ ]={"ls","-l","-a",NULL};
execv("usr/bin/ls","ls" ,argv);
4.int execvp(const char *file, char *const argv[]);(结合上边,不足举例)
5.int execvpe(const char *file, char *const argv[],const char* env[]);//e 环境变量 要求调用的程序使用全新的环境变量
6.int execve(char* filename,char *constargv[],char *const envp[]); (系统层面)上边五个所有接口底层都是调用这个函数进行的。

