进程创建
在Linux中使用fork函数,通过复制调用进程来创建一个新的进程,新的进程为子进程
原理:进程调用fork函数,当控制转移到内核中的fork代码后,内核分配新的内存块和内核数据结构给子进程,将父进程的部分数据结构拷贝到子进程,添加子进程到系统进程列表中,fork返回,开始调度器调度
父子进程:代码共享,数据独有
fork函数返回值:父进程返回子进程的pid;子进程返回0
代码实现:
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main() {
5 int g_val=10;
6 pid_t pid=fork();
7 if(pid<0){
8 return -1;
9 }else if(pid==0){
10 g_val=20;
11 printf("i am child:%d,%d\n",getpid(),g_val);
12 }else{
13 printf("i am parent:%d,%d\n",getpid(),g_val);
14 }
15 return 0;
16 }
使用fork函数成功创建了一个子进程,子进程pid为4186,父进程pid为4185。在子进程中将g_val修改为20,父进程打印出来的仍是10,不难看出父子进程之间代码共享,数据独有的特性
vfork:vfork也可以用来创建子进程,父子进程共用同一个虚拟地址空间。但现在已经被淘汰了
进程等待
进程等待就是等待子进程的退出(状态改变),获取子进程的退出返回值(退出原因),避免僵尸进程
进程等待的方法:
wait方法
pid_t wait(int *status);
status:为NULL时表示不关心子进程的退出状态
一直等待任意一个子进程的退出,子进程退出后返回值放入参数status中,若一直没有子进程退出,wait函数将一直阻塞
waitpid方法
pid_t waitpid(pid_t pid,int *status,int options);
pid: -1等待任意一个子进程,pid>0等待指定的子进程
options:0为默认阻塞;WNOHANG将waitpid设置为非阻塞
阻塞等待任意一个子进程或指定的子进程退出
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<errno.h>
5 #include<string.h>
6 #include<sys/wait>
7
8 int main() {
9 int pid=fork();
10 if(pid<0) {
11 //直接打印错误原因
12 perror("fork error");
13 //errno是一个全局变量,存储系统调用出现错误原因编号
14 printf("fork error:%s\n",strerror(errno));
15 }else if(pid ==0){
16 sleep(3);
17 exit(0);
18 }
19 //wait(NULL);
20 //waitpid(pid,NULL,0);
21 while(1){
22 printf("i am parent!\n");
23 sleep(1);
24 }
25 return 0;
26 }
没有进行进程等待,可以看出子进程先于父进程退出,操作系统为了保存退出原因,不能将子进程资源全部释放,子进程成为了僵尸进程
将注释掉的19/20行代码取消注释,使用wait或者waitpid方法来等待子进程的退出,可以看到子进程成功退出,并且没有成为僵尸进程
进程终止
进程退出的场景:
代码运行完毕,结果符合预期正常退出
代码运行完毕,结果不符合预期正常退出
代码异常终止
进程常见的退出方式:
正常终止:从main函数返回、调用exit库函数、调用_exit系统接口
异常终止:Ctrl + c
return退出
1 #include<stdio.h>
2 #include<stdlib.h>
3
4 int main() {
5 printf("hello!");
6 return 0;
7 }
exit函数
1 #include<stdio.h>
2 #include<stdlib.h>
3
4 int main() {
5 printf("hello!");
6 exit(1);
7 }
_exit系统调用接口
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<unistd.h>
4
5 int main() {
6 printf("hello!");
7 _exit(1);
8 }
对比上面三种退出方式,我们可以看到这三种退出,前两种的结果是打印内容然后退出,在退出前会刷新缓冲区,_exit系统调用接口直接退出,退出前不会刷新缓冲区,并将缓冲区中的数据丢弃