正文:


  1. 调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间。
  2. 多任务系统有两类:抢占式和非抢占式多任务。
  3. 进程在被抢占之前能够运行的时间是预先设置好的,叫时间片。当今的操作系统对程序运行都采用了动态时间片计算方式,并且引入了可配置的计算策略。
  4. 反转楼梯最后期限调度算法吸取了队列理论,将公平调度的概念引入Linux调度程序。并在2.6.23内核版本代替了O(1)调度算法。此刻它被称为完全公平调度算法。
  5. 调度程序总是选择时间片未用尽且优先级最高的进程运行。用户和系统可以设置进程的优先级来影响系统的调度。
  6. Linux采用两种不同的优先级范围,第一种是用nice(—20~19)值,nice值高的进程获得的处理器时间少;第二种范围是实时优先级(0~99),与nice值意义相反,越高的实时优先级意味着进程优先级越高。
  7. 时间片是一个数值,它表明进程在被抢占前所能维持运行的时间。
  8. Linux的CFS调度器并没有直接分配时间片到进程,而是将处理器的使用比例划分给了进程,这样进程锁获得的处理器时间和系统负载密切相关的。如果消耗的使用比比当前进程小,则新的进程立刻投入运行,抢占当前进程。
  9. Linux调度器是以模块方式提供的,这样做的目的是允许不同类型的进程可以有针对性地选择调度算法。这种模块化结构被称为调度器类,它允许多种不同的可动态添加的调度算法并存,调度属于自己范畴的进程。
  10. 调度时进程抢占会带来一定的代价:将一个进程换出,另一个换入本身有消耗,同时还会影响缓存的效率。
  11. 目标迟延
  12. 任何进程所获得的处理器时间是由它自己和其他所有可运行进程nice值的相对值决定的。nice值对时间片的作用是几个加权。任何nice值对应的绝对时间不再是一个绝对值,而是处理器的使用比。
  13. 进程调度的主要入口点是函数schedule(),它正是内核其他部分用于调用进程调度器的入口:选择哪个进程可以运行,何时将其投入运行。它通常都需要和一个具体的调度类相关联,它会找到一个最高优先级的调度类(有自己的可运行队列),然后问后者谁才是下一个该运行的进程。
  14. 进程休眠有很多原因,但肯定是为了等待一些事件。一个进程还可能在尝试获取一个已被占用的内核信号量时被迫进入休眠。
  15. 进程把自己标记成休眠状态,可从红黑树中移出,放入等待队列,然后调用schedule()选择和执行一个其他进程。唤醒过程则正好相反。
  16. 休眠有两种相关的进程状态:Task_interruptible,task_uninterruptible.
  17. 休眠通过等待队列进行处理。等待队列由等待某些事件发生的进程组成的简单链表。内核用wake_queue_head_t来代表等待队列。
  18. 唤醒操作通过函数wake_up()进行,它会唤醒指定的等待队列上的所有进程。
  19. 上下文切换:从一个可执行进程切换到另一个可执行进程,由定义在/kernel/sched.c中的context_switch()负责处理。每当一个新的进程被选出来准备投入运行时,schedule()就会调用该函数。
  20. 每个进程描述符都包含一个need_resched标志(其实在thread_info结构体内),因为访问进程描述符内的数值要比访问一个全局变量快(因为current宏速度很快并且描述符通常都在高速缓存中)。
  21. 内核无论是在中断处理程序还是在系统调用后返回(会发生用户抢占的时期),都会检查need_resched标志。
  22. 从中断处理程序或系统调用返回的路径都是跟体系结构相关的,在enter.S文件(包含内核入口部分的程序,内核退出部分的相关代码也在其中)中实现。
  23. 只要重新调度是安全的,内核就可以在任何时间抢占正在执行的任务。即只要没有持有锁,内核就可以进行抢占。锁是非抢占区域的标志。由于内核是支持S MP的,所以没有持有锁时,正在执行的代码就可以重新导入,进行抢占。(每个进程的thread_info中的preempt_count计数器)
  24. 内核抢占一般发生在:中断处理程序正在执行,且返回内核空间之前;内核代码再一次具有可抢占性的时候;如果内核中的任务显示地调用schedule();如果内核中的任务阻塞,导致调用schedule().
  25. Linux提供了两种实时调度策略:sched_fifo,sched_RR.(sched_normal是非实时调度策略)。
  26. sched_rr级的进程在耗费事先分配给它的时间后就不能再继续运行,它是带有时间片的sched_fifo.

引言:

其实linux内核并没有想象中的那么难,只要自己静下心,以源码为主线,再加上几本经典的书就可以慢慢的去学习kernel了.当自己有了一定的基础之后,学习kernel源码也变得比较容易了,没有了大二刚开始那会看kernel时的一脸茫然.这一节把关于进程管理的相关知识点总结下,这样不仅可以加深自己学习的印象,还便于日后查阅.


正文:

  1. 进程通常包含可执行程序代码,打开文件,挂起的信号,内核内部数据,处理器状态,一个或多个具有内存映射的地址空间及一个或多个执行线程。
  2. 每个线程都拥有一个独立的程序计数器,进程栈和一组进程寄存器。内核调度的是线程而不是进程。
  3. 现代操作系统中进程提供两种虚拟机制:虚拟处理器,虚拟内存。
  4. 线程之间可以共享虚拟内存,但每个都拥有各自的虚拟处理器。
  5. 程序本身并不是进程,而是处于执行期的程序以及相关的资源的总称。完全可能多个进程执行的是同一个程序。并且进程间还可以共享许多诸如打开的文件,地址空间之类的资源。
  6. 调用fork()结束时,在返回点这个相同位置上,父进程恢复执行,子进程开始执行。fork()系统调用从内核返回两次,一次回到父进程,另一次回到新的子进程。
  7. 通常,创建新的进程都是为了立即执行新的,不同的程序,而接着调用exec()这组函数就可以创建新的地址空间,并把新的程序载入其中。在现代linux内核中,fork()实际上是由clone()系统调用实现的。最终,程序通过exit()系统调用退出执行。这个函数会终结进程并释放其占用的资源。
  8. 父进程可以通过wait4()系统调用查询子进程是否终结。进程退出执行后被设置成僵死状态,直到它的父进程调用wait()或waitpid()为止。
  9. 内核把进程的列表存放在任务队列中(双向循环链表),链表的每一项都是task_struct,称为进程描述符。
  10. linux通过slab分配器分配task_struct结构,这样能达到对象复用和缓存着色的目的。因此只需在栈底或栈顶创建一个新的struct tread_info.
  11. 访问进程描述符是一个重要的频繁操作。
  12. 进程描述符中的state域描述了当前状态(运行态,可中断,不可中断,被其他进程跟踪,停止态)。
  13. 在执行ps(1)命令时,那些标为D状态而又不能被杀死的进程的原因是此进程状态为不可中断态,此状态不对信号做出相应。所以你不可能给它发送Sigkill信号,即使有,终结这样一个任务也是不明智的选择,因为该任务有可能正在执行重要的操作,甚至还可能持有一个信号量。
  14. 设置当前进程状态,set_task_state(task,state)
  15. 进程上下文:可执行代码是进程的重要组成部分,这些代码从一个可执行文件载入到进程的地址空间执行。一般程序在用户空间执行。 系统调用和异常处理程序是内核明确定义的接口。进程只有通过这些接口才能陷入内核执行。
  16. 每个进程可以拥有0个或多个子进程。
  17. 在中断上下文,系统不代表进程执行,而是执行一个中断处理程序。不会有进程去干扰这些中断处理程序,所以此时不存在进程上下文。
  18. init进程的进程描述符是作为init_task静态分配的。
  19. for_each_process()宏提供了依次访问整个任务队列的能力。
  20. fork()通过拷贝当前进程创建一个子进程。子进程与父进程的区别仅仅在PID,PPID,和某些资源和统计量。exec()函数负责读取可执行文件并将其载入地址空间开始运行。
  21. linux 的fork()使用写时拷贝页实现(一种可以推迟甚至免除拷贝数据的技术)。此时内核并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。
  22. fork()的实际开销就是复制父进程的页表以及给子进程创建唯一的进程描述符。
  23. fork()—>clone(){系统调用}—>do_fork()—>copy_process()
  24. 内核有意选择子进程首先执行,因为一般子进程都会马上调用exec()函数,可避免写时拷贝的额外开销。
  25. 除了不拷贝父进程的页表项以外,vfork()和fork()功能相同。子进程作为父进程的一个单独线程在它的地址空间里运行,父进程被阻塞,直到子进程退出或执行exec().子进程不能向地址空间写入。
  26. 线程机制提供了在同一程序内共享内存地址空间的一组线程,这些线程还可以共享打开的文件和其他资源。支持并发程序设计技术,在多处理器系统上,也能保证正真的并行处理。
  27. linux中线程和进程没有区分,把线程当做一个与其他进程共享某些资源的进程。
  28. 传递给clone()的参数标志决定了新创建进程的行为方式和父子进程间共享的资源种类。
  29. 内核线程没有独立的地址空间,它们只在内核空间运行。也可被调度和抢占。 内核线程只能由其他内核线程创建。内核是通过从kthreadd内核进程中衍生出所有新的内核线程来自动处理这一点的。
  30. 当一个进程终结时,内核必须释放它所占有的资源,并告知其父进程。
  31. 一般来说,进程的析构是自身引起的。它发生在进程调用exit()系统调用时。
  32. c编译器会在main函数返回点后面放置调用exit()的代码。
  33. 当进程接受到它既不能处理也不能忽略的信号或异常时,它还可能被动终结。终结大部分工作由do_exit()来完成。
  34. do_exit()会调用schedule()切换到新的进程。因为处于EXIT_ZOMBIE状态的进程不会再被调度,所以这是进程所执行的最后一段代码。do_exit()永不返回。
  35. 孤儿进程会在退出时永远处于僵死状态,白白耗费内存。解决方法是给子进程在当前线程组内找一个线程作为父亲,如果不行,就让init()做为它的父进程。
  36. init进程会例行调用wait()来检查其子进程,清除所有与其相关的僵死进程。

正文:

  1. 页高速缓存是Linux内核实现磁盘缓存,主要用来减少对磁盘的IO操作。它通过把磁盘中的数据缓存到物理内存中,把对磁盘的访问变成对物理内存的访问。
  2. 临时局部原理:在短期内集中访问同一片数据的原理。
  3. 页高速缓存是由内存中的物理页面组成的,其内容对应磁盘上的物理块。缓存大小可以动态调整。
  4. 写透缓存:写操作将自动更新内存缓存,同时也更新磁盘文件。
  5. 回写缓存:更新内存缓存,但不立即更新磁盘文件,而是由一个回写进程进行周期将脏页链表的页写回到磁盘。
  6. Linux的缓存回收是通过选择干净页进行简单替换。
  7. 缓存回收的双链策略:维护两个链表,活跃链表,非活跃链表。
  8. Linux页高速缓存的目标是缓存任何基于页的对象,包含各种类型的文件和各种类型的内存映射。
  9. 优先搜索树是一种将堆与radix树结合的快速检索树。
  10. 内核总是试图先通过页高速缓存来满足所有读请求,如果在页高速缓存中未搜索到需要的页,则内核将从磁盘读入需要的页,然后将该页加入到页高速缓存中;对于写操作,页高速缓存像一个存储平台,所有要被写出的页都要加入页高速缓存中。
  11. 在2.6版本以前,内核页高速缓存不是通过基树检索,而是通过一个维护了系统中所有页的全局散列表进行检索。
  12. flusher线程后台例程会被周期性唤醒,将那些在内存中驻留时间过长的脏页写出。确保内存中不会有长期存在的脏页。
  13. 膝抢型计算机模式是一种特殊的页回写策略,其主要意图是将硬盘转动的机械行为最小化,它除了当缓存中的页面太旧时要执行回写脏页以外,flusher还会找准磁盘运转时机,把所有其他的物理磁盘IO,刷新脏缓冲等通通写回到磁盘,以便保证不会专门为了写磁盘而去主动激活磁盘行为。
  14. flusher线程数量是根据磁盘数量变化的,flusher线程基于页面,它将整个脏页写回磁盘。
  15. 单一线程有可能堵塞在某个设备的已拥塞请求队列上,而其他设备的请求队列却没法得到处理。使用多个flusher线程可以解决次问题。

正文:

  1. 可被进程访问的虚拟内存的地址区间称为内存区域,通过内核,进程可以给自己的地址空间动态地添加或减少内存区域。
  2. 进程只能访问有效内存区域内的内存地址,每个内存区域具有相关的权限,如对相关进程有可读,可写,可执行属性。如果一个进程访问了不在有效范围中的内存区域,或以不正确的方式访问了有效地址,那么内核就会终止该进程,并返回段错误信息。
  3. 内存区域可以包含各种内存对象:可执行文件代码的内存映射(代码段);可执行文件的已初始化全局变量的内存映射(数据段);包含未初始化全局变量,也就是bss段的零页的内存映射;用于进程用户空间栈的零页的内存映射;每个诸如c库或动态链接程序等共享库的代码段,数据段和bss也会被载入进程的地址空间;任何内存映射文件;任何共享内存段;任何匿名内存映射,比如malloc()分配的内存。
  4. 内核使用内存描述符结构体来表示进程的地址空间,其中包含了进程地址空间有关的全部信息。
  5. 内核线程没有进程地址空间,也没有相关的内存描述符。内核线程的真正含义是它们没有用户上下文。内核线程并不需要访问任何用户空间的内存,其在用户空间中没有任何页。
  6. 创建地址区间函数:do_mmp().删除地址区间函数:do_munmmp().
  7. 地址转换需要将虚拟地址分段,使每段虚拟地址都作为一个索引指向页表,而页表项则指向下一级别的页表或最终的物理页面。Linux使用了三级页表。
  8. 每个进程都有自己的页表,操作和检索页表时必须使用page_table_lock锁,该锁在相应的进程的内存描述符中,以防止竞争条件。
  9. TLB作为一个将虚拟地址映射到物理地址的硬件缓存,可以加快内存访问。

正文:

  1. 高HZ会使时钟中断处理程序更频繁的执行,从而打乱处理器高速缓存并增加耗电。
  2. 对于无节拍系统而言,空闲期不会被不必要的时钟中断所打断,于是减少了系统能耗。
  3. jeffies全局变量用来记录自系统启动以来产生的节拍总数。
  4. 内核对进程时间计数时,是根据中断发生时处理器所处的模式进行分类统计的,它把上一个节拍全部算给了进程。(很明显这样做不精确)
  5. 内核为了提高搜索效率,将定时器按它们的超时时间划分为五组。当定时器超时时间接近时,定时器将随组一起下移。
  6. 延迟执行:忙等待(有时可以在等待期间重新调度一个新的进程来运行cond_resched()),短延迟(通常驱动程序只是需要很短暂的延迟),schedule_timeout()(该方法会让需要延迟执行的任务睡眠到指定的延迟时间耗尽后在重新运行)