进程创建
- fork函数介绍
创建子进程,会分配新的内存块和内核数据结构(PCB、地址空间、页表等),将父进程部分数据结构内容拷贝到子进程,添加子进程到系统进程列表中,然后fork返回,开始调度器调度
fork函数有2个返回值,因为在调用fork后,有两个进程,因此有2个返回值,子进程返回0,父进程返回子进程的pid;
fork()之后,父子进程代码共享,数据以写时拷贝的方式各自私有一份,父子进程让他们的代码段映射到相同的物理内存的段,因此可以做到代码共享。
fork调用失败原因:系统中存在太多的进程或者实际用户的进程数超过了限制
写时拷贝:(1)创建子进程,子进程复制父进程,因此与父进程运行的代码和数据看起来一样,因此子进程的虚拟地址空间和页表和父进程一样,因此父子进程映射之后,指的是同一块物理内存;
(2)子进程复制了父进程的程序计数器/上下文数据,因此连当前的运行位置都一样;
(3)若原物理内存数据发生改变,则为了保证进程独立性,因此给子进程重新开辟物理内存,拷贝数据; - vfork()函数介绍
它的作用也是创建子进程,在早期为了提高子进程的创建效率而设计的接口—现在fork有了写时拷贝,因此不怎么常用了,父进程调用vfork之后会阻塞,不再运行,直到子进程调用exit接口退出或者程序替换为止;
因为vfork创建的子进程与父进程共用同一块虚拟地址空间(两个进程同时运行会造成栈混乱);因此阻塞父进程,子进程不能在main函数中使用return退出,因为会释放所有资源(虚拟地址空间、页表等)
进程终止
- 进程终止场景
正常退出:代码运行完毕,结果正常和代码运行完毕,结果不正确
异常退出 - 进程常见退出方法
正常终止:
1、从main返回(main函数返回值等于当前进程的退出码,可以通过echo $?查看最近进程退出码)
2、调用exit(库函数)
3、_exit(系统调用函数)
例如编写代码
int main()
{
printf("hello bit!\n");
return 0;
}
运行后,可以通过echo $?查看它的退出码,例如:
[Daisy@localhost LinuxCode]$ ./myenv
hello bit!
[Daisy@localhost LinuxCode]$ echo $?
0
可以看出它的退出码是0
例如:
[Daisy@localhost LinuxCode]$ ls file
ls: 无法访问file: 没有那个文件或目录
[Daisy@localhost LinuxCode]$ echo $?
2
可以看出退出码是2,代表没有那个文件或目录
- exit函数
查看关于它的man手册
#include <stdlib.h>
void exit(int status);
DESCRIPTION 描述
函数 exit() 使得程序正常中止,status & 0377 的值被返回给父进程 (参见 wait(2)) 。所有用 atexit() 和on_exit() 注册的函数都以与注册时相反的顺序被依次执行。使用 tmpfile() 创建的文件被删除。
C 标准定义了两个值 EXIT_SUCCESS 和 EXIT_FAILURE,可以作为 exit() 的参数,来分别指示是否为成功退出。
发现它是用atexit函数注册的。
例如:
int main()
{
printf("hello bit!\n");
exit(123);
}
查看当前退出码例如:
[Daisy@localhost LinuxCode]$ ./myenv
hello bit!
[Daisy@localhost LinuxCode]$ echo $?
123
可以看出exit()函数()中放的是退出码,再比如:
void test()
{
exit(21);
}
int main()
{
printf("hello bit!\n");
test();
}
最终结果是:
[Daisy@localhost LinuxCode]$ ./myenv
hello bit!
[Daisy@localhost LinuxCode]$ echo $?
21
可以看出在任何地方调exit函数都表示当前进程退出,return返回值是在main函数中退出,比如:
void test()
{
exit(21);
}
int main()
{
printf("hello bit!\n");
test();
while(1)
{
printf("----------\n");
}
}
最终运行结果是:
[Daisy@localhost LinuxCode]$ ./myenv
hello bit!
可以看出并没有执行循环,说明是在函数test就退出了
调用exit函数会刷新缓冲区;
还有一个_exit函数(一般不建议使用),它的功能与exit函数基本一样,但是也有区别,例如:
int main()
{
printf("hello bit");
sleep(3);
_exit(32);
}
它的结果是:
[Daisy@localhost LinuxCode]$ ./myenv
[Daisy@localhost LinuxCode]$ echo $?
32
发现没有输出结果,说明它是直接退出,不会打印结果,而exi函数首先将所有用atexit函数注册的函数进行调用后再退出。
进程等待
必要性:
子进程退出,父进程如果不管它,就可能在成僵尸进程的问题,造成内存泄漏,进程一旦称为僵尸进程,即使是kill -9也无法杀死;
子进程的退出信息也需要获取
- 进程等待的方法
1、wait方法
例如编写代码:
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
using namespace std;
int main()
{
pid_t id=fork();
if(id==0)
{//child
cout<<"I am child....."<<endl;
sleep(5);
exit(1);
}
else
{
wait(NULL);
cout<<"I am parent......"<<endl;
}
return 0;
}
打开另一个终端,执行
while :; do ps ajx | grep test | grep -v grep;echo "###########################";sleep 1;done
让它循环执行这个命令
然后在当前终端开始运行test文件,然后看另一终端,结果是:
[Daisy@localhost ~]$ while :; do ps ajx | grep test | grep -v grep;echo "###########################";sleep 1;done
###########################
###########################
7264 9982 9982 7264 pts/3 9982 S+ 1000 0:00 ./test
9982 9983 9982 7264 pts/3 9982 S+ 1000 0:00 ./test
###########################
7264 9982 9982 7264 pts/3 9982 S+ 1000 0:00 ./test
9982 9983 9982 7264 pts/3 9982 S+ 1000 0:00 ./test
###########################
7264 9982 9982 7264 pts/3 9982 S+ 1000 0:00 ./test
9982 9983 9982 7264 pts/3 9982 S+ 1000 0:00 ./test
###########################
7264 9982 9982 7264 pts/3 9982 S+ 1000 0:00 ./test
9982 9983 9982 7264 pts/3 9982 S+ 1000 0:00 ./test
###########################
7264 9982 9982 7264 pts/3 9982 S+ 1000 0:00 ./test
9982 9983 9982 7264 pts/3 9982 S+ 1000 0:00 ./test
###########################
###########################
这时过了5秒后,子进程先退出,因为父进程调用了wait,如果子进程没有退出,父进程就会一直等,陷入阻塞状态,也就是父进程等了5秒钟,子进程退出,父进程才退出。
2、waitpid方法
例如vim myproc.c:
1 #include <unistd.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <sys/wait.h>
5 #include <sys/types.h>
6 int main()
7 {
8 pid_t id=fork();
9 if(id==0)
10 {
11 printf("child:pid:%d,ppid:%d\n",getpid(),getppid());
12 sleep(5);
13 exit(13);
14 //child
15 }
16 else
17 {
18 printf("father:pid:%d,ppid:%d\n",getpid(),getppid());
19 int status=0;
20 sleep(10);
21 int ret=waitpid(id,&status,0);
22 if(ret<0)
23 {
24 printf("wait error,wait ret:%d\n",ret);
25 }
26 else
27 {
28 printf("wait success .......\n");
29 }
30 sleep(5);
31 //father
32 }
33 return 0;
打开另一终端,执行
while :; do ps axj | grep myproc | grep -v grep;sleep 1;echo "######################";done
在当前终端make之后,./运行myproc,在另一终端查看,得到:
[Daisy@localhost LinuxCode]$ while :; do ps axj | grep myproc | grep -v grep;sleep 1;echo "######################";done
######################
######################
######################
4083 4273 4273 4083 pts/3 4273 S+ 1000 0:00 ./myproc
4273 4274 4273 4083 pts/3 4273 S+ 1000 0:00 ./myproc
######################
4083 4273 4273 4083 pts/3 4273 S+ 1000 0:00 ./myproc
4273 4274 4273 4083 pts/3 4273 S+ 1000 0:00 ./myproc
######################
4083 4273 4273 4083 pts/3 4273 S+ 1000 0:00 ./myproc
4273 4274 4273 4083 pts/3 4273 S+ 1000 0:00 ./myproc
######################
4083 4273 4273 4083 pts/3 4273 S+ 1000 0:00 ./myproc
4273 4274 4273 4083 pts/3 4273 S+ 1000 0:00 ./myproc
######################
4083 4273 4273 4083 pts/3 4273 S+ 1000 0:00 ./myproc
4273 4274 4273 4083 pts/3 4273 Z+ 1000 0:00 [myproc] <defunct>
######################
4083 4273 4273 4083 pts/3 4273 S+ 1000 0:00 ./myproc
4273 4274 4273 4083 pts/3 4273 Z+ 1000 0:00 [myproc] <defunct>
######################
4083 4273 4273 4083 pts/3 4273 S+ 1000 0:00 ./myproc
4273 4274 4273 4083 pts/3 4273 Z+ 1000 0:00 [myproc] <defunct>
######################
4083 4273 4273 4083 pts/3 4273 S+ 1000 0:00 ./myproc
4273 4274 4273 4083 pts/3 4273 Z+ 1000 0:00 [myproc] <defunct>
######################
4083 4273 4273 4083 pts/3 4273 S+ 1000 0:00 ./myproc
4273 4274 4273 4083 pts/3 4273 Z+ 1000 0:00 [myproc] <defunct>
######################
4083 4273 4273 4083 pts/3 4273 S+ 1000 0:00 ./myproc
######################
4083 4273 4273 4083 pts/3 4273 S+ 1000 0:00 ./myproc
######################
4083 4273 4273 4083 pts/3 4273 S+ 1000 0:00 ./myproc
######################
4083 4273 4273 4083 pts/3 4273 S+ 1000 0:00 ./myproc
######################
4083 4273 4273 4083 pts/3 4273 S+ 1000 0:00 ./myproc
######################
分析发现前5秒钟父子进程都在正常运行,都处于S状态,5秒后,子进程成为Z(僵尸)进程,父进程开始等待(只剩下父进程),5秒后父进程退出。
获取子进程
wait与waitpid都有一个status参数,这个参数是一个输出型参数,由操作系统填充
如果传递NULL,表示不关心子进程的退出状态信息,否则,操作系统会根据该参数,将子进程的推出信息反馈给父进程,status当做位图来看待,如图:
它的低8位表示信号,低7位表示退出时具体收到的信号编号,低8位的倒数第8位表示当前进程是否core dump(核心转储),剩下的次8位表示退出状态(子进程返回值),例如将代码改为:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
pid_t id=fork();
if(id==0)
{
printf("child:pid:%d,ppid:%d\n",getpid(),getppid());
sleep(5);
exit(13);
//child
}
else
{
printf("father:pid:%d,ppid:%d\n",getpid(),getppid());
int status=0;
sleep(10);
int ret=waitpid(id,&status,0);
if(ret<0)
{
printf("wait error,wait ret:%d\n",ret);
}
else
{
printf("wait success...:%d\n",ret);
printf("exit status...:%d\n",(status>>8)&0XFF);
printf("exit signals...:%d\n",status&0X7F);
}
sleep(5);
//father
}
return 0;
}
将这个运行起来,打开另一终端,输入
while :; do ps axj | grep myproc | grep -v grep;sleep 1;echo "######################";done
可以看到
[Daisy@localhost LinuxCode]$ while :; do ps axj | grep myproc | grep -v grep;sleep 1;echo "###################";done
###################
###################
4731 5252 5252 4731 pts/5 5252 S+ 1000 0:00 ./myproc
5252 5253 5252 4731 pts/5 5252 S+ 1000 0:00 ./myproc
###################
4731 5252 5252 4731 pts/5 5252 S+ 1000 0:00 ./myproc
5252 5253 5252 4731 pts/5 5252 S+ 1000 0:00 ./myproc
###################
4731 5252 5252 4731 pts/5 5252 S+ 1000 0:00 ./myproc
5252 5253 5252 4731 pts/5 5252 S+ 1000 0:00 ./myproc
###################
4731 5252 5252 4731 pts/5 5252 S+ 1000 0:00 ./myproc
5252 5253 5252 4731 pts/5 5252 S+ 1000 0:00 ./myproc
###################
4731 5252 5252 4731 pts/5 5252 S+ 1000 0:00 ./myproc
5252 5253 5252 4731 pts/5 5252 Z+ 1000