进程的执行状态

既然进程可以并发执行,那么他们是在程序运行是什么状态呢?不同的系统可能会有不同的状态,以下为大多数情况:

进程一般存在三种情况:

(1)就绪状态:我已经准备好,给我处理器,我就可以执行,这时的进程状态就是就绪状态;

(2)执行状态:我已经获得资源,并且正在工作中,这时的进程状态就是执行状态;

(3)阻塞状态:我刚刚正在工作,突然“断电”了,我被迫停止,这时的进程状态就是阻塞状态。

但是,不是说我处于一种状态就会不变,正在执行的总会做完,被迫停止的也可以重新开始。

关于三种状态,举个栗子:

在食堂买饭,有人已经买上饭菜了坐了正在吃,有人正在排队买菜,有人买好了但是没有座位,正在等待。

三、进程的控制

进程控制主要是负责进程的创建与撤销,进程状态之间的切换以及进程之间的通信等。当然这也是系统的基本功能,在内核中的相应程序中完成。

但是什么是操作系统内核?

操作系统内核是指扩充计算机硬件的第一层,广泛采用层次式结构,通常将一些与硬件密切相关的模块,比如中断处理程序,设备驱动程序,存储器管理等安排在紧靠硬件的软件层,并且让他们常住在内存中,施以保护。内核在实现实现其基本功能时基本常采用原语操作。

什么是原语?

可以简单的看作是命令。主要介绍一下进程控制语言,主要包括进程的创建与撤销、阻塞与唤醒、刮起与激活等六个原语。

(1)进程创建原语:进程通过调用进程创建原语来创建一个子进程,步骤为:申请空闲的PCB(进程控制模块),为子进程获得新的标识-->为子进程分配诸如内存空间的资源-->初始化进程控制模块-->将新进程插入到PCB的就绪队列中。创建ok。

(2)进程撤销原语:撤销原语在撤销进程时,连同该进程的子孙进程一同撤销。步骤为:根据被撤销的进程的标识符从PCB检索表中找到该进程的PCB,并获得该进程的状态-->若进程处于执行状态,立即终止其执行,并且将其逻辑值重置;若进程不是执行状态,直接将其从状态队列中删去-->递归的处理该进程的子孙进程-->撤销进程时,将所有资源归位,注销其资源描述清单-->释放该进程的PCB。撤销ok,但是如果其逻辑值为真,则会转入进程调度程序。

(3)进程阻塞原语:当进程请求某个事件尚未出现时,进行步骤:终止调用者自身的执行-->该进程调用进程阻塞原语使其从执行状态变为阻塞状态-->把调用者进程的PCB插入到相应的阻塞队列-->然后转入进程调度程序。

(4)进程唤醒原语:执行的进程释放某资源之后,调用进程唤醒原语将因等待该资源而阻塞的进程唤醒成就绪状态。进行步骤:找出相应被唤醒的进程的内部标识-->把该标识从阻塞队列中移去-->重设该状态为就绪-->将该进程插入到就绪队列中去。

Linux信息项目(The Linux Information)把进程定义为“程序的一个执行(即,运行)实例”。所以,要定义进程我们先要定义什么是程序。再次根据Linux信息项目里的定义,“程序是内存里的一个可执行文件。”

所以,我们知道进程是正在运行的程序的一部分。这是否意味着进程一定是在运行中的?不一定。

进程状态

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

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

C

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

/*

* The task state array is a strange "bitmap" of

* reasons to sleep. Thus "running" is zero, and

* you can test for combinations of others with

* simple bit tests.

*/

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 */

};

运行状态(running)并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。睡眠状态(sleeping)意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。

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

例如,可以用下面的方法来停止或继续运行进程:

Shell

1

2

kill -SIGSTOP <pid>

kill -SIGCONT <pid>

可以使用gdb终止进程来实现跟踪终止状态。如果我没有记错的话,这个状态和终止状态基本上是一样的。

死亡状态是内核运行 kernel/exit.c 里的 do_exit() 函数返回的状态。这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

僵死状态(Zombies)是一个比较特殊的状态。有些人认为这个状态是在父进程死亡而子进程存活时产生的。实际上不是这样的。父进程可能已经死了但子进程依然存活着,那个子进程的父进程将会成为init进程,pid 1。当进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵死进程。僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。

这里有一个创建维持30秒的僵死进程例子:

C

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

#include <stdio.h>

#include <stdlib.h>

/*

* A program to create a 30s zombie

* The parent spawns a process that isn‘t reaped until after 30s.

* The process will be reaped after the parent is done with sleep.

*/

int main(int argc, char **argv[])

{

int id = fork();

if ( id > 0 ) {

printf("Parent is sleeping..n");

sleep(30);

}

if ( id == 0 )

printf("Child process is done.n");

exit(EXIT_SUCCESS);

}

Linux进程状态是一篇非常棒的文章,它使用代码例子来讲述进程状态并使用 ptrace 来控制它。

进程包含了什么信息?

我简要地提过进程表,我将会在这解释什么是进程表。进程表是Linux内核的一种数据结构,它会被装载到RAM里并且包含着进程的信息。

每个进程都把它的信息放在 task_struct 这个数据结构里,task_struct 包含了这些内容:

  • 状态(任务状态,退出代码,退出信号。。。)
  • 优先级
  • 进程id(PID)
  • 父进程id(PPID)
  • 子进程
  • 使用情况(cpu时间,打开的文件。。。)
  • 跟踪信息
  • 调度信息
  • 内存管理信息

保存进程信息的数据结构叫做 task_struct,并且可以在 include/linux/sched.h 里找到它。所有运行在系统里的进程都以 task_struct 链表的形式存在内核里。

进程的信息可以通过 /proc 系统文件夹查看。要获取PID为400的进程信息,你需要查看 /proc/400 这个文件夹。大多数进程信息同样可以使用top和ps这些用户级工具来获取。

进程执行

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

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

  • 文本段,包含程序的源指令。
  • 数据段,包含了静态变量。
  • 堆,动态内存分区区域。
  • 栈,动态增长与收缩的段,保存本地变量。

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

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

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

注意,execve()和fork()创建的进程都是运行进程的子进程。

进程执行还有很多其他的内容,比如进程调度,权限许可,资源限制,库链接,内存映射… 然而这篇文章由于篇幅限制不可能都讲述,以后访问可能会加上

进程间通信(IPC)

为了进程间的通信,存在两个解决方法,共享内存,消息传递。

在共享内存的方案里,为了几个进程间能够通信创建了一个共享的区域。这个区域能被多个进程同时访问。这种方法通常在使用线程时使用。这是实现IPC最快的形式,因为这种形式只涉及到内存的读写。 但是,这需要进程在访问共享内存时受到的限制和访问内核实现的其他进程内存一样。

共享内存段的使用情况可以使用ipcs -m命令查看。

实现一个共享内存的服务器程序,代码如下:

C

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

#include <stdlib.h>

#include <stdio.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#define SEGMENT_SIZE 64

int main(int argc, char **argv[])

{

int shmid;

char *shmaddr;

/* Create or get the shared memory segment */

if ((shmid = shmget(555, SEGMENT_SIZE, 0644 | IPC_CREAT)) == -1) {

printf("Error: Could not get memory segmentn");

exit(EXIT_FAILURE);

}

/* Attach to the shared memory segment */

if ((shmaddr = shmat(shmid, NULL, 0)) == (char *) -1) {

printf("Error: Could not attach to memory segmentn");

exit(EXIT_FAILURE);

}

/* Write a character to the shared memory segment */

*shmaddr = ‘a‘;

/* Detach the shared memory segment */

if (shmdt(shmaddr) == -1) {

printf("Error: Could not close memory segmentn");

exit(EXIT_FAILURE);

}

exit(EXIT_SUCCESS);

}

通过把 *shmaddr = ‘a’; 替换为 printf(“Segment: %sn”, shmaddr) ,你将会得到一个客户端程序并且能够读取共享内存段的数据。

运行 ipcs -m 将会输出服务共享内存段的信息:

Shell

1

2

3

4

5

anton@shell:~$ ipcs -m

------ Shared Memory Segments --------

key shmid owner perms bytes nattch status

0x0000022b 0 anton 644 64 0

共享内存段可以使用 ipcrm 命令移除。要了解更多的共享内存实现IPC,可以阅读Beej的共享内存段教程

其他实现IPC的方法有文件,信号,套接字,消息队列,管道,信号灯和消息传递。这些方法我不可能全部都深入讲解,但我觉得信号和管道的方法我需要提供一些有趣的例子。

信号

介绍进程状态时,我们已经看了一个使用kill命令的信号示例。信号是把事件或者异常的发生通知进程的软件中断。

每个信号都有一个整型标识,但通常使用 SIGXXX 来描述信号,例如 SIGSTOP 或者 SIGCONT 。内核使用信号来通知进程事件的发生,进程也可以使用kill()系统调用发送信号给进程。接收信号的进程可以忽略信号,被杀死,或者被挂起。可以使用信号处理器来处理信号并且在信号出现时任意处理信号。SIGKILL 这个特殊的信号不能被捕获(处理器处理),要杀死一个挂起的进程时可以使用这个信号。不要把 SIGKILL 和 SIGTERM 混淆了,当使用 Ctrl+C 或者 kill <PID> 杀死进程时默认会发送 SIGKILL 信号。 SIGTERM 不会强制杀死进程并且它可以被捕获,使用 SIGTERM 的进程通常可以被清理。

管道

管道用来把一个进程的输出连接到另外一个进程的输入。这是实现IPC最古老的方法之一。普通管道是单向通信的, 它有一个单向流。可以使用pipe() 创建一个管道,管道和Linux的其他对象一样,都被看成文件对象。
通其他文件一样,read()和write()操作都适用于管道。

命名管道是普通管道的增强版,它是双向通信的并且可以实现管道的多进程读写。这都是普通管道不能实现的。无论有没有进程对命名管道进行读写,它都会实际存在。命名管道在文件系统里以特殊设备文件存在。在GNU/Linux里,命名管道也被称为FIFOs(先进先出,First In First Out)。

这里有一个创建命名管道的例子:

C

1

2

3

4

5

6

7

8

9

10

11

12

13

14

#include <stdlib.h>

#include <stdio.h>

#include <sys/types.h>

#include <sys/stat.h>

int main(int argc, char **argv[])

{

if (mknod("myfifo", S_IFIFO|0666, 0) == -1) {

printf("Failed to mknodn");

exit(EXIT_FAILURE);

}

exit(EXIT_SUCCESS);

}

在运行目录里,我们会看到myfifo文件。它的信息和下面的类似:

Shell

1

prw-rw-r-- 1 anton anton 0 Dec 16 16:14 myfifo

以上就是进程的基本介绍。写得越多我就越意识到进程有太多东西要讲了。从哪里开始讲进程和把不需要覆盖的知识划分出来,这是个很艰难的决定。共享内存段是我没有很好地规划好的一部分。回看进程间通信那部分是很有趣的。此外,因为有大量诸如Linux编程接口操作系统概念的好资源,使我们更容易回归概念思考。

时间: 2024-10-11 09:53:30

进程的执行状态的相关文章

进程 &amp; 线程相关知识

不管Java,C++都有进程.线程相关的内容.在这里统一整理吧. Python的线程,其实是伪线程,不能真正的并发.下面也有讲. 线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈). 多个线程共享内存. 参考了这篇文章:http://www.cnblogs.com/qiaoconglovelife/p/5319779.html 进程与PCB 进程:进程是程序的一次执行过程,是系统进行资源分配和调度的一个独立单位. 进程实体(进程映像):由程序段.相关数

【操作系统】进程管理(二)

一.前言 之前已经介绍了操作系统的各个模块,现在来具体深入学习操作系统中的进程管理. 二.进程的基本概念 在未配置OS的系统中,程序的执行方式是顺序执行,即必须在一个程序执行完成后,才允许另外一个程序执行:在多道程序环境下,则允许多个程序并发执行.也正是程序的并发执行,才导致引入进程. 2.1 程序的顺序执行 通常可以把一个应用程序分成若干个程序段,在各程序段之间,必须按照某种先后次序顺序执行,仅当前一操作(程序段)执行完后,才能执行后继操作.如典型的输入->计算->打印任务就是顺序执行. 程

操作系统核心原理-3.进程管理(中):进程调度

PS:在多进程并发的环境里,虽然从概念上看,有多个进程在同时执行,但在单个CPU下,在任何时刻只能有一个进程处于执行状态,而其他进程则处于非执行状态.那么问题来了,我们是如何确定在任意时刻到底由哪个进程执行,哪些不执行呢?这就涉及到进程管理的一个重要组成部分:进程调度,跟随本篇来一起复习下进程调度吧! 一.进程调度基础 1.1 进程调度定义 进程调度是操作系统进程管理的一个重要组成部分,其任务是选择下一个要运行的进程. 1.2 进程调度目标 首先,一般的程序任务分为三种:CPU计算密集型.IO密

关于Java中进程和线程的详解

一.进程:是程序的一次动态执行,它对应着从代码加载,执行至执行完毕的一个完整的过程,是一个动态的实体,它有自己的生命 周期.它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而被撤消.反映了一个程序在 一定的数据 集上运行的全部动态过程.通过进程控制块(PCB)唯一的标识某个进程.同时进程占据着相应的资源(例如包 括cpu的使用 ,轮转时间以及一些其它设备的权限).是系统进行资源分配和调度的一个独立单位. 程序和进程之间的主要区别在于: 状态         是否具有资源

进程与线程之间的区别及联系

一.定义: 1.进程:进程是一个具有独立功能的程序关于某个数据集合的以此运行活动.是系统进行资源分配和调度的独立单位,也是基本的执行单元.是一个动态的概念,是一个活动的实体.它不只是程序的代码,还包括当前的活动. 进程结构特征:由程序.数据和进程控制块三部分组成.具有独立性.并发性.异步性和动态性的特点. (1).进程的概念主要有两点: 第一,进程是一个实体.每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)--存储处理器执行的代码,数据区域(data regio

Android进程命令查看

?       进程 是指一个具有独立功能的程序在某个数据集上的一次动态运行过程,它是系统进行资源分配和调度的最小单元. ?       一个进程能够拥有多个线程.每一个线程必须有一个父进程. ?       进程特性:并发.动态.交互.独立和异步. 进程的生命周期 进程的五种状态 ?       执行(TASK_RUNNING) 进程正在被CPU运行,或已经准备就绪随时可由调度程序运行 ?       僵尸(TASK_ZOMBIE) 当进程已停止执行,但其父进程还没有调用wait()询问其状态

(转)进程控制:进程的创建、终止、阻塞、唤醒和切换

进程控制:进程的创建.终止.阻塞.唤醒和切换 进程控制的主要功能是对系统中的所有进程实施有效的管理,它具有创建新进程.撤销已有进程.实现进程状态转换等功能.在操作系统中,一般把进程控制用的程序段称为原语,原语的特点是执行期间不允许中断,它是一个不可分割的基本单位. 进程的创建 允许一个进程创建另一个进程.此时创建者称为父进程,被创建的进程称为子进程.子进程可以继承父进程所拥有的资源.当子进程被撤销时,应将其从父进程那里获得的资源归还给父进程.此外,在撤销父进程时,也必须同时撤销其所有的子进程.

进程管理1

从进程的观点研究操作系统,把OS看作是由若干个可独立运行的程序和一个可对这些程序进行协调控制的核心(内核)组成. 这些运行的程序称为进程,它是资源分配和独立运行的基本单位,每一个进程都完成某一特定任务.OS的内核则必须要控制和协调这些进程的运行,解决进程之间的通信,并从系统可并发工作为出发点,实现并发进程间通信,并解决由此带来的共享资源的竞争问题. 本文地址:http://www.cnblogs.com/archimedes/p/os-process-management1.html,转载请注明

操作系统——进程

为了实现程序的并发执行,才引入进程 程序的顺序执行: (顺序性,封闭性,可再现性) 程序的并行执行,提高CPU的效率和系统吞吐率:(间断性,失去封闭性,不可再现性) 引入进程解决程序并发的问题,进程的特征: ① 结构特性,为使程序能够独立运行,应为之配置一进程控制块,即PCB(Process Control Block).而程序段.相关的数据段和PCB三部分构成进程实体.所谓创建进程,实质上是创建进程实体中的PCB,撤销进程也是撤销进程中的PCB. ② 动态性,进行的实质是进程实体的一次执行过程