linux每个进程对应一个task_struct类型结构。该结构包含进程相关的所有信息。linux进程通过list_head组织双向链表,每一个结点就是一个进程描述符。进程描述符里面包含了进程所有的信息:进程所打开的文件、进程的地址空间、挂起信号、进程状态和其他更多的信息。以下是进程描述符的部分定义:
shruct task_struct
{
unsigned long state; //进程的状态,在2.6.23已经有9个状态
unsigned long policy; //描述进程调度策略.判断是实时进程还是非实时进程
struct task_struct *parent; //组织进程的层次关系,指向父进程
struct list_head tasks; //通过list_head组织成双向链表
pid_t pid; //每个进程唯一的标号
......
};
进程描述符里面有一个pid,它就是进程间区别标志。它实际上是一个短整型数据,也就是说它最大值为32767。
linux通过slab分配器分配task-struct结构。这样能做到对象复用,减少动态分配和释放带来的资源消耗。
linux 通过current宏查找当前正在运行的进程描述符。不同的硬件体系,有的硬件拿出专门的寄存器存放当前进程。X86在内核栈的尾端创建thread_info结构,该结构中task域指向该该任务task_struct。通过计算偏移计算出当前的进程。
current宏参考:http://blog.chinaunix.net/u1/55599/showart_1080763.html
linux进程的状态:
·运行(TASK_RUNNING):
就绪状态(指进程随时可以投入运行) 运行状态(指进程正在运行)。
注:linux没有就绪队列,已就绪进程和当前正在运行的进程state都为TASK_RUNNING
·可中断(TASK_INTERUPTIBLE):
该状态的进程能被信号中断,当收到信号时,进程就会从等待队列切换到运行状态执行信号处理程序。
当等待的条件或资源满足是,进程会被从等待队列移动到运行队列。如果优先级高于正在运行的进程,发生抢占直接运行。
·不可中断(TASK_UNINTERUPTIBLE):
当等待的条件或资源满足是,进程会被从等待队列移动到运行队列。如果优先级高于正在运行的进程,发生抢占直接运行。该进程不能被信号唤醒。
.跟踪状态(TASK_TRACED):
进程的执行已由debugger程序暂停。当一起进程被另一个进程监控时,任何信号都可以把这个进程至于TASK_TRACED状态。
·停止(TASK_STOPPED):
进程停止运行,当进程收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU等信号,就会停止。在调试期间,进程收到任何信号,也会停止运行。
.TASK_NONINTERACTIVE(不可交互睡眠状态):
该状态是在2.6.14中才引入的进程状态,用于描述进程处于一种不可交互的睡眠状态。该状态必须和 TASK_INTERRUPTIBLE 或者
TASK_UNINTERRUPTIBLE状态组合使用。它在进程处于睡眠状态并且无论它是否可以交互都不提供任何信息的时候使用。它最初是在进程
等待管道 缓冲区的时候使用,这样是为了防止使用管道的进程(例如内核编译)获得更或的交互。
.死亡状态(TASK_DEAD):
task_struct->state == EXIT_DEAD是一个特殊情况,为了避免混乱就引入了这个新的状态。EXIT_DEAD就只能用于->exit_state字段。
一个进程在退出(调用do_exit())时,state字段都被置于TASK_DEAD状态。
僵死进程(EXIT_ZOMBIE):
该状态是task_struct->exit_state字段的值,表示进程的执行被终止,但是服进程还没有发布wait4()或waitpid()系统调用来返回有关死亡
的进 程信息。发布wait()类系统调用前,内核不能丢弃包含在死亡进程描述符中的数据,因为父进程可能还需要它来取得进程的退出状态。
僵死撤销状态(EXIT_DEAD):
该状态也是task_struct->exit字段的值,表示进程的最终状态。由于父进程刚发出wait4()或waitpid()系统调用,因而进程由系统删除,为了防
止其他执行线程在同一个进程也执行wait()类系统调用,而把进程的状态由僵死状态(EXIT_ZOMBIE)改为撤销状态(EXIT_DEAD)。
进程状态参考:http://blog.chinaunix.net/u2/73528/showart_1106510.html
注意使用set_task_state(task,state)函数,就可以改变进程描述符里的进程状态state。
进程的组织关系:
首先,系统开启的最后一段时间,系统将创建一个init进程,它是系统的第一个进程,它是所有进程的祖先,任何进程,如果不断的追朔其父进程,那么都会回到这个init进程。
进程队列中的进程就有两种连接方式:一种就是刚才说的双向链表;还有一种就是各进程有父子关系。
进程关系是由双向链表组织成的,通过for_each_process(task)访问所有的进程。
struct task_struct *task
for_each_process(task){printk(KERN_INFO "%s[%d]\n",task->comm,task->pid);}
注:必须写内核模块,因为需要运行内核态。
关于创建进程。linux创建进程很复杂,首先调用fork(),最终调用do_fork(),而do_fork()调用copy_process()。
首先是copy_process()的一系列工作:
第一步调用dup_task_struct()复制父进程,此时子进程与父进程描述符完全相同,创建内核栈,检查进程数目有没有超过资源限制。
接着,把进程描述符中的各项设为0或者初始值,并把进程状态设为不可中断(TASK_UNINTERUPTIBLE),保证进程未创建完毕前不要投入运行。
第三步,调用copy_flags(),设置描述符中的flag,表明进程是否有超级权限。
然后调用get_pid()设置进程pid。
第五步,根据clone()参数标志,拷贝或者打开文件、文件系统信息信号处理函数、进程地址空间和命名空间等等。linux其实使用的是写时复制,也 就是共享资源。
第六步,让父进程和子进程平分剩余的时间片。
最后,返回一个指向子进程的指针。
如果成功的返回到do_fork(),这个新建的子进程会被唤醒,内核有意让子进程先执行。子进程一般立即调用exec()函数,这样避免了写时复制的额外开销。如果 父进程先执行,有可能向共享的地址空间写入。
linux线程:
在线程概念出现以前,为了减小进程切换的开销,操作系统设计者逐渐修正进程的概念,逐渐允许将进程所占有的资源从其主体剥离出来,允许某些进程共享一部分资源,例如文件、信号,数据内存,甚至代码,这就发展出轻量进程的概念。Linux内核在 2.0.x版本就已经实现了轻量进程,应用程序可以通过一个统一的clone()系统调用接口,用不同的参数指定创建轻量进程还是普通进程。
linux实现线程非常独特,线程仅仅被视为一个使用共享资源的进程。每个线程同样对应一个task_struct结构。linux下线程仅仅是一种进程共享资源的手段。
阅读(641) | 评论(0) | 转发(0) |