1、fork函数的使用
使用fork()函数创建一个进程
pid_t fork(void)
fork函数调用成功,返回两次
返回为0, 代表当前进程是子进程
返回为正数,代表当前进程为父进程
fork()函数运行后会创建一个进程,加上开始的进程一共有两个进程,会执行两次fork()函数之后的内容,两次的区别是pid号不同
1 #include <sys/types.h>
2 #include <unistd.h>
3 #include <stdio.h>
4 int main()
5 {
6 pid_t pid;
7 pid_t pid2;
8 pid = getpid();
9
10 printf("before fork, pid :%d\n", pid);
11
12 fork();
13
14 pid2 = getpid();
15
16 if (pid == pid2)
17 {
18 printf("this is father, pid :%d\n", pid2);
19 }
20 else{
21
22 printf("this is child, pid :%d\n", pid2);
23 }
24 return 0;
25 }
1 #include <sys/types.h>
2 #include <unistd.h>
3 #include <stdio.h>
4 int main()
5 {
6 pid_t pid;
7
8 printf("father pid :%d\n", getpid());
9
10 pid = fork();
11
12
13 if (pid > 0)
14 {
15 printf("this is father, pid :%d\n", getpid());
16 }
17 else if(pid == 0) {
18 printf("this is child, pid :%d\n", getpid());
19 }
20 return 0;
21 }
2、进程创建发生了什么事
发生了:将父进程的内容复制一份给子进程
在传统内核中,是复制P1的代码段,数据段,堆,栈这四个部分,注意是其内容相同。(拷贝一模一样的)
写时拷贝:(copy on wirte)
内核只为新生成的子进程创建虚拟空间结构,它们复制于父进程的虚拟空间结构,但是不为它们分配内存,而是共享父进程的物理内存,当父子进程有更改相应段的行为发生时,再为子进程相应段分配物理内存。
3、vfork函数创建进程
与fork的区别:
1、vfork使用父进程存储空间,不拷贝
2、vfork保证子进程先运行,当子进程调用exit()推出后,父进程才执行
先用fork创建一次进程,例如:
1 #include <sys/types.h>
2 #include <unistd.h>
3 #include <stdio.h>
4 int main()
5 {
6 pid_t pid;
7
8 pid = fork();
9
10
11 if (pid > 0)
12 {
13 while(1)
14 {
15 printf("this is father, pid :%d\n", getpid());
16 sleep(1);
17 }
18 }
19 else if(pid == 0)
20 {
21 while(1)
22 {
23 printf("this is child, pid :%d\n", getpid());
24 sleep(1);
25 }
26 }
27 return 0;
28 }
~
结果是father、child进程彼此争夺CPU,在彼此while(1)不断循环
现在用vfork()创建进程,例如:
1 #include <sys/types.h>
2 #include <unistd.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 int main()
6 {
7 pid_t pid;
8 int cnt = 0;
9 pid = vfork();
10
11 if (pid > 0)
12 {
13 while(1)
14 {
15 printf("cnt=%d\n", cnt);
16 printf("this is father, pid :%d\n", getpid());
17 sleep(1);
18 }
19 }
20 else if(pid == 0)
21 {
22 while(1)
23 {
24 printf("this is child, pid :%d\n", getpid());
25 sleep(1);
26
27 if (++cnt == 3)
28 {
29 exit(0);
30 }
31 }
32 }
33 return 0;
34 }
可见前期一直是子进程运行,运行三次后调用exit(0);正常退出后才轮到父进程运行,且在子进程里面修改的cnt的值在父进程中也“同步”修改,但并不是同步,是父子进程共用存储空间
4、进程退出
正常退出:
1、main函数调用return;
2、进程调用exit(0);标准C库;
3、进程调用_exit();或者_Exit(),属于系统调用;补充:
1、进程最后一个线程返回
2、最后一个线程调用pthread_exit
异常退出:
1、调用abort
2、当进程收到某些信号时,如ctrl+c
3、最后一个线程对取消(cancellation)请求做出响应
5、父进程等待子进程退出(一)
父进程等待子进程退出
并收集子进程的退出状态
子进程退出状态不被收集,变成僵死进程(僵尸进程),状态是Z+(zombie)
父进程收集子进程退出状态,相关函数:
status参数:是一个整型数指针
非空:子进程退出状态放在它所指向的地址中
空: 不关心退出状态
代码实现一下:
1 #include <sys/types.h>
2 #include <unistd.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 int main()
6 {
7 pid_t pid;
8 int cnt;
9 pid = fork();
10
11 if (pid > 0)
12 {
13 while(1)
14 {
15 wait(NULL);///
16 printf("cnt=%d\n", cnt);
17 printf("this is father, pid :%d\n", getpid());
18 sleep(1);
19 }
20 }
21 else if(pid == 0)
22 {
23 while(1)
24 {
25 printf("this is child, pid :%d\n", getpid());
26 sleep(1);
27
28 if (++cnt == 3)
29 {
30 exit(0);
31 }
32 }
33 }
34 return 0;
35 }
加入了wait(NULL)等待,父进程就会一直阻塞,等待子进程退出后再运行,但是不会收集子进程的状态码,
如果我们想要知道子进程的退出码怎么办呢:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int cnt;
int status; /
pid = fork();
if (pid > 0)
{
while(1)
{
wait(&status);/
printf("status = %d\n", status);/
printf("cnt=%d\n", cnt);
printf("this is father, pid :%d\n", getpid());
sleep(1);
}
}
else if(pid == 0)
{
while(1)
{
printf("this is child, pid :%d\n", getpid());
sleep(1);
if (++cnt == 3)
{
exit(3);//
}
}
}
return 0;
}
我们在exit()里面写的退出码是3,但是通过wait获取的状态码并不是3,这是为什么呢
要想得到状态码,要通过宏来解析它
用于检查 wait() 和 waitpid() 两个函数返回终止状态的宏: 这两个函数返回的子进程状态都保存在status指针中,用以下3个宏可以检查该状态:
WIFEXITED(status): 若为正常终止, 则为真。此时可执行 WEXITSTATUS(status): 取子进程传送给exit或_exit参数的低8位.
WIFSIGNALED(status): 若为异常终止, 则为真。此时可执行 WTERMSIG(status): 取使子进程终止的信号编号.
WIFSTOPPED(status): 若为当前暂停子进程, 则为真。此时可执行 WSTOPSIG(status): 取使子进程暂停的信号编号
printf("status = %d\n", status);
改写为:
printf("status = %d\n", WEXITSTATUS(status));
6、父进程等待子进程退出(二)
再来细说wait()函数
如果其所有子进程都还在运行,则阻塞
如果一个子进程已终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回
如果它没有任何子进程,即立即出错返回
wait()与waitpid()的区别:
wait()使调用者阻塞
waitpid()有一个选项,可以使调用者不阻塞
对于waitpid()的参数:
参数值 | 说明 |
---|---|
pid<-1 | 等待进程组号为pid绝对值的任何子进程。 |
pid=-1 | 等待任何子进程,此时的waitpid()函数就退化成了普通的wait()函数。 |
pid=0 | 等待进程组号与目前进程相同的任何子进程,也就是说任何和调用waitpid()函数的进程在同一个进程组的进程。 |
pid>0 | 等待进程号为pid的子进程。 |
参数 | 说明 |
---|---|
WNOHANG | 如果pid指定的子进程没有结束,则waitpid()函数立即返回0,而不是阻塞在这个函数上等待;如果结束了,则返回该子进程的进程号。 |
WUNTRACED | 如果子进程进入暂停状态,则马上返回。 |
但是调用waitpid(),并且使用WNOHANG 参数的话,虽然父进程不会阻塞等待子进程结束,但是等子进程结束后会变成僵尸进程
7、孤儿进程
父进程如果不等待子进程退出,在子进程之前就结束了总结的生命,此时的子进程叫做孤儿进程
Linux避免系统存在过多孤儿进程,init进程收留孤儿进程,变成孤儿进程的父进程
8、exec族函数
exec族函数相当于就是调用打包好的程序,执行一个shell指令,详情可见博文
在A进程可以启动B进程,跳到B进程去执行
9、system函数
https://www.cnblogs.com/leijiangtao/p/4051387.html
10、popen()函数
https://blog.csdn.net/libinbin_1014/article/details/51490568
比system的好处:可以获取运行的输出结果
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
char ret[1024] = {0};
FILE *fp;
fp = popen("ps", "r");
int nread = fread(ret, 1, 1024, fp);//读进ret数组,一次读一个,读1024次,从fp里面读
printf("read ret %d byte, ret=%s\n", nread, ret);
return 0;
}