Linux系统编程篇--进程控制篇
Posted 蚍蜉撼树谈何易
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux系统编程篇--进程控制篇相关的知识,希望对你有一定的参考价值。
进程控制篇
了解fork()函数
在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
头文件:
#include<unistd.h>
pid_t pd=fork();
返回值类型:
子进程中返回0,父进程返回子进程的pid,失败返回-1;
什么是子进程
子进程是以父进程为“模板”生成的一个新进程
系统调用fork,内核中做了什么?
1.分配新的内存块和内核数据结构给子进程
2.将父进程部分数据结构内容拷贝至子进程
3.添加子进程到系统进程列表当中
4.fork返回,开始调度器调度
fork()基本用途
1.一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
2.一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
fork()失败原因
1.系统中有太多的进程
2.实际用户的进程数超过了限制
了解写时拷贝
通俗点来说,就是什么时候修改,什么时候再进行拷贝。
概念
操作系统通过fork创建子进程之后,子进程复制了父进程,一开始父子进程指向同一块物理内存空间,因此数据看起来是一模一样的。但是进程间具有独立性,因此当物理空间的数据发生改变时,子进程就会重新开辟物理空间,将数据拷贝过来。这就是写时拷贝技术。因为代码是只读的,因此父子进程数据独有,代码共享。 (当物理空间数据发生改变时)
两种方式
对数据不做修改的情况下
对数据做出修改
进程退出
进程退出情况
1.代码运行完毕,结果正确
2.代码运行完毕,结果不正确
3.代码异常终止
查看进程退出码
echo $?
进程常见的退出方法
1.main()函数return退出。
1 #include<iostream>
2 using namespace std;
3 int main()
4 {
5 cout<<"hello Linux"<<endl;
6 return 21;
7 }
可以看到我们的退出码 (main函数的return值为退出码)为21。
说一下为什么我们main()函数的退出码为什么是0?
一般在编程中,我们一般认为0是程序正确执行的返回值,通过返回值的不同确定错误的类型。
2.调用exit()
#include <stdlib.h>
void exit(int status);
//status为退出码,这个函数是来终止程序的,只要执行到该语句,程序直接退出,哪怕后面还有未执行的语句,
3._exit()函数
#include <unistd.h>
void _exit(int status);
status仍为退出码
回调函数
exit()函数和_exit()函数区别
1.exit()函数为库函数,_exit()为系统调用函数
2.exit()函数会比_exit多做两件事情,刷新缓冲区,执行用户自定义清理函数。
3.exit()函数内部封装了_exit();
刷新缓冲区
1.\\n刷新
1 #include<iostream>
2 #include<stdlib.h>
3 using namespace std;
4 int main()
5 {
6
7 cout<<"hello Linux";
8 return 21;
9 }
2.fflush
头文件#include<stdio.h>
fflush(file* fp);
3.exit()函数刷新
4.main()函数return回来后
进程等待
进程等待的必要性
1.之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
2.另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
3.最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
4.父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
1 #include<iostream>
2 #include<stdlib.h>
3 using namespace std;
4 #include<unistd.h>
5 void mycallback()
6 {
7 cout<<"this is my callback"<<endl;
8 }
9 int main()
10 {
11
12 pid_t pd=fork();
13 if(pd<0)
14 {
15 cerr<<"fork failed"<<endl;
16 }
17 else if(pd==0)
18 {
19 cout<<"i am son"<<endl;
20 }
21 else
22 {
23 while(1)
24 {
25 cout<<" i am father"<<endl;
26 sleep(1);
27 }
28 }
return 0;
}
进程等待,wait()函数
#include <sys/wait.h>
pid_t wait(int *stat_loc);
1 #include<iostream>
2 #include<stdlib.h>
3 using namespace std;
4 #include<unistd.h>
5 #include<sys/wait.h>
6 void mycallback()
7 {
8 cout<<"this is my callback"<<endl;
9 }
10 int main()
11 {
12
13 pid_t pd=fork();
14 if(pd<0)
15 {
16 cerr<<"fork failed"<<endl;
17 }
18 else if(pd==0)
19 {
20 cout<<"i am son"<<endl;
21 }
22 else
23 {
24 while(1)
25 {
26 wait(NULL);
27 cout<<" i am father"<<endl;
28 sleep(1);
29 }
30 }
31 return 0;
32 }
wait函数的参数:
为一个输出型参数,区分一下输入型参数与输出型参数
返回值:
成功返回被等待进程pid,失败返回-1。
原理 :
父进程调用wait阻塞,wait是阻塞式的,父进程调用wait会被阻塞住,父进程调用wait就会一直在这里等SIGCHILD信号,收到这个信号就说明父进程要去回收僵尸进程了
阻塞的概念:
需要一直在这等着,不能去做其他的事情。
waitpid()
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
1 #include<iostream>
2 #include<stdlib.h>
3 using namespace std;
4 #include<unistd.h>
5 #include<sys/wait.h>
6 void mycallback()
7 {
8 cout<<"this is my callback"<<endl;
9 }
10 int main()
11
12 {
13 pid_t pid=fork();
14
15 if(pid<-1)
16 {
17 cerr<<"create failed"<<endl;
18 }
19 else if(pid==0)
20 {
21 cout<<"i am son"<<endl;
22 sleep(20);
23 }
24 cout<<"before receive"<<endl;
25 int result=waitpid(pid,NULL,0);
26 if(result<0)
27 {
28 cout<<"wait failed"<<endl;
29 }
30 else
31 {
32 cout<<"wait success"<<"pid is"<<pid <<endl;
33 }
34 return 0;
35 }
~
为什么会打印waitfailed,这是因为子进程也在调用这个方法。对它来说,它没有要等的,所以在子进程中result是小于0的。
可以看到在options为0的情况下,其父进程就一直在等子进程,等到了就退出,并返回子进程的pid,0为阻塞模式。
根据进程退出码判断进程退出情况?
进程替换
替换函数
#include <unistd.h>`
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
规律:带l的其参数列表以可变参数列表给出
带p的可以不用显式的给出其绝对路径
带v的起参数列表必须是一个指针数组
带e的表示程序员需要自己组织环境变量,不带e的可以不需要
execl
函数原型:
int execl(const charpath,const chararg,…);
1 #include<iostream>
2 #include<unistd.h>
3 using namespace std;
4 int main()
5 {
6 cout<<"process begin"<<endl;
7 execl("/usr/bin/ls","ls","-a","-l",NULL);
8 cout<<"process end"<<endl;
9 return 0;
10 }
代码运行结果:
execlp
1 #include<iostream>
2 #include<unistd.h>
3 using namespace std;
4 int main()
5 {
6 cout<<"process begin"<<endl;
7 //execl("/usr/bin/ls","ls","-a","-l",NULL);
8 execlp("ls","ls","-a","-l",NULL);
9 cout<<"process end"<<endl;
10 return 0;
11 }
~
exelce
execve
execv
1 #include<unistd.h>
2 #include<stdio.h>
3 int main()
4 {
5 printf("process begin\\n");
6 //execl("/usr/bin/ls","ls","-a","-l",NULL);
7 //execlp("ls","ls","-a","-l",NULL);
8 char *env[]={
9 "ls",
10 "-a",
11 "-l",
12 NULL,};
13
14 // execle("./mycom","./mycom",NULL,env);
15 execv("/usr/bin/ls",env);
16 printf("process end\\n");
17 return 0;
18 }
execvp
1 #include<unistd.h>
2 #include<stdio.h>
3 int main()
4 {
5 printf("process begin\\n");
6 //execl("/usr/bin/ls","ls","-a","-l",NULL);
7 //execlp("ls","ls","-a","-l",NULL);
8 char *env[]={
9 "ls",
10 "-a",
11 "-l",
12 NULL,};
13
14 // execle("./mycom","./mycom",NULL,env);
15 // execv("/usr/bin/ls",env);
16 execvp("ls",env);
17 printf("process end\\n");
18 return 0;
19 }
~
六个替换函数的关系
,execlp,execl,execle,execvp,execv它们五个为库函数,这五个库函数是封装了execve()系统调用函数的。
替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动
例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变
进程替换的常见使用场景
1.bash命令行解释器。众所周知,bash通过创建子进程来执行程序,其本质原因是利用了进程独立性的特点,保护bash。
2。守护进程
好了,下课!!!
以上是关于Linux系统编程篇--进程控制篇的主要内容,如果未能解决你的问题,请参考以下文章
Linux 编程之进程篇:task_struct进程创建和退出
Linux 编程之进程篇:task_struct进程创建和退出