我们把正在执行的程序称为进程,这是一种广义的定义。更具体一点来说,进程是由正文端 (text) 、用户数据段 (usr segment) 以及系统数据段 (system segment)共同组成的一个程序执行环境。
进程和程序
程序
程序是一个包含及其代码指令和数据的可执行文件,这个文件一般储存在磁盘上(储存设备)上,所以,程序是一个静态的实体。比如,我们用 C 语言写了一个C源程序,这个程序要经过 “预处理-编译-汇编-链接”这一过程 才能生成可执行文件,这个可执行文件在 Windows 下一般为 xxx.exe
,该可执行文件才是我们所说的程序。
进程
我们可以认为上述所说的程序是:你期望完成某项任务的方法和步骤,它只浮现在纸面上,等待去实现。而这个实现过程就是由进程来完成的,进程可以认为是运行中的程序。它除了包含程序中的所有数据之外,还包含一些额外数据。
当程序被装入内存中并且获取到所需资源后就可运行了:在程序计数器 (PC) 和其它一些寄存器的控制下,机器指令被取至 cpu 运行。
下图是其大概情况:
进程在运行过程中,还需要一些系统资源。其中最重要的就是 cpu 资源了,除此之外还包括但不限于物理内存(以容纳进程本身和其有关数据)、打印机、键盘等等。
由上可见进程是一个动态的实体,它每时每刻都在发生着变化。那么如何管理和描述这个动态的进程呢?请看下面详情。
进程控制块
在 Linux 中每个进程由一个
task_struct
结构体来描述,该结构体也被称为进程控制块(PCB)。它被定义于
include/linux/sched.h
(
Linux源码github地址 )。
task_struct
容纳了一个进程的所有信息,它是系统对进程进行控制的唯一手段,也是最有效的手段。
每当系统创建一个进程,就会给该进程动态的分配一个
task_struct
结构体对象。一个系统内所允许的最大进程个数一般由机器硬件 (物理内存) 决定。在一台
IA32 体系结构中,内存为 512M 的机器上所允许的最大进程数是 32k 。
总之包含进程所有信息的
task_struct
内容是比较庞大复杂的,我们将其部分内容罗列如下:
struct task_struct{
...
...
...
...
...
...
...
...
...
...
};
操作系统中有很多进程,不管对于用户还是对于内核,如何用一种简单的方式以区分不同进程呢?这就引入了进程标识符 (PID:process identifier),每个进程都拥有一个唯一的进程标识符,内核以此来区分不同进程,同时,用户也可以通过此标识符来给具体进程发号施令。Linux 中我们可以通过以下几种方式获取 PID:
shell中:通过ps aux
列出所有进程详细信息,在其中我们可以看到进程的 PID, 也可以通过 ps aux | grep '进程名'
查看指定进程信息,除此之外还有 top
命令也可以查看进程信息。
查看所有进程信息:
查看指定进程信息:
#include .h>
pid_t getpid() ;
pid_t getppid() ;
uid_t getuid() ;
uid_t geteuid() ;
gid_t getgid() ;
gid_t getegid() ;
也可以通过文件 /proc/
文件来查看进程信息。
除此之外的其它 id:
上下文信息
上下文信息一般和处理器密切相关。进程作为一个程序执行环境的综合,当处理器调度执行某个程序时,需要将相关指令和数据加载到对应的寄存器和堆栈中,当进程暂停或者等时,必须将其对应的寄存器和堆栈信息暂存起来,以便稍后重新调度该进程时,将其回复到暂停之前的状态,那么这一部分信息就是进程的上下文信息。
进程状态
进程在执行时,会根据环境改变其状态,进程状态是进程调度的依据。在 Linux 中进程更主要有这些状态:
#define TASK_RUNNING 0x0000 //可运行
#define TASK_INTERRUPTIBLE 0x0001 //可中断的等待
#define TASK_UNINTERRUPTIBLE 0x0002 //不可中断的等待
#define __TASK_STOPPED 0x0004 //暂停
#define EXIT_DEAD 0x0010 //死亡
#define EXIT_ZOMBIE 0x0020 //僵死
#define TASK_STATE_MAX 0x1000 //进程最大个数:8k
可运行状态:处于该状态的进程,要么正在运行,要么准备运行(在等待cpu资源)。系统通过一个运行队列 (run_queue) 来管理处于此状态的进程。
等待状态:处于该状态的进程在等待某个事件或某个资源(磁盘、打印机),这些进程位于系统中的等待队列中(wait_queue),对于等待不同资源设置有不同等待队列,比如,需要打印机的进程被置于打印机的等待队列中,需要磁盘的进程被置于磁盘等待队列中。可中断的等待可以被信号唤醒,如果被唤醒,该进程就被加入到运行队列中,等待被调度,不可中断的等待是由于没有所需的硬件而等待,需要的磁盘资源暂时被其它进程占用,这一类进程不可以被信号唤醒,直到它获取到需要的硬件资源。
暂停状态:此时进程停止运行等待接收某种处理通常进程接收到 SIGSTOP、SIGTSTP、SIGTTIN 或 SIGTTOU信号后就处于这种状态。正在调试的进程就处于这种状态,如下图所示:
僵死状态:当子进程退出,而父进程没有退出,也没有读取(wait()) 子进程退出状态时,此时子进程就进入僵尸状态 (僵尸进程详情 )。
进程优先级
调度程序依靠这一部分信心决定进程的执行顺序,并结合进程的状态信息保证系统运转的公平和高效。
Linux通过以下几种方式查看进程优先级:
top
: 动态列出系统的整体运行情况;
ps -l
:采用详细格式显示进程情况。
下面是用
top
查看的详情:
如上图,有两列和进程优先级有关:
PR (priority):进程优先级,越小代表优先级越高;
NI (nice):优先级修正参数。
如何设置优先级?
在此之前我们先创建一个小脚本文件,它件循环执行累减操作,以达到占用更多cpu资源的目的。然后我们再以此脚本文件为例,来修改其优先级,观察其达到的效果。
该脚本文件如下:
文件名:
count.sh
文件内容:
#!/bin/bash
x=100000000
echo $(date)
while [ $x -gt 0 ]; do x=$((x-1 )); done
echo $(date)
有了上面的脚本文件,我们就可以依次通过下图命令行指令来执行该脚本文件(用
top
查看进程情况):
显然此时该进程优先级为默认的20,而且该进程占用了极高的cpu资源。
修改优先级的第一种方法是:在程序开始执行前,通过如下命令修改:
nice + n + 5 -p fileName
详情如下图所示:
可以看到当降低 (PR越大,优先级越小) 进程优先级后,其占用 cpu 资源降低了很多。
修改优先的第二中方法:在程序执行时修改,其命令如下:
renice +5 -p pi
详情如下图:
进程通信有关信息
为了可以使进程相互协作完成任务,不同进程间必须进行通信,即交流数据。Linux 支持多种不同的通信机制。支持电信的 Unix 通信机制(IPC) ,比如管道、信号,也支持 System V 通信机制:共享内存、信号量和消息队列。
时间和定时器
一个进程从创建到终止叫做该进程的生存期。一个进程在其生存期内使用 cpu 的时间系统都要进程记录,以便进程统计、“计费”等操作。“时间”对于操作系统是及其重要的,比如在进程调度的时间片轮转中,操作系统要根据当前进程在 cpu 上执行时间的长短以确定是否要将 cpu 分配给其它进程,这个关乎到系统对进程调度是否公平和高效。
文件系统信息
进程可以打开和关闭文件,文件属于系统资源,Linux 内核需要记录进程对文件的使用情况。task_struct 进程控制块中有两个结构体用于记录文件相关信息。其中 fs_struct
中描述了两个 VFS节点,分别是 root 和 pwd,一个指向根目录,一个指向当前目录。还有 files_struct
记录进程打开的文件描述符。
在文件系统中,每个 VFS 索引节点唯一描述一个文件或目录,同时该节点也是向更低
层的文件系统提供的统一的接口。
虚拟内存信息
除了内核线程(kernel thread),每个进程都拥有自己的地址空间(也叫虚拟空间),
用 mm_struct 来描述。另外 Linux2.4 还引入了另外一个域 active_mm,这是为内核线程而引
入。因为内核线程没有自己的地址空间,为了让内核线程与普通进程具有统一的上下文切换
方式,当内核线程进行上下文切换时,让切换进来的线程的 active_mm 指向刚被调度出去
的进程的 active_mm(如果进程的 mm 域不为空,则其 active_mm 域与 mm 域相同)。
我们对进程的 task_struct 结构进行了归类讨论,还有一些域没有涉及到。task_struct 结构是进程实体的核心,Linux 内核通过该结构来控制进程:首先通过其中的调度信息决定该进程是否运行;当该进程运行时,根据其中保存的处理机状态信息来恢复进程运行现场,然后根据虚拟内存信息,找到程序的正文和数据;通过其中的通信信息和其他进程实现同步、通信等合作。几乎所有的操作都要依赖该结构,所以,task_struct 结构是一个进程存在的唯一标志。
task_struct 在内存中的存放
进程内核栈
每个进程都有自己的内核栈。当进程从用户态进入内核态时,CPU 就自动地设置
该进程的内核栈,也就是说,CPU 从任务状态段中装入内核栈指针 esp。
当前进程
当一个进程在某个 CPU 上正在执行时,内核如何获得指向它的 task_struct 的指针?current
指针指向当前 cpu 上运行的进程。
除此之外还有当前进程状态相关信息:
进程的组织方式
哈希表
哈希表是一种查找效率极高的数据结构,Linux 中以哈希表来组织进程。
下面代码判断进程是否闲置(这个我也不太清楚)。
上述我们讨论进程控制块的部分内容,有关该结构的内容实在是太多了(有大约1600行代码),有兴趣的朋友可以自行研究。
参考资料
【作者:果冻
http://blog.csdn.net/jelly_9 】