目录
一、进程创建
代码内fork():创建子进程,每个fork只会执行一次
子进程拥有和父进程相同的代码和数据结构,并且从fork()的下一行开始运行
fork()的头文件:<unistd.h>
pid:进程id ppid:父进程id
printf("A,pid:%d,ppid:%d",getpid(),getppid()); //pid:1,ppid:bash
fork();
printf("B,pid:%d,ppid:%d",getpid(),getppid()); //父进程,pid:1,ppid:bash
//printf("B,pid:%d,ppid:%d",getpid(),getppid()); //子进程,pid:2,ppid:1
fork()之后通常要用if分流,根据返回值区分父进程和子进程
int ret = fork(); -- 返回值:pid_t fork();
当子进程创建成功,返回子进程的pid给父进程,返回0给子进程
当创建失败,返回-1给父进程
fork之后:
1.执行流会变一为二
2.谁先运行由调度器决定
3.代码共享,通常用if else来分流
原理:
fork做了什么?
进程 = 内核数据结构 + 自己的代码和数据 = pcb + 代码数据
在内核中创建子进程的pcb,子进程和父进程的pcb指向同一块代码和数据
fork如何看待代码和数据?
代码:只读
数据:当有一个执行流尝试修改数据时,OS会自动给当前进程触发写时拷贝
如何理解fork有两个返回值?
fork本质就是OS提供的一种函数,在return之前子进程已经完成创建,所以子进程也会进行return
int main(){
pid_t ret = fork();
if(ret == 0){
//子进程
while(1){
printf("child ,pid: %d ,ppid: %d\n",getpid(),getppid());
sleep(1);
}
}else if(ret > 0){
//父进程
while(1){
printf("parent ,pid: %d ,ppid: %d\n",getpid(),getppid());
sleep(1);
}
}else{
perror("error!\n");
}
return 0;
}
写时拷贝
在Linux环境下,子进程刚创建时会和父进程指向同一块虚拟空间,也是同一块物理空间,当父进程或者子进程对某项数据进行修改时,会对子进程的该数据进行拷贝,复制一份后再进行修改。
此时虽然父子进程该数据的虚拟地址一样,但是物理地址已经不同,但只要父/子进程没有在子进程创建后对数据进行修改,就不会专门开空间来存放子进程中的数据。
写时拷贝编译器一种节省空间的手段,赌你不会写入,如果写入才拷贝,如果没有进行写入就赌赢了,就算要进行写入也无非是把本来要做的事情做一遍
二、进程终止
情况分类:
1、正常执行结束 -- 正确/不正确
返回值可供用户进行进程退出健康状态的判定
echo $?:获取最近一次执行的进程的退出码 -- 因为该指令也是一个进程,所以连续的两次echo $?中的第二次获取的是第一次的退出码
2、崩溃 -- 进程异常
崩溃的本质:信号,进程因为某些原因收到了来自操作系统的信号
理解:
OS内少了一个进程,OS就要释放进程对应的内核数据结构+代码和数据
操作:
mian函数用return
函数用exit(),C语言库函数,等价于main的return
上述两种退出的流程为:执行用户定义的清理函数 -> 刷新缓冲区,关闭stream -> 终止程序
_exit()直接终止程序,不会刷新缓冲区
exit()本质:
exit(int code){
//……
_exit(code);
}
三、进程等待
进程等待:通过系统调用,获取子进程退出码或者信号的方式
用途:
1、避免内存泄露
2、获取子进程的执行结果 -- 信号+退出码
通过wait()、waitpid()获取子进程的退出码 -- 头文件<sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
pid: > 0 等待指定进程
= -1等待任意进程,与wait等效
status:输出型参数,获取子进程的状态
status用位图存放,把32个bit位对半切,只用前16位
8~15位存放退出码
0~6位存放终止信号(0:代表正常退出,正常退出再查退出码,非正常退出就不用退出码)
7存放code dump标志
options: = 0,阻塞等待 -- 只问一次结果,没结果就阻塞,等待结束
= WNOHANG,非阻塞等待 -- 过一段时间问一次结果,没结束就干自己的事情
父进程通过获取子进程的内核数据结构来获取子进程的退出信息
子进程没有退出时,父进程只能一直在调用waitpid进行等待 -- 阻塞
int main(){
pid_t id = fork();
if(id == 0){
//子进程
int cnt = 5;
while(cnt){
cout << "child "<<"pid:"<<getpid()<<" ppid:"<<getppid()<< " 剩余时间:" <<cnt-- << endl;
sleep(1);
}
exit(0);
}
//父进程
//pid_t ret = wait(NULL);
//父进程开始时睡眠10s,意味着当子进程结束后其退出码还有5s没被父进程获取,此时子进程为僵尸状态
sleep(10);
int status = 0;
pid_t ret = waitpid(id,&status,0);
cout <<"parent "<<"pid:"<<getpid()<<" ppid:"<<getppid()<<" ret:"<<ret<<" status:"<<status<<" 退出状态:"<<((status<<8)&0xFF)<<" 终止信号:"<<(status&0x7F)<<endl;
return 0;
}
四、进程程序替换
创建子进程的目的:
1、让子进程执行父进程的一部分代码
2、执行一个磁盘中已有的进程 -- 进程的程序替换
原理:
没有创建新的进程,只替换数据和代码,不替换PCB
原代码被新代码替换后,剩余的代码已经没了,就不会再执行
如果没有成功替换,则继续执行源代码
操作:
1、找到一个程序
2、加载执行 -- 语法和在命令行上执行的语法一样
3、以NULL结尾
// 路径(要找到该进程) 可执行程序 可变参数
int execl(const char *path, const char *arg, ...);
//如果替换成功就不会有返回值,只要有返回值就代表替换失败 -- 不用判断该函数的返回值,只要继续向后执行就代表失败
int main(){
pid_t id = fork();
if(id == 0){
cout << "child:" <<getpid()<<endl;
成功
//execl("/bin/ls", "ls", "-a", "-l",NULL);
//失败
execl("/bin/ls1111111","ls","-a","-l",NULL);
cout <<"child fail"<<endl;
exit(1);
}else{
sleep(5);
int status = 0;
cout << "parent:"<<getpid()<<endl;
waitpid(id,&status,0);
cout << "child exit code:"<<WEXITSTATUS(status)<<endl;
}
return 0;
}
其他接口:
// 路径(要找到该进程) 可执行程序 可变参数
//int execl(const char *path, const char *arg, ...);
//调用自己写的程序:
execl("./other/othertest", "othertest", NULL);
//v:vector 路径 二维数组
//int execv(const char *path, char *const argv[]);
char *const myargv[]={"ls", "-a", "-l", NULL};
execv("/bin/ls", myargv);
//p:PATH -- 在环境变量中查找,需要先在环境变量中设置
// 环境变量名-进程名
//int execlp(const char *file, const char *arg, ...);
execlp("ls", "ls", "-a", "-l", NULL);
int execvp(const char *file, char *const argv[]);
char *const myargv[]={"ls", "-a", "-l", NULL};
execvp("ls", myargv);
// 环境变量(会覆盖系统的环境变量)
//int execle(const char *path, const char *arg,..., char * const envp[]);
const char* myenv[]={"MYENV=thismyenv", NULL};
execle("./other/othertest", "othertest", NULL, myenv);
//不传自定义的环境变量,传父进程的环境变量:
//获取环境变量
extern char** environ;
execle("./other/othertest", "othertest", NULL, environ);
//在当前进程中新增环境变量:int putenv(char *string);
//添加环境变量
putenv("MYENV=NEWENV");
execle("./other/othertest", "othertest", NULL, environ);