Linux系统编程 | 进程控制

程序和进程

程序:程序是指编译好的二进制文件,在磁盘上,占用磁盘空间,是一个静态的概念
进程:进程是一个启动的程序,进程占用的是系统资源,如:物理内存、CUP、终端等,是一个动态的概念


并行和并发

并发:指的是在同一个时间段内,在同一个CPU上,同时运行多个程序,CPU将时间分成时间片,每个进程执行完一个时间片必须无条件的让出CPU使用权。

并行:指的是两个或者两个以上的程序在同一时刻发生(需要有多核)


PCB-进程控制块

说明:每个进程在内核中都有一个进程控制块PCB来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体

task_struct结构体中重要的内部成员有:

  • 进程id,系统中每个进程都有唯一的id,在C语言中用pid_t类型来表示,其实就是一个非负整数
  • 进程的状态,有就绪、运行、挂起、停止等状态
  • 进程切换时需要保存和恢复的一些CPU寄存器
  • 描述虚拟地址空间的信息
  • 描述控制终端的信息
  • 当前的工作目录
  • umask掩码
  • 文件描述符表,包含很多的file结构体指针
  • 和信号相关的信息
  • 用户id和组id
  • 会话和进程组
  • 进程可以使用的资源上限

进程的状态(面试考的重点)

说明:进程的基本状态有5中,分别为初始态、就绪态、运行态、挂起态和终止态,其中初始态为进程准备阶段,常与就绪态结合来看

图解进程状态转换:

在这里插入图片描述



进程相关的函数
1、fork函数

函数作用:创建子进程

函数原型: pid_t fork(void);

函数的参数:无

函数的返回值:

  • 调用成功后,父进程返回子进程的PID,子进程返回0
  • 调用失败,返回-1,并设置errno值

调用fork函数的内核实现原理图解:

在这里插入图片描述

说明:fork函数返回的两个值不是由一个进程返回的,父进程返回一个,子进程返回一个

fork函数总结:

问题一:子进程创建成功之后,代码的执行位置?

答:父进程执行到什么位置,子进程就从哪里开始执行

问题二:如何区分父子进程

答:通过fork函数的返回值进行区分

问题三:父子进程的执行顺序

答:不一定,哪个进程抢到CPU,哪个进程就先执行

代码示例1:

//fork函数测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
	printf("before fork, pid:[%d]\n", getpid());
	//创建子进程
	//pid_t fork(void);
	pid_t pid = fork();
	if(pid<0) //fork失败的情况
	{
		perror("fork error");
		return -1;
	}
	else if(pid>0)//父进程
	{
		printf("father: [%d], pid==[%d], fpid==[%d]\n", pid, getpid(),getppid());
		//sleep(1);
	}
	else if(pid==0) //子进程
	{
		printf("child: pid==[%d], fpid==[%d]\n", getpid(), getppid());
	}
	
	printf("after fork, pid:[%d]\n", getpid());

	return 0;
}

代码示例2:

//fork函数测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
int g_var = 99;

int main()
{
	//创建子进程
	pid_t pid = fork();
	if(pid<0) //fork失败的情况
	{
		perror("fork error");
		return -1;
	}
	else if(pid>0)//父进程
	{
		printf("father: [%d], pid==[%d], fpid==[%d]\n", pid, getpid(),getppid());
		g_var++;
		printf("[%p]", &g_var);
	}
	else if(pid==0) //子进程
	{
		sleep(1); //为了避免父进程还没有执行, 子进程已经结束了
		printf("[%p]", &g_var);
		printf("child: pid==[%d], fpid==[%d]\n", getpid(), getppid());
		printf("child: g_var==[%d]\n", g_var);
	}
	
	return 0;
}

代码示例3

//循环创建n个子进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
	int i = 0;
	for(i=0; i<3; i++)
	{
		//创建子进程
		pid_t pid = fork();
		if(pid<0) //fork失败的情况
		{
			perror("fork error");
			return -1;
		}
		else if(pid>0)//父进程
		{
			printf("father: pid==[%d], fpid==[%d]\n", getpid(),getppid());
			//sleep(1);
		}
		else if(pid==0) //子进程
		{
			printf("child: pid==[%d], fpid==[%d]\n", getpid(), getppid());
			break;
		}
	}

	//第1个子进程
	if(i==0)
	{
		printf("[%d]--[%d]: child\n", i, getpid());	
	}

	//第2个子进程
	if(i==1)
	{
		printf("[%d]--[%d]: child\n", i, getpid());	
	}
	//第3个子进程
	if(i==2)
	{
		printf("[%d]--[%d]: child\n", i, getpid());	
	}
	//父进程
	if(i==3)
	{
		printf("[%d]--[%d]: child\n", i, getpid());	
	}
	sleep(10);

	return 0;
}

ps命令和kill命令

1、ps aux | grep "xxx"
2、ps ajx | grep "xxx"

ps命令的一些参数说明:

  • -a: (all)当前系统所有用户的进程
  • -u: 查看进程所有者以及其他一些信息
  • -x: 显示没有控制终端的进程–不能与用户进行交互的进程
  • -j: 列出与作业控制相关的信息

3、kill -l查看系统有哪一些信号
4、kill -9 pid 杀死某个进程



exec函数族
函数的作用和函数介绍

有时候需要在一个进程里面执行其他的命令或者是用户自定义的应用程序,此时就用到了exec函数族当中的函数,使用的方法一般都是在父进程里面调用fork创建子进程,然后在子进程里面调用exec函数。

execl函数

函数原型:int execl(conse char * path,const char * arg,.../*(char *) NULL */);

参数介绍:

1、path: 要执行程序的绝对路径
2、变参arg: 要执行的程序需要的参数
3、arg: 占位,通常写应用程序的名字
4、arg后面的参数:命令参数
5、参数写完之后: NULL

函数返回值:若是成功,则不返回,不会再执行exec函数后面的代码;若是失败,会执行exec后面的代码,可以用perror打印序错误的原因。exec函数一般执行自己写的程序。

execlp函数

函数原型:int execlp(const char * file, const char * arg,../*(char *) NULL*/);

参数介绍:
1、file: 执行命令的名字,根据PATH环境变量来搜索该命令
2、arg:占位
3、arg后面的: 命令的参数
4、参数写完之后: NULL

函数返回值:若成功,则不返回,不会再执行exec函数后面的代码;若是失败,则会执行exec后面的代码,可以用perror打印错误原因。execlp函数一般是执行系统自带的程序或者是命令


exec函数族原理介绍

exec族函数的实现原理图:

如:execlp("ls","ls","-l",NULL);

图解:

在这里插入图片描述

总结:exec函数是用一个新程序替换了当前进程的代码段、数据段、堆和栈;原有的进程空间没有发生变化,并没有创建新的进程,进程PID没有发生变化

exec函数代码测试

1、使用execl函数执行一个用户自定义的应用程序
2、使用execlp函数执行一个linux系统命令

代码示例:

//fork函数测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
	//创建子进程
	pid_t pid = fork();
	if(pid<0) //fork失败的情况
	{
		perror("fork error");
		return -1;
	}
	else if(pid>0)//父进程
	{
		printf("father: [%d], pid==[%d], fpid==[%d]\n", pid, getpid(),getppid());
		//sleep(1);
	}
	else if(pid==0) //子进程
	{
		printf("child: pid==[%d], fpid==[%d]\n", getpid(), getppid());
		//execl("/bin/ls", "ls", "-l", NULL);
		//execl("./test", "test", "hello", "world", "ni", "hao", NULL);
		//execlp("ls", "ls", "-l", NULL);
		execlp("./test", "TESTING", "hello", "world", "ni", "hao", NULL);
		//execlp("./iitest", "test", "hello", "world", "ni", "hao", NULL);
		perror("execl error");
	}
	
	return 0;
}

总结:当execl和execlp函数执行成功之后,不返回,并且不会执行execl后面的代码逻辑,原因是调用execl函数成功之后,exec函数执行的代码段已经将原有的代码段给替换了。



进程回收
为什么要进行进程资源的回收

当一个进程退出之后,进程能够回收自己的用户区的资源,但是不能回收内核空间的PCB资源,必须由它的父进程调用wait或者waitpid函数完成对子进程的回收,避免造成系统资源的浪费。

孤儿进程

1、孤儿进程的概念:若子进程的父进程已经死掉,而子进程还存活着,这个进程就成了孤儿进程
2、为了保证每个进程都有一个父进程,孤儿进程会别init进程领养,init进程成为了孤儿进程的养父进程,当孤儿进程退出之后,由init进程完成对孤儿进程的资源回收。

代码测试:编写模拟孤儿进程的代码,验证孤儿进程的父进程是否由原来的父进程编程了init进程。

代码示例:

//孤儿进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
	//创建子进程
	pid_t pid = fork();
	if(pid<0) //fork失败的情况
	{
		perror("fork error");
		return -1;
	}
	else if(pid>0)//父进程
	{
		sleep(5);
		printf("father: [%d], pid==[%d], fpid==[%d]\n", pid, getpid(),getppid());
	}
	else if(pid==0) //子进程
	{
		
		printf("child: pid==[%d], fpid==[%d]\n", getpid(), getppid());
		sleep(20);
		printf("child: pid==[%d], fpid==[%d]\n", getpid(), getppid());
	}
	

	return 0;
}


僵尸进程

1、僵尸进程的概念:若子进程死了,父进程还活着,但是父进程没有调用wait或者waitpid函数完成对子进程的回收,则该子进程就成了僵尸进程。

2、如何解决僵尸进程

  • 由于僵尸进程是一个已经死亡的进程,所以不能使用kill命令将其杀死
  • 通过杀死其父进程的方法可以消除僵尸进程。杀死其父进程后,这个僵尸进程回被init进程领养,由init进程完成对僵尸进程的回收

3、僵尸进程的代码测试:编写模拟僵尸进程的代码,验证若子进程先与父进程退出,而父进程没有调用wait或者wait函数进行回收,从而使得子进程成为了僵尸进程

代码示例:

//僵尸进程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
	//创建子进程
	pid_t pid = fork();
	if(pid<0) //fork失败的情况
	{
		perror("fork error");
		return -1;
	}
	else if(pid>0)//父进程
	{
		sleep(100);
		printf("father: [%d], pid==[%d], fpid==[%d]\n", pid, getpid(),getppid());
	}
	else if(pid==0) //子进程
	{
		
		printf("child: pid==[%d], fpid==[%d]\n", getpid(), getppid());
	}
	

	return 0;
}

进程回收函数wait

函数原型:pid_t wait(int *status);

函数的作用;

1、阻塞并等待子进程退出
2、回收子进程残留的资源
3、获取子进程结束状态(退出的原因)

函数的返回值:

1、成功:返回清理掉的子进程的ID
2、失败:返回-1(没有子进程)

status参数:子进程退出的状态—传出参数

1、WIFEXITED(status):为非0,进程正常结束
WEXITSTATUS(status):获取进程退出状态
2、WIFSIGNALED(status):为非0,进程异常终止
WTERMSIG(status):取得进程终止的信号编号

函数测试:使用wait函数完成父进程对子进程的回收

代码示例:

//父进程调用wait函数完成对子进程的回收
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

int main()
{
	//创建子进程
	pid_t pid = fork();
	if(pid<0) //fork失败的情况
	{
		perror("fork error");
		return -1;
	}
	else if(pid>0)//父进程
	{
		printf("father: [%d], pid==[%d], fpid==[%d]\n", pid, getpid(),getppid());
		int status;
		pid_t wpid = wait(&status);
		printf("wpid==[%d]\n", wpid);
		if(WIFEXITED(status)) //正常退出
		{
			printf("child normal exit, status==[%d]\n", WEXITSTATUS(status));
		}
		else if(WIFSIGNALED(status)) //被信号杀死
		{
			printf("child killed by signal, signo==[%d]\n", WTERMSIG(status));
		}
		
	}
	else if(pid==0) //子进程
	{
		
		printf("child: pid==[%d], fpid==[%d]\n", getpid(), getppid());
		sleep(20);
		return 9;
	}

	return 0;
}

进程回收函数waitpid函数

函数原型:pid_t waitpid(pid_t pid,int * status,int options);

函数的作用:

1、阻塞并等待子进程退出
2、回收子进程残留的资源
3、获取子进程结束状态(退出的原因)

函数的参数:

1、pid

  • pid = -1,等待任一子进程,与wait函数等效。
  • pid > 0,等待其进程ID与pid相等的子进程
  • pid = 0, 等待进程组ID与目前进程在同一个进程组的进程,也就是说任何和调用waitpid()函数的进程在同一个进程组的进程。
  • pid < 0,等待其组ID等于pid的绝对值的任一子进程。(适用于子进程在其他组的情况)

2、status: 子进程的退出状态,用法同wait函数

3、options:设置为WNOHANG,函数非阻塞,设置为0,函数阻塞。

函数的返回值:

  • 大于0: 返回回收掉的子进程ID
  • -1: 无子进程
  • = 0 :参数3为WNOHANG,且子进程正在运行
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值