


























进程就是处于执行期的程序。进程是处于执行期的程序以及相关资源的总称。
线程是进程中的活动对象,每个线程都拥有一个独立的程序计数器、进程栈和一组进程寄存器。
内核调度的对象是线程而不是进程。
内核吧进程的列表存放在叫做任务队列的双向循环列表中。链表中每一项的类型都是task_struct、被称为进程描述符。该结构包含了内核管理一个进程所需的所有信息。
Linux通过slab分配器分配task_struct结构,这样能够达到对象复用和缓存着色的目的。
在2.6之前为了减少对寄存器的使用,task_struct存储在内核栈的尾端,而在2.6之后使用slab分配器动态生成task_stuct,所以只需要在栈第或栈顶创建一个thread_info,它的内部包含了task_struct等信息。

每个任务的thread_info结构在它的内核栈的尾端分配。结构中task域中存放的是指向该任务实际task_struct的指针。
内核通过一个唯一的进程标识符或PID来标识每个进程。
在内核中,任务访问通常需要获得指向其task_struct的指针。内核使用current宏可以计算出当前进程task_struct的指针。
进程描述符中state域描述了进程的当前状态。系统中每个进程都必然处于五种状态中的一种。
ptrace对调试程序进行跟踪。
一般的程序是在用户空间执行,当程序进行了系统调用或触发了某一异常,那么它就会陷入到内核空间。此时,我们称内核“代表进程执行”并处于进程上下文中。在此上下问中current宏是有效的。
在Unix操作系统中进程的创建分为两步完成,第一步是调用fork()方法拷贝当前进程创建一个子进程(父子进程的唯一区别就是PID),然后调用exec()方法载入可执行文件并开始执行。
而Linux对整个进程的创建做出了优化:
写时拷贝
写时拷贝时一种可以推迟甚至免除拷贝数据的技术,在创建进程的时候,内核并不需要再一开始就复制整个进程地址空间,而是让父子进程共享一个拷贝。只有再需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。也就是所,资源的复制只有在需要写入的时候才进行,在此之前,只是以只读的方式共享,这种技术使得地址空间上的页的拷贝被推迟到实际发生写入的时候才进行,在页根本不会被写入的情况下(比如fork后立即调用exec)它们就无需复制了。
也就是只有进程空间的各段的内容要发生变化时,才将父进程的内容复制一份给子进程。
fork()
Linux通过clone()系统调用来实现fork()。然后clone()去调用do_fork()。do_fork()完成了创建中的大部分工作,该函数调用了copy_process()函数,然后让进程开始运行。copy_process()函数完成的工作如下:
dup_task_struct()为新建成创建一个内核栈,thread_info结构和task_struct,这些只与当前进程的只相同。此时,子进程和父进程的描述符使完全相同的。TASK_UNINTERRUPTIBLE,以保证它不会投入运行。copy_process()调用copy_flags()以更新task_struct的flag成员。alloc_pid()为新进程分配一个有效的PID。clone()的参数标志,决定拷贝或共享打开的资源。copy_process做扫尾工作并返回一个指向子进程的指针。vfork()
除了不拷贝父进程的页表项外,vfork()系统调用和fork()的功能相同。子进程作为父进程的一个单独的线程在他的地址空间里运行,父进程被阻塞,直到子进程退出或执行exec().
vfork()系统调用的实现是通过项clone()系统调用实现的:
copy_process()时,task_struct的vfor_done成员被设置为NULL。do_fork()时,如果给定特别标志,则vfork_done会指向特定地址。vfork_done指针向它发送信号。mm_release()时,该函数用于进程退出内存地址空间,并且检查vfork_done是否为空,如果不为空,则会向父进程发送信号。do_fork(),父进程醒来并返回。线程机制时现代编程技术中常用的一种抽象概念,该机制提供了在同一程序内共享内存地址空间运行的一组线程。这些线程还可以共享打开的文件或其它资源,线程机制支持并发程序设计技术,在多处理器系统上,它也能保证真正的并行处理。
在Linux中的内核实现中,它不严格区分线程和进程,线程仅仅被视为一个与其它进程共享某些资源的进程。
线程的创建于普通进程的创建类似,只不过在调用clone()的时候徐娅传递一些参数标志来指明需要共享的资源。
内核进程需要在后台执行一些操作,这些操作由内核线程来完成。
内核线程和普通的进程的区别在于内核线程没有独立的地址空间。它们只在内核空间运行,从来不切换到用户空间去。内核进程和普通进程一样,可以被调度,也可以被抢占。内核线程也只能由其它内核线程创建。
当一个进程终结时,内核必须要释放它所占有的资源并通知其父进程。
进程终结时,最终大多都是通过do_exit()来完成的。它主要完成了以下工作:
task_struct中的标志成员设置为PF_EXITINGdel_timer_sync()删除任一内核定时器,根据返回的结果,保证没有定时器在排队,也没有定时器处理程序在运行。acct_update_integerals()来输出记账信息。exit_mm()函数释放进程占用mm_struct,如果没有别的进程使用它,就释放。sem_exit()函数,如果进程排队等候IPC信号,它则离开队列。exit_files()和exit_fs(),以分别递减文件描述符,文件系统数据的引用计数。如果引用计数降为0,那么它代表没有进程使用相应的资源,此时就可以释放。task_struct的exit_code成员中的任务退出代码置为由exit()提供的退出代码。exit_notify()向父进程发送行信号,给子进程重新找养父,养父为线程组中的其它进程或者init进程,并把进程状态设置为EXIT_ZOMBIE.do_exit()调用schedule()切换到新的进程,因为处于EXIT_ZOMBIE状态的进程不会再调度,所以这是进程所执行的最后一段代码。do_exit()永不返回。调用do_exit()之后,尽管线程已经僵死不能再运行了,但是系统还是保留了它们的进程描述符。这样做的好处就是有办法再进程终结后仍然能够获得它的信息。
当需要释放进程描述符的时候,会调用release_task().
如果父进程在子进程之前退出,必须有机制来保证子进程能找到一个新的父进程,否则这些成为孤儿的进程就会在退出时永远处于僵死状态,白白的消耗内存。
处理孤儿进程的方案是,首先会尝试在当前线程组内找一个线程作为父进程,如果不行,就让init做它们的父进程。
多任务操作系统就是能够同时并发地交互执行多个进程地操作系统。
多任务系统可以划分为两类:非抢占式多任务和抢占式多任务。Linux提供了抢占式多任务模式。
在此模式下,由调度程序来决定什么时候停止一个进程的运行,以便其它进程能够得到执行的机会。这个强制挂起的动作叫做抢占。进程在被抢占之前能够运行的时间式预先分配号的,叫做进程的时间篇。
在非抢占式多任务模式下,除非进程自己主动停止运行,否则它会一直执行。进程主动挂起自己的操作称为让步。
Linux2.5之后采用了一种叫做O(1)的调度程序。
进程可以分为IO消耗型和处理器消耗性。前者指进程的大部分时间用来提交IO请求或是等待IO请求。因此,这样的进程进程处于可运行的状态,但通常都是运行短短的一会,因为它在等待更多的IO请求时最后总会阻塞。
而处理器消耗型进程把时间大多用在执行代码上,除非被抢占,否则它门通常都一直不停的运行,因为它们没有太多的IO需求。
调递策略通常要在两个矛盾的目标中间寻找平衡:进程响应迅速(响应时间短)和最大系统利用率(高吞吐率)。
调度算法中最基本的一类就是基于优先级的调度。这是一种根据进程的价值和对处理器时间的需求来对进程分级的想法。
Linux采用了两种不同的优先级范围,第一种是用nice值,它的范围是从-20到+19,nice值越小,优先级越高。第二种范围是实时范围,其值是可配置的,默认情况下它的变化范围是从0到99,值越大,优先级越大。
实时优先级和nice优先级处于互不相交的两个范畴。
时间片是一个数值,它表示进程在被抢占前所能持续运行的时间。调度策略必须规定一个默认的时间片。
一个处于就绪状态的进程能够进入运行态的完全是由进程优先级和是否有时间片决定的。
Linux中提供了一种CFS调度算法,即完全公平调度算法。CFS的实现思路就是根据进程的权重分配运行时间。这里的权重其实是和进程的nice值之间有一一对应的关系,可以通过全局数组prio_to_weight来转换。
$$
分配给进程的运行时间=调度周期*进程权重/所有进程权重之和
$$
调度器实体结构
CFS不再有时间片的概念,但是它也必须维护每个进程运行的时间记账,因为它需要确保每个进程只在分配给它的时间片内运行。CFS使用调度器实体结构来最终进程运行记账。

虚拟实时
vruntime变量存放进程的虚拟运行时间,该运行时间的计算是经过了所有可运行进程总数的标准化。
进程选择
当CFS需要选择下一个运行进程是,它会挑一个具有最小vruntime的进程。CFS使用红黑树来组织可运行进程队列,并利用其迅速找到vruntime值最小的进程。
等待队列
休眠通过等待队列进行处理。等待队列是由等待某些事件发生的进程组成的简单链表。
唤醒
唤醒操作通过函数wake_up()进行,它会唤醒指定的等待队列上的所有进程。它调用函数wake_up()进行,它会唤醒指定的等待队列上的所有进程。
用户抢占
用户抢占在以下情况下产生:
内核抢占
内核抢占会发生在:
schedule()此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。