操作系统:进程介绍 很久前我就想写这篇文章了,但总是以各种理由来拖延。操作系统是我日常工作的主要部分,特别是GNU/Linux,这篇文章主要关注GUN/Linux。 进程是个大话题,我不确定如何才能覆盖进程的所有知识点。这篇文章将会包含足够多的代码让你学会如何与进程交互。这些代码例子将会侧重于GNU/Linux系统,因为这是我最熟悉的系统。 那么,什么是进程呢?Linux信息项目(The Linux Information)把进程定义为“程序的一个执行(即,运行)实例”。所以,要定义进程我们先要定义什么是程序。再次根据Linux信息项目里的定义,“程序是内存里的一个可执行文件。” 所以,我们知道进程是正在运行的程序的一部分。这是否意味着进程一定是在运行中的?不一定。 进程状态 为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。 下面的状态在 fs/proc/array.c 文件里定义:
运行状态(running)并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。睡眠状态(sleeping)意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT信号让进程继续运行。 例如,可以用下面的方法来停止或继续运行进程:
可以使用gdb终止进程来实现跟踪终止状态。如果我没有记错的话,这个状态和终止状态基本上是一样的。 死亡状态是内核运行 kernel/exit.c 里的 do_exit() 函数返回的状态。这个状态只是一个返回状态,你不会在任务列表里看到这个状态。 僵死状态(Zombies)是一个比较特殊的状态。有些人认为这个状态是在父进程死亡而子进程存活时产生的。实际上不是这样的。父进程可能已经死了但子进程依然存活着,那个子进程的父进程将会成为init进程,pid 1。当进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵死进程。僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。 这里有一个创建维持30秒的僵死进程例子:
Linux进程状态是一篇非常棒的文章,它使用代码例子来讲述进程状态并使用 ptrace 来控制它。 进程包含了什么信息? 我简要地提过进程表,我将会在这解释什么是进程表。进程表是Linux内核的一种数据结构,它会被装载到RAM里并且包含着进程的信息。 每个进程都把它的信息放在 task_struct 这个数据结构里,task_struct 包含了这些内容:
保存进程信息的数据结构叫做 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()创建的进程都是运行进程的子进程。 进程执行还有很多其他的内容,比如进程调度,权限许可,资源限制,库链接,内存映射… 然而这篇文章由于篇幅限制不可能都讲述,以后访问可能会加上 |