Linux:进程管理 | 进程创建 | 进程终止 | 进程等待 | 进程替换
文章目录
- 进程创建
- fork
- 写时拷贝机制
- 进程终止
- 退出码
- 进程退出方法
- 进程等待
- 阻塞、非阻塞的等待
- 进程替换
- 替换函数
- exec l
- exec lp
- exec le
- exec ve
- exec vpe
- exec vp
- exec v
- 寄语
全文约 8351 字,预计阅读时长: 22分钟
进程创建
- 命令行启动命令(程序、指令等)
- 通过程序自身
fork
出来的子进程
- OS是进程、与内存的管理者。
fork
- . fork是系统提供的调用接口;进程调用fork,当控制转移到内核中的fork代码后,内核做:
- 分配新的内存块和内核数据结构给子进程;
- 将父进程部分数据结构内容拷贝至子进程;
- 添加子进程到系统进程列表当中;
- fork返回,开始调度器调度
创建子进程,本质是多了一个进程;多了一个进程是多了一套进程相关的数据结构。
- 所有fork出来的子进程大部分数据,都是以父进程为模板拷贝的
- 再不写入的情况下,用户的代码和数据是父子只读共享的
- 为什么是共享的?
- 因为子进程没有加载代码数据的过程,只能用父进程的。
- PC计数器也是共享的,fork这条指令执行的时候,PC已经指向下一条指令了。所以只能执行后面的代码
int main()
{const char* str ="hello world\n";fork();//父进程调用fork//子进程只会执行后面的代码while(1){printf(" pid: %s,ppid: %s, str:%s\n",getpid(),getppid(),str);sleep(1);}return 0;
}
- fork 常规用法:
- 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
- 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
- fork 调用失败的原因:
- 系统中统中有太多的进程
- 实际用户的进程数超过了限制
写时拷贝机制
- 一个变量里面,为什么会有两个不同的值?从而让父子进入不同的业务逻辑。
int main()
{const char* str ="hello world\n";pid_t ret = fork();//父进程调用fork//子进程只会执行后面的代码if(ret==0){while(1){printf("child: pid: %s,ppid: %s, str:%s\n",getpid(),getppid(),str);sleep(1);}}else if(ret>0)//返回子进程的PID{while(1){printf("father: pid: %s,ppid: %s, str:%s\n",getpid(),getppid(),str);sleep(1);}}else{perror("fork");}return 0;
}
- 通常,父子代码共享,父子再不写入时,数据也是共享的、只读的;当任意一方试图写入,便以写时拷贝的方式各自一份副本。
- 在fork返回之前,子进程已经创建出来了,也需要创建变量接受,将返回值写入变量里。
- 父子页表的映射数据映射到了不同的内存区域
- 父子进程其中一个要修改数据时,页表层会有一个报错,OS拦截下来;看看是什么原因报错(野指针、越界等);
- 一看是父子进程某一个写入,行吧,我知道了,给你拷贝一份和原数据一样大的空间…
- 所有的数据,子进程都拷贝一份不就好了吗?
(1). 不行,父进程有很多只读的数据,而子进程只需要修改其中的一些或几个。如果全拷贝,浪费内存和系统资源。
(2).fork 时,创建数据额结构,如果全拷贝,会降低fork的效率。
(3).fork 本身就要像系统要更多的资源,要的太多可能会导致 fork 失败。
进程终止
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码异常终止
退出码
- 为何main函数中,总是返回0?
- 给系统看,代表进程退出,0表示成功运行。
- 给系统看,代表进程退出,0表示成功运行。
- 退出码:可以人为的定义,也可以使用系统的错误码list。
#include<string.h>
#include<stdio.h>
int main()
{for(int i=0;i<135;++i){printf("[%d]:%s\n",i,strerror());}return 0;
}
- 当程序运行失败时,最关心程序为什么失败;退出码就承担着int—>string(错误信息描述)的转换。
- 父进程可以关心子进程的运行,也可以不关心。
- 在进程非正常结束的情况下,退出码毫无意义。
进程退出方法
- main 函数的return ;任何函数的 exit 。
- 非main 函数的 return 不是终止进程,而是结束函数;
- 任何函数的exit ,都表示终止进程。
- exit 在退出的时候,会进行后续资源处理,包括刷新缓冲区;_exit 则不会。
man 2/3 exit
站在操作系统角度,理解终止进程,核心思想:
- “释放”曾经为了管理进程所维护的所有数据结构对象。…释放:不是真的把数据结构对象销毁,而是设置成不用的状态,然后保存起来;如果这样的不用的对象多了,就会有一个“数据结构的池。”
- 释放程序代码和数据占用的内存空间。…不是把代码数据清空,而是把改内存空间设置成无效就可以。
- 取消曾经该进程的连接关系。…双向链表的指向关系取消。
- C语言也是有内存清理资源的函数。
进程等待
- 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
- 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
- 如果不存在该子进程,则立即出错返回。
pid_t wait ( int* status );
父进程调用等待。- 等待任意一个进程;
- 返回值是子进程的PID;失败返回-1
status
:操作系统在PCB里面存着的退出码(比特位第15位到第8位),需要(status>>8) & 0xFF
获得status
:如果传递NULL
,表示不关心子进程的退出状态信息。- 多个子进程的情况,利用循环调用等待子进程。
#include<stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0) { int count=5; while(count) { printf("child: i am running %d, ppid: %d, pid: %d\n",count--,getppid(),getpid()); sleep(1); } printf("child: i quit....\n"); exit(10); } else { printf("father is waiting:\n"); int status =0; pid_t ret = wait(&status); printf("father waited done :exit num: %d, ret: %d\n",(status>>8) & 0xFF,ret);//拿到 status 的第15 - 8的比特位 printf("father quit ...\n");}//if(ret == -1)...return 0;
}
pid_ t waitpid ( pid_t pid , int * status, int options);
options
:OS在PCB存着的退出信号:kill -num
;- 给 0 意味着采用阻塞等待的方式调用
waitpid
;退出信号存放在status
的比特位第6位到第0位,通过status & 0X7F
获得 - 给
WNOHANG
,表示采用非阻塞等待轮询探测调用waitpid
; WNOHANG
:pid指定的子进程没有结束,则waitpid()
函数返回0,不予以等待;父进程继续去做别的事情。- 若正常结束,则
waitpid()
返回该子进程的ID。
- 给 0 意味着采用阻塞等待的方式调用
- 参数:
pid
:Pid=-1
,等待任一个子进程,与wait等效。Pid>0
.等待其进程ID与pid相等的子进程。
- 返回值:当正常返回的时候waitpid返回收集到的子进程的进程ID;
- 如果设置了选项
WNOHANG
,而调用中waitpid
发现没有子进程可收集,则返回0; - 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
- 如果设置了选项
- 阻塞等待:
//childepid_t id = fork();if(id == 0) { int count=5; while(count) { printf("child: i am running %d, ppid: %d, pid: %d\n",count--,getppid(),getpid()); sleep(1); } printf("child: i quit....\n"); exit(10); } //fatherint status =0;pid_t ret = waitpid(-1,&status,0); if(ret >0 && WIFEXITED(status)== 0){printf("wait sucess quit normal !\n");printf("exit code: %d,quit signal: %d, pid: %d\n",WEXITSTATUS(status),WIFEXITED(status),ret);}else{printf("wait failed child quit kill signal:%d\n", WIFEXITED(status));//推出信号}
- 为什么要让进程等待:
- 回收僵尸进程,解决内存泄漏;
- (不是必须的)需要获取进程的运行结束状态
- 尽量让父进程晚于子进程退出,可以规范化进行回收资源
status的系统提供的使用方法
WIFEXITED(status)
: 若为正常终止子进程,WIFSIGNALED(status)
终止信号返回 0。若信号 为非0,表明进程异常终止,此时可通过WTERMSIG(status)
获取使得进程退出的信号编号。
阻塞、非阻塞的等待
- 阻塞等待:父进程调用
waitpid(id,&status,0)
时,会一直等着子进程做完事情,什么事情也做不了。- 父进程从R状态进入非R状态,进入等待队列;
- 直到子进程执行完毕,OS唤醒父进程,再将父进程加入调度队列。由OS完成一系列操作。
- 非阻塞等待或非阻塞轮询检测方案:
- 探测失败:子进程还在运行,下次在检测,父进程继续做事情…
- 探测成功:获得ID
- 真失败…,返回-1
WNOHANG
的方式:
//childpid_t id = fork();if(id == 0) { int count=5; while(count) { printf("child: i am running %d, ppid: %d, pid: %d\n",count--,getppid(),getpid()); sleep(1); } printf("child: i quit....\n"); exit(10); }
//fatherint status =0;pid_t ret = waitpid(-1,&status,WNOHANG); while(1){if(ret == 0){printf("wait next !\n"); printf("fatehr do other thing\n");}else if(ret >0){printf("wait sucess \n");printf("exit code: %d,quit signal: %d, pid: %d\n",WEXITSTATUS(status),WIFEXITED(status),ret);break;}else{printf("wait failed\n");break;}}
进程替换
- 创建子进程的目的:
- 执行父进程的代码
- 执行其他程序的代码
- 进程程序替换也就是把原来父进程的代码替换成其他程序的代码,执行全新的程序。
- 程序 = 代码 + 数据,程序在磁盘就是一个普通的可执行文件。
- 如何替换?OS完成:
- 通过中断,信号等触发写实拷贝机制,OS在物理内存上重新开辟一块儿代码空间和一块儿数据空间;页表重新构建子进程的进程地址空间与物理内存之间的关系,再把新程序的代码数据加载到内存…
替换函数
exec函数
算是一种特殊的加载器。
- 有六种以exec开头的函数,统称exec函数:
#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[]);int execve(const char *path, char *const argv[], char *const envp[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);...
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
- 如果调用出错则返回-1
- 所以exec函数只有出错的返回值而没有成功的返回值。
- 命名理解:
- l(list) : 表示参数采用列表
- v(vector) : 参数用数组
- p(path) : 有p自动搜索环境变量PATH
- e(env) : 表示自己维护环境变量
- 事实上,只有
execve
是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。
exec l
- 一:
int execl(const char *path, const char *arg, ...);
:
int main()
{printf("my process begined.....\n");execl("/user/bin/ls","ls","-a",NULL)//要以空 结尾
}
exec lp
- 二:
int execlp(const char *file, const char *arg, ...);
#include <stdlib.h>
#include<unistd.h>
#include <stdio.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{pid_t rid=fork();if(rid==0){int cou=5;while(cou--){printf("child: %d, ppid: %d,read to run other,%d\n",getpid(),getppid(),cou);sleep(1);}execlp("top","top",NULL);exit(11);}int status =0;pid_t ret = waitpid(-1,&status,0);if(ret >0 && WIFEXITED(status)== 0)//信号{printf("wait sucess !\n");printf("exit code: %d,quit signal: %d, pid: %d\n",WEXITSTATUS(status),WIFEXITED(status),ret);}else{printf("wait failed quit signal: %d\n", WIFEXITED(status));}return 0;
}
exec le
- 三:
int execle(const char *path, const char *arg, ...,char *const envp[]);
#include <stdlib.h>
#include<unistd.h>
#include <stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
int main(int argc,char* argv[],char* env[])
{pid_t rid=fork();if(rid==0){int cou=5;while(cou--){printf("child: %d, ppid: %d,read to run other,%d\n",getpid(),getppid(),cou);sleep(1);}execle("/usr/bin/ls","ls","-i",NULL,env);exit(11);}.....
}
exec ve
- 四:
int execve(const char *path, char *const argv[], char *const envp[]);
- 调用自己写的 t1 程序,t1 打印获取传过来的自定义环境变量。
//T1.C
#include <stdio.h>
#include <stdlib.h>
int main()
{int i=0;for(;i<2;++i){printf("i am here hh, test for execve(); use getenv(...)\n");}printf("environment variable: %s\n",getenv("MY_ENV"));return 0;
}
---------------------
int main()
{pid_t rid=fork();if(rid==0){int cou=5;while(cou--){printf("child: %d, ppid: %d,read to run other,%d\n",getpid(),getppid(),cou);sleep(1);}char* const my_argv[]={"t1",NULL};char* const my_env[]={"MY_ENV= hello execve!",NULL};execve("./t1",my_argv,my_env);exit(11);}....
}
exec vpe
- 五:
int execvpe(const char *file, char *const argv[], char *const envp[]);
- 调用自己写的 t1 程序,t1 打印获取传过来的环境变量。
[saul@VM-12-7-centos tt729]$ export my_env="hello execvpe hh cannot add gantanhao" ...自定义的环境变量
[saul@VM-12-7-centos tt729]$ PATH=$PATH:/home/saul/tt729 ..运行程序不用加 。/
[saul@VM-12-7-centos tt729]$ vim t1.c
//t1。c
#include <stdio.h>
#include <stdlib.h>
int main()
{int i=0;for(;i<2;++i){printf("i am here hh, test for execvpe(); use getenv(...)\n");}printf("environment variable: %s\n",getenv("my_env"));return 0;
}
//main
int main(int argc,char* argv[],char* env[])
{pid_t rid=fork();if(rid==0){int cou=5;while(cou--){printf("child: %d, ppid: %d,read to run other,%d\n",getpid(),getppid(),cou);sleep(1);}char* const my_argv[]={"t1",NULL};execvpe("t1",my_argv,env);exit(11);}}
exec vp
- 六:
int execvp(const char *file, char *const argv[]);
int main()
{char *const argv[] = {"ps", "-ef", NULL};
// 带p的,可以使用环境变量PATH,无需写全路径execvp("ps", argv);
}
v
:exec参数列表:传的是:该程序命令行参数列表的字符指针数组,p
:自动获取全局变量PATH;exec参数列表:前面的第一个参数可以不加路径;l
:在exec 参数列表一个个传过去 程序的使用方法。l , v 都以NULL
结尾。e
:在exec 参数列表:- 传过去继承的环境变量字符指针数组、
- 或自己定义的env 环境变量的字符指针数组
- 可以看出,通过这种
exec*
函数的方式可以在一个语言程序内,跑另外一个语言的程序。
exec v
- 七:
int execv(const char *path, char *const argv[]);
int main()
{char *const argv[] = {"ps", "-ef", NULL};
execv("/user/bin/ps", argv);
}
寄语
- 当用户层面某个程序很卡(非网络问题)时,不是进程过多,CPU忙不过来;就是当前进程进入了等待队列,此时会提示你继续等待还是退出的框框弹出来…
- …有点饶
Linux:进程管理 | 进程创建 | 进程终止 | 进程等待 | 进程替换相关推荐
- Linux——进程控制:创建、终止、等待、替换
进程创建 fork #include <unistd.h> pid_t fork(void); 操作系统做了什么? 调用fork之后,内核的工作: 分配新的内存块和内核数据结构给子进程 将 ...
- linux——进程(创建、终止、等待、替换)
进程的基本操作 概念 程序运行的一个实例,其占有一定的空间. 查询某一进程当前情况 ps aux | grep 进程名 终止进程 kill -9 pid: //pid指需要终止的进程pid 创建 pi ...
- 【Linux】Linux进程控制 --- 进程创建、终止、等待、替换、shell派生子进程的理解…
柴犬: 你好啊,屏幕前的大帅哥or大美女,和我一起享受美好的今天叭
- 【详细解读】进程管理 -死锁问题 系统有三个进程:A B C 这3个进程都需要5个系统资源。如果系统至少有多少个资源,则不可能发生死锁
进程管理 -死锁问题 系统有三个进程:A B C 这3个进程都需要5个系统资源.如果系统至少有多少个资源,则不可能发生死锁? 最多的状态是:每个进程恰好都分到了四个资源, 都只需要一个资源就可以运行, ...
- 【Linux】进程控制(创建、终止、等待)
环境:centos7.6,腾讯云服务器 Linux文章都放在了专栏:[Linux]欢迎支持订阅 相关文章推荐: [Linux]冯.诺依曼体系结构与操作系统 [Linux]进程理解与学习Ⅰ-进程概念 [ ...
- 进程控制-创建、退出、等待、替换
目录 进程创建 1.子进程继承 2.写时拷贝 进程退出 echo $? 退出码 进程异常退出的情况模拟: 退出进程的方式 退出码的意义: 进程退出,在系统中发生了什么? 进程等待 为什么要有进程等待呢 ...
- java实现进程管理_用java来实现一个进程管理系统
展开全部 构造方法摘要 ProcessBuilder(List command) 利用指定的操作系统程序和参数62616964757a686964616fe59b9ee7ad9431333361303 ...
- 利用进程管理利器supervise监控并自动重启进程
一.什么是supervise supervise是Daemontools里的一个核心工具,Daemontools是一个包含了很多管理Unix服务的工具的软件包.而其中最核心的工具就是supervise ...
- MFC关于进程使用:创建、关闭及查询进程
// 启动进程 bool StartProgress(CString& strError) {CString strExeName;strExeName.Format(_T("%s& ...
- Linux系统编程36:多线程之线程控制之pthread线程库(线程创建,终止,等待和分离)
文章目录 (1)POSIX线程库 (2)pthread_create--创建线程 A:关于Linux线程的再理解 B:线程ID及地址空间布局 (3)pthread_exit--线程终止 (4)pthr ...
最新文章
- 想做网络工程师不?最好学下Linux
- Android关于SQLiteOpenHelper的封装
- Python基本语法_异常处理详解
- Asp.Net+SqlServer+EntityFrameWork(项目问题总结)
- MySQL——数据库的增删改操作
- python按键盘上哪个键运行_pythonshell哪个键执行命令
- anaconda 安装tensorfollow 镜像_手把手教新手安装Anaconda配置开发环境
- 关于着色器LinearGradient的使用
- ExpandListView onChildClickListener 失效
- 我的MVVM框架 v3教程——todos例子
- 易筋SpringBoot 2.1 | 第三十六篇:Spring Boot RestTemplate超时配置示例
- android刷成苹果手机版下载地址,安卓怎么刷苹果系统 安卓变苹果系统方法教程...
- 输出100以内的奇数
- python爬虫做毕业论文_基于Python的网络爬虫(智联招聘)开发与实现毕业论文+作品源码+演示视频...
- 推断性统计部分(一)---样本与分布的关系及其检验统计量
- 使用快捷指令和carplay发送停车位置(高德地图)
- python pdf 定位关键字_Python对pdf中的关键字过滤(pdfminer3k或pdfminer使用)
- 西电软工oop面向对象程序设计实验三上机报告
- php脚本爬取头像图片
- 计算机互联网职业高中排名,职业高中有哪些热门专业可选择