Linux0。11kernel目录进程管理sched。c详解
sched.c主要功能是负责进程的调度,其最核心的函数就是 schedule 。除schedule以外, sleep_on和wake_up也是相对重要的函数。 schedulevoid schedule(void)
schedule函数的基本功能可以分为两大块, 第一块是 检查task中的报警信息和信号 , 第二块则是 进行任务的调度 。
在第一块中,首先从任务数组的尾部任务开始,检查alarm是否小于当前系统滴答值,如果小于则代表alarm时间已经到期。将进程的signal中的SIGALARM位置1。
接着就看如果检查进程的信号中如果处理BLOCK位以外还有别的信号,并且如果任务处于可中断状态,则将任务置为就绪状态。 int i,next,c; struct task_struct ** p; for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) if (*p) { if ((*p)->alarm && (*p)->alarm < jiffies) { //如果设置了任务定时的值alarm, 并且已经过期 (*p)->signal |= (1<<(SIGALRM-1)); //将信号的SIGALARM位置为1 (*p)->alarm = 0; } if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) && (*p)->state==TASK_INTERRUPTIBLE)//如果信号位图中除了被阻塞的信号外还有其他信号, 并且任务处于可中断状态 (*p)->state=TASK_RUNNING; //修改任务的状态为就绪态 }
第二块的代码就是任务调度的核心代码。
这里会从任务数组的尾部任务开始进行遍历,从所有任务从选取counter值最大的任务作为下一个运行的任务去执行。 while (1) { c = -1; next = 0; i = NR_TASKS; p = &task[NR_TASKS];//从最后一个任务开始 while (--i) { //遍历所有的task, 取出其中counter最大的task if (!*--p) continue; if ((*p)->state == TASK_RUNNING && (*p)->counter > c)//取出所有任务中counter值最大的任务作为下一个任务 c = (*p)->counter, next = i; } if (c) break; //如果当前没有RUNNING状态的任务的counter可以大于-1,那么则去更新counter的值,counter = counter/2 + priority for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) if (*p) (*p)->counter = ((*p)->counter >> 1) + (*p)->priority;//更新counter值 counter = counter/2 + priority } //切换任务执行next switch_to(next);show_taskvoid show_task(int nr,struct task_struct * p)
该函数的作用是显示任务序号为nr的进程的pid,进程状态以及内核栈剩余的大小。 int i,j = 4096-sizeof(struct task_struct); printk("%d: pid=%d, state=%d, ",nr,p->pid,p->state); i=0;
此时j指向PCB所在内存页的顶部, i指向task_struct结构体的下一个字节。下面这段代码的所用实际就是统计内核栈中空闲大小。
show_taskwhile (istate = TASK_INTERRUPTIBLE; schedule();sleep_onvoid sleep_on(struct task_struct **p)
该函数的作用是将当前的task置为 不可中断的等待状态 , 直到被wake_up唤醒再继续执行。入参p是等待任务队列的头指针。通过p指针和tmp变量将等待的任务串在了一起。
sleep_on示意图
该函数首先对一些异常情况进行了处理他, 例如p是空指针。或者当前task是任务0。 struct task_struct *tmp; // 若指针无效,则退出。(指针所指的对象可以是NULL,但指针本身不会为0)。 if (!p) return; if (current == &(init_task.task)) // 如果当前任务是任务0,则死机(impossible!)。 panic ("task[0] trying to sleep");
接着让当前等待任务的头指针指向当前任务。并将当前任务修改为 不可中断的等待状态 。进行调用schedule函数让操作系统切换其他任务执行。 tmp = *p; *p = current; current->state = TASK_UNINTERRUPTIBLE; schedule();
当程序从schedule()返回继续执行时,说明任务已经被显式的wake_up,如果此时还有其他进程仍然在等待,那么也一同唤醒。
因为任务都在等待同样的资源, 那么当资源可用的时候, 就可以唤醒所有等待的任务。 if (tmp) // 若还存在等待的任务,则也将其置为就绪状态(唤醒)。 tmp->state = 0;interruptible_sleep_onvoid interruptible_sleep_on (struct task_struct **p)
该函数与sleep_on类似,但是该函数会将任务的状态修改为 可中断的等待状态 , 而sleep_on则是将任务修改为 不可中断的等待状态 。因此通过interruptible_sleep_on而等待的task是可以被信号唤醒的。 而通过sleep_on而等待的task是 不会被信号唤醒的 ,只能通过wake_up函数唤醒。
interruptible_sleep_on示意图
下面这段代码与sleep_on并无太大区别, 只是将进程的状态修改为可中断的等待状态。 struct task_struct *tmp; if (!p) return; if (current == &(init_task.task)) panic ("task[0] trying to sleep"); tmp = *p; *p = current; repeat: current->state = TASK_INTERRUPTIBLE; schedule ();
由于任务是可以被信号唤醒的,因此下面需要判断唤醒的任务是否是等待任务队列的头节点。如果不是则需要等待其他任务。 if (*p && *p != current) { (**p).state = 0; goto repeat; }
下面一句代码有误,应该是*p = tmp,让队列头指针指向其余等待任务,否则在当前任务之前插入 等待队列的任务均被抹掉了 *p = NULL; if (tmp) tmp->state = 0;wake_upvoid wake_up(struct task_struct **p)
该函数的作用就是唤醒某一个任务。其用于唤醒p指向的等待队列中的任务。 if (p && *p) { (**p).state = 0; // 置为就绪(可运行)状态。 *p = NULL; }ticks_to_floppy_onint ticks_to_floppy_on(unsigned int nr)
该函数指定软盘到正常运转状态所需延迟滴答数(时间)。 floppy_onvoid floppy_on(unsigned int nr)
该函数等待指定软驱马达启动所需时间。 floppy_offvoid floppy_off(unsigned int nr)
关闭相应的软驱马达停转定时器3s。 moff_timer[nr]=3*HZ;do_floppy_timervoid do_floppy_timer(void)
如果马达启动定时到则唤醒进程。 if (mon_timer[i]) { if (!--mon_timer[i]) wake_up(i+wait_motor);
如果马达停转定时到期则复位相应马达启动位,并更新数字输出到寄存器。 else if (!moff_timer[i]) { current_DOR &= ~mask; outb(current_DOR,FD_DOR);add_timeradd_timer(long jiffies, void (*fn)(void)) ```、 该函数的作用是设置定时值和相应的处理函数。 如果定时的值小于0, 那么立即调用处理函数。 ```c if (jiffies <= 0) (fn)();
如果定时的值大于0, 那么首先取timer_list数组中寻找一个位置,将该位置上的滴答数设置为jiffies,将该位置上的fn设置为入参fn。并让next_timer指向它。 for (p = timer_list ; p < timer_list + TIME_REQUESTS ; p++) if (!p->fn) break; if (p >= timer_list + TIME_REQUESTS) panic("No more time requests free"); p->fn = fn; p->jiffies = jiffies; p->next = next_timer; next_timer = p;
下面这段代码的作用是将刚刚插入链表中的timer移动的合适的位置。
由于next_timer这个链表上的jiffies是一个相对值,即相对于前面一个timer还有多久到期。因此上面步骤的timer也需要进行转换。
timer移动示意图
while (p->next && p->next->jiffies < p->jiffies) { p->jiffies -= p->next->jiffies;//减去下一个timer的jiffies fn = p->fn;//将当前的fn保存给临时变量 p->fn = p->next->fn;//将当前的fn设置为下一个timer的fn p->next->fn = fn;//将下一个timer的fn设置为临时变量fn jiffies = p->jiffies;//将jiffies保存给一个临时变量 p->jiffies = p->next->jiffies;//将当前的jiffies设置为下一个timer的jiffies p->next->jiffies = jiffies;//将下一个timer的jiffies设置为当前的jiffies p = p->next; //这一步骤实际上将p向后挪动到合适的位置, 并把jiffies转化成相对值。 }do_timervoid do_timer(long cpl)
该函数是时钟中断的处理函数。其在system_call.s中的timer_interrupt函数中被调用。
参数cpl表示的是当前的特权级, 0表示时钟中断发生时,当前运行在内核态,3表示时钟中断发生时,当前运行在用户态。
下面的代码根据cpl的值将进程PCB中的utime和stime进行修改。如果cpl为0,则增加stime(supervisor time), 如果cpl为3, 则增加utime。 if (cpl) current->utime++; else current->stime++;
下面对定时器的链表进行遍历。 将链表的第一个定时器的滴答数减1。如果滴答数已经等于0, 代表该定时器已经到期,那么需要调用相应的处理程序进行处理。 if (next_timer) { next_timer->jiffies--; while (next_timer && next_timer->jiffies <= 0) { void (*fn)(void); fn = next_timer->fn; next_timer->fn = NULL; next_timer = next_timer->next; (fn)(); } }
下面代码则是将当前运行的进程的时间片减去1,如果此时进程时间片没有用完,该函数则返回。 如果此时进程时间已经用完,则将时间片设置为0。并且如果此时cpl表明中断发生用户态,那么还将会触发进程的调度。 if ((--current->counter)>0) return; current->counter=0;sys_alarmint sys_alarm(long seconds)
该函数用于设置 报警值 。
jiffies是指的是系统开机到目前经历的滴答数。
current->alarm的单位也是系统滴答数。
因此(current->alarm - jiffies) /100 就代表就是当前的定时器还剩下多少秒。
而设置alarm值则需要加上系统当前的滴答数据jiffies, 如下图所示:
sys_alarm
sys_getpidint sys_getpid(void)
该函数用于获取进程的pid。 sys_getppidint sys_getppid(void)
该函数用于获取父进程的pid。 sys_getuidint sys_getuid(void)
该函数用于获取用户的uid。 sys_geteuidint sys_geteuid(void)
该函数用于获取用户的有效id(euid)。 sys_getgidint sys_getgid(void)
获取组和id号(gid)。 sys_getegidint sys_getegid(void)
取有效的组id(egid) sys_niceint sys_nice(long increment)
该函数的作用是降低进程在调度时的优先级。 sched_initvoid sched_init(void)
该函数的作用是初始化进程调度模块。
首先在gdt表中设置任务0的tss和ldt值。接着对其他任务的tss和ldt进行初始化。 set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss)); set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt)); p = gdt+2+FIRST_TSS_ENTRY; for(i=1;ia=p->b=0; p++; p->a=p->b=0; p++; }
显式地将任务0的tss加载到寄存器tr中, 显式地将任务0的ldt加载到ldtr中。 ltr(0); lldt(0);
下面的代码用于初始化8253定时器。通道0,选择工作方式3,二进制计数方式。 outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */ outb_p(LATCH & 0xff , 0x40); /* LSB */ outb(LATCH >> 8 , 0x40); /* MSB */
设置时钟中断处理程序的处理函数, 设置系统调用的中断处理函数。 set_intr_gate(0x20,&timer_interrupt); outb(inb_p(0x21)&~0x01,0x21); set_system_gate(0x80,&system_call);
110!皇马在欧冠决赛中,最终击败了利物浦,拿到了本赛季三冠王。在此之前,皇马已经拿到西班牙超级杯和西甲冠军,加上这个欧冠冠军,本赛季的皇马,无疑是欧洲最成功的球队。本场比赛,皇马的
小学教材插画被恶搞,是情怀还是故意丑化国人?在人教版的数学教材里,我们丝毫看不出学生的那份朝气蓬勃与阳光,反倒是看出眼睛无神,甚至嘴歪眼斜的。教材是什么?它是学生在启蒙阶段了解知识的桥梁,而教材里面的文字,是认知这个世界的主
金卡戴珊穿肉粉贴身衣,和小13岁男友久违团聚,自拍时胸衣滑落当地时间5月27日,金卡戴珊出现在她所拥有的洛杉矶一处办公室的外面,准备为自创塑身衣品牌的最新作品当模特。而她的男友皮特戴维斯也出现在了镜头中,这也是现年41岁的真人秀女星和现年2
为何课本还有黑丝兔女郎?最近其实大家都在关注课本的问题,发现课本上出现了一些不该出现在课本上的东西!其中最令大家惊讶的就是这些绘图实在是太辣眼睛!某些图片即使给我们大人看也是非常的不合适!比如说这个兔女郎
房贷利率下调,站在高位的我们该怎么做呢2022年新政前的贷款买房人,估计绝大部分站在了房贷利率的相对高位。除非你是公积金贷款,那恭喜你!3。25的贷款利率真是太香了,能贷多少就贷多少吧。纵观全网,有网友的房贷利率甚至高
哪来的十几万亿刺激?千万别被带了节奏哪来的十几万亿刺激?千万别被带了节奏!作者知识星球找齐俊杰昨天,有朋友在后台向老齐提问,说12万亿刺激是不是要来了,当年2009年都让楼市起飞了,这次12万亿,是当年刺激的3倍,是
DNF百花齐放提前结束了!105热门装备一览,9件套足够百搭众所周知,105级装备由于没有套装属性,各个顶尖装备的触发条件又有所不同,所以理论上,就存在着千百种搭配可能,但这里也要一个问题,大多数玩家都只会追求认可度更高的装备,于是,想要的
逃跑之路所感夜色宁静,一个易容成长相滑稽的盗窃惯犯穿着一件笔挺大衣,手提一个大公文包,快速穿梭在小路上,一不小心迎面撞到了一个路人,公文包掉落地上,好几条首饰露了出来,路人觉得可疑,大叫警察,
2019年,福建民警查出急性白血病,被他抓的罪犯却要求捐骨髓文纪中百事编辑纪中百事2019年,福建一名民警被查出急性白血病,生命危在旦夕,就在这危急时刻,他亲手抓捕的杀人犯却强烈要求我要为他捐骨髓!那么这个警官到底做了什么?为何杀人犯会主动
紧急召回!这种零食不要购买,立即停止食用据海关总署网站25日消息,美国食品药物监督管理局宣布,玛氏箭牌糖果有限公司正在召回特定批次的SKITTLES软糖STARBURST软糖和LIFESAVERS软糖,召回原因为相关批次
半场0利物浦本泽马进球被吹马内中柱库尔图瓦屡救险直播吧5月29日讯北京时间5月29日凌晨3点36分,202122赛季欧冠决赛在法兰西大球场展开角逐,利物浦对阵皇家马德里。上半场马内中柱,库尔图瓦屡救险,本泽马进球被吹越位,利物浦