Linux内核设计与实现阅读笔记——进程管理

发表于:2011-7-11 10:25

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:Hanyan225    来源:51Testing软件测试网采编

  进程和线程的概念我就不讲了。总之,你记着:内核调度的对象是线程,而不是进程。linux系统中的线程很特别,它对线程和进程并不做特别区分。进程的另外一个名字叫任务(task).我和作者一样,习惯了把用户空间运行的程序叫做进程,把内核中运行的程序叫做任务。

  内核把进程存放在叫做任务队列(task list)的双向循环链表中,链表中的每一项都是类型为task_struct,名称叫做进程描述符(process descriptor)的结构,该结构定义在include/linux/sched.h文件中,它包含了一个具体进程的所有信息。

  linux通过slab分配器分配task_struct结构,这样能达到对象复用和缓存着色的目的。在2.6以前的内核中,各个进程的task_struct存放在它们内核栈的尾端。由于现在用slab分配器动态生成task_struct,所以只需在栈底或栈顶创建一个新的结构(struct thread_info),他在asm/thread_info.h中定义,需要的请具体参考。每个任务中的thread_info结构在它的内核栈中的尾端分配,结构中task域存放的是指向该任务实际task_struct指针。

  在内核中,访问任务通常需要获得指向其task_struct指针。实际上,内核中大部分处理进程的代码都是通过task_struct进行的。通过current宏查找到当前正在执行的进程的进程描述符就显得尤为重要。在x86系统上,current把栈指针的后13个有效位屏蔽掉,用来计算thread_info的偏移,该操作通过current_thread_info函数完成,汇编代码如下:

movl $-8192, %eax

andl  %esp, %eax

  最后,current再从thread_info的task域中提取并返回task_struct的值:current_thread_info()->task;

  进程描述符中的state域描述了进程的当前状态。系统中的每个进程都必然处于五种进程状态中的一种,什么运行态啦,阻塞态啦,它们之间转化的条件啦等等,这一点我也不细说了,为啥?随便一本操作系统的书里,讲得都比我好,要讲就要讲别人讲不好的,是不?现在我关心的问题是:当内核需要调整某个进程的状态时,该怎么做?这时最好使用set_task_state(task, state)函数,该函数将指定的进程设置为指定的状态,必要的时候,它会设置内存屏蔽来强制其他处理器作重新排序。(一般只有在SMP系统中有此必要)否则,它等价于:task->state = state; 另外set_current_state(state)和set_task_state(current, state)含义是等价的。

  一般程序在用户空间执行。当一个程序执行了系统调用或者触发了某个异常,它就陷入内核空间。系统调用和异常处理程序是对内核明确定义的接口,进程只有通过这些接口才能陷入内核执行----对内核的所有访问都必须通过这些接口。

  linux进程之间存在一个明显的继承关系。所有的进程都是PID为1的init进程的后代,内核在系统启动的最后阶段启动init进程。该进程读取系统的初始化脚本并执行其他的相关程序,最终完成系统启动的整个过程。

  系统中的每个进程必有一个父进程,每个进程也可以拥有一个或多个子进程。进程既然有父子之称,当然就有兄弟之意了。每个task_struct都包含一个指向其父进程task_struct且叫做parent的指针,同时包含一个称为children的子进程链表。所以访问父进程:struct task_struct *task = current->parent;按照如下方式访问子进程:

struct task_struct *task;
struct list_head *list;
list_for_each(list, &current->children){
           task = list_entry(list, struct task_struct, sibling);
}

  其中init进程描述符是作为init_task静态分配的。通过上面的init进程,父子进程关系,兄弟进程关系以及进程描述符的结构,我们可以得到一个惊人的事实:可以通过这种关系从系统的任何一个进程出发查找到任意指定的其他进程。而且方式还挺多的,这个就看书了,内容挺多我就不说了,只是最后需要指出的是,在一个拥有大量进程的系统中通过重复来遍历所有的进程是非常耗费时间的,因此,如果没有充足的理由千万别这样做。爱要一万个理由,这么做呢,没看出来.

  许多的操作系统都提供了产生进程的机制,linux这优秀的系统也不例外。Unix很简单:首先fork()通过拷贝当前进程创建一个子进程。子父进程的区别仅仅在于PID,PPID和某些资源和统计量。然后exec()函数负责读取可执行文件并将其载入地址空间并执行。从上面分析可以看出,传统的fork()系统调用直接把所有的资源复制给心创建的进程。这种方式过于简单但效率底下。在Linux下使用了一种叫做写时拷贝(copy-on-write)页实现。这种技术原理是:内存并不复制整个进程地址空间,而是让父进程和子进程共享同一拷贝,只有在需要写入的时候,数据才会被复制。不懂?简单点,就是资源的复制只是发生在需要写入的时候才进行,在此之前,都是以只读的方式共享。

  linux通过clone()系统调用实现fork(),通过参数标志来说父子进程共享的资源。无论是fork(),还是vfork(),__clone()最后都根据各自需要的参数标志去调用clone().然后有clone()去调用do_fork().这样一说,我想大家明白我的意思了,问题的关键纠结于do_fork(),它定义在kernel/fork.c中,完成了大部分工作,该函数调用copy_process()函数,然后让进城开始运行,copy_precess()函数完成的工作很有意思:

31/3123>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号