鸿蒙内核源码:谁来触发调度工作?

为什么学个东西要学那么多的概念?
鸿蒙的内核中 task 和 线程 在广义上可以理解为是一个东西,但狭义上肯定会有区别,区别在于管理体系的不同,task是调度层面的概念,线程是进程层面概念。比如 main() 函数中首个函数 ossetmaintask(); 就是设置启动任务,但此时啥都还没开始呢,kprocess 进程都没创建,怎么会有大家一般意义上所理解的线程呢。狭义上的后续有 鸿蒙内核源码分析(启动过程篇) 来说明。不知道大家有没有这种体会,学一个东西的过程中要接触很多新概念,尤其像 java/android 的生态,概念贼多,很多同学都被绕在概念中出不来,痛苦不堪。那问题是为什么需要这么多的概念呢?
举个例子就明白了:
假如您去深圳参加一个面试老板问你哪里人?你会说是 江西人,湖南人... 而不会说是张家村二组的张全蛋,这样还谁敢要你。但如果你参加同乡会别人问你同样问题,你不会说是来自东北那旮沓的,却反而要说张家村二组的张全蛋。明白了吗?张全蛋还是那个张全蛋,但因为场景变了,您的说法就得必须跟着变,否则没法愉快的聊天。程序设计就是源于生活,归于生活,大家对程序的理解就是要用生活中的场景去打比方,更好的理解概念。
那在内核的调度层面,咱们只说task, task是内核调度的单元,调度就是围着它转。
进程和线程的状态迁移图
先看看task从哪些渠道产生:
渠道很多,可能是shell 的一个命令,也可能由内核创建,更多的是大家编写应用程序new出来的一个线程。
调度的内容task已经有了,那他们是如何被有序调度的呢?答案:是32个进程和线程就绪队列,各32个哈,为什么是32个,鸿蒙系统源码分析(总目录) 文章里有详细说明,自行去翻。这张进程状态迁移示意图一定要看明白.
注意:进程和线程的队列内的内容只针对就绪状态,其他状态内核并没有用队列去描述它,(线程的阻塞状态用的是pendlist链表),因为就绪就意味着工作都准备好了就等着被调度到cpu来执行了。所以理解就绪队列很关键,有三种情况会加入就绪队列。
init→ready:
进程创建或fork时,拿到该进程控制块后进入init状态,处于进程初始化阶段,当进程初始化完成将进程插入调度队列,此时进程进入就绪状态。
pend→ready / pend→running:
阻塞进程内的任意线程恢复就绪态时,进程被加入到就绪队列,同步转为就绪态,若此时发生进程切换,则进程状态由就绪态转为运行态。
running→ready:
进程由运行态转为就绪态的情况有以下两种:
有更高优先级的进程创建或者恢复后,会发生进程调度,此刻就绪列表中最高优先级进程变为运行态,那么原先运行的进程由运行态变为就绪态。
若进程的调度策略为sched_rr,且存在同一优先级的另一个进程处于就绪态,则该进程的时间片消耗光之后,该进程由运行态转为就绪态,另一个同优先级的进程由就绪态转为运行态。
谁来触发调度工作?
就绪队列让task各就各位,在其生命周期内不停的进行状态流转,调度是让task交给cpu处理,那又是什么让调度去工作的呢?它是如何被触发的?
笔者能想到的触发方式是以下四个:
tick(时钟管理),类似于java的定时任务,时间到了就触发。系统定时器是内核时间机制中最重要的一部分,它提供了一种周期性触发中断机制,即系统定时器以hz(时钟节拍率)为频率自行触发时钟中断。当时钟中断发生时,内核就通过时钟中断处理程序ostickhandler对其进行处理。鸿蒙内核默认是10ms触发一次,执行以下中断函数:
/* * description : tick interruption handler */lite_os_sec_text void ostickhandler(void){ uint32 intsave; tick_lock(intsave); g_tickcount[archcurrcpuid()]++; tick_unlock(intsave);#ifdef loscfg_kernel_vdso osupdatevdsotimeval();#endif#ifdef loscfg_kernel_tickless ostickirqflagset(osticklessflagget());#endif#if (loscfg_base_core_tick_hw_time == yes) halclockirqclear(); /* diff from every platform */#endif ostimeslicecheck();//时间片检查 ostaskscan(); /* task timeout scan *///任务扫描,发起调度#if (loscfg_base_core_swtmr == yes) osswtmrscan();//软时钟扫描检查#endif} 里面对任务进行了扫描,时间片到了或就绪队列有高或同级task, 会执行调度。
第二个是各种软硬中断,如何usb插拔,键盘,鼠标这些外设引起的中断,需要去执行中断处理函数。
第三个是程序主动中断,比如运行过程中需要申请其他资源,而主动让出控制权,重新调度。
最后一个是创建一个新进程或新任务后主动发起的抢占式调度,新进程会默认创建一个main task, task的首条指令(入口函数)就是我们上层程序的main函数,它被放在代码段的第一的位置。
哪些地方会申请调度?看一张图。
这里提下图中的 oscopyprocess(), 这是fork进程的主体函数,可以看出fork之后立即申请了一次调度。
lite_os_sec_text int32 los_fork(uint32 flags, const char *name, const tsk_entry_func entry, uint32 stacksize){ uint32 cloneflag = clone_parent | clone_thread | clone_vfork | clone_files; if (flags & (~cloneflag)) { print_warn(clone dont support some flags!\n); } flags |= clone_files; return oscopyprocess(cloneflag & flags, name, (uintptr)entry, stacksize);}static int32 oscopyprocess(uint32 flags, const char *name, uintptr sp, uint32 size){ uint32 intsave, ret, processid; losprocesscb *run = oscurrprocessget(); losprocesscb *child = osgetfreepcb(); if (child == null) { return -los_eagain; } processid = child->processid; ret = osforkinitpcb(flags, child, name, sp, size); if (ret != los_ok) { goto error_init; } ret = oscopyprocessresources(flags, child, run); if (ret != los_ok) { goto error_task; } ret = oschildsetprocessgroupandsched(child, run); if (ret != los_ok) { goto error_task; } los_mpschedule(os_mp_cpu_all); if (os_scheduler_active) { los_schedule();// 申请调度 } return processid;error_task: scheduler_lock(intsave); (void)ostaskdeleteunsafe(os_tcb_from_tid(child->threadgroupid), os_pro_exit_ok, intsave);error_init: osdeinitpcb(child); return -ret;} 原来创建一个进程这么简单,真的就是在copy!
源码告诉你调度过程是怎样的
以上是需要提前了解的信息,接下来直接上源码看调度过程吧,文件就三个函数,主要就是这个了:
void osschedresched(void){ los_assert(los_spinheld(&g_taskspin));//调度过程要上锁 newtask = osgettoptask(); //获取最高优先级任务 osschedswitchprocess(runprocess, newprocess);//切换进程 (void)ostaskswitchcheck(runtask, newtask);//任务检查 oscurrtaskset((void*)newtask);//*设置当前任务 if (osprocessisusermode(newprocess)) {//判断是否为用户态,使用用户空间 oscurrusertaskset(newtask->userarea);//设置任务空间 } /* do the task context switch */ ostaskschedule(newtask, runtask); //切换cpu任务上下文,汇编代码实现} 函数有点长,笔者留了最重要的几行,看这几行就够了,流程如下:
调度过程要自旋锁,多核情况下只能被一个cpu core 执行. 不允许任何中断发生, 没错,说的是任何事是不能去打断它,否则后果太严重了,这可是内核在切换进程和线程的操作啊。
在就绪队列里找个最高优先级的task
切换进程,就是task归属的那个进程设为运行进程,这里要注意,老的task和老进程只是让出了cpu指令执行权,其他都还在内存,资源也都没有释放.
设置新任务为当前任务
用户模式下需要设置task运行空间,因为每个task栈是不一样的.空间部分具体在系列篇内存中查看
是最重要的,切换任务上下文,参数是新老两个任务,一个要保存现场,一个要恢复现场。
什么是任务上下文?鸿蒙内核源码分析(总目录)任务切换篇已有详细的描述,请自行翻看.
请读懂osgettoptask()
读懂osgettoptask(),就明白了就绪队列是怎么回事了。这里提下goto语句,几乎所有内核代码都会大量的使用goto语句,鸿蒙内核有617个goto远大于264个break,还有人说要废掉goto,你知道内核开发者青睐goto的真正原因吗?


量研科技正式推出了电容式触摸传感系列产品
找方案 | 基于ST 意法半导体BLUENRG-M2SP的智能插头解决方案
NASA为建造月球挖掘机器人寻求帮助
手持土壤重金属分析光谱仪【恒美仪器HM-X800】应用领域
AI对于5G的建设有什么作用
鸿蒙内核源码:谁来触发调度工作?
性价比是瓶毒药 红米独立或是目前小米走出“性价比”困境的最好办法
机器上常见的装配结构
蓝牙耳机的工作原理、声音传播方式及编码方式
华为MateBook D intel版开启预订最高搭载英特尔酷睿i7-10510U处理器
CadSoft推出EAGLE PCB设计工具版本 6.0
为何华为不出售麒麟960?这就是原因!
中国联通官方宣布,贵州省的首个联通5G基站在贵阳市开通,峰值速率达到了1.8Gbps
盘点10个一行强大的、有趣的Python源代码
人工长晶工艺
升压型DC-DC的工作路径和工作原理
通过GPIO模拟IIC通信对接SHT20温湿度计
特斯拉带动新能源汽车市场回温 国产半导体厂商发展空间广阔
核磁共振检查对人体有危害吗?
印制电路板设计中手工设计和自动设计的比较