进程的一些基本概念

进程的基本状态

为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。

下面的状态在 fs/proc/array.c 文件中定义: 


static const char* const task_state_array[] = {
"R (running)",/* 0 */
"S (sleeping)",/* 1 */
"D (disk sleep)",/* 2 */
"T (stopped)",/* 4 */
"t (tracing stop)",/* 8 */
"X (dead)",/* 16 */
"Z (zombie)",/* 32 */
}; 

其中,我分别进行一下简单的介绍:

①R(running)表示运行状态,但是这并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列中。

②S(sleeping)表示睡眠状态,意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))

③D(disk sleep)表示磁盘休眠状态,有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束,这是一种特殊的S状态,不能被打断,仅能通过自己醒来。

④T(stopped)表示停止状态,可以通过发送SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行。

⑤t(tracing stop)表示追踪停止状态。

⑥X(dead)表示死亡状态。

⑦Z(zombie)表示僵尸状态,是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵死进程。僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。 


进程的优先级

进程cpu资源分配就是指进程的优先权(priority)。优先权高的进程有优先执行权利。

配置进程优先权对多任务环境的linux很有用,可以改善系统性能。还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。 

一、先看系统进程:

无论在linux或者unix系统中,用ps –l命令则会类似输出以下几个内容:

 
我们很容易注意到其中的几个重要信息,有下:
①UID:代表执行者的身份

②PID:代表这个进程的代号

③PPID:代表这个进程由哪个进程发展衍生而来,即父进程的代号

④PRI:代表这个进程可被执行的优先级,其值越小越早被执行

⑤NI:代表这个进程的nice值

其实,除了NI,其余的几个概念都是比较容易被理解的,但是NI是用来干嘛的呢?其表示进程可被执行的优先级的修正数值。如前面所说,PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice。 这样,当nice值为负值的时候,那么该程序将会优先级值将变小,即其优先级会变高,则其越快被执行。 

但是呢,现在更需要强调的一点是,进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化 。

二、修改进程优先级的命令主要有两个: nice,renice

1、一开始执行程序就指定nice值:nice 

2.1、调整已存在进程的nicerenice

renice -5 -p 2243  ---->nice值被调整为-5;

renice 15 -p 2243 ---->nice值被调整为15;

参数:

-g <程序群组名称> 使用程序群组名称,修改所有属于该程序群组的程序的优先权。

-p <程序识别码> 改变该程序的优先权等级,此参数为预设值。

-u <用户名称> 指定用户名称,修改所有属于该用户的程序的优先权。 

2.2、也可以用top命令更改已存在进程的nice 

1.top

2.#进入top后按“r”–>输入进程PID–>输入nice 

进程的创建执行

当进程执行时,它会被装载进虚拟内存,为程序变量分配空间,并把相关信息添到task_struct中

进程内存布局分为四个不同的段:

• 文本段,包含程序的源指令。

数据段,包含了静态变量。

堆,动态内存分区区域。

栈,动态增长与收缩的段,保存本地变量。 

这里有两种创建进程的方法, fork()execve()。它们都是系统调用,但它们的运行方式有点不同。 

要创建一个子进程可以执行fork()系统调用。然后子进程会得到父进程中数据段,栈段和堆区域的一份拷贝。子进程独立可以修改这些内存段。但是文本段是父进程和子进程共享的内存段,不能被子进程修改。 

如果使用execve()创建一个新进程。这个系统调用会销毁所有的内存段去重新创建一个新的内存段。然后,execve()需要一个可执行文件或者脚本作为参数,这和fork()有所不同。注意,execve()fork()创建的进程都是运行进程的子进程。 

孤儿进程:

一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集操作。

验证孤儿进程

代码:


然后我们运行代码:


然后再通过kill指令杀死父进程


然后我们可以得到:


剩余的子进程就会被1号进程领养了。

僵尸进程:

一个子进程在其父进程没有调用wait()waitpid()的情况下退出。这个子进程就是僵尸进程。如果其父进程还存在而一直不调用wait,则该僵尸进程将无法回收,等到其父进程退出后该进程将被init回收。 

先简单介绍一下wait()和waitpid()两个函数:

函数作用:父进程用来获取已终止进程的退出状态,并彻底清除该进程

wait函数调用后的情况

1、若该进程的所有子进程都在运行,则wait阻塞

2、若该进程的一个进程结束了,则获取该结束进程的死亡信息,返回

3、若该进程没有子进程,则出错返回

验证僵尸进程

代码:

运行代码:


可以看到子进程的pid是2824,然后我们通过grep去找到这个子进程


我们可以看到子进程的状态变成了Z,即子进程变成了僵尸状态。


子进程的异步等待方式

上面呢我们提到了僵尸进程是是可以通过调用wait和waitpid函数来清理的,父进程可以通过阻塞等待子进程结束,也可以非阻塞的查询是否有子进程结束等待清理(轮询的方式)。

这里呢我们仔细介绍一下wait和waitpid两个函数

wait函数

#include<sys/types.h>
#include<sys/wait.h>
 pid_t wait(int* status)

函数说明: 

进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。 

参数: 

参数 statloc 是一个整形指针。如果status不是一个空指针,则终止进程的终止状态将存储在该指针所指向的内存单元中。如果不关心终止状态,可以将 status参数设置为NULL。 

返回值: 

如果执行成功则返回子进程识别码(PID),如果有错误发生则返回-1。失败原因存于errno 中。 

waitpid函数

#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid,int* status,int options);

函数说明: 

从本质上讲,系统调用waitpid和 wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,从而为我们编程提供了另一种更灵活的方式。

参数: 

(1)pid:从参数的名字pid和类型 pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。

①pid>0时,等待进程ID等于 pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。

②pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。

③pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。

④pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

(2)ststus与wait()函数的参数基本相同。 

(3)options: 

①当options参数为0时,与wait功能相同,仍是阻塞式等待,不提供额外功能,如果为下列常量按位或则提供更多功能: 

②WCONTINUED:若实现支持作业控制,那么由pid指定的任一子进程在暂停后已经继续,但状态尚未报告,则返回状态 

③WNOHANG:若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,即此时以非阻塞方式(轮询式访问的必要条件)等待子进程,并且返回0。 

④WUNTRACED:若实现支持作业控制,而pid指定的任一子进程已经暂停,且其状态尚未报告,则返回其状态 

返回值: 

waitpid的返回值比wait稍微复杂一些,一共有3种情况:

①当正常返回的时候,waitpid返回收集到的子进程的进程ID;

②如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0; 

③如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD;

另外,waitpid提供了三个wait所没有的功能:

①waitpid可等待一个特定的进程

②waitpid提供了一个wait的非阻塞版本

③waitpid支持作业控制

SIGCHLD

我这里再提一个信号的概念,这个信号叫做SIGCHLD,当子进程退出时,它会向父进程发送SIGCHLD信号,该信号的默认处理方式为忽略,当父进程以阻塞方式等待时,它不能处理自己的工作。 我们可以自定义一个捕捉信号的函数handler。

验证子进程退出时会给父进程发送SIGCHLD信号

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

void myhandler(int sig)
{
	printf("get a sig : %d, pid = %d\n",sig, getpid());
}

int main()
{
	signal(SIGCHLD, myhandler);
	pid_t id = fork();
	if(id == 0)//child
	{
		printf("i am child, pid = %d\n", getpid());
		exit(1);
	}
	else//father
	{
		waitpid(id, NULL, 0);
	}
	return 0;
}
运行后得到


我们可以看到17就是SIGCHLD的值

验证父进程等待子进程的异步方式

父进程自定义SIGCHLD信号的处理函数,并采用非阻塞方式等待,当子进程退出时,会向父进程发送信号,父进程会进行回收。父子进程互不干扰,继续执行各自任务。

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

void myhandler(int sig)
{
	printf("father catch, child quit\n");

	pid_t id;
	while((id = waitpid(-1, NULL, WNOHANG)) > 0)
	{
		printf("wait child success %d\n", id);
	}
}

int main()
{
	signal(SIGCHLD,	myhandler);
	pid_t id = fork();
	if(id == 0)//child
	{
		printf("i am child, quit pid: %d\n", getpid());
		exit(1);
	}
	else//father
	{
		while(1)
		{
			printf("father is running\n");
			sleep(1);
		}
	}

	return 0;
}
然后运行程序得到


我们可以看到父进程做自己的事情,而且子进程退出之后等待成功,父进程非阻塞等待,继续做自己的事情。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值