进程

进程编程

一、进程、程序

1、程序:
	程序是保存在磁盘中的可以实现某个独立功能的代码块,它包含代码和数据,是静态的,没有运行的。

2、进程:
	进程是程序的一次动态执行过程,包括了动态的创建、调度、执行和消亡的过程。进程是操作系统进行资源
分配和调度的基本单元。

二、Linux系统下进程的结构

	进程不断包括代码和数据还包括系统资源,如程序计数器PC(存放下一条指令的地址)、处理器、寄存器、
以及存储临时数据的堆栈等。

1、task_struct结构体

	Linux是一个多任务的操作系统,所以进程必须等待操作系统将处理器的使用权分配给自己才能运行,Linux
内核将所有的进程存放在一个双向链表中(进程链表),链表中的每一项都是“task_struct”的结构体,被称为
进程控制块,“task_struct”进程控制块是pcb进程控制块的一种,在该结构体中存放着一些关于某个进程的全
部信息,例如进程的状态、进程标识符、内存相关信息等。

“task_struct”中最为重要的两个域:state(进程状态)、pid(进程标识符)。

(1)Linux下进程的状态

	Linux系统下进程有如下几种状态:
(1)R		运行状态:表示进程正在运行,或者在进程队列中等待调度。
(2)S		可中断的阻塞状态:处于该状态下的进程可以被信号中断,接收到信号或者被显式的唤醒之后,
			进程转变为运行态。
(3)D		不可中断的阻塞状态:处于该状态下的进程不会处理信号,即不会被信号中断,此状态下的进程
			只有在特定的事件发生时,进程才会被显式的唤醒,进入运行态。
(4)T		暂停状态:进程的执行被暂停,当进程接收到SIGSTOP等信号时进程会进入暂停状态
(5)Z		僵尸状态:当子进程先于父进程结束,并且父进程中未调用wait或者waitpid函数来对子进程
			进行回收时,子进程为僵尸态。此时的子进程无内存空间、无数据、也不能被调度,只是它的
			进程控制块未被释放而已。
(6)X		消亡状态:进程退出、资源回收、进程控制块“task_struct”被释放。

在这里插入图片描述

(2)进程的标识符pid

	Linux内核通过进程标识符来唯一的标识一个进程,进程标识符pid存放在进程控制块“task_struct”的
pid字段。

三、Linux下进程的类型

Linux系统主要包含以下几种进程:交互式进程、批处理进程、守护进程。
交互式进程:这类进程主要用于与用户之间进行交互,需要接收用户的输入。例如shell命令实际上就是交互式
		   式进程。
批处理进程:批处理进程是进程的集合,维护一个进程的队列,负责按顺序启动队列中的进程。
守护进程:这类进程一直在后台运行,和任何终端都不关联,守护进程是脱离终端的,从开机开始运行,到关机
		 结束运行。像一些服务进程就是守护进程。

四、进程的相关命令

ps -aux			详细的显示进程的相关信息
ps -ef 
ps -ef | more	分屏显示进程(利用了管道)
top				动态的显示进程的相关信息
env				查看当前进程的环境变量
bg				将挂起的进程放在后台运行(CTRL Z命令可以将进程挂起)
jobs			查看后台运行的进程
pstree			查看进程树

五、虚拟内存映射

	Linux系统使用了虚拟内存管理技术,使得每个进程都有自己独立的进程空间,但又不会接触到实际的物理
内存地址,更加的安全。这就是为什么通过fork函数创建的子进程中的数据和父进程中的数据具有相同的物理地
址,但是两个进程中的数据互不影响。

在这里插入图片描述
六、进程编程相关函数

1、fork函数

函数原型:pid_t fork();
函数功能:在已存在的进程中创建一个新的进程,新进程被称为子进程,已存在的进程被称为父进程。
返回值: >0	代表当前进程为父进程,返回值为子进程的进程号
		=0	代表当前进程为子进程
		-1	创建进程失败。
返回值说明:fork函数会有两个返回值,用于区分子进程与父进程。当fork函数的返回值为0时,表示当前的
		  进程是子进程;当返回值大于0时,表示当前的进程为父进程,返回值代表子进程的进程号。

fork函数说明:
(1)fork函数用于在已存在的进程中创建一个子进程,使用fork函数创建的子进程是父进程的一个复制品,子
进程从父进程处继承了整个的地址空间、数据、代码段、上下文、程序计数器PC(存放下一条指令的地址)、
打开的文件描述符等。而子进程所独有的只有它的进程号、资源使用、计时器(时间片)。
(2)由于虚拟内存映射技术,因此父子进程之间互不影响,同时需要注意子进程和父进程具有相同的程序计数
器PC(存放下一条语句的地址),因此实际上子进程是从fork语句的下一条语句开始执行。
(3)父子进程有一个很重要的区别就是fork函数的返回值。子进程中fork函数的返回值是0;父进程中fork
函数的返回值是大于0的整数。


getpid()	获得当前进程的进程号
getppid()	获取当前进程的父进程的进程号
getpgid()	获得当前进程的进程组ID

在这里插入图片描述
在这里插入图片描述

运行结果分析:
(1)“Hello World”只输出一次的原因是因为子进程和父进程具有相同的程序计数器PC,因此子进程从fork
的下一条语句开始执行。
(2)父进程调用fork函数创建子进程后,父进程的时间片可能还没有用完,因此系统首先给父进程分配处理器
(3)上图中第23行的printf语句打印两次的原因:子进程是父进程的复制品,因此该printf语句在子进程中也
有一份。如果把该条printf语句放在第16-20行之间,那么只会在父进程中打印而子进程中不会打印。
(4)多次运行时在子进程中打印的父进程的pid可能与父进程中打印的pid不同,因为父进程可能运行结束,进
入了消亡态,同时把子进程“过继”给了系统的“init”进程。所以在子进程中打印的getppid()可能是“init”进
程的进程号。

孤儿进程与僵尸进程

孤儿进程:孤儿经常产生的原因是因为父进程先于子进程结束退出,子进程由init进程收养,此时的子进程被
		 称为孤儿进程。
僵尸进程:僵尸进程产生的原因是因为子进程先于父进程结束退出,而父进程没有调用wait或waitpid等系统
		 调用对子进程进行回收,此时的子进程被称为僵尸进程。

2、exec函数簇

	由上可以知道,通过fork函数创建的子进程几乎和父进程做了一样的工作,这在实际工作中是毫无意义的。exec
函数簇就提供了一个在新程序中执行另一个程序的方法。exec函数簇可以根据指定的文件名或目录名找到可执行文件,
并用它来取代当前进程的数据段、代码段和堆栈段。

exec函数簇中有如下一些函数:
int execl(const char *path, const char *arg, ...);
int execv(const char *path, char * const argv[]);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execlp(const char *file, cosnt char *arg, ...);
int execve(cosnt char *path, char * const argv[], char * const envp[]);
int execvp(const char *file, char * const argv[]);

	可以从以下几个方面来对exec函数簇中的函数进行区分:可执行文件的查找方式、传递给可执行文件参数
的方方式、是否传递给可执行文件环境变量。

“l v e p”分析:
l	list,以列举的方式给可执行文件传递参数,参数列举要以NULL作为结尾。
v	vector,以指针数组的方式将参数传递给可执行文件,指针数组要以NULL作为数组元素的结尾。
e	environment,以指针数组的方式将环境变量传递给可执行文件,指针数组要以NULL作为数组的尾元素。
p	path,表示可以只传递可执行文件的文件名即可,不需要完整的路径。但是需要在PATH环境变量中添加
	可执行文件的路径。PATH环境变量中包含的是所有可执行文件的存放路径。

以下为exec函数簇的使用用例:
1)execlp函数举例:

在这里插入图片描述

2)execle函数举例:

在这里插入图片描述

3)execl函数举例:

在这里插入图片描述

3、进程终止函数

退出进程主要使用exit()和_exit()函数:
函数原型:void exit(int status);
		 void _exit(int status);
函数参数:status是一个整形的参数,可以利用该函数传递进程退出时的状态。一般用0表示进程正常退出;
		 非0表示进程非正常退出。
通常用宏来表示进程的退出状态:#define EXIT_FAILURE	1
							#define EXIT_SUCCESS 0

exit()函数和_exit()函数的区别:
	exit函数在进程退出时会刷新进程的缓冲区,将缓冲区中的内容写入指定的文件;而_exit函数不会刷新
缓冲区,这样便会导致数据的丢失。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
4、进程的回收

进程的回收实际上及时进程控制块的回收,在父进程中通过调用wait()和waitpid()函数来回收子进程。

(1)wait函数

函数原型:pid_t wait(int *status);
函数参数:status	status指向的整型对象用来保存子进程退出时的状态,另外子进程的退出状态可以由Linux
				的一些宏来进行测定。
返回值:>0	表示子进程回收成功,返回值是所回收的子进程的进程号。
	   ==-1	表示子进程回收失败 

参数说明:
1)参数status是一个整型变量,用来保存子进程的退出状态。当在子进程中调用exit或_exit()函数退出子进
程的时候,这两个函数的参数就代表进程的退出状态;在父进程中调用wait函数对子进程退出状态进行回收,而
wait函数的参数就是保存子进程退出信息的变量的地址。
2)当wait函数的参数为空时,表示父进程直接回收子进程,释放子进程的进程控制块,不关心子进程的退出状态。

**Linux中对子进程的退出状态进行测定的宏函数**
1)WIFEXITED(status)		判断进程是否正常退出,返回一个逻辑值
2)WEXITSTATUS(status)		当进程正常退出时,通过该宏函数将子进程的退出状态提取出来。
3)WIFSIGNALED(status)		判断进程是否因为信号中断而退出,返回一个逻辑值
4)WTERMSIG(status)			当进程是因为信号中断而退出时,利用该宏函数获取中断信号的类型。

在这里插入图片描述
在这里插入图片描述

(2)waitpid函数

函数原型:pid_t waitpid(pid_t pid, int *status, int position);
函数参数:pid		>0,表示回收指定进程号的进程
					-1,表示回收任意子进程(此时和wait函数一样)
		 status		指向的整型对象用来保存子进程的退出状态
		 options	WNOHANG,以非阻塞的方式回收子进程,如果此时子进程还没有结束退出,则waitpid返回
		 					0,并不会阻塞。
					0,同wait函数一样,以阻塞的方式回收子进程。
返回值:>0	表示回收子进程的进程号
	   =0 	表示回收的进程没有退出,父进程不阻塞,立刻返回0
	   ==-1	表示子进程回收失败,出错

在这里插入图片描述
在这里插入图片描述

七、守护进程

	守护进程是周期性的执行某种任务或者等待某些事件发生的进程。
守护进程的特点:
1)守护进程是脱离终端的进程、不依赖于任何终端
2)守护进程是周期性的执行某项任务,生命期很长。

ps -auj 	可以查看进程的相关信息;
			PID			PGID		TTY		
			进程号		进程组ID		当前进程所属终端:所属终端为?表示的是守护进程

守护进程的编写步骤

(1)创建子进程、父进程退出

	因为守护进程是脱离终端的,当子进程创建好之后,父进程退出,这样子进程就变为了孤儿进程,从形式上变
做到了与终端脱离关系,但是实际上子进程并没有真正意义上脱离终端。

(2)在子进程中创建新会话

1、首先介绍两个概念:进程组与会话期

进程组:(1)进程组是一个或多个进程的集合。进程组由进程组ID来唯一的标识。
	   (2)每个进程组都有一个组长进程,组长进程的进程号pid等于进程组id,并且进程组id不会因为组长进
	   	程的退出而受到影响。
会话期:(1)会话组是一个或多个进程组的集合。可以将会话期理解为一个shell终端,即会话期中的进程是依赖
		与shell终端的。


2、setsid()函数的功能:
	setsid函数用于创建一个新的会话,并且担任该会话组的组长。setsid函数有一下三个作用:让进程摆脱原有
会话的控制、让进程摆脱原进程组的控制、让进程摆脱原控制终端的控制。

	实际上在调用fork函数创建子进程的时候,子进程复制了父进程的会话期、进程组和控制终端。即使父进程
退出,但原有的会话期、进程组和控制终端依然存在,子进程并未真正意义上做到独立,而setsid函数能够使进程
完全脱离出来,从而脱离其它进程的控制。

setsid函数的原型:pid_t setsid(void);
说明:当在子进程中调用了setsid函数之后,实际上当前的子进程已经是一个守护进程了,后续的工作是为了守护
	 进程做一些优化。

(3)改变当前目录

	由于进程在运行时,当前目录所在的文件系统是不能被卸载的,子进程复制了父进程的当前工作目录,这样会
造成诸多的麻烦。因此通常的做法是让根目录作为守护进程的当前公国目录,这样便可以避免上述问题。

(4)取消文件权限掩码

	通常将守护进程的文件权限掩码设置为umask(0),这样守护进程就有足够的权限,增强了守护进程的灵活性。

(5)关闭文件描述符

	在父进程中打开的文件的文件描述符会被子进程复制过来,但是守护进程是永远不会访问这些打开的文件的,
这样就造成了资源的浪费,因此需要将打开的文件进行关闭。
	特别是守护进程和终端无关,所以标准输入、标准输出、标准错误已经失去了价值,应当被关闭。
int num;
num = getdtablesize();//获得当前进程文件描述符表的大小 
for(i = 0; i < num; i++)
	close(i);

6、编写守护进程的业务逻辑

守护进程示例:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值