热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux0.11kernel目录进程管理sched.c详解

Linux-0.11kernel目录进程管理sched.
Linux-0.11 kernel目录进程管理sched.c详解

sched.c主要功能是负责进程的调度,其最核心的函数就是schedule。除schedule以外, sleep_on和wake_up也是相对重要的函数。

schedule

void schedule(void)

schedule函数的基本功能可以分为两大块, 第一块是检查task中的报警信息和信号, 第二块则是进行任务的调度

在第一块中,首先从任务数组的尾部任务开始,检查alarm是否小于当前系统滴答值,如果小于则代表alarm时间已经到期。将进程的signal中的SIGALARM位置1。

接着就看如果检查进程的信号中如果处理BLOCK位以外还有别的信号,并且如果任务处于可中断状态,则将任务置为就绪状态。

int i,next,c;
struct task_struct ** p;for(p &#61; &LAST_TASK ; p > &FIRST_TASK ; --p)if (*p) {if ((*p)->alarm && (*p)->alarm < jiffies) { //如果设置了任务定时的值alarm&#xff0c; 并且已经过期(*p)->signal |&#61; (1<<(SIGALRM-1)); //将信号的SIGALARM位置为1(*p)->alarm &#61; 0;}if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&(*p)->state&#61;&#61;TASK_INTERRUPTIBLE)//如果信号位图中除了被阻塞的信号外还有其他信号&#xff0c; 并且任务处于可中断状态(*p)->state&#61;TASK_RUNNING; //修改任务的状态为就绪态}

第二块的代码就是任务调度的核心代码。

这里会从任务数组的尾部任务开始进行遍历&#xff0c;从所有任务从选取counter值最大的任务作为下一个运行的任务去执行。

while (1) {c &#61; -1;next &#61; 0;i &#61; NR_TASKS;p &#61; &task[NR_TASKS];//从最后一个任务开始while (--i) { //遍历所有的task&#xff0c; 取出其中counter最大的taskif (!*--p)continue;if ((*p)->state &#61;&#61; TASK_RUNNING && (*p)->counter > c)//取出所有任务中counter值最大的任务作为下一个任务c &#61; (*p)->counter, next &#61; i;}if (c) break;//如果当前没有RUNNING状态的任务的counter可以大于-1&#xff0c;那么则去更新counter的值&#xff0c;counter &#61; counter/2 &#43; priorityfor(p &#61; &LAST_TASK ; p > &FIRST_TASK ; --p)if (*p)(*p)->counter &#61; ((*p)->counter >> 1) &#43;(*p)->priority;//更新counter值 counter &#61; counter/2 &#43; priority
}
//切换任务执行next
switch_to(next);

show_task

void show_task(int nr,struct task_struct * p)

该函数的作用是显示任务序号为nr的进程的pid&#xff0c;进程状态以及内核栈剩余的大小。

int i,j &#61; 4096-sizeof(struct task_struct);printk("%d: pid&#61;%d, state&#61;%d, ",nr,p->pid,p->state);
i&#61;0;

此时j指向PCB所在内存页的顶部&#xff0c; i指向task_struct结构体的下一个字节。下面这段代码的所用实际就是统计内核栈中空闲大小。

在这里插入图片描述

while (i<j && !((char *)(p&#43;1))[i])i&#43;&#43;;
printk("%d (of %d) chars free in kernel stack\n\r",i,j);

show_stat

void show_stat(void)

该函数内部调用show_task函数&#xff0c;实际上就是遍历task数组&#xff0c; 调用show_stat函数显示进程相关信息。

int i;for (i&#61;0;i<NR_TASKS;i&#43;&#43;)//遍历task数组if (task[i])show_task(i,task[i]);//调用show_task

math_state_restore

void math_state_restore()

该函数的作用是将当前协处理器内容保存到老协处理器状态数组中&#xff0c;并将当前任务的协处理器内容加载进协处理器。

sys_pause

int sys_pause(void)

该函数是pause的系统调用。该函数会将当前任务的状态修改为可中断的状态&#xff0c; 并调用schedule函数去进行进程的调度。

调用pause函数的进程会进入睡眠状态&#xff0c; 直到收到一个信号。

current->state &#61; TASK_INTERRUPTIBLE;
schedule();

sleep_on

void sleep_on(struct task_struct **p)

该函数的作用是将当前的task置为不可中断的等待状态&#xff0c; 直到被wake_up唤醒再继续执行。入参p是等待任务队列的头指针。通过p指针和tmp变量将等待的任务串在了一起。

在这里插入图片描述

该函数首先对一些异常情况进行了处理他&#xff0c; 例如p是空指针。或者当前task是任务0。

struct task_struct *tmp;// 若指针无效&#xff0c;则退出。&#xff08;指针所指的对象可以是NULL&#xff0c;但指针本身不会为0)。
if (!p)return;
if (current &#61;&#61; &(init_task.task)) // 如果当前任务是任务0&#xff0c;则死机(impossible!)。panic ("task[0] trying to sleep");

接着让当前等待任务的头指针指向当前任务。并将当前任务修改为不可中断的等待状态。进行调用schedule函数让操作系统切换其他任务执行。

tmp &#61; *p;
*p &#61; current;
current->state &#61; TASK_UNINTERRUPTIBLE;
schedule();

当程序从schedule()返回继续执行时&#xff0c;说明任务已经被显式的wake_up&#xff0c;如果此时还有其他进程仍然在等待&#xff0c;那么也一同唤醒。

因为任务都在等待同样的资源&#xff0c; 那么当资源可用的时候&#xff0c; 就可以唤醒所有等待的任务。

if (tmp) // 若还存在等待的任务&#xff0c;则也将其置为就绪状态&#xff08;唤醒&#xff09;。tmp->state &#61; 0;

interruptible_sleep_on

void interruptible_sleep_on (struct task_struct **p)

该函数与sleep_on类似&#xff0c;但是该函数会将任务的状态修改为可中断的等待状态&#xff0c; 而sleep_on则是将任务修改为不可中断的等待状态。因此通过interruptible_sleep_on而等待的task是可以被信号唤醒的。 而通过sleep_on而等待的task是不会被信号唤醒的&#xff0c;只能通过wake_up函数唤醒。

interruptible_sleep_on示意图

下面这段代码与sleep_on并无太大区别&#xff0c; 只是将进程的状态修改为可中断的等待状态。

struct task_struct *tmp;if (!p)return;if (current &#61;&#61; &(init_task.task))panic ("task[0] trying to sleep");tmp &#61; *p;*p &#61; current;
repeat:current->state &#61; TASK_INTERRUPTIBLE;schedule ();

由于任务是可以被信号唤醒的&#xff0c;因此下面需要判断唤醒的任务是否是等待任务队列的头节点。如果不是则需要等待其他任务。

if (*p && *p !&#61; current)
{(**p).state &#61; 0;goto repeat;
}

下面一句代码有误&#xff0c;应该是*p &#61; tmp&#xff0c;让队列头指针指向其余等待任务&#xff0c;否则在当前任务之前插入
等待队列的任务均被抹掉了

*p &#61; NULL;
if (tmp)tmp->state &#61; 0;

wake_up

void wake_up(struct task_struct **p)

该函数的作用就是唤醒某一个任务。其用于唤醒p指向的等待队列中的任务。

if (p && *p)
{(**p).state &#61; 0; // 置为就绪&#xff08;可运行&#xff09;状态。*p &#61; NULL;
}

ticks_to_floppy_on

int ticks_to_floppy_on(unsigned int nr)

该函数指定软盘到正常运转状态所需延迟滴答数&#xff08;时间&#xff09;。

floppy_on

void floppy_on(unsigned int nr)

该函数等待指定软驱马达启动所需时间。

floppy_off

void floppy_off(unsigned int nr)

关闭相应的软驱马达停转定时器3s。

moff_timer[nr]&#61;3*HZ;

do_floppy_timer

void do_floppy_timer(void)

如果马达启动定时到则唤醒进程。

if (mon_timer[i]) {if (!--mon_timer[i])wake_up(i&#43;wait_motor);

如果马达停转定时到期则复位相应马达启动位&#xff0c;并更新数字输出到寄存器。

else if (!moff_timer[i]) {current_DOR &&#61; ~mask;outb(current_DOR,FD_DOR);

add_timer

add_timer(long jiffies, void (*fn)(void))
&#96;&#96;&#96;、
该函数的作用是设置定时值和相应的处理函数。如果定时的值小于0&#xff0c; 那么立即调用处理函数。
&#96;&#96;&#96;c
if (jiffies <&#61; 0)(fn)();

如果定时的值大于0&#xff0c; 那么首先取timer_list数组中寻找一个位置&#xff0c;将该位置上的滴答数设置为jiffies&#xff0c;将该位置上的fn设置为入参fn。并让next_timer指向它。

for (p &#61; timer_list ; p < timer_list &#43; TIME_REQUESTS ; p&#43;&#43;)if (!p->fn)break;
if (p >&#61; timer_list &#43; TIME_REQUESTS)panic("No more time requests free");
p->fn &#61; fn;
p->jiffies &#61; jiffies;
p->next &#61; next_timer;
next_timer &#61; p;

下面这段代码的作用是将刚刚插入链表中的timer移动的合适的位置。

由于next_timer这个链表上的jiffies是一个相对值&#xff0c;即相对于前面一个timer还有多久到期。因此上面步骤的timer也需要进行转换。

在这里插入图片描述

while (p->next && p->next->jiffies < p->jiffies) {p->jiffies -&#61; p->next->jiffies;//减去下一个timer的jiffiesfn &#61; p->fn;//将当前的fn保存给临时变量p->fn &#61; p->next->fn;//将当前的fn设置为下一个timer的fnp->next->fn &#61; fn;//将下一个timer的fn设置为临时变量fnjiffies &#61; p->jiffies;//将jiffies保存给一个临时变量p->jiffies &#61; p->next->jiffies;//将当前的jiffies设置为下一个timer的jiffiesp->next->jiffies &#61; jiffies;//将下一个timer的jiffies设置为当前的jiffiesp &#61; p->next;//这一步骤实际上将p向后挪动到合适的位置&#xff0c; 并把jiffies转化成相对值。
}

do_timer

void do_timer(long cpl)

该函数是时钟中断的处理函数。其在system_call.s中的timer_interrupt函数中被调用。

参数cpl表示的是当前的特权级&#xff0c; 0表示时钟中断发生时&#xff0c;当前运行在内核态&#xff0c;3表示时钟中断发生时&#xff0c;当前运行在用户态。

下面的代码根据cpl的值将进程PCB中的utime和stime进行修改。如果cpl为0&#xff0c;则增加stime(supervisor time)&#xff0c; 如果cpl为3&#xff0c; 则增加utime。

if (cpl)current->utime&#43;&#43;;
elsecurrent->stime&#43;&#43;;

下面对定时器的链表进行遍历。 将链表的第一个定时器的滴答数减1。如果滴答数已经等于0&#xff0c; 代表该定时器已经到期&#xff0c;那么需要调用相应的处理程序进行处理。

if (next_timer) {next_timer->jiffies--;while (next_timer && next_timer->jiffies <&#61; 0) {void (*fn)(void);fn &#61; next_timer->fn;next_timer->fn &#61; NULL;next_timer &#61; next_timer->next;(fn)();}
}

下面代码则是将当前运行的进程的时间片减去1&#xff0c;如果此时进程时间片没有用完&#xff0c;该函数则返回。 如果此时进程时间已经用完&#xff0c;则将时间片设置为0。并且如果此时cpl表明中断发生用户态&#xff0c;那么还将会触发进程的调度。

if ((--current->counter)>0) return;
current->counter&#61;0;

sys_alarm

int sys_alarm(long seconds)

该函数用于设置报警值

jiffies是指的是系统开机到目前经历的滴答数。

current->alarm的单位也是系统滴答数。

因此(current->alarm - jiffies) /100 就代表就是当前的定时器还剩下多少秒。

而设置alarm值则需要加上系统当前的滴答数据jiffies&#xff0c; 如下图所示:

在这里插入图片描述

sys_getpid

int sys_getpid(void)

该函数用于获取进程的pid。

sys_getppid

int sys_getppid(void)

该函数用于获取父进程的pid。

sys_getuid

int sys_getuid(void)

该函数用于获取用户的uid。

sys_geteuid

int sys_geteuid(void)

该函数用于获取用户的有效id(euid)。

sys_getgid

int sys_getgid(void)

获取组和id号(gid)。

sys_getegid

int sys_getegid(void)

取有效的组id(egid)

sys_nice

int sys_nice(long increment)

该函数的作用是降低进程在调度时的优先级。

sched_init

void sched_init(void)

该函数的作用是初始化进程调度模块。

首先在gdt表中设置任务0的tss和ldt值。接着对其他任务的tss和ldt进行初始化。

set_tss_desc(gdt&#43;FIRST_TSS_ENTRY,&(init_task.task.tss));
set_ldt_desc(gdt&#43;FIRST_LDT_ENTRY,&(init_task.task.ldt));
p &#61; gdt&#43;2&#43;FIRST_TSS_ENTRY;
for(i&#61;1;i<NR_TASKS;i&#43;&#43;) {task[i] &#61; NULL;p->a&#61;p->b&#61;0;p&#43;&#43;;p->a&#61;p->b&#61;0;p&#43;&#43;;
}

显式地将任务0的tss加载到寄存器tr中&#xff0c; 显式地将任务0的ldt加载到ldtr中。

ltr(0);
lldt(0);

下面的代码用于初始化8253定时器。通道0&#xff0c;选择工作方式3&#xff0c;二进制计数方式。

outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */
outb_p(LATCH & 0xff , 0x40); /* LSB */
outb(LATCH >> 8 , 0x40); /* MSB */

设置时钟中断处理程序的处理函数&#xff0c; 设置系统调用的中断处理函数。

set_intr_gate(0x20,&timer_interrupt);
outb(inb_p(0x21)&~0x01,0x21);
set_system_gate(0x80,&system_call);


推荐阅读
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
author-avatar
_i逗比_985
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有