当前位置:网站首页>【Liunx】进程控制和父子进程
【Liunx】进程控制和父子进程
2022-07-07 03:39:00 【影中人lx】
文章目录
1.进程和程序
1.1进程和程序的概念
程序的概念:程序是一种文件,编译好的二进制文件
进程:运行中的程序。
站在程序员的角度,是运行一系列指令的过程
站在操作系统的角度:分配系统资源的基本单位
区别:程序占用的是磁盘资源,不占用系统的资源
内存占用系统资源
一个程序可以对应多个进程,而一个进程对应一个程序。
因此程序没有生命周期,但是进程有生命周期。
1.2单道和多道程序设计
单道程序设计:一次只有一个处于程序在运行中;其他程序处于等待状态。(DOS)
多道程序设计:设计出时间片,在一个时间片中执行一个程序,在下一个时间片立马然后切换为下一个程序,从而让出cpu的资源给其他程序。
由于一个时间片的时间很短,属于是毫秒级别的。所以在人的感知上,几个程序是并发进行的;但是在微观上,在一个时间片上,只有一个程序在运行。微观上串行,宏观上并行
1.3进程状态的转换
cpu和MMU(内存管理单元)
- 寄存器的运算速度最快
- 算术逻辑单元只能继续+计算和<<运算
1.3.1进程的状态切换
1.3.2MMU(内存管理单元的作用)
**在32位机器下,一个进程可以管理的虚拟内存空间大小为4G。**而实际上的物理空间大小不是4G。而物理内存和虚拟内存自己的对应和管理就是通过MMU和地址转换记录表进行。
比如在.data中存放了a=10,那么就可以通过MMU(记录了虚拟内存和物理内存的映射关系),将他的虚拟地址转换为物理地址,从而访问内容10。
内存访问级别:
0是最高的级别,内核访问的权限
3是允许用户访问权限级别的权限
映射问题:
用户空间映射到物理空间内存是独立的(不同的进程,即使虚拟地址相同,对应的也是不同的物理空间)
内核空间映射到物理空间内存是相同的。(上图中,两个进程的kernal指向了同一块权限为0的MMU)
1.3.4PCB(进程控制块)的认识
每一个进程在内核中都有一个进程控制块PCB(如上图)来维护进程的相关信息,在linux下内核的进程控制块是struct task_struct 结构体。
task_struct的重点成员解析:
- 进程id,系统中每一个进程有唯一的id,在结构体中用pid_t类型表示,本质上是一个非负整数。(方便进程的管理)
- 进程的状态:就绪,运行,挂起,停止
- 进程切换时需要保存和恢复的一些CPU寄存器
- 描述虚拟地址空间的信息
- 描述控制终端的信息
- 当前的工作目录
- umask掩码
- 文件描述符表,包含很多指向file结构体的指针
- 和信号相关的信息
- 用户id和组id
- 会话和进程组
- 进程可以使用的资源上限
1.3.5获取环境变量
env #获取所有环境变量
#常用的环境变量:PATH,SHELL,TERM,LANG,HOME
PATH:可执行文件的搜索路径
SHELL:显示当前SHELL
echo $TERM:当前的终端类型,在图形界面终端下值通常为xterm,
echo $LANG:语言和locale,决定了字符串编码以及时间、货币等信息的显示格式
echo $HOME:当前用户的主目录路径,很多程序需要在自己的主目录下保存配置文件,使得每个用户在执行该程序时都有自己的一套配置。
getenv获取环境变量函数
//获取环境变量
char* getenv(const char* name);
/* 成功:返回环境变量的值,失败:返回NULL */
mygetenv.c
int main(){
const char*s="PATH";
char*print=getenv(s);
prinf("%s\n",print);
return 0;
}
gcc mygetenv.c
./a.out
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
setenv设置环境变量函数
int setenv(const char*name,const char*value,int overwrite);
/* 参数overwrite取值: 1.覆盖原环境变量 0.不覆盖,常用于设置新环境变量 */
/* 使用指令的方式添加环境变量 key为环境变量类型,value为环境变量取值 */
export key=value
unsetenv删除环境变量函数
int unsetenv(const char*name);
/* 成功:0 失败:-1 name不存在:0 */
2.控制进程
2.1进程控制函数fork
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
#include<unistd.h>
pid_t fork(void);
/* pid_t是进程的ID 失败:返回-1 返回值,成功:有两次返回;父进程返回子进程的ID 子进程返回0; */
//进程获取函数
#include<sys/types.h>
#include<unistd.h>
//获得当前进程的ID
pid_t getpid(void);
//获得当前进程父进程的ID
pid_t getppid(void);
#include<stdio.h>
#include<unistd.h>
int main(){
printf("begin.....\n");
pid_t pid=fork();
printf("end.......\n");
return 0;
}
printf(“end…\n”);执行了两次,声明fork()执行之前只有一个进程,fork()执行之后出现了两个进程。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
printf("begin()...\n");
pid_t pid=fork();
//如果小于0,表示进程打开失败
if(pid<0){
perror("fork err");
exit(-1);
}
if(pid==0){
printf("I am child pid,pid=%d,ppid=%d",getpid(),getppid());
}
else if(pid>0){
printf("I am father pid,pid=%d,child=%d,ppid=%d",getpid(),pid,getppid());
}
printf("end......\n");
return 0;
}
/* 解释pid的值为什么在父子进程中不同。 其实就相当于链表,进程形成了链表,父进程的pid(p意味point)指向子进程的进程id, 因为子进程没有子进程,所以其pid为0。这里也解释了为什么父进程fork返回的是子进程的id,而子进程返回的是0; 可以证明的是:观察上面的父子进程id,父进程的id刚好比子进程少1 */
分析:
进程从fork()函数执行后开始变成两个进程,在linux下,子进程和父进程是并发进行的。当然我们应该让子进程先结束,然后父进程再结束,否则就会出现孤儿进程。
int main(){
printf("begin()...\n");
pid_t pid=fork();
//如果小于0,表示进程打开失败
if(pid<0){
perror("fork err");
exit(-1);
}
if(pid==0){
printf("I am child pid,pid=%d,ppid=%d",getpid(),getppid());
}
else if(pid>0){
printf("I am father pid,pid=%d,child=%d,ppid=%d",getpid(),pid,getppid());
sleep(1);
}
printf("end......\n");
return 0;
}
延缓父进程的结束时间,让子进程先结束。
2.1.2fork()函数进阶
//先看一份代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(void)
{
int i=0;
printf("i son/pa ppid pid cpid\n");
//ppid指当前进程的父进程pid
//pid指当前进程的pid,
//fpid指fork返回给当前进程的值
for(i=0;i<2;i++){
pid_t cpid=fork();
if(fpid==0)
printf("%d child %4d %4d %4d\n",i,getppid(),getpid(),cpid);
else
printf("%d parent %4d %4d %4d\n",i,getppid(),getpid(),cpid);
}
return 0;
}
执行顺序
for i=0 1
father father
son
son father
son
//问题是下面的进程一个创建了多少个进程(除去main进程)
#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
fork();
fork() && fork() || fork();
fork();
return 0;
}
//一共有20个进程,除去main还有19个进程。
A&&B,如果A=0,就没有必要继续执行&&B了;A非0,就需要继续执行&&B
A||B,如果A非0,就没有必要继续执行||B了,A=0,就需要继续执行||B。
2.2进程命令控制
int main(){
printf("begin()...\n");
pid_t pid=fork();
//如果小于0,表示进程打开失败
if(pid<0){
perror("fork err");
exit(-1);
}
if(pid==0){
printf("I am child pid,pid=%d,ppid=%d",getpid(),getppid());
while(1){
printf("I am child\n");
sleep(1);
}
}
else if(pid>0){
printf("I am father pid,pid=%d,child=%d,ppid=%d",getpid(),pid,getppid());
while(1){
printf("I am father\n");
sleep(1);
}
}
printf("end......\n");
return 0;
}
可以看出子进程和父进程是并发进行的;
ps aux #查看进程信息
ps ajx #查看进程和进程的血缘关系
kill -9 pid #杀死进程ID为pid的进程 -9表示强制杀死进程
kill -9 11549
2.3创建n个进程
让父进程创建多个子进程
int main(){
int n=5;
int i=0;
pid_t pid=0;
for(i;i<n;i++){
pid=fork();
if(pid==0){
printf("I am child,pid=%d,ppid=%d",getpid(),getppid());
break;//子进程跳出循环,不再创建子进程
}
else{
printf("I am father,pid=%d,ppid=%d",getpid(),getppid());
}
}
//保证进程不会死掉
while(1){
sleep(1);
}
}
2.4循环创建n个子进程控制顺序
//实现按顺序退出进程,main进程最后退出
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
int n=5;
int i=0;
pid_t pid=0;
for(i;i<n;i++){
pid=fork();
if(pid==0){
//不同的子进程i的值不同,main进程最后的值为n
printf("I am child%d,pid=%d,ppid=%d",i,getpid(),getppid());
break;//子进程跳出循环,不再创建子进程
}
else if(pid>0){
printf("I am father%d,pid=%d,ppid=%d",i,getpid(),getppid());
}
}
//通过i控制睡眠长度控制按顺序关掉进程
sleep(i);
if(i<n){
printf("I am child%d,will exit,pid=%d\n",i,getpid());
}else{
printf("I am father,will exit,pid=%d\n",getpid());
}
return 0;
}
3.父子进程
3.1父子进程可以共享的内容
看下面的代码
int main(int argc,char*argv){
printf("begin......");
fork();
printf("end........\n");
return 0;
}
begin…没有\n因此存放在缓冲区中。fork()之前的内容属于是共享区,因此end…\n的换行刷新缓冲区,所以打印出两行begin…end…
进程共享
父子进程之间在fork()分叉后,相同处和不同处之处。
刚fork之后:
父子相同处:全局变量,.data区域,.text区域,栈,堆,环境变量(0-3G的用户空间),用户ID,宿主目录,进程工作目录,信号处理方式…
父子不同处:1.进程ID,2.fork返回值,3.父进程id,4.进程运行时间,5.闹钟(定时器),6.未决信号集
子进程与父进程0-3G的用户空间内容相同,并复制了父进程PCB,但是pid不同。
但是子进程并不是复制父进程0-3G的用户空间,父子进程之间满足读时共享写时复制的原则,从而减少空间的花销。
int var=100;
int main()
{
pid_t pid=fork();
if(pid==0){
printf("var=%d,child,pid=%d\n",var,getpid());
var=1001;
printf("var=%d,child,pid=%d\n",var,getpid());
sleep(2);
printf("var=%d,child,pid=%d\n",var,getpid());
}else{
sleep(1);
printf("var=%d,parent,pid=%d\n",var,getpid());
var=1002;
sleep(5)
printf("var=%d,parent,pid=%d\n",var,getpid());
}
return 0;
}
可以看出,父子进程分别修改全局变量的值互不影响,说明满足读时共享写时复制。
3.2exec进程执行族函数
#include <unistd.h>
extern char **environ;
//程序中间嵌入执行其他程序
int execl(const char *path, const char *arg, ...);
//执行程序的时候,使用PATH环境变量,执行的程序可以不用加路径
int execlp(const char *file, const char *arg, ...);
/* 返回值:只有在发生err时才会返回值 参数解释: path/file:文件名或者路径 arg:可变参数列表 参数列表最后需要NULL结尾,作为哨兵位 执行原理:将当前进程的.text,.data替换为所要加载的程序的.text,.data,然后让进程从新的.text.的第一条指令开始执行。 */
实例
int main(){
execlp("ls","ls","-l",NULL);
execl("/bin/ls","ls","-l",NULL)
/* 参数解释: 第一个ls为文件名 第二个ls是可变参数列表的第一个参数,与前面的文件名保持一致用于占位。参数列表的最后一位要用NULL */
perror("exec err");
return 0;
}
执行结果
3.3孤儿和僵尸进程
孤儿进程:父亲进程结束的子进程,子进程的父进程为init进程
僵尸进程:子进程结束,父进程没有回收子进程的资源(进程管理块PCB);
#include<unistd.h>
//孤儿进程
int main(){
//返回子进程的ID
pid_t pid=fork();
if(pid==0){
printf("I am child,pid=%d\n",getpid());
while(1){
sleep(1);
}
}else{
printf("I am parent,pid=%d\n",getpid());
printf("parent end.....\n")
}
return 0;
}
//僵尸进程,没有回收子进程的资源
int main(){
//返回子进程的ID
pid_t pid=fork();
if(pid==0){
printf("I am child,pid=%d\n",getpid());
}
else{
while(1){
printf("I am father,pid=%d",getpid());
sleep(1);
}
}
return 0;
}
僵尸进程不能使用kill命令清除,因为kill命令只是用来终止进程,而僵尸进程已经终止,只是没有回收资源。
3.4wait回收子进程函数
#include <sys/types.h>
#include <sys/wait.h>
/* 函数作用: 1.阻塞等待进程死亡 2.回收子进程资源 3.调查子进程死亡原因 */
pid_t wait(int *status);
/* 参数说明: status是一个传出参数,可以得到进程死亡的原因 返回值: 成功:返回回收子进程的的ID 失败:返回-1 */
int main(){
pid_t pid=fork();
if(pid==0){
printf("I am child,will die,pid=%d\n",getpid());
sleep(3);
}else if(pid>0){
printf("I am parent,waiting for child die,pid=%d\n",getpid());
//阻塞等待子进程死亡
pid_t wpid=wait(NULL);
printf("pid=%d die",wpid);
}
return 0;
}
输出
I am parent,waiting for child die,pid=23746
I am child,will die,pid=23747
#父进程发生了阻塞,等待子进程死亡再进行后面的程序
pid=23747 die
3.5获得进程死亡原因
/* 进程死亡的原因: WIFEXITED(status) 如果WIFEXITED为真,即为正常死亡退出。使用WEXITSTATUS(status)得到退出状态。 WIFSIGNALED(status) 如果WIFSIGNALED为真,即为非正常死亡(被信号杀死),使用WTERMSIG(status)得到信号 */
int main(){
pid_t pid=fork();
if(pid==0){
printf("I am child,will die,pid=%d\n",getpid());
sleep(3);
return 101;
}
else if(pid>0){
printf("I am parent,waiting for child die,pid=%d\n",getpid());
//阻塞等待子进程死亡
int status;
pid_t wpid=wait(&status);
if(WIFEXITED(status)){
printf("child exit with %d\n",WIFEXITED(status));
}
if(WIFSIGNALED(status)){
printf("child killed by %d\n",WTERMSIG(status));
}
printf("pid=%d die",wpid);
}
return 0;
}
3.6waitpid回收子进程函数
pid_t waitpid(pid_t pid, int *status, int options);
/* 参数options: WNOHANG:如果子进程还活着就立即返回(不是阻塞等待) WUNTRACED:暂停状态 0和wait相同,会发生阻塞等待 参数pid: < -1 如果一个进程组ID为pid,传入-pid可以回收该进程组 -1 回收所有任意的进程 0 回收和调用进程组ID相同的组内子进程 > 0 回收ID为pid的进程 返回值: 如果设置了WNOHANG,那么如果没有子进程退出,返回0 如果有,返回子进程pid 如果失败返回-1 其他:失败返回-1 成功:返回子进程pid */
int main(){
pid_t pid=fork();
if(pid==0){
printf("I am child ,pid=%d\n",getpid());
sleep(2);
}
else if(pid>0){
printf("I am parent,pid=%d\n",getpid());
//回收任意的子进程,如果进程没有死亡,就返回0;如果有,返回进程ID
int ret=0;
while(ret==0)
{
ret=waitpid(-1,NULL,WNOHANG);
printf("ret=%d\n",ret);
}
if(ret<0){
perror("wait err");
}
printf("child die,child=%d",pid);
}
return 0;
}
3.6.1回收多个子进程
wait函数回收子进程
//用wait回收子进程
int main(){
int n=5;
int i=0;
pid_t pid;
for(;i<n;i++){
pid=fork();
//避免子进程再生成孙子进程
if(pid==0){
break;
}
else if(pid>0){
printf("I am child,pid=%d\n",getpid());
}
}
//按顺序展开进程
sleep(i);
//如果是main进程,就回收子进程
if(i==5){
//wait回收n次子进程
for(i=0;i<n;i++){
pid_t wpid=wit(NULL);
printf("child=%d,回收\n",wpid);
}
}
return 0;
}
waitpid回收子进程
int main(){
int n=5;
int i=0;
pid_t pid;
for(;i<n;i++){
pid=fork();
//避免子进程再生成孙子进程
if(pid==0){
break;
}
}
sleep(i);
//如果是main进程,就回收子进程
if(i==5){
int ret=0;
while(1){
ret=waitpid(-1,NULL,WNOHANG);
if(ret>0){
printf("child=%d回收\n",ret);
}
else if(ret==-1)
{
break;
}
}
}
if(i<n){
printf("I am child,pid=%d\n",getpid());
}
return 0;
}
边栏推荐
- 詳解機器翻譯任務中的BLEU
- Advantages of using net core / why
- Lm11 reconstruction of K-line and construction of timing trading strategy
- toRefs API 与 toRef Api
- Chinese and English instructions prosci LAG-3 recombinant protein
- Reflection (II)
- . Net 5 fluentftp connection FTP failure problem: this operation is only allowed using a successfully authenticated context
- Causes and solutions of oom (memory overflow)
- MySQL binlog related commands
- Implementation of AVL tree
猜你喜欢
. Net core accesses uncommon static file types (MIME types)
ROS2规划系统plansys2简单的例子
父组件传递给子组件:Props
freeswitch拨打分机号源代码跟踪
Paranoid unqualified company
Academic report series (VI) - autonomous driving on the journey to full autonomy
Abnova circulating tumor DNA whole blood isolation, genomic DNA extraction and analysis
非父子组件的通信
Use of completable future
$refs:组件中获取元素对象或者子组件实例:
随机推荐
AVL树的实现
Stack Title: nesting depth of valid parentheses
普通测试年薪15w,测试开发年薪30w+,二者差距在哪?
Multidisciplinary integration
Nesting and splitting of components
虚拟机的作用
Fullgc problem analysis and solution summary
Causes and solutions of oom (memory overflow)
Cloud backup project
RuntimeError: CUDA error: CUBLAS_ STATUS_ ALLOC_ Failed when calling `cublascreate (handle) `problem solving
修改Jupyter Notebook文件路径
Sqlmap tutorial (IV) practical skills three: bypass the firewall
Outlier detection technology of time series data
main函数在import语句中的特殊行为
Bindingexception exception (error reporting) processing
考研失败,卷不进大厂,感觉没戏了
Example of Pushlet using handle of Pushlet
About binary cannot express decimals accurately
抽絲剝繭C語言(高階)數據的儲存+練習
Project practice five fitting straight lines to obtain the center line