Linux学习笔记--进程控制
Posted 水澹澹兮生烟.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux学习笔记--进程控制相关的知识,希望对你有一定的参考价值。
目录
一.进程创建
1.1写实拷贝
一般情况下,父子进程代码共享,数据独有,如下图:
二.进程终止
2.1进程终止退出场景
- 代码执行完成且正确
- 代码执行完成,但是不正确
- 代码异常终止
2.2常见的终止方法
- main函数中return
- exit()函数,_eixt()函数
- cttrl+c
介绍exit()和_eixt()函数:
void exit(int status);
- 他是一个库函数
- 谁调用谁退出
- status是进程退出的状态码
void _eixt(int status);
- 他是一个系统调用函数
- 谁调用谁退出
eixt()的内部封装了_eixt(),eixt()在退出时要比_eixt()多执行两个步骤,分别是:用户自定义的清理函数和刷新缓冲区。
2.3exit()与_eixt()函数的区别
2.3.1回调函数
int atexit(void (*function)(void));
//先注册回调函数
//再调用回调函数
参数为函数指针类型,接受一个函数的地址,函数的返回值为void类型,参数也是void类型。atexit()是注册了一个函数mycallback,注册并非是调用。当main函数结束后,才会调用刚刚注册的mycallback()。
代码展示:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
void mycallback(void){
printf("i am callback!!\\n");
}
int mian(){
atexit(mycallback);
printf("hello world\\n");
return 0;
}
2.3.2刷新缓冲区
刷新缓冲区的几种办法:
- main()中的return返回后刷新
- fflush();
- \\n 进行刷新
- eixt()
三.进程等待
3.1作用
父进程调用等待的方法,等待子进程退出,防止子进程变成僵尸进程。
3.2进程等待的方法
3.2.1wiat()
pid_t wait(int *status);
参数:输出型参数,将wait函数内部计算的结果通过status返回给调用者;
在这里给出其他的一些参数:输入型参数:调用者给被调用者传参;输出型参数给指针,输入型参数给引用。
status扩展:如何判定当前程序一定是正常或者异常退出呢?
在这里我们要知道,并不是所有异常都会产生coredump标志位,当发生硬限制(磁盘因素,内存空间不足)或者软限制时不会产生,因此直接用coredump标志位直接判断是不可取的。因此我们只能查看是否有退出信号。
举例:现在给你了值status,它调用了wiat()函数,现在wait将status将其后两个字节进行填充,那么判断如下:status&0x7F。
判断是否有coredump标志位:(status>>>7)&0x1
判断退出码:s(status>>8)&0xFF
代码实展示:
#include<stdio.h>
#include<unistd.h>
#include<wait.h>
int main(){
pid_t pid=fork();
if(pid < 0){
printf("create process failed");
return 0;
}else if(pid == 0){
printf("i am child process!!!\\n");
sleep(5);
}else{
while(1){
wait(NULL);
printf("i am father process\\n!");
sleep(2);
}
}
}
阻塞的概念:在调用结果返回之前,当前线程(执行流)会被挂起,并在得到结果之后返回。
3.2.2waitpid()
pid_t wiatpid(pid_t pid,int *status,int options);
pid=-1 | 等待人一个子进程,与wiat等效 |
pid>0 | 等待其进程ID与pid相等的子进程 |
status | 同wait |
options为0 | 阻塞模式 |
options为WNOHANG | 非阻塞模式 |
waitpid()中非阻塞模式一定要与循环一起使用。非阻塞模式时,如果不能立刻得到结果,则该调用者不会阻塞当前线程,因此非阻塞模式的情况,调用者需要定时轮询查看状态。waitpid默认阻塞等待任意或者指定子程序退出,当options被设置为WNOHANG,那么此时此时无子程序退出,waitpid返回值为0.
四.进程程序替换
替换正在运行的程序,让正在运行的进程执行其他程序。
4.1替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数 以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动 例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
4.2替换函数(exec函数簇)
4.2.1常用函数
a.execl()函数
int execl(const char* path,const char* arg,...);
参数:
path:待要替换的可执行程序,需要指定该程序再那个路径下
arg:可执行程序传递的命令行参数,在这要注意,第一个参数必须是可执行程序本身;可变参数列表(...)也是填充可执行程序的参数们需要以NULL结尾
返回值:如果替换成功,就执行其他程序了,如果替换失败,返回-1
代码展示:
b.execlp()函数
int execlp(const char* file,const char* arg,...);
参数:
file:带要替换的可执行程序,可以不给出路径,单数一定再PATH环境变量中可以找得到
arg:可执行程序传递的命令行参数,在这要注意,第一个参数必须是可执行程序本身;可变参数列表(...)也是填充可执行程序的参数们需要以NULL结尾
返回值:如果替换成功,就执行其他程序了,如果替换失败,返回-1
c.execle()函数
int execle(const char* path,const char* arg,...,char *const envp[]);
参数:
path:待要替换的可执行程序,需要指定该程序再那个路径下
arg:可执行程序传递的命令行参数,在这要注意,第一个参数必须是可执行程序本身;可变参数列表(...)也是填充可执行程序的参数们需要以NULL结尾
envp[]:表示程序员自己组织的环境变量
返回值:如果替换成功,就执行其他程序了,如果替换失败,返回-1
d.execv()函数
int execv(const char* path,char *const argv[]);
参数:
path:待要替换的可执行程序,需要指定该程序再那个路径下
argv[]:它是一个指针数组,保存的是给可执行程序传递的参数(这里要注意,数组的第一个元素为可执行程序本身,最后一个为NULL)
返回值:如果替换成功,就执行其他程序了,如果替换失败,返回-1
e.execvp()函数
int execvp(const char* file,char *const argv[]);
参数:
file:带要替换的可执行程序,可以不给出路径,单数一定再PATH环境变量中可以找得到
argv[]:它是一个指针数组,保存的是给可执行程序传递的参数(这里要注意,数组的第一个元素为可执行程序本身,最后一个为NULL)
返回值:如果替换成功,就执行其他程序了,如果替换失败,返回-1
f.execve()函数
int execve(coanst char* filename,char *const argv[],char *const envp[]);
参数:
filename:带有路径的可执行程序
argv[]:它是一个指针数组,保存的是给可执行程序传递的参数(这里要注意,数组的第一个元素为可执行程序本身,最后一个为NULL)
envp[]:表示程序员自己组织的环境变量,最后一个参数为NULL
返回值:如果替换成功,就执行其他程序了,如果替换失败,返回-1
4.2.2总结exec()
a.函数名称当中带p与不带p区别:如果函数名称中带p,则会自动搜索环境变量,而不带p则不会自动搜索。
b.函数名称中带e与不带e的区别:如果函数名称带e,表示程序员需要自己组织环境变量,不带则不需要自己组织。
c.函数名称中带l与不带l的区别:如果函数名称中带l,则表示函数的参数为可变参数列表形式。
d.函数名称中带v与不带v的区别:如果函数名称中带v,表示参数用数组。
e.替换之前的进程与替换之后的进程,他们的进程号是否是一样的?
4.3exec常用场景
4.3.1bash应用场景
子进程被创建出来后,子进程进行进程程序替换。
4.3.2守护进程
- 启动业务进程时并非直接启动业务进程,而是启动守护进程;
- 让守护进程创建一个子进程,让子进程进程程序替换业务程序进程;
- 守护进程和业务进程进行进程通信,让守护进程得知业务进程的情况。
业务进程正常,则守护进程不做任何处理,业务进程崩溃或者异常,则守护进程重新拉起来一个子进程,让子进程再进行进程程序替换。
以上是关于Linux学习笔记--进程控制的主要内容,如果未能解决你的问题,请参考以下文章