Linux Kernel Development 3rd Edition 读书笔记(2)

第四章: Process Scheduling
1. 多任务操作系统有2种类型:cooperative and preemptive multitasking, 即非抢占式和抢占式. Linux是抢占式多任务操作系统.
2. Linux 2..5版本使用叫o(1)的scheduler, Linux2.6.23使用叫completely fair scheduler(CFS).
3. 进程可分为I/O-bound和processor-bound两种类型.前者依赖于IO或者输入,如UI等,后者一直占用CPU,如算法等. Linux调度时倾向于IO-bound类型进程,但也不会怱略processor-bound进程.
4. Linux有两种优先级:一种叫nice value,值从-20到19,值越大,优先级越低.通过控制时间片的比例来区分优先级.使用ps-el命令可以查看进程优先级。NI列显示的值.
另一种是real-time优先级范围,值从0到99.PTPRIO列显示的.如以下命令:
ps -eo state,uid,pid,ppid,rtprio,time,comm.
5.Linux支持针对不同进程的不同算法,称之为Scheduler class,定义在kernel/sched.c.
cfs是常规进程的调度算法,SCHED_NORMAL,定义在kernel/sched_fair.c.
6. Linux’s CFS scheduler不分配时间片给进程,而是分配占用处理器的比例.进程获得处理器的时间是由一个系统负载函数决定的.这个比例是由进程的nice value影响的,nice value值大的(低优先级)挂起的处理器时间比例较小,反之较大.一个进程如果消耗的处理器时间比例比当前进程小,就会抢占当前进程.
7. CFG基于一个概念: 调度模型就如同系统有理想的,完美多任务处理器. CFS不给进程分配timeslice值,而是计算一个进程需要运行多久,基于需要运行的进程数的函数计算而得.CFS不使用nice value来计算timeslice,而是通过nice value计算进程能够获得占用处理器运行的时间比例.value大的(高优先级)获得比重较大,value小的(低优先级)获得比重较小.
CFS使用targeted Latency来计算timeslice,latency越小,越接近完美多任务. CFS用minimum granularity来表示timeslice的最小值,因为越小的值会加重处理器切换进程的负载.
处理器时间的分配比例是由nice value的相对值决定的.CFS给每个进程一个公平的享有处理器时间,它不是绝对完美的(进程特别多的时候),但是是相对完美的.
8.CFS使用scheduler entity structure, struct sched_entity, defined in <linux/sched.h>,来记录进程运行的时间.

struct sched_entity { 
    struct load_weight      load; 
    struct rb_node          run_node; 
    struct list_head        group_node; 
    unsigned int            on_rq; 
    u64                     exec_start; 
    u64                     sum_exec_runtime; 
    u64                     vruntime; 
    u64                     prev_sum_exec_runtime;
    u64                     last_wakeup; 
    u64                     avg_overlap; 
    u64                     nr_migrations; 
    u64                     start_runtime; 
    u64                     avg_wakeup;
/* many stat variables elided, enabled only if CONFIG_SCHEDSTATS is set */ 
};
该scheduler entity structure是process descriptor, struct task_stuct的成员,名称为se.
9. 变量vruntime存储了进程的虚拟运行时间,就是根据运行的进程数得到的实际运行时间.单位是ns.CFS使用vruntime计算进程已经运行的时间和还要运行的时间.函数update_curr(), defined in kernel/sched_fair.c, 来进行这个工作.
update_curr()来计算当前进程运行的时间存储在delta_exec.然后传递给__update_curr()来根据运行进程数计算weighted value.当然进程的vruntime 然后增加该weighted value.
update_curr()被系统定时器周期调用,以及进程变为运行态或者阻塞或者不可执行的时候.vruntime精确测量给定进程的运行时间,也能指示下一个运行的进程.
10. CFS选择vruntime最小的进程作为下一个运行的进程.CFS算法的核心就是选择vruntime最小的进程. CFS使用red-black tree来管理就绪进程的表,来有效的寻找vruntime最小的进程.red-black tree,Linux中叫做rbtree.是一个自我平衡的二叉树(self-balancing binary search tree)
11. vruntime最小的进程位于rbtree的最左端(leftmost node),函数__pick_next_entity(), defined in kernel/sched_fair.c完成这样的选择.这个函数并没有遍历这个树,而是从rb_leftmost直接获得.
12. 进程就绪(wake up)或者第一次通过vfork()创建时,将该进程节点保存到rbtree,并获得leftmost的节点.enqueue_entity()完成这个工作.enqueue_entity()更新运行时和统计信息,然后调用__enqueue_entity()进行实际的插入节点工作.
13. 进程阻塞或者终止时,CFS将该进程从rbtree移除.dequeue_entity()通过调用 __dequeue_entity()(__dequeue_entity()调用rb_erase())来完成移除.
14. 进程调度的主入口函数是schedule(),定义在kernel/sched.c.schedule()调用 pick_next_task()来遍历scheduler class,选择优先级最高的scheduler class中优先级最高的进程.
15. 等待队列(wait queue):  wake_queue_head_t, 通过 DECLARE_WAITQUEUE()静态创建,通过init_waitqueue_head()动态创建.
内核任务睡眠的推荐方法:
/* ‘q’ is the wait queue we wish to sleep on */ 
DEFINE_WAIT(wait);
add_wait_queue(q, &wait); 
while (!condition) {    /* condition is the event that we are waiting for */
  prepare_to_wait(&q, &wait, TASK_INTERRUPTIBLE); 
  if (signal_pending(current))
    /* handle signal */ 
    schedule();
} 
finish_wait(&q, &wait);
16. wake_up()来实现唤醒. 调用It calls try_to_wake_up()来将任务状态设置为TASK_RUNNING,调用enqueue_task()将任务添加到the red-black tree, 如果优先级比当前运行的任务高就设置need_resched.下图描述了调度状态之间的关系.

17. context_switch()来进行任务切换(kernel/sched.c).需要切换任务时, schedule()会调用context_switch().
调用switch_mm()(<asm/mmu_context.h>), 来切换新进程的虚拟内存.
调用switch_to()(<asm/system.h>), 切换新进程的处理器状态.包括保存和恢复栈信息,处理器寄存器以及其他需要在进程运行之前保存的架构特定的状态.
18. 内核提供变量need_resched来提示调度需要进行.在scheduler_tick()中当进程需要被抢占时,need_resched被设置.当有比当前进程优先级高的进程被唤醒时, try_to_wake_up()设置need_resched.这个变量是每个进程都有的,而不是简单的全局变量.在2.6内核中, 是thread_info结构的一个变量的位成员.
19. 用户抢占发生在:从系统调用返回用户空间时;从中断处理函数返回用户空间时.
20. Linux支持内核任务的抢占, 在每个进程的thread_info结构中有个变量preempt_count,进行lock操作的计数.当为0的时候,内核进程就可以被抢占.
内核任何调度发生在:
中断处理函数结束,返回内核空间之前时;
内核代码有一次可被抢占时;
内核任务显式调用schedule();
内核任务阻塞时(这会导致调用schedule().
21. Linux提供两种实时调度的策略:SCHED_FIFO和SCHED_RR.实时调度不使用CFS,而是real-time scheduler(kernel/sched_rt.c).
SCHED_FIFO实现了先进先出的调度算法,没有时间片概念.运行的SCHED_FIFO任务能够抢占任何SCHED_NORMAL任务.当SCHED_FIFO就绪时,它会运行知道自己阻塞或者显式放弃处理器.它没有时间片会一直运行至完成.只有高优先级的SCHED_FIFO或SCHED_RR任务能够抢占.
SCHED_RR与SCHED_FIFO相似,仅仅在于每个运行运行预先定义好的时间片,就是SCHED_FIFO  with timeslices,real-time, round-robin scheduling algorithm.时间片仅用来调度相同优先级的进程.
22. Real-time scheduling policies提供软实时服务.实时优先级范围从0到MAX_RT_PRIO - 1.MAX_RT_PRIO默认值是100.这与SCHED_NORMAL进程共享优先级空间.范围是MAX_RT_PRIO  to (MAX_RT_PRIO + 40),默认值就是100-139,对应的nice value是-20~19.
23. Scheduler-Related System Calls

第五章: Process Scheduling
1. POSIX API, the C library and system call

2.  getpid()
SYSCALL_DEFINE0(getpid)
{
    return task_tgid_vnr(current); // returns current->tgid
}
 thread group ID(tpid) is equal to pid.

3. SYSCALL_DEFINE0定义没有参数的系统调用.  相当于: asmlinkage long sys_getpid(void). asmlinkage告诉编译器从栈寻找参数,所有的系统调用都需要这个关键字.
系统调用getpid()在内核中定义为sys_getpid(), 有这样的对应关系.

4. 所有的系统调用都有对应的syscall number. Linux提供一个sys_ni_syscall(),什么也不做仅仅返回-ENOSYS, 表示无效的系统调用. 内核保存了所有注册的系统调用的表.存储在sys_call_table.这个表是架构相关的,在x86-64上定义在arch/i386/kernel/syscall_64.c.这张表给有效的syscall分配唯一的syscall number.

5. 软中断.用户进程要调用系统调用时使用软中断通知内核运行系统调用.用户程序产生异常(exception)或者陷阱(trap)来让内核调用异常处理函数,而这个函数在调用系统调用函数.在x86中调用system_call().

6.

7. 系统调用的参数存储在寄存器中,在x86-32中,开始5个参数存储在寄存器ebx, ecx, edx, esi和edi, 多于5个参数时,一个寄存器用来存储参数在用户空间的地址.返回值保存在eax.

8.  copy_to_user()和copy_from_user()是内核提供的2种方法来检查从内核空间复制到用户空间和用户空间复制到内核空间.

9. capable()用来检查用户任务是否有权限执行相应的系统调用.如capable(CAP_SYS_NICE)检查任务是否有修改nice value的权限.

10. 创建系统调用的步骤:
(1) 在system call table添加新的system call的入口.每种平台需要分别添加.然后该系统调用就获得了一个syscall number.
(2) 在<asm/unistd.h>定义syscall number.
(3) 编译该系统调用到内核映像中,最简单的办法是定义在内核文件中(kernel/).

11. 在用户空间调用系统调用.
一般情况下,C库函数提供了对系统调用的访问.如果想直接访问系统调用,方法如下:
(1) 声明宏_syscalln(), n代表系统调用的参数个数. 如:
#define __NR_open 5
_syscall3(long, open, const char *, filename, int, flags, int, mode)
实际参数是2+2*n个,第一个是返回类型,第二个是名称,后面的是实际参数的名称和类型.
__NR_open定义在<asm/unistd.h>,是syscall numnber值.

12. 一般情况下,不轻易添加系统调用.可以实现设备节点read()和write(),然后使用ioctl()来操作特殊设置或者获得特殊信息.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值