进程管理

进程是UNIX系统中仅次于文件的基本抽象概念。当代目标代码执行时,正在运行的不仅仅是汇编代码,而是由数据、资源、状态和虚拟的计算机组成。

一、程序、进程和线程

程序(program)是指编译过的、可执行的二进制代码,保存在存储介质,如磁盘上。规模很大的二进制程序集可以称为应用。/bin/ls和/usr/bin/X11都属于二进制程序。

进程(process)是指正在运行的程序,是分配资源的基本单位。进程包括二进制镜像,加载到内存中,还涉及很多其他的方面:虚拟内存实例、内核资源如打开的文件、安全上下文如关联的用户,以及一个或多个线程。

线程(thread)是进程内的活动单元,是操作系统能够进行运算调度的最小单位。线程自己基本不拥有系统资源,运行时每个线程包含自己的虚拟存储器,包括栈、进程状态和寄存器,以及指令指针。

在单线程的进程中,进程即线程。一个进程只拥有一个虚拟内存实例,一个虚拟处理器。在多线程的进程中,一个进程拥有多个线程。由于虚拟内存和进程关联的,所有线程会共享相同的内存地址空间。

二、进程ID

系统给每个进程定义了一个唯一标识该进程的非负整数,称作进程标识符(PID:Process Identifier)。

进程ID是可复用的,当一个进程终止后,其进程ID就成为复用的候选者。大多数unix系统实现延时复用算法,使得赋予新建进程的ID不同于最近终止进程所使用的ID。这防止了新进程误认为是使用同一ID的某个已终止的先前进程。

ID为0的进程通常为空闲进程(idle process),常常被称为调度进程或交换进程。该进程是内核的一部分,它不执行任何磁盘上的程序,因此也被称为系统进程。idle进程由系统自动创建, 运行在内核态。其前身是系统创建的第一个进程,也是唯一一个没有通过fork或者kernel_thread产生的进程。完成加载系统后,演变为进程调度、交换。

ID为1的进程为init进程,init进程由idle通过kernel_thread创建,在内核空间完成初始化后, 加载init程序, 并最终用户空间。由0进程创建,完成系统的初始化. 是系统中所有其它用户进程的祖先进程 。Linux中的所有进程都是有init进程创建并运行的。首先Linux内核启动,然后在用户空间中启动init进程,再启动其他系统进程。在系统启动完成完成后,init将变为守护进程监视系统其他进程内核init线程,最终执行/sbin/init进程(unix早期版本为/etc/init),变为所有用户态程序的根进程(pstree命令显示),即用户空间的init进程。

  • 所有进程追溯其祖先最终都会落到进程号为1的进程身上,这个进程叫init进程;
  • init进程是linux内核启动后第一个执行的进程;
  • init引导系统,启动守护进程并且运行必要的程序;

2.1、分配进程ID

缺省情况下,内核将进程ID的最大值设置为32768,这是为了和老UNIX系统兼容,因为这些系统使用了有符号16位数来表示进程ID。系统管理员可以通过修改/proc/sys/kernal/pid_max把这个值设置为更大的值,但是会牺牲一些兼容性。

内核分配进程ID是以严格的线性的方式执行的。如果当前的pid的最大值是17,那么分配给新进程的pid的值就是18,即使当新进程开始运行时,pid为17的进程已经不在运行。内核分配的值达到了/proc/sys/kernal/pid_max之后,才会重用以前分配过的pid值。因此,尽管内核不保证长时间的进程id的唯一性,但这种分配方式至少可以保证pid在短时间内是稳定且唯一的。

2.2、进程体系

创建新进程的进程为父进程,被创建的新进程为子进程。
每个进程都是由其他进程创建的(除init进程),因此每个进程都有一个父进程。这种关系保存在每个进程的父进程ID号(ppid)中。

每个进程都属于某个用户和某个组。对于内核来说,用户和组都是整数值,可以通过/etc/passwd/etc/group这两个文件查看。

从编程角度看,进程ID是由数据结构pid_t来表示的,头文件<sys/types.h>中定义。

三、运行新进程

在UNIX中,把程序载入内存执行的操作和创建新进程的操作是分离的。

一次系统调用会把二进制程序加载到内存中,替换地址空间原来的内容,并开始执行。这个过程称为执行一个新的程序,是通过一系列exec系统调用完成的。

另外一个系统调用是用于创建一个新的进程,他基本相当于复制其父进程。创建新进程的方式是派生(fork)。

一般在新进程中创建一个新进程的步骤是:fork + exec

四、终止进程

POSIX和C89都定义了一个标准函数,可以终止当前进程:

#include<stdlib.h>
void exit(int status);
  • exit(0): 正常运行程序并退出程序,exit(0)可以告知你的程序的使用者:你的程序是正常结束的。
  • exit(1): 非正常运行导致退出程序;

对exit的调用通常会执行一些基本的关闭步骤,然后通知内核终止这个进程。

参数status用于标识进程的退出状态。其他程序比如说shell用户,可以检查这个值。

exit内部会调用系统调用_exit,主要注意的是vfork用户终止进程时必须调用_exit,而不是exit

4.1、atexit

功能:
注册终止函数(即main执行结束后调用的函数)。

注意:
exit调用这些注册函数的顺序与它们 登记时候的顺序相反。同一个函数如若登记多次,则也会被调用多次。

#include<stdlib.h>
void atexit(void (*func)(void));
#include<stdio.h> 
#include<stdlib.h>  //atexit函数所属头文件
   
void func1() 
{ 
    printf("The process is done...\n"); 
} 
void func2() 
{ 
    printf("Clean up the processing\n"); 
} 
void func3() 
{ 
    printf("Exit sucessful..\n"); 
} 
int main() 
{ 
  //其作用是注册某一个函数,当进程执行结束时,会自动调用注册的函数
  //注册几次,就执行几次
    atexit(func1); 
    atexit(func2); 
    atexit(func3); 
    exit(0); 
} 

运行截图

4.2、SIGCHLD

当一盒个进程终止时,内核会向其父进程发送SIGCHLD信号。默认情况下,父进程会忽略此信号,也不会采取任何操作。但是进程可以调用signal()等函数来处理这个信号。

五、等待子进程终止

如果子进程在父进程之前结束,内核应该把该子进程设置为特殊的进程状态。处于这种进程状态的进程称为僵尸进程。僵尸进程值保留最小的概要信息(一些基本的内核数据结构,保存可能有用的信息)。僵尸进程会等待父进程来查询自己的状态,只有父进程获取到了已终止的子进程信息,这个子进程才会正式消失,不再处于僵尸状态。

Linux提供了一些接口:

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

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

wait3wait4函数除了可以获取子进程状态转变信息外,还可以获得子进程的资源使用信息。

pid_t wait3( int *status, int option, struct rusage *ru );
pid_t wait4( pid_t pid, int *status, int option, struct rusage *ru ); 

option的可选值有:WNOHANG、WCONTINUED、WUNTRACED。

wait3等待所有的子进程;wait4可以像waitpid一样指定要等待的子进程:pid>0表示子进程ID;pid=0表示当前进程组中的子进程;pid=-1表示等待所有子进程;pid<-1表示进程组ID为pid绝对值的子进程。

因为wait3和wait4不是由POSIX所定义的,所以最好不要使用他们,除非真的需要了解子进程的资源使用情况。尽管wait3和wait4不是由POSIX所定义的,但是几乎所有的UNIX系统都支持它们。

六、进程组和会话

6.1、进程组

进程组就是多个进程的集合,其中肯定有一个组长,其进程PID等于进程组的PGID。
进程组的概念并不难理解,可以将人与人之间的关系做类比。一起工作的同事,自然比毫不相干的路人更加亲近。shell中协同工作的进程属于同一个进程组,就如同协同工作的人属于同一个部门一样。

最常见的创建进程组的场景就是在shell中执行管道命令,代码如下:cmd1 | cmd2 | cmd3

[root@localhost home]# sleep 100 |sleep 200 |sleep 300 &
[1] 51239
[root@localhost home]# ps -axj | head -n1
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND
[root@localhost home]# ps -axj | grep sleep
41037 51237 51237 41037 pts/12   51259 S        0   0:00 sleep 100
41037 51238 51237 41037 pts/12   51259 S        0   0:00 sleep 200
41037 51239 51237 41037 pts/12   51259 S        0   0:00 sleep 300
41037 51260 51259 41037 pts/12   51259 S+       0   0:00 grep --color=auto sleep
  • PID:进程的唯一标识。对于多线程的进程而言,所有线程调用getpid函数会返回相同的值。
  • PGID:进程组ID。每个进程都会有进程组ID,表示该进程所属的进程组。默认情况下新创建的进程会继承父进程的进程组ID。
  • SID:会话ID。每个进程也都有会话ID。默认情况下,新创建的进程会继承父进程的会话ID。

我们通过管道创建了三个进程, 可以看到他们都属于一个进程组,其中组长进程是 51237 。
我们首先应该注意到,只要该进程组内有任何一个进程存留,那么该进程组都存在。
最后可以看到他们的父进程都是 41037。

6.2、会话

会话(Session)是一个或多个进程组的集合,一个会话可以有一个控制终端.这通常是登录到其上的终端设备(在终端登录情况下) 或伪终端(在网络登录情况下).建立与控制终端连接的会话首进程被称为控制进程.一个会话中的及格进程组可被分为一个前台进程组以及一个或多个后台进程组.所以一个会话中,应该包括控制进程(会话首进程),一个前台进程组和任意后台进程组 。
当我们多打开几个终端就会发现,就是多创建了几个新的会话.。

每个终端的话首进程就是bash(命令行解释器)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值