linux系统(进程控制)05_进程,进程状态的转化,进程控制,fork函数,父子进程,exec函数族,孤儿进程与僵尸进程,wait和waitpid函数,回收进程

01 学习目标

1.了解进程相关的概念
2.掌握fork/getpid/getppid函数的使用
3.熟练掌握ps/kill命令的使用
4.熟练掌握execl/execlp函数的使用
5.说出什么是孤儿进程什么是僵尸进程
6.熟练掌握wait函数的使用
7.熟练掌握wait函数的使用
8.熟练掌握waitpid函数的使用

02 进程和程序

什么是程序?

编译好的二进制文件

什么是进程?

运行着的程序。
站在程序员的角度:运行一系列指令的过程。
站在操作系统角度:分配系统资源的基本单位

区别:

  • 程序占用磁盘,不占用系统资源
  • 内存占用系统资源
  • 一个程序对应多个进程,一个进程对应一个程序
  • 程序没有生命周期,进程有生命周期

03 单道和多道程序设计

宏观上并行,微观上串行。
在这里插入图片描述

04 进程的状态转化

在这里插入图片描述

05 MMU的作用(Memory Management Unit)

中央处理器CPU:
在这里插入图片描述
内存管理单元MMU:
在这里插入图片描述
MMU的作用:

  • 虚拟内存和物理内存的映射
  • 修改内存访问级别

虚拟内存地址和物理地址存在对应关系:
在这里插入图片描述
用户空间映射到物理内存是独立的(相同的值在物理内存中也是不一致的值)。

06 PCB 的概念(Process Control Block)

PCB进程控制块
可以用如下命令查看struct task_struct 结构体定义:

sudo grep -rn "struct task_struct {" /usr/

sched.h->/usr/src/linuc-headers-4.8.0-46/include/linuc.sched.h

光标停留在{ 上,按%

其内部成员有很多,我们重点掌握以下部分即可:

  1. 进程id。系统中每个进程有唯一的id,在C语音中用pid_t类型表示,其实就是一个非负整数。
  2. 进程的状态,有就绪,运行,挂起,停止等状态。
  3. 进程切换时需要保存和恢复的一些CPU寄存器。
  4. 描述虚拟地址空间的信息。
  5. 描述控制终端的信息。
  6. 当前工作目录(Current Working Directory)。
  7. umask掩码。
  8. 文件描述表,包含很多指向file结构体的指针。
  9. 和信号相关的信息。
  10. 用户id和组id。
  11. 会话(Session)和进程组。
  12. 进程可以使用的资源上限(Resource Limit)。

可以使用:ulimit -a 查看资源上限。
*可以加图*

07 获取环境变量(了解)

环境变量:指在操作系统中用来指定操作系统运行环境的一些参数。通常具备以下特征:

  1. 字符串(本质)
  2. 有统一格式:名=值[:值]
  3. 值用来描述进程环境信息。

常见的环境变量

  • PATH

-

 $ echo $PATH
  • SHELL
    当前Shell,它的值通常是/bin/bash.

  • TERM
    当前终端类型,在图形界面终端下它的值通常是xterm,终端类型决定了一些程序的输出显示方式,比如图形界面终端可以显示汉字,而字符终端一般不行。

  • LANG
    语言和locale,决定了字符编码以及时间,货币等信息的显示格式。

  • HOME
    当前用户主目录的路径,很多程序需要在主目录下保存配置文件,使得每个用户在运行该程序时都有自己的一套配置。
    如下:
    在这里插入图片描述

getenv函数(掌握)
getenv 获取环境变量值

 char * getenv(const char* name)
  • 成功:返回环境变量的值;失败:NULL(name不存在)

setenv函数
setenv 设置环境变量的值

int setenv(const char* name,const char* value,int overwrite)
  • overwrite:
    1:覆盖原环境变量
    0:不覆盖.(该参数常用于设置新环境变量,如:ABC = haha-day-night)

  • 返回值:0成功,-1失败。

unsetenv函数
unsetenv删除环境变量name的定义

 int unsetenv(const char* name)
  • 注意事项:name不存在仍返回0(存在),当name命令为“ABC=”时则会出错。
  • 返回值:0成功,-1失败。

命令行设置变量:

export key=val;
.bashrc

08 进程控制

1.fork函数

fork 创建一个新的进程。

pid_t fork(void);

  • 返回值
    失败-1
    成功,两次返回。父进程返回的子进程的id,子进程返回的0.

2.getpid函数

getpid获得pid,进程id,获得当前进程

pid_t getpid(void)

3.getppid函数

getppid获得当前进程父进程的id。

pid_t getppid(void);

4.fork创建子进程

#include<stdio.h>
#include<unistd.h>

int main()
{
	printf("Begin...\n);
	pid_t pid = fork();
	if(pid<0)
	{
		perror("fork err");
		exit(1);
	}
	if(pid == 0)
	{
		//子进程
		printf("I am a child,pid = %d,ppid=%d\n",getpid(),getppid());
	}
	else if(pid > 0)
	{
		//父进程的逻辑
		printf("childpid=%d,self=%d,ppid=%d\n",pid,getpid(),getppid());
	}
	printf("End....\n");
	return 0;
}

此时子线程的ppid=1,是因为父进程已经结束。
从最后一份End…在命令行可知。
在这里插入图片描述

为解决上述问题:

#include<stdio.h>
#include<unistd.h>

int main()
{
	printf("Begin...\n);
	pid_t pid = fork();
	if(pid<0)
	{
		perror("fork err");
		exit(1);
	}
	if(pid == 0)
	{
		//子进程
		printf("I am a child,pid = %d,ppid=%d\n",getpid(),getppid());
	}
	else if(pid > 0)
	{
		//父进程的逻辑
		printf("childpid=%d,self=%d,ppid=%d\n",pid,getpid(),getppid());
		sleep(1);
	}
	printf("End....\n");
	return 0;
}

在这里插入图片描述
效果如同输入:vsp fork.c

在这里插入图片描述
fork:
在这里插入图片描述
fork代码执行分叉:
在这里插入图片描述

5.进程控制命令

#include<stdio.h>
#include<unistd.h>

int main()
{
	printf("Begin...\n);
	pid_t pid = fork();
	if(pid<0)
	{
		perror("fork err");
		exit(1);
	}
	if(pid == 0)
	{
		//子进程
		printf("I am a child,pid = %d,ppid=%d\n",getpid(),getppid());
		while(1)
		{
			printf("I am a child\n");
			sleep(1);
		}
	}
	else if(pid > 0)
	{
		//父进程的逻辑
		printf("childpid=%d,self=%d,ppid=%d\n",pid,getpid(),getppid());
		while(1)
		{
			sleep(1);
		}
	}
	printf("End....\n");
	return 0;
}

查看进程信息
ps

  • ps auj
  • ps ajx – 可以追述进程之间的血缘关系

kill

  • 给进程发送一个信号
  • SIGKILL 9号信号
  • kill -SIGKILL pid --杀死进程

**1张图**

6.创建n个子进程

nfork.c

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int main()
{
	int n=5;
	int i=0;
	pid_t pid =0;
	for(i=0;i<5;i++)
	{
		if(pid ==0)
		{
			//son
			printf("I am child,pid=%d,ppid=%d\n",getpid(),getppid());
		}
		else if(pid>0)
		{
			//father
			printf("I am father,pid=%d,ppid=%d\n",getpid(),getppid());
		}
	}
	while(1)
	{
		sleep(1);
	}
	return 0;
}

2^5=32个
*加图*
在这里插入图片描述

以上代码不符合创建n个,裂变,创建n个以下代码:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int main()
{
	int n=5;
	int i=0;
	pid_t pid =0;
	for(i=0;i<5;i++)//父进程循环结束
	{
		if(pid ==0)
		{
			//son
			printf("I am child,pid=%d,ppid=%d\n",getpid(),getppid());
			break;//子进程退出循环的接口
		}
		else if(pid>0)
		{
			//father
			printf("I am father,pid=%d,ppid=%d\n",getpid(),getppid());
		}
	}
	while(1)
	{
		sleep(1);
	}
	return 0;
}

在这里插入图片描述
父进程1个,子进程5个,共6个。
在这里插入图片描述

7.循环创建n个子进程控制顺序

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

int main()
{
	int n=5;
	int i=0;
	pid_t pid =0;
	for(i=0;i<5;i++)//父进程循环结束
	{
		if(pid ==0)
		{
			//son
			printf("I am child,pid=%d,ppid=%d\n",getpid(),getppid());
			break;//子进程退出循环的接口
		}
		else if(pid>0)
		{
			//father
			//printf("I am father,pid=%d,ppid=%d\n",getpid(),getppid());
		}
	}
	sleep(i);//控制子进程输出顺序
	if(i<5)
	{
		printf("I am child,will exit,pid=%d,ppid=%d\n",getpid(),getppid());
	}
	else
	{
		printf("I am parent,will out exit,pid=%d,ppid=%d\n",getpid(),getppid());
	}
	return 0;
}

在这里插入图片描述

09 父子进程共享的内容

刚fork后,父子进程有哪些相同:
全局变量,.data,.text,栈,堆,环境变量,用户ID,宿主目录,进程工作目录,信号处理方式…
刚fork后,父子进程有哪些不同:
1.进程ID 2.fork返回值 3.父进程ID 4.进程运行时间 5.闹钟(定时器) 6.未决信号集
似乎,子进程复制了父进程0-3G用户空间内容。

父子进程间遵循读时共享写时复制的原则。这样设计,无论子进程执行父进程逻辑还是执行自己逻辑都能节省内存开销。
在这里插入图片描述

10 父子进程不共享全局变量

shared.c

#include<stdio.h>
#include<unistd.h>

int var = 100;
int main()
{
	pid_t pid = fork();
	if(pid==0)
	{
		//son
		printf("var = %d,child,pid=%d,ppid=%d\n",var,getpid(),getppid());
		var = 1001;
		printf("var = %d,child,pid=%d,ppid=%d\n",var,getpid(),getppid());
	}
	else if(pid>0)
	{
		//parent
		sleep(1);//保障子进程能够修改
		printf("var = %d,parent,pid=%d,ppid=%d\n",var,getpid(),getppid());
	}
	return 0;
}

子进程修改全局变量,父进程并没有被修改。
在这里插入图片描述
shared.c

#include<stdio.h>
#include<unistd.h>

int var = 100;
int main()
{
	pid_t pid = fork();
	if(pid==0)
	{
		//son
		printf("var = %d,child,pid=%d,ppid=%d\n",var,getpid(),getppid());
		var = 1001;
		printf("var = %d,child,pid=%d,ppid=%d\n",var,getpid(),getppid());
		sleep(3);
		printf("var = %d,child,pid=%d,ppid=%d\n",var,getpid(),getppid());
	}
	else if(pid>0)
	{
		//parent
		sleep(1);//保障子进程能够修改
		printf("var = %d,parent,pid=%d,ppid=%d\n",var,getpid(),getppid());
		var =2000;
		printf("var = %d,parent,pid=%d,ppid=%d\n",var,getpid(),getppid());
	}
	return 0;
}

父进程修改全局变量,子进程也没有被影响。
在这里插入图片描述

11 exec函数族

execl 执行其他程序

int execl(const char* path,const char* arg,…/*(char *)NULL */);

execlp 执行程序的时候,使用PATH环境变量,执行的程序可以不用加路径

int execlp(const char* file,const char* arg,…/*(char *)NULL */);

  • file 要执行的程序

  • arg 参数列表
    参数列表最后需要一个NULL作为结尾,哨兵

  • 返回值
    只有失败才返回

使用原理其实就是其他程序的text替换自己的text,如下:
在这里插入图片描述
execlp.c

#include<stdio.h>

int main()
{
	//execlp(const char *_file,const char *_arg,...)
	//因为参数是从0开始的,第二个ls事占位0.
	execlp("ls","ls","-1","--color=auto",NULL);
	
	//不需要判断返回值
	perror("exec err");
	return 0;
}

execl.c

#include<stdio.h>

int main()
{
	//execlp(const char *_file,const char *_arg,...)
	//因为参数是从0开始的,第二个ls事占位0.
	execl("/bin/ls","ls","-1","--color=auto",NULL);
	
	//不需要判断返回值
	perror("exec err");
	return 0;
}

# 加图

1.exec族用法

char *const ps_argv[]={"ps","-o","pid,ppid,pgrp,session,comm",NULL};
char const ps_envp[]={"PATH=/bin/usr/bin","TERM=console",NULL};
//1.execl
execl("/bin/ps","ps","-o","pid,ppid,pgrp,session,comm",NULL);
//2.execv
execv("/bin/ps",ps_argv);
//3.execle
execle("/bin/ps","ps","-o","pid,ppid,pgrp,session,comm",NULL,ps_envp);
//4.execlp
execlp("ps","ps","-o","pid,ppid,pgrp,session,comm",NULL);
//5.execvp
execvp("ps",ps_argv);

2.exec函数族

在这里插入图片描述

3.exec实现自定义程序

shell终端就是调用fork()。
在这里插入图片描述

12 孤儿进程与僵尸进程

1.孤儿进程

孤儿进程:父亲死了,子进程被init进程领养

#include<stdio.h>
#include<unistd.h>

int main()
{
	pid_t pid = fork();
	if(pid == 0)
	{
		while(1)
		{
			printf("I am child,pid=%d,ppid=%d\n",getpid(),getppid());
			sleep(1);
		}
	}
	else if(pid>0)
	{
		printf("I am parent,pid=%d,ppid=%d\n",getpid(),getppid());
		sleep(5);
		printf("I am parent,I will die!\n");
	}
	return 0;
}

# 加图
因为子线程被init领养了,无法ctrl+c退出,使用kill杀死进程退出。
kill -9 4965

2.僵尸进程

僵尸进程:子进程死了,父进程没有回收子进程的资源(PCB)

#include<stdio.h>
#include<unistd.h>

int main()
{
	pid_t pid = fork();
	if(pid == 0)
	{
		printf("I am child,pid=%d,ppid=%d\n",getpid(),getppid());
		sleep(2);
		printf("I am child,I will die!\n");
	}
	else if(pid>0)
	{
		while(1)
		{
			printf("I am parent,pid=%d,ppid=%d\n",getpid(),getppid());
		sleep(1);
		}
	}
	return 0;
}

在这里插入图片描述
Z+ :僵尸进程

# 加图
僵尸进程无法杀死掉的子线程。
如何回收僵尸进程:杀死父亲,init领养,负责回收。

13 wait函数简单使用和说明

wait 回收子进程,知道子进程的死亡原因(僵尸进程的回收)。
作用:

  • 阻塞等待
  • 回收子进程资源
  • 查看死亡原因

pid_t wait(int * status)

  • status 传出参数
  • 返回值
    成功返回终止的子进程ID
    失败,返回-1

子进程的死亡原因:

  • 正常死亡WIFEXITED
    如果WIFEXITED为真,使用WEXITSTATUS得到退出状态
  • 非正常死亡WIFSIGNALED
    如果WIFSIGNALED为真,使用WTERMSIG得到信号(杀死子线程的信号)
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
	pid_t pid = fork();
	if(pid == 0)
	{
		printf("I am child,will die!\n");
		sleep(2);
	}
	else if(pid>0)
	{
		printf("I am parent,wait for child die!");
		pid_t wpid = wait(NULL);
		printf("wait ok,wpid=%d,pid=%d\n",wpid,pid);
		while(1)
		{
			sleep(1);
		}
	}
	return 0;
}

在这里插入图片描述

14 wait回收并且查看死亡原因

正常死亡:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
	pid_t pid = fork();
	if(pid == 0)
	{
		printf("I am child,will die!\n");
		sleep(2);
		//return 101;//第一种退出方式
		exit(102);//第二种退出方式
	}
	else if(pid>0)
	{
		printf("I am parent,wait for child die!");
		int status;
		
		pid_t wpid = wait(&status);
		printf("wait ok,wpid=%d,pid=%d\n",wpid,pid);
		if(WIFEXITED(status))
		{
			printf("child exit with %d\n",WEXITSTATUS(status));
		}
		if(WIFSIGNALED(status))
		{
			printf("child killed by %d\n",WTERMSIG(status));
		}
		
		while(1)
		{
			sleep(1);
		}
	}
	return 0;
}

# 加图

非正常死亡:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
	pid_t pid = fork();
	if(pid == 0)
	{
		printf("I am child,will die!\n");
		sleep(2);
		while(1)
		{
			printf("I am child,guo lai da wo!\n");
			sleep(1);
		}
		//return 101;//第一种退出方式
		exit(102);//第二种退出方式
	}
	else if(pid>0)
	{
		printf("I am parent,wait for child die!");
		int status;
		
		pid_t wpid = wait(&status);
		printf("wait ok,wpid=%d,pid=%d\n",wpid,pid);
		if(WIFEXITED(status))
		{
			printf("child exit with %d\n",WEXITSTATUS(status));
		}
		if(WIFSIGNALED(status))
		{
			printf("child killed by %d\n",WTERMSIG(status));
		}
		
		while(1)
		{
			sleep(1);
		}
	}
	return 0;
}

命令行输入:kill -9 5814(pid)
也可以输入:kill 5814,此时的信号是15
在这里插入图片描述

15 waitpid回收子进程

pid_t waitpid(pid_t pid,int *status,int options)

pid:

  • <-1 组id
  • -1 回收任意
  • 0 回收和调用进程组id相同组内的子进程
  • 大于0,回收指定的pid

options:

  • 0与wait相同,也会阻塞
  • WNOHANG如果当前没有子进程退出的,会立刻返回

返回值

  • 如果设置了WNOHANG,那么如果没有子进程退出,返回0
  • 如果设置了WNOHANG,那么如果子进程退出,返回退出的pid
  • 失败返回-1(没有子进程)
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>

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());
		int ret;
		while((ret=waitpid(-1,NULL,WNOHANG))==0)
		{
			sleep(1);
		}
		printf("ret=%d\n",ret);
		ret = waitpid(-1,NULL,WNOHANG);
		if(ret<0)
		{
			perror("wait err");
		}
		while(1)
		{
			sleep(1);
		}
	}
}

# 加图

16 用wait回收多个子进程

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
	int n=5;
	int i=0;
	pid_t pid;
	for(i=0;i<5;i++)
	{
		pid=fork();
		if(pid==0)
		{
			printf("I am child,pid=%d\n",getpid());
			break;
		}
	}
	sleep(i);
	if(i==5)
	{
		for(i=0;i<5;i++)
		{
			pid_t wpid=wait(NULL);
			printf("wpid=%d\n",wpid);
		}
		while(1)
		{
			sleep(1);
		}
	}
	return 0;
}

# 加图

17 waitpid回收多个子进程

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
	int n=5;
	int i=0;
	pid_t pid;
	for(i=0;i<5;i++)
	{
		pid=fork();
		if(pid==0)
		{
			break;
		}
	}
	if(i==5)
	{
		//parent
		printf("I am parent!");
		//如何使用waitpid回收?-1 代表子进程都死了,都收了
		while(1)
		{
			pid_t wpid=wiatpid(-1,NULL,WNOHANG);
			if(wpid==-1)
			{
				break;
			}
			else if(wpid>0)
			{
				printf("waitpid wpid =%d\n",wpid);
			}
		}
		while(1)
		{
			sleep(1);
		}
	}
	if(i<5)
	{
		printf(:I am child,i=%d,pid=%d\n",i,getpid());
	}
	return 0;
}

# 加图

18 作业

1.创建子进程,调用fork之后,在子进程调用自定义程序(段错误,浮点型错误),用waitpid回收,查看退出状态。
fpe.c:

#include<stdio.h>
#include<unistd.h>

int main()
{
	int a =10;
	int b =a/0;
	return 0;
}

直接运行效果:
在这里插入图片描述
execl.c:

#include<stdio.h>
#include<unistd.h>
int main()
{
	execl("./fpe","fpe",NULL);

	printf("bye bye!\n");
	return 0;
}

在这里插入图片描述

2.验证子进程是否共享文件描述符,子进程负责写入数据,父进程负责读数据。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>

int main(int argc,char* argv[])
{
	if(argc!=2)
	{
		printf("./a.out filename\n");
		return -1;
	}
	int fd = open(argv[1],O_RDWR);
	if(fd<0)
	{
		perror(“open err");
		exit(1);
	}
	pid_t pid = fork();
	if(pid==0)
	{
		//son
		write(fd,"hello\n”,6);
	}
	else if(pid>0)
	{
		//parent
		sleep(1);
		write(fd,"world\n”,6);
		sleep(NULL);
	}
	return 0;
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值