1、在计算机内部所有的正在运行的进程都在等待被CPU执行,操作系统按一定的规律让所有的进程都得到CPU的执行.方法是一会儿让QQ进程执行,过了一定时间后又让MP3播放器进程得到CPU的执行,再过一定时间后让浏览器进程得到CPU的执行.相对于人的反应,CPU的执行速度太快,所以人根本感觉不到这些进程的切换过程,也不会对人的操作产生影响现在的问题是操作系统是按照什么样的规律给个各进程分配CPU资源的.操作系统是按照个各进程的属性来分配CPU资源的.那进程的属性在什么呢?当操作系统创建一个进程时都就为这个进程分配了一定的内存空间来存放它的属性.在Linux操作系统中,操作系统定义了一个数据结构task_
2、struct来描述各个进程的进程属性,task_struct结构叫进程控制块(PCB), 它是操作系统中重要的数据结构之一.具体定义在/include/linux/sched.h中,注意在Linux操作系统中所有的操作系统源代码都在/usr/src目录下.task_struct结构的代码如下所示:(这里以2.40内核为准,可能不同版本的内核代码会有一点点的不同)(为了保持简洁,从宏关上把握,不拘泥于具体,这里只说一部分,其它的以后会讲到,这里只说功能)_/include/linux/sched.h struct task_struct volatile long state; /* 进程的状态
3、,在代码后面有说明*/unsigned long flags; /* 进程标志 */int sigpending;mm_segment_t addr_limit; /* 线性地址空间:0-0xBFFFFFFF 为用户线性空间地址;0-0xFFFFFFFF 为内核线性空间地址 */struct exec_domain *exec_domain;volatile long need_resched; unsigned long ptrace;int lock_depth;long counter; /* 进程的动态优先级,在代码后面有说明 */long nice; /* 进程的静态优先级,在代码后
4、面有说明 */unsigned long policy; /* 进程采用的调度策略,在代码后面有说明 */struct mm_struct *mm; /* 进程属性中指向内存管理的数据结构mm_structd的 指针,在代码后面有说明 */int has_cpu, processor;unsigned long cpus_allowed;struct list_head run_list;unsigned long sleep_time;struct task_struct *next_task , *prev_task; /* 所以进程通过这两个指针组成一个双向链表*/struct mm_s
5、truct *active_mm; /* 指向活动地址空间,在后面的代码有说明 */ /* task state */struct linux_binfmt *binfmt;int exit_code, exit_signal;int pdeath_signal;unsigned long personality;int did_exec:1;int dumpable:pid_t pid; /* 进程标志符,在代码后面有说明 */pid_t pgrp; /* 进程组标号,在代码后面有说明 */pid_t tty_old_pgrp;pid_t session;pid_t tgid;int lea
6、der;struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr;/* 这五个标志表示一个进程的在计算机中的亲属关系,分别标志祖先进程,父进程,子进程,弟进程和兄进程,为了在两个进程之间共享方便而设立 */struct list_head thread_group;struct task_struct *pidhist_next;struct task_struct *pidhist_pprev; /* 上面两个指针是为了在计算机中快速查 一个进程而设立,具体方式以后讲*/wait_queue_head_t wait_c
7、hldexit;struct completion *vfork_sem;unsigned long rt_priority; /* 实时进程的优先级标志*/ unsigned long it_real_value, it_prof_value, it_virt_value;unsigned long it_real_incr, it_prof_incr, it_virt_incr;struct timer_list real_timer;struct tms times;struct tms group_times;unsigned long start_time;long per_cpu_
8、utimeNR_CPUS, per_cpu_stimeNR_CPUS;unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;int swappable:uid_t uid,euid,suid,fsuid;gid_t gid,egid,sgid,fsgid;int ngroups;gid_t groupsNGROUPS;kernel_cap_t cap_effective, cap_inheritable, cap_permitted;int keep_capabilities:struct user_struct *
9、user;struct rlimit rlimRLIM_NLIMITS;unsigned short used_math;char comm16;int link_count;struct tty_struct *tty; /* NULL if no tty */unsigned int locks; /* How many file locks are being held */struct sem_undo *semundo;struct sem_queue *semsleeping;struct thread_struct thread;struct fs_struct *fs; /*
10、进程属性中指向和文件管理有关的数据结构*/ struct files_struct *files;spinlock_t sigmask_lock;struct signal_struct *sig;sigset_t blocked;struct sigpending pending;unsigned long sas_ss_sp;size_t sas_ss_size;int (*notifier)(void *priv);void *notifier_data;sigset_t *notifier_mask;u32 parent_exec_id;u32 self_exec_id;spinloc
11、k_t alloc_lock;spinlock_t switch_lock;程序说明如下:volatile long state 定义了进程的状态,进程总共有6种状态标志,分别是:一. TASK_RUNING. (正在运行状态或者是可运行状态)二. TASK_INTERRUPTIBLE,(可打断睡眠状态)三. TASK_UNINTERRUPTIBLE,(不可打断睡眠状态)四. TASK_ZOMBLE. (僵死状态)五. TAKS_STOPPED.(暂停状态)六. 交换状态.这六种属性标志了此进程现在的状态,各种不同的状态决定了进程什么时候获得CPU而被执行.正在运行状态是当前正在运行进程的状态
12、.也就是说他的volatile long state被标志成TASK_RUANING.可运行状态是那些正在等待CPU资源的进程的状态,这些进程在就绪队列run-queqe中.这些进程只要得到CPU在个资源就马上可以被运行.其也被标志也为TASK_RUANING.可打断睡眠状态是哪些在等待队列中等待除CPU之外的其它资源的进程,只要它们所等待的资源到达就马上可以从等待队列进入就绪队列,其状态标志也从TASK_INTERRUPTIBLE状态变成TASK_RUANING可运行状态.不可打断睡眠状态和可睡眠状态类似,区别在于前者可以用信号唤醒,而后者不可以.僵死状态是标志那些已经运行完毕释放了大部分资
13、源并退出但还没有释放进程控制快task_struct的进程.暂停状态是那些遇到突发情况或者收到暂停信号而暂时停止运行的进程的状态.counter是进程的动态优先级,它定义了一个在就绪队列的进程当它得到CPU后可运行的时间,用静态优先级初始化,当然计算机是以时钟中断做为时间的计数器,每发送一个时钟中断,动态优先级上的时间片就减少一个时钟中断的时间,时间片减到0的时候就退出该进程而调度另一个进程获得CPU.nice是进程的静态优先级,当一个在就绪队列中的进程获得CPU之后,它被赋予此进程可占有CPU的时间.这个时间被称为时间片. policy是进程的调度策略标志.有三种调度标志:SCHED_OTH
14、ER 普通进程的调度策略,基于优先权的轮转法.SCHED_FIFO 实时进程的调度策略,基于先进先出的算法.SCHED_RR 实时进程的调度策略,基于优先权的轮转法.在linux中有两种进程,一种是普通进程,一种的实时进程,实时进程的优先级总是高于普通进程,也就是说当就绪队列中即有实时进程也有普通进程时,总是先运行实时进程.不同的进程采用不同的调度策略.rt_priority 实时进程的优先级.need_resched 标志下一次有调度机会的时候是否调用此进程.如果此进程没有没有被用户关闭或者其代码全被执行完了,在下一次调度机会应该还被调用.如果被用户关闭则直接退出该进程.mm_struct
15、*mm mm_struct数据结构是描述内存存储信息的数据结构,进程控制块task_struct中用mm指针指想mm_struct数据结构.也就是在进程的属性中通过mm指针来管理起对应的内存区.mm_struct *active_active 内核线程用来指向调用它的普通进程的内存地址空间.当普通进程在运行时如果发生系统调用,程序就会从用户态转为内核态,内核态中执行的是内核线程,内核线程没有内存空间地址结构mm_struct,当他需要内存空间地址的时候就会调用用户态对应进程的用以空间地址结构mm_struct.内核线程就是就是通过active_mm指针来指向用户态进程的mm_struct结构.
16、 pid_t pid pid是进程标志符,操作系统每创建一个新的进程就要为这个新进程分配一个进程控制快(PCB),那么系统内核是怎样区分这些进程的呢?就是通过进程标志符pid,系统在为新的进程分配进程控制块的候,它不是自己去创建,而是直接从上一个进程中复制它的进程控制块,其中里面的大部分东西保留下来,只做少量的改动,然后它的进程标志符加1赋值给新的进程控制块. 进程控制块的总共有80多项,在这里就不一一介绍了,以后我们在学习过程用到什么属性就介绍什么属性,这样在宏观上把握,不至于被一个小小的进程控制块给挡住了. 接着上面的进程说明,前面我们说了一下进程的基本概念和它的属性,接下来我们介绍一下在
17、操作系统内部是怎样通过调度函数调度进程的.我们在使用计算机的时候,可能同时打开了很多程序,比如同时打开IE浏览器,QQ,word文档等,那么这就意味着内核要同时管理多个用户进程.这些进程中每个进程每隔一段时间就要被CPU执行一次,所有的进程都是这样轮流执行,在Linux操作系统中一个进程占有CPU的时间为50ms(也就是5个时间滴答,关于时间滴答的概念后面介绍),这个时间对于人的反应来讲是相当快的,所以人更不感觉不到进程的调度和切换,各个进程也有轻重缓急之分,后面还会说一个goodness函数,这个函数比较简单,主要作用是综合进程属性(进程控制快)中的各种因素和数据算出每个进程的权值,权值最大
18、的进程就是调度程序(schedule()最应该调用的进程.在计算机中进程以六种状态存在,这在进程的属性里已经有了介绍,其中存在于就绪队列TASK_RUNNING中处于运行状态的进程是等待计算机中唯一的资源CPU的进程,它们只要一得到CPU就马上可以被执行,那么操作系统是安什么样的规则来让这些进程合理和公平的得到CPU的呢,这就要用到计算机的调度函数schedule(),_/kernel/sched.casmlinkage void schedule(void)struct schedule_data *sched_data;struct task_struct *prev, *next, *p
19、;struct list_struct *tmp;int this_cpu,c;if(!current-active_mm) BUG(); /* current指当前进程,当前进程所指的内存空间 如果为空,那么进程必定有问题,出错返回 */need_resched_back:prev=current;this_cpu=prev-processor;if(in_interrupt() goto scheduling_in_interrupt;/*上面代码的意思是: 将局部变量prev和this_cpu初始化为当前进程和当前CPU.然后检查schedule()是否中断调用,若是则直接返回,其中go
20、to后面的转到的地方在本程序中有定义,比如本进程后面的scheduling_in_interrupt在本程序的最后有定义,意思是: 打印出”Scheduling in interrupt”字符后退出 */release_kernel_lock(prev,this_cpu);if(softirq_active(this_cpu) & softirq_mask(this_cpu)goto handle_softirq; /* 检查是否有软中断请求,软中断softirq将在后 面讲解 */hand_softirp_back:sched_data= & aligned_datathis_cpu.sch
21、edule_data;spin_lock_irp(&runqueue_lock);/* 局部变量sched_data中保存当前进程的数据区,*/if(prev-policy = SCHED_RR) goto move_rr_last;* 当前基于优先权轮转法的调度策略实时进程,如果要调用它,先检查其count是否0若是,则将其挂到运行队列的最后面.具体做法在后面的 move_rr_last中有定义 */move_rr_back:switch(prev-state)case TESK_INTERRUPTIBLE:if(sign_pending(prev)prev-state = TASK_RUN
22、NING;break;default:del_form_runqueue(prev);case TASK_RUNNING;need_resched=0;/* 上面这段代码意思是: 如果进程的状态为可中断睡眠状态且唤醒它的信号正在迫近, 则将其置为可运行状态,如果状态为运行状态则忽略,如果状态除上述两种之外的其它状态则直接删除.最后将neet_resched置为0,neet_resched置0意味着本能启动调度函数shchedule(); */repeat_schedule:next = idlt_task(this_cpu);c = -1000;state = TASK_RUNNING)got
23、o still_running;/* 上面的代码中的c是优先级运算函数goodness的一个变量,优先级运算函数 goodness将是我们介绍的下一个函数,它的返回值是进程的权值,调度函数判断绪队列中权值最大的进程就是最优先要被调用的进程.goodness的具体算法看下一个函数.上面代码的意思是: 将next设置为当前cpu的idlt_task.将当前优先级的变量c赋值为-1000,其中-1000是goodness中最小的权值.然后判断当前进程是否是可运行进程,若是则转到still_running处执行,still_running在本函数的后面有解释.意思是: 将当前进程的goodness值赋
24、予goodness优先级变量c并返回still_running_back:list_for_each(tmp,&runqueue_head)p = list_entry(tmp, struct task_struct, run_list);if(can_schedule(p,this_cpu)int weight = goodness(p,this_cpu,prev-active_mm);if(weightc)c=weight,next=p;/* 上面代码的意思是: 遍历整个就绪队列,计算出每一个进程的goodness权值并且与当前进程做比较,选择goodness权值最大的那个进程获得cpu资
25、源. */ c)goto recalculate; 如果当前goodness权值为0,则运行recalculate:,起代码在后面有说明,意思是:重新计算每一进程的优先权,最后转到repeat_schedule:处执行,repeat_schedule:部分在前面已经有讲过,自己看有会过头去看一下吧 */sched_data-curr = next; /* 将当前进程信息保存到局部变量sched_data中*/#ifdef CONFIG_SMPnext-has_cpu = 1;processor = this_cpu;/* 意思为: 将调度数据区指针指向下一个要调度的进程并将当前进程的cpu设置为下一个进程的当前cpu. */#endifspin_unlock_irq(&if(prev = next)goto same_processor; 如果当前运行的进程的goodness还是队列中所以进程中最高的,也就是
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1