首页 文章 精选 留言 我的

精选列表

搜索[harmonyos开发],共10000篇文章
优秀的个人博客,低调大师

Android开发笔记--Android开发时常用控件(二

MicrosoftInternetExplorer402DocumentNotSpecified7.8Normal0 RadioGroup,RadioButton(单选) CheckBox(多选) Toast(像一个遮罩层) 例1,<RadioGroupAndroid:id="@+id/genderGroup" Android:layout_width="wrap_content" Android:layout_height="wrap_content" Android:orientation="vertical"> <RadioButtonAndroid:id="@+id/female" Android:layout_width="wrap_content" Android:layout_height="wrap_content" Android:text="@string/female"/> <RadioButtonAndroid:id="@+id/male" Android:layout_width="wrap_content" Android:layout_height="wrap_content" Android:text="@string/male"/> </RadioGroup> 代码:1.控件对象声明 2.取得对象控件 3.设置监听器(只为RadioGroup注册一个) PublicvoidonCheckedChange(RadioGroupgroup,intcheckedId){ If(femaleButton.getId()==checkedId){ System.out.println("Female"); } Elseif(maleButton.getId()==checkedId){ System.out.println("Male"); } } 4.触发事件 //为RadioGroup设置 RadioGroup.setOnCheckedChangeListener(newRadioGroup.OnCheckedChangeListener()) 例2.<CheckBoxandroid:id="@+id/swim" Android:layout_width="wrap_content" Android:layout_height="wrap_content" Android:text="@string/swim"/> <CheckBoxandroid:id="@+id/run" Android:layout_width="wrap_content" Android:layout_height="wrap_content" Android:text="@string/run"/> <CheckBoxandroid:id="@+id/read" Android:layout_width="wrap_content" Android:layout_height="wrap_content" Android:text="@string/read"/> 代码:1.控件对象声明 2.取得对象控件 3.设置监听器(为每一个CheckBox注册监听器) PublicvoidonCheckedChanged(CompoundButtonbuttonView,booleanisChecked){ If(isChecked){ System.out.println("yes"); } Else{ System.out.println("NO"); } } 4.触发事件 CheckBox.setOnCheckedChangeLinstener(newCompoundButton.onCheckedChangeLinstener(){}) 例3.Toast Toast.makeText(this.class,displaystring,messageTime).show() 本文转自My_King1 51CTO博客,原文链接:http://blog.51cto.com/apprentice/1360574,如需转载请自行联系原作者

优秀的个人博客,低调大师

Windows下React Native开发01 -- Android开发环境搭建

1.安装jdk 推荐将JDK的bin目录加入系统PATH环境变量(自己百度下怎么配置)。 2.安装SDK 直接安装Android Studio 推荐从AndroidDevTools下载。(也可以直接安装 android sdk,这里是直接安装的android tools) 1.下载管理工具 一个是安装的、一个是解压版 2.进入SDKManager,确保以下项目已经安装并更新到最新: Tools/Android SDK Tools (24.3.3) Tools/Android SDK Platform-tools (22) Tools/Android SDK Build-tools (23.0.1)(这个必须版本严格匹配23.0.1) Android 6.0 (API 23)/SDK Platform (1) Extras/Android Support Library(23.0.1) Extras/Android Support Repository 国内有墙,有时候会出现获取失败的情况。 更改host文件 host文件在C:\Windows\System32\drivers\etc目录下,在该文件内添加下面两条。域名解析到对应IP上 203.208.46.146 dl.google.com 203.208.46.146 dl-ssl.google.com 将Android SDK Manage上的https请求改成http请求 然后在更新。。 3.安装c++环境 (node环境需要) 推荐从itellyou下载并安装Visual Studio 2013或2015 如果使用VS2015,你需要在命令行中设置npm config set msvs_version 2015 --global 4.安装Python 从官网下载并安装python 2.7.x(3.x版本不行) 5.安装node/react-native命令行工具 node直接百度,然后到官网上面下载安装。 从官网下载node.js的官方4.1版本或更高版本。 npm install -g react-native-cli 6.安装git,获取项目 自己百度如何安装git

优秀的个人博客,低调大师

鸿蒙内核源码分析(进程回收篇) | 进程在临终前如何向老祖宗托孤 | 百篇博客分析HarmonyOS源码 | v47.02

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< oschina | 51cto | csdn | harmony > 进程关系链 进程是家族式管理的,父子关系,兄弟关系,朋友关系,子女关系,甚至陌生人关系(等待你消亡)在一个进程的生命周期中都会记录下来.用什么来记录呢?当然是内核最重要的胶水结构体LOS_DL_LIST,进程控制块(以下简称PCB)用了8个双向链表来记录进程家族的基因关系和运行时关系.如下: typedef struct ProcessCB { //...此处省略其他变量 LOS_DL_LIST pendList; /**< Block list to which the process belongs */ //进程所属的阻塞列表,如果因拿锁失败,就由此节点挂到等锁链表上 LOS_DL_LIST childrenList; /**< my children process list */ //孩子进程都挂到这里,形成双循环链表 LOS_DL_LIST exitChildList; /**< my exit children process list */ //那些要退出孩子进程挂到这里,白发人送黑发人。 LOS_DL_LIST siblingList; /**< linkage in my parent's children list */ //兄弟进程链表, 56个民族是一家,来自同一个父进程. LOS_DL_LIST subordinateGroupList; /**< linkage in my group list */ //进程是组长时,有哪些组员进程 LOS_DL_LIST threadSiblingList; /**< List of threads under this process *///进程的线程(任务)列表 LOS_DL_LIST threadPriQueueList[OS_PRIORITY_QUEUE_NUM]; /**< The process's thread group schedules thepriority hash table */ //进程的线程组调度优先级哈希表 LOS_DL_LIST waitList; /**< The process holds the waitLits to support wait/waitpid *///进程持有等待链表以支持wait/waitpid } LosProcessCB; 解读 pendList 个人认为它是鸿蒙内核功能最多的一个链表,它远不止字面意思阻塞链表这么简单,只有深入解读源码后才能体会它真的是太会来事了,一般把它理解为阻塞链表就行.上面挂的是处于阻塞状态的进程. childrenList孩子链表,所有由它fork出来的进程都挂到这个链表上.上面的孩子进程在死亡前会将自己从上面摘出去,转而挂到exitChildList链表上. exitChildList退出孩子链表,进入死亡程序的进程要挂到这个链表上,一个进程的死亡是件挺麻烦的事,进程池的数量有限,需要及时回收进程资源,但家族管理关系复杂,要去很多地方消除痕迹.尤其还有其他进程在看你笑话,等你死亡(wait/waitpid)了通知它们一声. siblingList兄弟链表,和你同一个父亲的进程都挂到了这个链表上. subordinateGroupList 朋友圈链表,里面是因为兴趣爱好(进程组)而挂在一起的进程,它们可以不是一个父亲,不是一个祖父,但一定是同一个老祖宗(用户态和内核态根进程). threadSiblingList线程链表,上面挂的是进程ID都是这个进程的线程(任务),进程和线程的关系是1:N的关系,一个线程只能属于一个进程.这里要注意任务在其生命周期中是不能改所属进程的. threadPriQueueList线程的调度队列数组,一共32个,任务和进程一样有32个优先级,调度算法的过程是先找到优先级最高的进程,在从该进程的任务队列里去最高的优先级任务运行. waitList 是等待子进程消亡的任务链表,注意上面挂的是任务.任务是通过系统调用 pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options); 将任务挂到waitList上.鸿蒙waitpid系统调用为SysWait,稍后会讲. 进程正常死亡过程 一个进程的自然消亡过程如下 //一个进程的自然消亡过程,参数是当前运行的任务 STATIC VOID OsProcessNaturalExit(LosTaskCB *runTask, UINT32 status) { LosProcessCB *processCB = OS_PCB_FROM_PID(runTask->processID);//通过task找到所属PCB LosProcessCB *parentCB = NULL; LOS_ASSERT(!(processCB->threadScheduleMap != 0));//断言没有任务需要调度了,当前task是最后一个了 LOS_ASSERT(processCB->processStatus & OS_PROCESS_STATUS_RUNNING);//断言必须为正在运行的进程 OsChildProcessResourcesFree(processCB);//释放孩子进程的资源 #ifdef LOSCFG_KERNEL_CPUP OsCpupClean(processCB->processID); #endif /* is a child process */ if (processCB->parentProcessID != OS_INVALID_VALUE) {//判断是否有父进程 parentCB = OS_PCB_FROM_PID(processCB->parentProcessID);//获取父进程实体 LOS_ListDelete(&processCB->siblingList);//将自己从兄弟链表中摘除,家人们,永别了! if (!OsProcessExitCodeSignalIsSet(processCB)) {//是否设置了退出码? OsProcessExitCodeSet(processCB, status);//将进程状态设为退出码 } LOS_ListTailInsert(&parentCB->exitChildList, &processCB->siblingList);//挂到父进程的孩子消亡链表,家人中,永别的可不止我一个. LOS_ListDelete(&processCB->subordinateGroupList);//和志同道合的朋友们永别了,注意家里可不一定是朋友的,所有各有链表. LOS_ListTailInsert(&processCB->group->exitProcessList, &processCB->subordinateGroupList);//挂到进程组消亡链表,朋友中,永别的可不止我一个. OsWaitCheckAndWakeParentProcess(parentCB, processCB);//检查父进程的等待任务并唤醒任务,此处将会切换到其他任务运行. OsDealAliveChildProcess(processCB);//老父亲临终向各自的祖宗托孤 processCB->processStatus |= OS_PROCESS_STATUS_ZOMBIES;//贴上僵死进程的标签 (VOID)OsKill(processCB->parentProcessID, SIGCHLD, OS_KERNEL_KILL_PERMISSION);//以内核权限发送SIGCHLD(子进程退出)信号. LOS_ListHeadInsert(&g_processRecyleList, &processCB->pendList);//将进程通过其阻塞节点挂入全局进程回收链表 OsRunTaskToDelete(runTask);//删除正在运行的任务 return; } LOS_Panic("pid : %u is the root process exit!\n", processCB->processID); return; } 解读 退群,向兄弟姐妹siblingList告别,向朋友圈(进程组)告别subordinateGroupList. 留下你的死亡记录,老父亲记录到exitChildList,朋友圈记录到exitProcessList中. 告诉后人死亡原因OsProcessExitCodeSet,因为waitList上挂的任务在等待你的死亡信息. 向老祖宗托孤,用户态和内核态进程都有自己的祖宗进程(1和2号进程),老祖宗身子硬朗,最后死.所有的短命鬼进程都可以把自己的孩子委托给老祖宗照顾,老祖宗会一视同仁. 将自己变成了OS_PROCESS_STATUS_ZOMBIES僵尸进程. 老父亲跑到村口广播这个孩子已经死亡的信号OsKill. 将自己挂入进程回收链表,等待回收任务ResourcesTask回收资源. 最后删除这个正在运行的任务,很明显其中一定会发生一次调度OsSchedResched. //删除一个正在运行的任务 LITE_OS_SEC_TEXT VOID OsRunTaskToDelete(LosTaskCB *taskCB) { LosProcessCB *processCB = OS_PCB_FROM_PID(taskCB->processID);//拿到task所属进程 OsTaskReleaseHoldLock(processCB, taskCB);//task还锁 OsTaskStatusUnusedSet(taskCB);//task重置为未使用状态,等待回收 LOS_ListDelete(&taskCB->threadList);//从进程的线程链表中将自己摘除 processCB->threadNumber--;//进程的活动task --,注意进程还有一个记录总task的变量 processCB->threadCount LOS_ListTailInsert(&g_taskRecyleList, &taskCB->pendList);//将task插入回收链表,等待回收资源再利用 OsEventWriteUnsafe(&g_resourceEvent, OS_RESOURCE_EVENT_FREE, FALSE, NULL);//发送释放资源的事件,事件由 OsResourceRecoveryTask 消费 OsSchedResched();//申请调度 return; } 但这是一个自然死亡的进程,还有很多非正常死亡在其他篇幅中已有说明.请自行翻看.非正常死亡的会产生僵尸进程.这种进程需要别的进程通过 waitpid来回收. 孤儿进程 一般情况下往往是白发人送黑发人,子进程的生命周期是要短于父进程.但因为fork之后,进程之间相互独立,调度算法一视同仁,父子之间是弱的关系力,就什么情况都可能发生了.内核是允许老父亲先走的,如果父进程退出而它的一个或多个子进程还在运行,那么这些子进程就被称为孤儿进程,孤儿进程最终将被两位老祖宗(用户态和内核态)所收养,并由老祖宗完成对它们的状态收集工作。 //当一个进程自然退出的时候,它的孩子进程由两位老祖宗收养 STATIC VOID OsDealAliveChildProcess(LosProcessCB *processCB) { UINT32 parentID; LosProcessCB *childCB = NULL; LosProcessCB *parentCB = NULL; LOS_DL_LIST *nextList = NULL; LOS_DL_LIST *childHead = NULL; if (!LOS_ListEmpty(&processCB->childrenList)) {//如果存在孩子进程 childHead = processCB->childrenList.pstNext;//获取孩子链表 LOS_ListDelete(&(processCB->childrenList));//清空自己的孩子链表 if (OsProcessIsUserMode(processCB)) {//是用户态进程 parentID = g_userInitProcess;//用户态进程老祖宗 } else { parentID = g_kernelInitProcess;//内核态进程老祖宗 } for (nextList = childHead; ;) {//遍历孩子链表 childCB = OS_PCB_FROM_SIBLIST(nextList);//找到孩子的真身 childCB->parentProcessID = parentID;//孩子磕头认老祖宗为爸爸 nextList = nextList->pstNext;//找下一个孩子进程 if (nextList == childHead) {//一圈下来,孩子们都磕完头了 break; } } parentCB = OS_PCB_FROM_PID(parentID);//找个老祖宗的真身 LOS_ListTailInsertList(&parentCB->childrenList, childHead);//挂到老祖宗的孩子链表上 } return; } 解读 函数很简单,都一一注释了,老父亲临终托付后事,请各自的老祖宗照顾孩子. 从这里也可以看出进程的家族管理模式,两个家族从进程的出生到死亡负责到底. 僵尸进程 一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。 如果一个进程已经终止,但是它的父进程尚未调用wait或waitpid对它进行清理,这时的进程状态称为僵尸(Zombie)进程,即 Z 进程.任何进程在刚终止时都是僵尸进程,正常情况下,僵尸进程都立刻被父进程清理了. 不正常情况下就需要手动waitpid清理了. waitpid 在鸿蒙系统中,一个进程结束了,但是它的父进程没有等待(调用wait waitpid)它,那么它将变成一个僵尸进程。通过系统调用 waitpid可以彻底的清理掉子进程.归还pcb.最终调用到SysWait #include <sys/wait.h> #include "syscall.h" pid_t waitpid(pid_t pid, int *status, int options) { return syscall_cp(SYS_wait4, pid, status, options, 0); } //等待子进程结束 int SysWait(int pid, USER int *status, int options, void *rusage) { (void)rusage; return LOS_Wait(pid, status, (unsigned int)options, NULL); } //返回已经终止的子进程的进程ID号,并清除僵死进程。 LITE_OS_SEC_TEXT INT32 LOS_Wait(INT32 pid, USER INT32 *status, UINT32 options, VOID *rusage) { (VOID)rusage; UINT32 ret; UINT32 intSave; LosProcessCB *childCB = NULL; LosProcessCB *processCB = NULL; LosTaskCB *runTask = NULL; ret = OsWaitOptionsCheck(options);//参数检查,只支持LOS_WAIT_WNOHANG if (ret != LOS_OK) { return -ret; } SCHEDULER_LOCK(intSave); processCB = OsCurrProcessGet(); //获取当前进程 runTask = OsCurrTaskGet(); //获取当前任务 ret = OsWaitChildProcessCheck(processCB, pid, &childCB);//先检查下看能不能找到参数要求的退出子进程 if (ret != LOS_OK) { pid = -ret; goto ERROR; } if (childCB != NULL) {//找到了进程 return OsWaitRecycleChildPorcess(childCB, intSave, status);//回收进程 } //没有找到,看是否要返回还是去做个登记 if ((options & LOS_WAIT_WNOHANG) != 0) {//有LOS_WAIT_WNOHANG标签 runTask->waitFlag = 0;//等待标识置0 pid = 0;//这里置0,是为了 return 0 goto ERROR; } //等待孩子进程退出 OsWaitInsertWaitListInOrder(runTask, processCB);//将当前任务挂入进程waitList链表 //发起调度的目的是为了让出CPU,让其他进程/任务运行 OsSchedResched();//发起调度 runTask->waitFlag = 0; if (runTask->waitID == OS_INVALID_VALUE) { pid = -LOS_ECHILD;//没有此子进程 goto ERROR; } childCB = OS_PCB_FROM_PID(runTask->waitID);//获取当前任务的等待子进程ID if (!(childCB->processStatus & OS_PROCESS_STATUS_ZOMBIES)) {//子进程非僵死进程 pid = -LOS_ESRCH;//没有此进程 goto ERROR; } //回收僵死进程 return OsWaitRecycleChildPorcess(childCB, intSave, status); ERROR: SCHEDULER_UNLOCK(intSave); return pid; } 解读 pid是数据参数,根据不同的参数代表不同的含义,含义如下: 参数值 说明 pid<-1 等待进程组号为pid绝对值的任何子进程。 pid=-1 等待任何子进程,此时的waitpid()函数就退化成了普通的wait()函数。 pid=0 等待进程组号与目前进程相同的任何子进程,也就是说任何和调用waitpid()函数的进程在同一个进程组的进程。 pid>0 等待进程号为pid的子进程。 pid不同值代表的真正含义可以看这个函数OsWaitSetFlag. //设置等待子进程退出方式方法 STATIC UINT32 OsWaitSetFlag(const LosProcessCB *processCB, INT32 pid, LosProcessCB **child) { LosProcessCB *childCB = NULL; ProcessGroup *group = NULL; LosTaskCB *runTask = OsCurrTaskGet(); UINT32 ret; if (pid > 0) {//等待进程号为pid的子进程结束 /* Wait for the child process whose process number is pid. */ childCB = OsFindExitChildProcess(processCB, pid);//看能否从退出的孩子链表中找到PID if (childCB != NULL) {//找到了,确实有一个已经退出的PID,注意一个进程退出时会挂到父进程的exitChildList上 goto WAIT_BACK;//直接成功返回 } ret = OsFindChildProcess(processCB, pid);//看能否从现有的孩子链表中找到PID if (ret != LOS_OK) { return LOS_ECHILD;//参数进程并没有这个PID孩子,返回孩子进程失败. } runTask->waitFlag = OS_PROCESS_WAIT_PRO;//设置当前任务的等待类型 runTask->waitID = pid; //当前任务要等待进程ID结束 } else if (pid == 0) {//等待同一进程组中的任何子进程 /* Wait for any child process in the same process group */ childCB = OsFindGroupExitProcess(processCB->group, OS_INVALID_VALUE);//看能否从退出的孩子链表中找到PID if (childCB != NULL) {//找到了,确实有一个已经退出的PID goto WAIT_BACK;//直接成功返回 } runTask->waitID = processCB->group->groupID;//等待进程组的任意一个子进程结束 runTask->waitFlag = OS_PROCESS_WAIT_GID;//设置当前任务的等待类型 } else if (pid == -1) {//等待任意子进程 /* Wait for any child process */ childCB = OsFindExitChildProcess(processCB, OS_INVALID_VALUE);//看能否从退出的孩子链表中找到PID if (childCB != NULL) {//找到了,确实有一个已经退出的PID goto WAIT_BACK; } runTask->waitID = pid;//等待PID,这个PID可以和当前进程没有任何关系 runTask->waitFlag = OS_PROCESS_WAIT_ANY;//设置当前任务的等待类型 } else { /* pid < -1 */ //等待指定进程组内为|pid|的所有子进程 /* Wait for any child process whose group number is the pid absolute value. */ group = OsFindProcessGroup(-pid);//先通过PID找到进程组 if (group == NULL) { return LOS_ECHILD; } childCB = OsFindGroupExitProcess(group, OS_INVALID_VALUE);//在进程组里任意一个已经退出的子进程 if (childCB != NULL) { goto WAIT_BACK; } runTask->waitID = -pid;//此处用负数是为了和(pid == 0)以示区别,因为二者的waitFlag都一样. runTask->waitFlag = OS_PROCESS_WAIT_GID;//设置当前任务的等待类型 } WAIT_BACK: *child = childCB; return LOS_OK; } status带走进程退出码,exitCode分成了三个部分格式如下 /* * Process exit code * 31 15 8 7 0 * | | exit code | core dump | signal | */ #define OS_PRO_EXIT_OK 0 //进程正常退出 //置进程退出码第七位为1 STATIC INLINE VOID OsProcessExitCodeCoreDumpSet(LosProcessCB *processCB) { processCB->exitCode |= 0x80U;// 0b10000000 } //设置进程退出信号(0 ~ 7) STATIC INLINE VOID OsProcessExitCodeSignalSet(LosProcessCB *processCB, UINT32 signal) { processCB->exitCode |= signal & 0x7FU;//0b01111111 } //清除进程退出信号(0 ~ 7) STATIC INLINE VOID OsProcessExitCodeSignalClear(LosProcessCB *processCB) { processCB->exitCode &= (~0x7FU);//低7位全部清0 } //进程退出码是否被设置过,默认是 0 ,如果 & 0x7FU 还是 0 ,说明没有被设置过. STATIC INLINE BOOL OsProcessExitCodeSignalIsSet(LosProcessCB *processCB) { return (processCB->exitCode) & 0x7FU; } //设置进程退出号(8 ~ 15) STATIC INLINE VOID OsProcessExitCodeSet(LosProcessCB *processCB, UINT32 code) { processCB->exitCode |= ((code & 0x000000FFU) << 8U) & 0x0000FF00U; /* 8: Move 8 bits to the left, exitCode */ } 0 - 7为信号位,信号处理有专门的篇幅,此处不做详细介绍,请自行翻看,这里仅列出部分信号含义. #define SIGHUP 1 //终端挂起或者控制进程终止 #define SIGINT 2 //键盘中断(如break键被按下) #define SIGQUIT 3 //键盘的退出键被按下 #define SIGILL 4 //非法指令 #define SIGTRAP 5 //跟踪陷阱(trace trap),启动进程,跟踪代码的执行 #define SIGABRT 6 //由abort(3)发出的退出指令 #define SIGIOT SIGABRT //abort发出的信号 #define SIGBUS 7 //总线错误 #define SIGFPE 8 //浮点异常 #define SIGKILL 9 //常用的命令 kill 9 123 | 不能被忽略、处理和阻塞 #define SIGUSR1 10 //用户自定义信号1 #define SIGSEGV 11 //无效的内存引用, 段违例(segmentation violation),进程试图去访问其虚地址空间以外的位置 #define SIGUSR2 12 //用户自定义信号2 #define SIGPIPE 13 //向某个非读管道中写入数据 #define SIGALRM 14 //由alarm(2)发出的信号,默认行为为进程终止 #define SIGTERM 15 //终止信号 #define SIGSTKFLT 16 //栈溢出 #define SIGCHLD 17 //子进程结束信号 #define SIGCONT 18 //进程继续(曾被停止的进程) #define SIGSTOP 19 //终止进程 | 不能被忽略、处理和阻塞 #define SIGTSTP 20 //控制终端(tty)上 按下停止键 #define SIGTTIN 21 //进程停止,后台进程企图从控制终端读 #define SIGTTOU 22 //进程停止,后台进程企图从控制终端写 #define SIGURG 23 //I/O有紧急数据到达当前进程 #define SIGXCPU 24 //进程的CPU时间片到期 #define SIGXFSZ 25 //文件大小的超出上限 #define SIGVTALRM 26 //虚拟时钟超时 #define SIGPROF 27 //profile时钟超时 #define SIGWINCH 28 //窗口大小改变 #define SIGIO 29 //I/O相关 #define SIGPOLL 29 // #define SIGPWR 30 //电源故障,关机 #define SIGSYS 31 //系统调用中参数错,如系统调用号非法 #define SIGUNUSED SIGSYS //系统调用异常 options是行为参数,提供了一些另外的选项来控制waitpid()函数的行为。 参数值 鸿蒙支持 说明 LOS_WAIT_WNOHANG 支持 如果没有孩子进程退出,则立即返回,而不是阻塞在这个函数上等待;如果结束了,则返回该子进程的进程号。 LOS_WAIT_WUNTRACED 不支持 报告终止或停止的子进程的状态 LOS_WAIT_WCONTINUED 不支持 鸿蒙目前只支持了LOS_WAIT_WNOHANG模式,内核源码中虽有LOS_WAIT_WUNTRACED和LOS_WAIT_WCONTINUED的实现痕迹,但是整体阅读下来比较乱,应该是没有写好. 鸿蒙源码百篇博客 往期回顾 v47.xx (进程回收篇) | 进程在临终前如何向老祖宗托孤 < csdn | 51cto | harmony > v46.xx (特殊进程篇) | 龙生龙,凤生凤,老鼠生儿会打洞 < csdn | 51cto | harmony > v45.xx (fork篇) | fork是如何做到调用一次,返回两次的 ? < csdn | 51cto | harmony > v44.xx (中断管理篇) | 硬中断的实现<>观察者模式 < csdn | 51cto | harmony > v43.xx (中断概念篇) | 外人眼中权势滔天的当红海公公 < csdn | 51cto | harmony > v42.xx (中断切换篇) | 中断切换到底在切换什么? < csdn | 51cto | harmony > v41.xx (任务切换篇) | 汇编逐行注解分析任务上下文 < csdn | 51cto | harmony > v40.xx (汇编汇总篇) | 所有的汇编代码都在这里 < csdn | 51cto | harmony > v39.xx (异常接管篇) | 社会很单纯,复杂的是人 < csdn | 51cto | harmony > v38.xx (寄存器篇) | ARM所有寄存器一网打尽,不再神秘 < csdn | 51cto | harmony > v37.xx (系统调用篇) | 全盘解剖系统调用实现过程 < csdn | 51cto | harmony > v36.xx (工作模式篇) | CPU是韦小宝,有哪七个老婆? < csdn | 51cto | harmony > v35.xx (时间管理篇) | Tick是操作系统的基本时间单位 < csdn | 51cto | harmony > v34.xx (原子操作篇) | 是谁在为原子操作保驾护航? < csdn | 51cto | harmony > v33.xx (消息队列篇) | 进程间如何异步解耦传递大数据 ? < csdn | 51cto | harmony > v32.xx (CPU篇) | 内核是如何描述CPU的? < csdn | 51cto | harmony > v31.xx (定时器篇) | 内核最高优先级任务是谁? < csdn | 51cto | harmony > v30.xx (事件控制篇) | 任务间多对多的同步方案 < csdn | 51cto | harmony > v29.xx (信号量篇) | 信号量解决任务同步问题 < csdn | 51cto | harmony > v28.xx (进程通讯篇) | 进程间通讯有哪九大方式? < csdn | 51cto | harmony > v27.xx (互斥锁篇) | 互斥锁比自旋锁可丰满许多 < csdn | 51cto | harmony > v26.xx (自旋锁篇) | 真的好想为自旋锁立贞节牌坊! < csdn | 51cto | harmony > v25.xx (并发并行篇) | 怎么记住并发并行的区别? < csdn | 51cto | harmony > v24.xx (进程概念篇) | 进程在管理哪些资源? < csdn | 51cto | harmony > v23.xx (汇编传参篇) | 汇编如何传递复杂的参数? < csdn | 51cto | harmony > v22.xx (汇编基础篇) | CPU在哪里打卡上班? < csdn | 51cto | harmony > v21.xx (线程概念篇) | 是谁在不断的折腾CPU? < csdn | 51cto | harmony > v20.xx (用栈方式篇) | 栈是构建底层运行的基础 < csdn | 51cto | harmony > v19.xx (位图管理篇) | 为何进程和线程优先级都是32个? < csdn | 51cto | harmony > v18.xx (源码结构篇) | 内核500问你能答对多少? < csdn | 51cto | harmony > v17.xx (物理内存篇) | 这样记伙伴算法永远不会忘 < csdn | 51cto | harmony > v16.xx (内存规则篇) | 内存管理到底在管什么? < csdn | 51cto | harmony > v15.xx (内存映射篇) | 什么是内存最重要的实现基础 ? < csdn | 51cto | harmony > v14.xx (内存汇编篇) | 什么是虚拟内存的实现基础? < csdn | 51cto | harmony > v13.xx (源码注释篇) | 热爱是所有的理由和答案 < csdn | 51cto | harmony > v12.xx (内存管理篇) | 虚拟内存全景图是怎样的? < csdn | 51cto | harmony > v11.xx (内存分配篇) | 内存有哪些分配方式? < csdn | 51cto | harmony > v10.xx (内存主奴篇) | 紫禁城的主子和奴才如何相处? < csdn | 51cto | harmony > v09.xx (调度故事篇) | 用故事说内核调度 < csdn | 51cto | harmony > v08.xx (总目录) | 百万汉字注解 百篇博客分析 < csdn | 51cto | harmony > v07.xx (调度机制篇) | 任务是如何被调度执行的? < csdn | 51cto | harmony > v06.xx (调度队列篇) | 就绪队列对调度的作用 < csdn | 51cto | harmony > v05.xx (任务管理篇) | 谁在让CPU忙忙碌碌? < csdn | 51cto | harmony > v04.xx (任务调度篇) | 任务是内核调度的单元 < csdn | 51cto | harmony > v03.xx (时钟任务篇) | 触发调度最大的动力来自哪里? < csdn | 51cto | harmony > v02.xx (进程管理篇) | 进程是内核资源管理单元 < csdn | 51cto | harmony > v01.xx (双向链表篇) | 谁是内核最重要结构体? < csdn | 51cto | harmony > 参与贡献 Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request 新建 Issue 喜欢请「点赞+关注+收藏」 关注「鸿蒙内核源码分析」公众号 各大站点搜 「鸿蒙内核源码分析」.欢迎转载,请注明出处. 进入 >> oschina | csdn | 51cto | 简书 | 掘金 | harmony

优秀的个人博客,低调大师

鸿蒙内核源码分析(特殊进程篇) | 龙生龙,凤生凤,老鼠生儿会打洞 | 百篇博客分析HarmonyOS源码 | v46.02

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< oschina | csdn | harmony > 三个进程 鸿蒙有三个特殊的进程,创建顺序如下: 2号进程,KProcess,为内核态根进程.启动过程中创建. 0号进程,KIdle为内核态第二个进程,它是通过KProcess fork 而来的.这有点难理解. 1号进程,init,为用户态根进程.由任务SystemInit创建. 发现没有在图中看不到0号进程,在看完本篇之后请想想为什么? 家族式管理 进程(process)是家族式管理,总体分为两大家族,用户态家族和内核态家族. 用户态的进程是平民阶层,干着各行各业的活,权利有限,人数众多,活动范围有限.中南海肯定不能随便进出.这个阶层有个共同的老祖宗g_userInitProcess (1号进程). g_userInitProcess = 1; /* 1: The root process ID of the user-mode process is fixed at 1 *///用户态的根进程 //获取用户态进程的根进程,所有用户进程都是g_processCBArray[g_userInitProcess] fork来的 LITE_OS_SEC_TEXT UINT32 OsGetUserInitProcessID(VOID) { return g_userInitProcess; } 内核态的进程是贵族阶层,管理平民阶层的,维持平民生活秩序的,拥有超级权限,人数不多.这个阶层老祖宗是 g_kernelInitProcess(2号进程). g_kernelInitProcess = 2; /* 2: The root process ID of the kernel-mode process is fixed at 2 *///内核态的根进程 //获取内核态进程的根进程,所有内核进程都是g_processCBArray[g_kernelInitProcess] fork来的,包括g_processCBArray[g_kernelIdleProcess]进程 LITE_OS_SEC_TEXT UINT32 OsGetKernelInitProcessID(VOID) { return g_kernelInitProcess; } 这两个阶层可以相互流动吗,有没有可以通过高考改变命运的机会? 答案是: 绝对不可能!!! 龙生龙,凤生凤,老鼠生儿会打洞.从老祖宗创建的那一刻起就被刻在基因里了,抹不掉了. 因为所有的进程都是由这两位老同志克隆(clone)来的,继承了这份基因.LosProcessCB有专门的标签来processMode区分这两个阶层.整个鸿蒙内核源码并没有提供改变命运机会的set函数. #define OS_KERNEL_MODE 0x0U //内核态 #define OS_USER_MODE 0x1U //用户态 STATIC INLINE BOOL OsProcessIsUserMode(const LosProcessCB *processCB)//用户模式进程 { return (processCB->processMode == OS_USER_MODE); } typedef struct ProcessCB { // ... UINT16 processMode; /**< Kernel Mode:0; User Mode:1; */ //模式指定为内核还是用户进程 } LosProcessCB; 2号进程 KProcess 2号进程为内核态的老祖宗,也是内核创建的首个进程,源码过程如下,省略了不相干的代码. bl main @带LR的子程序跳转, LR = pc - 4 ,执行C层main函数 /****************************************************************************** 内核入口函数,由汇编调用,见于reset_vector_up.S 和 reset_vector_mp.S up指单核CPU, mp指多核CPU bl main ******************************************************************************/ LITE_OS_SEC_TEXT_INIT INT32 main(VOID)//由主CPU执行,默认0号CPU 为主CPU { // ... 省略 uwRet = OsMain();// 内核各模块初始化 } LITE_OS_SEC_TEXT_INIT INT32 OsMain(VOID) { // ... ret = OsKernelInitProcess();// 创建内核态根进程 // ... ret = OsSystemInit(); //中间创建了用户态根进程 } //初始化 2号进程,即内核态进程的老祖宗 LITE_OS_SEC_TEXT_INIT UINT32 OsKernelInitProcess(VOID) { LosProcessCB *processCB = NULL; UINT32 ret; ret = OsProcessInit();// 初始化进程模块全部变量,创建各循环双向链表 if (ret != LOS_OK) { return ret; } processCB = OS_PCB_FROM_PID(g_kernelInitProcess);// 以PID方式得到一个进程 ret = OsProcessCreateInit(processCB, OS_KERNEL_MODE, "KProcess", 0);// 初始化进程,最高优先级0,鸿蒙进程一共有32个优先级(0-31) 其中0-9级为内核进程,用户进程可配置的优先级有22个(10-31) if (ret != LOS_OK) { return ret; } processCB->processStatus &= ~OS_PROCESS_STATUS_INIT;// 进程初始化位 置1 g_processGroup = processCB->group;//全局进程组指向了KProcess所在的进程组 LOS_ListInit(&g_processGroup->groupList);// 进程组链表初始化 OsCurrProcessSet(processCB);// 设置为当前进程 return OsCreateIdleProcess();// 创建一个空闲状态的进程 } 解读 main函数在系列篇中会单独讲,请留意自行翻看,它是在开机之初在SVC模式下创建的. 内核态老祖宗的名字叫 KProcess,优先级为最高 0 级."KProcess"进程是长期活跃的,很多重要的任务都会跑在其之下.例如: Swt_Task oom_task system_wq tcpip_thread SendToSer SendToTelnet eth_irq_task TouchEventHandler USB_GIANT_Task 此处不细讲这些任务,在其他篇幅有介绍,但光看名字也能猜个八九,请自行翻看. 紧接着KProcess 以CLONE_FILES的方式 fork了一个 名为KIdle的子进程(0号进程). 内核态的所有进程都来自2号进程这位老同志,子子孙孙,代代相传,形成一颗家族树,和人类的传承所不同的是,它们往往是白发人送黑发人,子孙进程往往都是短命鬼,老祖宗最能活,子孙都死绝了它还在,有些收尸的工作要交给它干. 0 号进程 KIdle 0号进程是内核创建的第二个进程,在OsKernelInitProcess的末尾将2号进程设为当前进程后,紧接着就fork了0号进程.为什么一定要先设置当前进程,因为fork需要一个父进程,而此时系统处于启动阶段,并没有当前进程. 是的,您没有看错.进程是操作系统为方便管理资源而衍生出来的概念,系统并不是非要进程,任务才能运行的. 开机阶段就是啥都没有,默认跑在svc模式下,默认起始地址reset_vector都是由硬件上电后规定的. 进程,线程都是跑起来后慢慢赋予的意义,OsCurrProcessSet是从软件层面赋予了此为当前进程的这个概念.KProcess是内核设置的第一个当前进程. //创建一个名叫"KIdle"的0号进程,给CPU空闲的时候使用 STATIC UINT32 OsCreateIdleProcess(VOID) { UINT32 ret; CHAR *idleName = "Idle"; LosProcessCB *idleProcess = NULL; Percpu *perCpu = OsPercpuGet(); UINT32 *idleTaskID = &perCpu->idleTaskID;//得到CPU的idle task ret = OsCreateResourceFreeTask();// 创建一个资源回收任务,优先级为5 用于回收进程退出时的各种资源 if (ret != LOS_OK) { return ret; } //创建一个名叫"KIdle"的进程,并创建一个idle task,CPU空闲的时候就待在 idle task中等待被唤醒 ret = LOS_Fork(CLONE_FILES, "KIdle", (TSK_ENTRY_FUNC)OsIdleTask, LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE); if (ret < 0) {//内核进程的fork并不会一次调用,返回两次,此子进程执行的开始位置是参数OsIdleTask return LOS_NOK; } g_kernelIdleProcess = (UINT32)ret;//返回 0号进程 idleProcess = OS_PCB_FROM_PID(g_kernelIdleProcess);//通过ID拿到进程实体 *idleTaskID = idleProcess->threadGroupID;//绑定CPU的IdleTask,或者说改变CPU现有的idle任务 OS_TCB_FROM_TID(*idleTaskID)->taskStatus |= OS_TASK_FLAG_SYSTEM_TASK;//设定Idle task 为一个系统任务 #if (LOSCFG_KERNEL_SMP == YES) OS_TCB_FROM_TID(*idleTaskID)->cpuAffiMask = CPUID_TO_AFFI_MASK(ArchCurrCpuid());//多核CPU的任务指定,防止乱串了,注意多核才会有并行处理 #endif (VOID)memset_s(OS_TCB_FROM_TID(*idleTaskID)->taskName, OS_TCB_NAME_LEN, 0, OS_TCB_NAME_LEN);//task 名字先清0 (VOID)memcpy_s(OS_TCB_FROM_TID(*idleTaskID)->taskName, OS_TCB_NAME_LEN, idleName, strlen(idleName));//task 名字叫 idle return LOS_OK; } 解读 看过fork篇的可能发现了一个参数, KIdle被创建的方式和通过系统调用创建的方式不一样,一个用的是CLONE_FILES,一个是 CLONE_SIGHAND 具体的创建方式如下: #define CLONE_VM 0x00000100 //子进程与父进程运行于相同的内存空间 #define CLONE_FS 0x00000200 //子进程与父进程共享相同的文件系统,包括root、当前目录、umask #define CLONE_FILES 0x00000400 //子进程与父进程共享相同的文件描述符(file descriptor)表 #define CLONE_SIGHAND 0x00000800 //子进程与父进程共享相同的信号处理(signal handler)表 #define CLONE_PTRACE 0x00002000 //若父进程被trace,子进程也被trace #define CLONE_VFORK 0x00004000 //父进程被挂起,直至子进程释放虚拟内存资源 #define CLONE_PARENT 0x00008000 //创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子” #define CLONE_THREAD 0x00010000 //Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群 KIdle创建了一个名为Idle的任务,任务的入口函数为OsIdleTask,这是个空闲任务,啥也不干的.专门用来给cpu休息的,cpu空闲时就待在这个任务里等活干. LITE_OS_SEC_TEXT WEAK VOID OsIdleTask(VOID) { while (1) {//只有一个死循环 #ifdef LOSCFG_KERNEL_TICKLESS //低功耗模式开关, idle task 中关闭tick if (OsTickIrqFlagGet()) { OsTickIrqFlagSet(0); OsTicklessStart(); } #endif Wfi();//WFI指令:arm core 立即进入low-power standby state,进入休眠模式,等待中断. } } fork 内核态进程和fork 用户态进程有个地方会不一样,就是SP寄存器的值.fork用户态的进程 一次调用两次返回(父子进程各一次),返回的位置一样(是因为拷贝了父进程陷入内核时的上下文).所以只能通过返回值来判断是父还是子返回.这个在fork篇中有详细的描述.请自行翻看. 但fork内核态进程虽也有两次返回,但是返回的位置却不一样,子进程的返回位置是由内核指定的OsIdleTask,即Idle任务的入口函数.详见代码: //任务初始化时拷贝任务信息 STATIC VOID OsInitCopyTaskParam(LosProcessCB *childProcessCB, const CHAR *name, UINTPTR entry, UINT32 size, TSK_INIT_PARAM_S *childPara) { LosTaskCB *mainThread = NULL; UINT32 intSave; SCHEDULER_LOCK(intSave); mainThread = OsCurrTaskGet();//获取当前task,注意变量名从这里也可以看出 thread 和 task 是一个概念,只是内核常说task,上层应用说thread ,概念的映射. if (OsProcessIsUserMode(childProcessCB)) {//用户态进程 childPara->pfnTaskEntry = mainThread->taskEntry;//拷贝当前任务入口地址 childPara->uwStackSize = mainThread->stackSize; //栈空间大小 childPara->userParam.userArea = mainThread->userArea; //用户态栈区栈顶位置 childPara->userParam.userMapBase = mainThread->userMapBase; //用户态栈底 childPara->userParam.userMapSize = mainThread->userMapSize; //用户态栈大小 } else {//注意内核态进程创建任务的入口由外界指定,例如 OsCreateIdleProcess 指定了OsIdleTask childPara->pfnTaskEntry = (TSK_ENTRY_FUNC)entry;//参数(sp)为内核态入口地址 childPara->uwStackSize = size;//参数(size)为内核态栈大小 } childPara->pcName = (CHAR *)name; //拷贝进程名字 childPara->policy = mainThread->policy; //拷贝调度模式 childPara->usTaskPrio = mainThread->priority; //拷贝优先级 childPara->processID = childProcessCB->processID; //拷贝进程ID if (mainThread->taskStatus & OS_TASK_FLAG_PTHREAD_JOIN) { childPara->uwResved = OS_TASK_FLAG_PTHREAD_JOIN; } else if (mainThread->taskStatus & OS_TASK_FLAG_DETACHED) { childPara->uwResved = OS_TASK_FLAG_DETACHED; } SCHEDULER_UNLOCK(intSave); } 结论是创建0号进程中的OsCreateIdleProcess调用LOS_Fork后只会有一次返回.而且返回值为0,因为 g_freeProcess中0号进程还没有被分配.详见代码,注意看最后的注释: //进程模块初始化,被编译放在代码段 .init 中 LITE_OS_SEC_TEXT_INIT UINT32 OsProcessInit(VOID) { UINT32 index; UINT32 size; g_processMaxNum = LOSCFG_BASE_CORE_PROCESS_LIMIT;//默认支持64个进程 size = g_processMaxNum * sizeof(LosProcessCB);//算出总大小 g_processCBArray = (LosProcessCB *)LOS_MemAlloc(m_aucSysMem1, size);// 进程池,占用内核堆,内存池分配 if (g_processCBArray == NULL) { return LOS_NOK; } (VOID)memset_s(g_processCBArray, size, 0, size);//安全方式重置清0 LOS_ListInit(&g_freeProcess);//进程空闲链表初始化,创建一个进程时从g_freeProcess中申请一个进程描述符使用 LOS_ListInit(&g_processRecyleList);//进程回收链表初始化,回收完成后进入g_freeProcess等待再次被申请使用 for (index = 0; index < g_processMaxNum; index++) {//进程池循环创建 g_processCBArray[index].processID = index;//进程ID[0-g_processMaxNum-1]赋值 g_processCBArray[index].processStatus = OS_PROCESS_FLAG_UNUSED;// 默认都是白纸一张,贴上未使用标签 LOS_ListTailInsert(&g_freeProcess, &g_processCBArray[index].pendList);//注意g_freeProcess挂的是pendList节点,所以使用要通过OS_PCB_FROM_PENDLIST找到进程实体. } g_userInitProcess = 1; /* 1: The root process ID of the user-mode process is fixed at 1 *///用户态的根进程 LOS_ListDelete(&g_processCBArray[g_userInitProcess].pendList);// 将1号进程从空闲链表上摘出去 g_kernelInitProcess = 2; /* 2: The root process ID of the kernel-mode process is fixed at 2 *///内核态的根进程 LOS_ListDelete(&g_processCBArray[g_kernelInitProcess].pendList);// 将2号进程从空闲链表上摘出去 //注意:这波骚操作之后,g_freeProcess链表上还有,0,3,4,...g_processMaxNum-1号进程.创建进程是从g_freeProcess上申请 //即下次申请到的将是0号进程,而 OsCreateIdleProcess 将占有0号进程. return LOS_OK; } 1号进程 init 1号进程为用户态的老祖宗.创建过程如下, 省略了不相干的代码. LITE_OS_SEC_TEXT_INIT INT32 OsMain(VOID) { // ... ret = OsKernelInitProcess();// 创建内核态根进程 // ... ret = OsSystemInit(); //中间创建了用户态根进程 } UINT32 OsSystemInit(VOID) { //.. ret = OsSystemInitTaskCreate();//创建了一个系统任务, } STATIC UINT32 OsSystemInitTaskCreate(VOID) { UINT32 taskID; TSK_INIT_PARAM_S sysTask; (VOID)memset_s(&sysTask, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S)); sysTask.pfnTaskEntry = (TSK_ENTRY_FUNC)SystemInit;//任务的入口函数,这个函数实现由外部提供 sysTask.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;//16K sysTask.pcName = "SystemInit";//任务的名称 sysTask.usTaskPrio = LOSCFG_BASE_CORE_TSK_DEFAULT_PRIO;// 内核默认优先级为10 sysTask.uwResved = LOS_TASK_STATUS_DETACHED;//任务分离模式 #if (LOSCFG_KERNEL_SMP == YES) sysTask.usCpuAffiMask = CPUID_TO_AFFI_MASK(ArchCurrCpuid());//cpu 亲和性设置,记录执行过任务的CPU,尽量确保由同一个CPU完成任务周期 #endif return LOS_TaskCreate(&taskID, &sysTask);//创建任务并加入就绪队列,并立即参与调度 } //SystemInit的实现由由外部提供 比如..\vendor\hi3516dv300\module_init\src\system_init.c void SystemInit(void) { // ... if (OsUserInitProcess()) {//创建用户态进程的老祖宗 PRINT_ERR("Create user init process faialed!\n"); return; } } //用户态根进程的创建过程 LITE_OS_SEC_TEXT_INIT UINT32 OsUserInitProcess(VOID) { INT32 ret; UINT32 size; TSK_INIT_PARAM_S param = { 0 }; VOID *stack = NULL; VOID *userText = NULL; CHAR *userInitTextStart = (CHAR *)&__user_init_entry;//代码区开始位置 ,对应 LITE_USER_SEC_ENTRY CHAR *userInitBssStart = (CHAR *)&__user_init_bss;// 未初始化数据区(BSS)。在运行时改变其值 对应 LITE_USER_SEC_BSS CHAR *userInitEnd = (CHAR *)&__user_init_end;// 结束地址 UINT32 initBssSize = userInitEnd - userInitBssStart; UINT32 initSize = userInitEnd - userInitTextStart; LosProcessCB *processCB = OS_PCB_FROM_PID(g_userInitProcess);//"Init进程的优先级是 28" ret = OsProcessCreateInit(processCB, OS_USER_MODE, "Init", OS_PROCESS_USERINIT_PRIORITY);// 初始化用户进程,它将是所有应用程序的父进程 if (ret != LOS_OK) { return ret; } userText = LOS_PhysPagesAllocContiguous(initSize >> PAGE_SHIFT);// 分配连续的物理页 if (userText == NULL) { ret = LOS_NOK; goto ERROR; } (VOID)memcpy_s(userText, initSize, (VOID *)&__user_init_load_addr, initSize);// 安全copy 经加载器load的结果 __user_init_load_addr -> userText ret = LOS_VaddrToPaddrMmap(processCB->vmSpace, (VADDR_T)(UINTPTR)userInitTextStart, LOS_PaddrQuery(userText), initSize, VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE | VM_MAP_REGION_FLAG_PERM_EXECUTE | VM_MAP_REGION_FLAG_PERM_USER);// 虚拟地址与物理地址的映射 if (ret < 0) { goto ERROR; } (VOID)memset_s((VOID *)((UINTPTR)userText + userInitBssStart - userInitTextStart), initBssSize, 0, initBssSize);// 除了代码段,其余都清0 stack = OsUserInitStackAlloc(g_userInitProcess, &size);//分配任务在用户态下的运行栈,大小为1M if (stack == NULL) { PRINTK("user init process malloc user stack failed!\n"); ret = LOS_NOK; goto ERROR; } param.pfnTaskEntry = (TSK_ENTRY_FUNC)userInitTextStart;// 从代码区开始执行,也就是应用程序main 函数的位置 param.userParam.userSP = (UINTPTR)stack + size;// 用户态栈底 param.userParam.userMapBase = (UINTPTR)stack;// 用户态栈顶 param.userParam.userMapSize = size;// 用户态栈大小 param.uwResved = OS_TASK_FLAG_PTHREAD_JOIN;// 可结合的(joinable)能够被其他线程收回其资源和杀死 ret = OsUserInitProcessStart(g_userInitProcess, &param);// 创建一个任务,来运行main函数 if (ret != LOS_OK) { (VOID)OsUnMMap(processCB->vmSpace, param.userParam.userMapBase, param.userParam.userMapSize); goto ERROR; } return LOS_OK; ERROR: (VOID)LOS_PhysPagesFreeContiguous(userText, initSize >> PAGE_SHIFT);//释放物理内存块 OsDeInitPCB(processCB);//删除PCB块 return ret; } 解读 从代码中可以看出用户态的老祖宗创建过程有点意思,首先它的源头和内核态老祖宗一样都在OsMain. 通过创建一个分离模式,优先级为10的系统任务 SystemInit,来完成.任务的入口函数 SystemInit()的实现由平台集成商来指定. 本篇采用了hi3516dv300的实现.也就是说用户态祖宗的创建是在 sysTask.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;//16K 栈中完成的.这个任务归属于内核进程KProcess. 用户态老祖宗的名字叫 Init,优先级为28级. 用户态的每个进程有独立的虚拟进程空间vmSpace,拥有独立的内存映射表(L1,L2表),申请的内存需要重新映射,映射过程在内存系列篇中有详细的说明. init创建了一个任务,任务的入口地址为 __user_init_entry,由编译器指定. 用户态进程是指应有程序运行的进程,通过动态加载ELF文件的方式启动.具体加载流程系列篇有讲解,不细说.用户态进程运行在用户空间,但通过系统调用可陷入内核空间.具体看这张图: 鸿蒙源码百篇博客 往期回顾 v46.xx (特殊进程篇) | 龙生龙,凤生凤,老鼠生儿会打洞 < csdn | harmony > v45.xx (fork篇) | fork是如何做到调用一次,返回两次的 ? < csdn | harmony > v44.xx (中断管理篇) | 硬中断的实现<>观察者模式 < csdn | harmony > v43.xx (中断概念篇) | 外人眼中权势滔天的当红海公公 < csdn | harmony > v42.xx (中断切换篇) | 中断切换到底在切换什么? < csdn | harmony > v41.xx (任务切换篇) | 汇编逐行注解分析任务上下文 < csdn | harmony > v40.xx (汇编汇总篇) | 所有的汇编代码都在这里 < csdn | harmony > v39.xx (异常接管篇) | 社会很单纯,复杂的是人 < csdn | harmony > v38.xx (寄存器篇) | ARM所有寄存器一网打尽,不再神秘 < csdn | harmony > v37.xx (系统调用篇) | 全盘解剖系统调用实现过程 < csdn | harmony > v36.xx (工作模式篇) | CPU是韦小宝,有哪七个老婆? < csdn | harmony > v35.xx (时间管理篇) | Tick是操作系统的基本时间单位 < csdn | harmony > v34.xx (原子操作篇) | 是谁在为原子操作保驾护航? < csdn | harmony > v33.xx (消息队列篇) | 进程间如何异步解耦传递大数据 ? < csdn | harmony > v32.xx (CPU篇) | 内核是如何描述CPU的? < csdn | harmony > v31.xx (定时器篇) | 内核最高优先级任务是谁? < csdn | harmony > v30.xx (事件控制篇) | 任务间多对多的同步方案 < csdn | harmony > v29.xx (信号量篇) | 信号量解决任务同步问题 < csdn | harmony > v28.xx (进程通讯篇) | 进程间通讯有哪九大方式? < csdn | harmony > v27.xx (互斥锁篇) | 互斥锁比自旋锁可丰满许多 < csdn | harmony > v26.xx (自旋锁篇) | 真的好想为自旋锁立贞节牌坊! < csdn | harmony > v25.xx (并发并行篇) | 怎么记住并发并行的区别? < csdn | harmony > v24.xx (进程概念篇) | 进程在管理哪些资源? < csdn | harmony > v23.xx (汇编传参篇) | 汇编如何传递复杂的参数? < csdn | harmony > v22.xx (汇编基础篇) | CPU在哪里打卡上班? < csdn | harmony > v21.xx (线程概念篇) | 是谁在不断的折腾CPU? < csdn | harmony > v20.xx (用栈方式篇) | 栈是构建底层运行的基础 < csdn | harmony > v19.xx (位图管理篇) | 为何进程和线程优先级都是32个? < csdn | harmony > v18.xx (源码结构篇) | 内核500问你能答对多少? < csdn | harmony > v17.xx (物理内存篇) | 这样记伙伴算法永远不会忘 < csdn | harmony > v16.xx (内存规则篇) | 内存管理到底在管什么? < csdn | harmony > v15.xx (内存映射篇) | 什么是内存最重要的实现基础 ? < csdn | harmony > v14.xx (内存汇编篇) | 什么是虚拟内存的实现基础? < csdn | harmony > v13.xx (源码注释篇) | 热爱是所有的理由和答案 < csdn | harmony > v12.xx (内存管理篇) | 虚拟内存全景图是怎样的? < csdn | harmony > v11.xx (内存分配篇) | 内存有哪些分配方式? < csdn | harmony > v10.xx (内存主奴篇) | 紫禁城的主子和奴才如何相处? < csdn | harmony > v09.xx (调度故事篇) | 用故事说内核调度 < csdn | harmony > v08.xx (总目录) | 百万汉字注解 百篇博客分析 < csdn | harmony > v07.xx (调度机制篇) | 任务是如何被调度执行的? < csdn | harmony > v06.xx (调度队列篇) | 就绪队列对调度的作用 < csdn | harmony > v05.xx (任务管理篇) | 谁在让CPU忙忙碌碌? < csdn | harmony > v04.xx (任务调度篇) | 任务是内核调度的单元 < csdn | harmony > v03.xx (时钟任务篇) | 触发调度最大的动力来自哪里? < csdn | harmony > v02.xx (进程管理篇) | 进程是内核资源管理单元 < csdn | harmony > v01.xx (双向链表篇) | 谁是内核最重要结构体? < csdn | harmony > 参与贡献 访问注解仓库地址 Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request 新建 Issue 喜欢请「点赞+关注+收藏」 关注「鸿蒙内核源码分析」公众号,百万汉字注解 + 百篇博客分析 => 深挖鸿蒙内核源码 各大站点搜 「鸿蒙内核源码分析」 .欢迎转载,请注明出处. oschina | csdn | harmony | 简书 | 掘金

优秀的个人博客,低调大师

鸿蒙内核源码分析(特殊进程篇) | 龙生龙,凤生凤,老鼠生儿会打洞 | 百篇博客分析HarmonyOS源码 | v46.01

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< oschina | csdn | harmony > 三个进程 鸿蒙有三个特殊的进程,创建顺序如下: 2号进程,KProcess,为内核态根进程.启动过程中创建. 0号进程,KIdle为内核态第二个进程,它是通过KProcess fork 而来的.这有点难理解. 1号进程,init,为用户态根进程.由任务SystemInit创建. 发现没有在图中看不到0号进程,在看完本篇之后请想想为什么? 家族式管理 进程(process)是家族式管理,总体分为两大家族,用户态家族和内核态家族. 用户态的进程是平民阶层,干着各行各业的活,权利有限,人数众多,活动范围有限.中南海肯定不能随便进出.这个阶层有个共同的老祖宗g_userInitProcess (1号进程). g_userInitProcess = 1; /* 1: The root process ID of the user-mode process is fixed at 1 *///用户态的根进程 //获取用户态进程的根进程,所有用户进程都是g_processCBArray[g_userInitProcess] fork来的 LITE_OS_SEC_TEXT UINT32 OsGetUserInitProcessID(VOID) { return g_userInitProcess; } 内核态的进程是贵族阶层,管理平民阶层的,维持平民生活秩序的,拥有超级权限,人数不多.这个阶层老祖宗是 g_kernelInitProcess(2号进程). g_kernelInitProcess = 2; /* 2: The root process ID of the kernel-mode process is fixed at 2 *///内核态的根进程 //获取内核态进程的根进程,所有内核进程都是g_processCBArray[g_kernelInitProcess] fork来的,包括g_processCBArray[g_kernelIdleProcess]进程 LITE_OS_SEC_TEXT UINT32 OsGetKernelInitProcessID(VOID) { return g_kernelInitProcess; } 这两个阶层可以相互流动吗,有没有可以通过高考改变命运的机会? 答案是: 绝对不可能!!! 龙生龙,凤生凤,老鼠生儿会打洞.从老祖宗创建的那一刻起就被刻在基因里了,抹不掉了. 因为所有的进程都是由这两位老同志克隆(clone)来的,继承了这份基因.LosProcessCB有专门的标签来processMode区分这两个阶层.整个鸿蒙内核源码并没有提供改变命运机会的set函数. #define OS_KERNEL_MODE 0x0U //内核态 #define OS_USER_MODE 0x1U //用户态 STATIC INLINE BOOL OsProcessIsUserMode(const LosProcessCB *processCB)//用户模式进程 { return (processCB->processMode == OS_USER_MODE); } typedef struct ProcessCB { // ... UINT16 processMode; /**< Kernel Mode:0; User Mode:1; */ //模式指定为内核还是用户进程 } LosProcessCB; 2号进程 KProcess 2号进程为内核态的老祖宗,也是内核创建的第一个进程,源码过程如下,省略了不相干的代码. bl main @带LR的子程序跳转, LR = pc - 4 ,执行C层main函数 /****************************************************************************** 内核入口函数,由汇编调用,见于reset_vector_up.S 和 reset_vector_mp.S up指单核CPU, mp指多核CPU bl main ******************************************************************************/ LITE_OS_SEC_TEXT_INIT INT32 main(VOID)//由主CPU执行,默认0号CPU 为主CPU { // ... 省略 uwRet = OsMain();// 内核各模块初始化 } LITE_OS_SEC_TEXT_INIT INT32 OsMain(VOID) { // ... ret = OsKernelInitProcess();// 创建内核态根进程 // ... ret = OsSystemInit(); //中间创建了用户态根进程 } //初始化 2号进程,即内核态进程的老祖宗 LITE_OS_SEC_TEXT_INIT UINT32 OsKernelInitProcess(VOID) { LosProcessCB *processCB = NULL; UINT32 ret; ret = OsProcessInit();// 初始化进程模块全部变量,创建各循环双向链表 if (ret != LOS_OK) { return ret; } processCB = OS_PCB_FROM_PID(g_kernelInitProcess);// 以PID方式得到一个进程 ret = OsProcessCreateInit(processCB, OS_KERNEL_MODE, "KProcess", 0);// 初始化进程,最高优先级0,鸿蒙进程一共有32个优先级(0-31) 其中0-9级为内核进程,用户进程可配置的优先级有22个(10-31) if (ret != LOS_OK) { return ret; } processCB->processStatus &= ~OS_PROCESS_STATUS_INIT;// 进程初始化位 置1 g_processGroup = processCB->group;//全局进程组指向了KProcess所在的进程组 LOS_ListInit(&g_processGroup->groupList);// 进程组链表初始化 OsCurrProcessSet(processCB);// 设置为当前进程 return OsCreateIdleProcess();// 创建一个空闲状态的进程 } 解读 main函数在系列篇中会单独讲,请留意自行翻看,它是在开机之初在SVC模式下创建的. 内核态老祖宗的名字叫 KProcess,优先级为最高 0 级."KProcess"进程是长期活跃的,很多重要的任务都会跑在其之下.例如: Swt_Task oom_task system_wq tcpip_thread SendToSer SendToTelnet eth_irq_task TouchEventHandler USB_GIANT_Task 此处不细讲这些任务,在其他篇幅有介绍,但光看名字也能猜个八九,请自行翻看. 紧接着KProcess 以CLONE_FILES的方式 fork了一个 名为"KIdle"的子进程. 内核态的所有进程都来自2号进程这位老同志,子子孙孙,代代相传,形成一颗家族树,和人类的传承所不同的是,它们往往是白发人送黑发人,子孙进程往往都是短命鬼,老祖宗最能活,子孙都死绝了它还在,有些收尸的工作要交给它干. 0 号进程 KIdle 0号进程是内核创建的第一个进程,在OsKernelInitProcess的末尾将2号进程设为当前进程后,紧接着就fork了0号进程.为什么一定要先设置当前进程,因为fork需要一个父进程,而此时系统处于启动阶段,并没有当前进程. 是的,你没有看错.进程是操作系统为方便管理资源而衍生出来的概念,系统并不是非要进程,任务才能运行的. 开机阶段就是啥都没有,默认跑在svc模式下,默认指定了入口地址reset_vector都是由硬件上电后规定的. 进程,线程都是跑起来后慢慢赋予的含义,OsCurrProcessSet是从软件层面赋予了此为当前进程的这个概念.此处是内核设置的第一个当前进程. //创建一个名叫"KIdle"的0号进程,给CPU空闲的时候使用 STATIC UINT32 OsCreateIdleProcess(VOID) { UINT32 ret; CHAR *idleName = "Idle"; LosProcessCB *idleProcess = NULL; Percpu *perCpu = OsPercpuGet(); UINT32 *idleTaskID = &perCpu->idleTaskID;//得到CPU的idle task ret = OsCreateResourceFreeTask();// 创建一个资源回收任务,优先级为5 用于回收进程退出时的各种资源 if (ret != LOS_OK) { return ret; } //创建一个名叫"KIdle"的进程,并创建一个idle task,CPU空闲的时候就待在 idle task中等待被唤醒 ret = LOS_Fork(CLONE_FILES, "KIdle", (TSK_ENTRY_FUNC)OsIdleTask, LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE); if (ret < 0) {//内核进程的fork并不会一次调用,返回两次,此子进程执行的开始位置是参数OsIdleTask return LOS_NOK; } g_kernelIdleProcess = (UINT32)ret;//返回 0号进程 idleProcess = OS_PCB_FROM_PID(g_kernelIdleProcess);//通过ID拿到进程实体 *idleTaskID = idleProcess->threadGroupID;//绑定CPU的IdleTask,或者说改变CPU现有的idle任务 OS_TCB_FROM_TID(*idleTaskID)->taskStatus |= OS_TASK_FLAG_SYSTEM_TASK;//设定Idle task 为一个系统任务 #if (LOSCFG_KERNEL_SMP == YES) OS_TCB_FROM_TID(*idleTaskID)->cpuAffiMask = CPUID_TO_AFFI_MASK(ArchCurrCpuid());//多核CPU的任务指定,防止乱串了,注意多核才会有并行处理 #endif (VOID)memset_s(OS_TCB_FROM_TID(*idleTaskID)->taskName, OS_TCB_NAME_LEN, 0, OS_TCB_NAME_LEN);//task 名字先清0 (VOID)memcpy_s(OS_TCB_FROM_TID(*idleTaskID)->taskName, OS_TCB_NAME_LEN, idleName, strlen(idleName));//task 名字叫 idle return LOS_OK; } 解读 看过fork篇的可能发现了一个参数, KIdle被创建的方式和通过系统调用创建的方式不一样,一个用的是CLONE_FILES,一个是 CLONE_SIGHAND 具体的创建方式如下: #define CLONE_VM 0x00000100 //子进程与父进程运行于相同的内存空间 #define CLONE_FS 0x00000200 //子进程与父进程共享相同的文件系统,包括root、当前目录、umask #define CLONE_FILES 0x00000400 //子进程与父进程共享相同的文件描述符(file descriptor)表 #define CLONE_SIGHAND 0x00000800 //子进程与父进程共享相同的信号处理(signal handler)表 #define CLONE_PTRACE 0x00002000 //若父进程被trace,子进程也被trace #define CLONE_VFORK 0x00004000 //父进程被挂起,直至子进程释放虚拟内存资源 #define CLONE_PARENT 0x00008000 //创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子” #define CLONE_THREAD 0x00010000 //Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群 KIdle创建了一个名为Idle的任务,任务的入口函数为OsIdleTask,这是个空闲任务,啥也不干的.专门用来给cpu休息的,cpu空闲时就待在这个任务里等活干. LITE_OS_SEC_TEXT WEAK VOID OsIdleTask(VOID) { while (1) {//只有一个死循环 #ifdef LOSCFG_KERNEL_TICKLESS //低功耗模式开关, idle task 中关闭tick if (OsTickIrqFlagGet()) { OsTickIrqFlagSet(0); OsTicklessStart(); } #endif Wfi();//WFI指令:arm core 立即进入low-power standby state,等待中断,进入休眠模式。 } } fork 内核态进程和fork 用户态进程有个地方会不一样,就是SP寄存器的值.fork用户态的进程 一次调用两次返回(父子进程各一次),返回的位置一样(是因为拷贝了父进程陷入内核时的上下文).所以只能通过返回值来判断是父还是子返回.这个在fork篇中有详细的描述.请自行翻看. 但fork内核态进程虽也有两次返回,但是返回的位置却不一样,子进程的返回位置是由内核指定的.例如:OsIdleTask就是入口函数.详见代码: //任务初始化时拷贝任务信息 STATIC VOID OsInitCopyTaskParam(LosProcessCB *childProcessCB, const CHAR *name, UINTPTR entry, UINT32 size, TSK_INIT_PARAM_S *childPara) { LosTaskCB *mainThread = NULL; UINT32 intSave; SCHEDULER_LOCK(intSave); mainThread = OsCurrTaskGet();//获取当前task,注意变量名从这里也可以看出 thread 和 task 是一个概念,只是内核常说task,上层应用说thread ,概念的映射. if (OsProcessIsUserMode(childProcessCB)) {//用户态进程 childPara->pfnTaskEntry = mainThread->taskEntry;//拷贝当前任务入口地址 childPara->uwStackSize = mainThread->stackSize; //栈空间大小 childPara->userParam.userArea = mainThread->userArea; //用户态栈区栈顶位置 childPara->userParam.userMapBase = mainThread->userMapBase; //用户态栈底 childPara->userParam.userMapSize = mainThread->userMapSize; //用户态栈大小 } else {//注意内核态进程创建任务的入口由外界指定,例如 OsCreateIdleProcess 指定了OsIdleTask childPara->pfnTaskEntry = (TSK_ENTRY_FUNC)entry;//参数(sp)为内核态入口地址 childPara->uwStackSize = size;//参数(size)为内核态栈大小 } childPara->pcName = (CHAR *)name; //拷贝进程名字 childPara->policy = mainThread->policy; //拷贝调度模式 childPara->usTaskPrio = mainThread->priority; //拷贝优先级 childPara->processID = childProcessCB->processID; //拷贝进程ID if (mainThread->taskStatus & OS_TASK_FLAG_PTHREAD_JOIN) { childPara->uwResved = OS_TASK_FLAG_PTHREAD_JOIN; } else if (mainThread->taskStatus & OS_TASK_FLAG_DETACHED) { childPara->uwResved = OS_TASK_FLAG_DETACHED; } SCHEDULER_UNLOCK(intSave); } 结论是创建0号进程中的OsCreateIdleProcess调用LOS_Fork后只会有一次返回.而且返回值为0,因为 g_freeProcess中0号进程还没有被分配.详见代码,注意看最后的注释: //进程模块初始化,被编译放在代码段 .init 中 LITE_OS_SEC_TEXT_INIT UINT32 OsProcessInit(VOID) { UINT32 index; UINT32 size; g_processMaxNum = LOSCFG_BASE_CORE_PROCESS_LIMIT;//默认支持64个进程 size = g_processMaxNum * sizeof(LosProcessCB);//算出总大小 g_processCBArray = (LosProcessCB *)LOS_MemAlloc(m_aucSysMem1, size);// 进程池,占用内核堆,内存池分配 if (g_processCBArray == NULL) { return LOS_NOK; } (VOID)memset_s(g_processCBArray, size, 0, size);//安全方式重置清0 LOS_ListInit(&g_freeProcess);//进程空闲链表初始化,创建一个进程时从g_freeProcess中申请一个进程描述符使用 LOS_ListInit(&g_processRecyleList);//进程回收链表初始化,回收完成后进入g_freeProcess等待再次被申请使用 for (index = 0; index < g_processMaxNum; index++) {//进程池循环创建 g_processCBArray[index].processID = index;//进程ID[0-g_processMaxNum-1]赋值 g_processCBArray[index].processStatus = OS_PROCESS_FLAG_UNUSED;// 默认都是白纸一张,贴上未使用标签 LOS_ListTailInsert(&g_freeProcess, &g_processCBArray[index].pendList);//注意g_freeProcess挂的是pendList节点,所以使用要通过OS_PCB_FROM_PENDLIST找到进程实体. } g_userInitProcess = 1; /* 1: The root process ID of the user-mode process is fixed at 1 *///用户态的根进程 LOS_ListDelete(&g_processCBArray[g_userInitProcess].pendList);// 将1号进程从空闲链表上摘出去 g_kernelInitProcess = 2; /* 2: The root process ID of the kernel-mode process is fixed at 2 *///内核态的根进程 LOS_ListDelete(&g_processCBArray[g_kernelInitProcess].pendList);// 将2号进程从空闲链表上摘出去 //注意:这波骚操作之后,g_freeProcess链表上还有,0,3,4,...g_processMaxNum-1号进程.创建进程是从g_freeProcess上申请 //即下次申请到的将是0号进程,而 OsCreateIdleProcess 将占有0号进程. return LOS_OK; } 1号进程 init 1号进程为用户态的老祖宗源码过程如下, 省略了不相干的代码. LITE_OS_SEC_TEXT_INIT INT32 OsMain(VOID) { // ... ret = OsKernelInitProcess();// 创建内核态根进程 // ... ret = OsSystemInit(); //中间创建了用户态根进程 } UINT32 OsSystemInit(VOID) { //.. ret = OsSystemInitTaskCreate();//创建了一个系统任务, } STATIC UINT32 OsSystemInitTaskCreate(VOID) { UINT32 taskID; TSK_INIT_PARAM_S sysTask; (VOID)memset_s(&sysTask, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S)); sysTask.pfnTaskEntry = (TSK_ENTRY_FUNC)SystemInit;//任务的入口函数,这个函数实现由外部提供 sysTask.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;//16K sysTask.pcName = "SystemInit";//任务的名称 sysTask.usTaskPrio = LOSCFG_BASE_CORE_TSK_DEFAULT_PRIO;// 内核默认优先级为10 sysTask.uwResved = LOS_TASK_STATUS_DETACHED;//任务分离模式 #if (LOSCFG_KERNEL_SMP == YES) sysTask.usCpuAffiMask = CPUID_TO_AFFI_MASK(ArchCurrCpuid());//cpu 亲和性设置,记录执行过任务的CPU,尽量确保由同一个CPU完成任务周期 #endif return LOS_TaskCreate(&taskID, &sysTask);//创建任务并加入就绪队列,并立即参与调度 } //SystemInit的实现由由外部提供 比如..\vendor\hi3516dv300\module_init\src\system_init.c void SystemInit(void) { // ... if (OsUserInitProcess()) {//创建用户态进程的老祖宗 PRINT_ERR("Create user init process faialed!\n"); return; } } //用户态根进程的创建过程 LITE_OS_SEC_TEXT_INIT UINT32 OsUserInitProcess(VOID) { INT32 ret; UINT32 size; TSK_INIT_PARAM_S param = { 0 }; VOID *stack = NULL; VOID *userText = NULL; CHAR *userInitTextStart = (CHAR *)&__user_init_entry;//代码区开始位置 ,对应 LITE_USER_SEC_ENTRY CHAR *userInitBssStart = (CHAR *)&__user_init_bss;// 未初始化数据区(BSS)。在运行时改变其值 对应 LITE_USER_SEC_BSS CHAR *userInitEnd = (CHAR *)&__user_init_end;// 结束地址 UINT32 initBssSize = userInitEnd - userInitBssStart; UINT32 initSize = userInitEnd - userInitTextStart; LosProcessCB *processCB = OS_PCB_FROM_PID(g_userInitProcess);//"Init进程的优先级是 28" ret = OsProcessCreateInit(processCB, OS_USER_MODE, "Init", OS_PROCESS_USERINIT_PRIORITY);// 初始化用户进程,它将是所有应用程序的父进程 if (ret != LOS_OK) { return ret; } userText = LOS_PhysPagesAllocContiguous(initSize >> PAGE_SHIFT);// 分配连续的物理页 if (userText == NULL) { ret = LOS_NOK; goto ERROR; } (VOID)memcpy_s(userText, initSize, (VOID *)&__user_init_load_addr, initSize);// 安全copy 经加载器load的结果 __user_init_load_addr -> userText ret = LOS_VaddrToPaddrMmap(processCB->vmSpace, (VADDR_T)(UINTPTR)userInitTextStart, LOS_PaddrQuery(userText), initSize, VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE | VM_MAP_REGION_FLAG_PERM_EXECUTE | VM_MAP_REGION_FLAG_PERM_USER);// 虚拟地址与物理地址的映射 if (ret < 0) { goto ERROR; } (VOID)memset_s((VOID *)((UINTPTR)userText + userInitBssStart - userInitTextStart), initBssSize, 0, initBssSize);// 除了代码段,其余都清0 stack = OsUserInitStackAlloc(g_userInitProcess, &size);//分配任务在用户态下的运行栈,大小为1M if (stack == NULL) { PRINTK("user init process malloc user stack failed!\n"); ret = LOS_NOK; goto ERROR; } param.pfnTaskEntry = (TSK_ENTRY_FUNC)userInitTextStart;// 从代码区开始执行,也就是应用程序main 函数的位置 param.userParam.userSP = (UINTPTR)stack + size;// 用户态栈底 param.userParam.userMapBase = (UINTPTR)stack;// 用户态栈顶 param.userParam.userMapSize = size;// 用户态栈大小 param.uwResved = OS_TASK_FLAG_PTHREAD_JOIN;// 可结合的(joinable)能够被其他线程收回其资源和杀死 ret = OsUserInitProcessStart(g_userInitProcess, &param);// 创建一个任务,来运行main函数 if (ret != LOS_OK) { (VOID)OsUnMMap(processCB->vmSpace, param.userParam.userMapBase, param.userParam.userMapSize); goto ERROR; } return LOS_OK; ERROR: (VOID)LOS_PhysPagesFreeContiguous(userText, initSize >> PAGE_SHIFT);//释放物理内存块 OsDeInitPCB(processCB);//删除PCB块 return ret; } 解读 从代码中可以看出用户态的老祖宗创建过程有点意思,首先它的源头和内核态老祖宗一样都在OsMain. 通过创建一个分离模式,优先级为10的系统任务 SystemInit,来完成.任务的入口函数 SystemInit()的实现由平台集成商来指定. 本篇采用了hi3516dv300的实现.也就是说用户态祖宗的创建是在 sysTask.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;//16K 栈中完成的.这个任务归属于内核进程KProcess. 用户态老祖宗的名字叫 Init,优先级为28级. 用户态的每个进程有独立的虚拟进程空间vmSpace,拥有独立的内存映射表(L1,L2表),申请的内存需要重新映射,映射过程在内存系列篇中有详细的说明. init创建了一个任务,任务的入口地址为 __user_init_entry,由编译器指定. 用户态进程是指应有程序运行的进程,通过动态加载ELF文件的方式启动.具体加载流程系列篇有讲解,不细说.用户态进程运行在用户空间,但通过系统调用可以陷入内核空间.具体看这张图: 鸿蒙源码百篇博客 往期回顾 v46.xx (特殊进程篇) | 龙生龙,凤生凤,老鼠生儿会打洞 < csdn | harmony > v45.xx (fork篇) | fork是如何做到调用一次,返回两次的 ? < csdn | harmony > v44.xx (中断管理篇) | 硬中断的实现<>观察者模式 < csdn | harmony > v43.xx (中断概念篇) | 外人眼中权势滔天的当红海公公 < csdn | harmony > v42.xx (中断切换篇) | 中断切换到底在切换什么? < csdn | harmony > v41.xx (任务切换篇) | 汇编逐行注解分析任务上下文 < csdn | harmony > v40.xx (汇编汇总篇) | 所有的汇编代码都在这里 < csdn | harmony > v39.xx (异常接管篇) | 社会很单纯,复杂的是人 < csdn | harmony > v38.xx (寄存器篇) | ARM所有寄存器一网打尽,不再神秘 < csdn | harmony > v37.xx (系统调用篇) | 全盘解剖系统调用实现过程 < csdn | harmony > v36.xx (工作模式篇) | CPU是韦小宝,有哪七个老婆? < csdn | harmony > v35.xx (时间管理篇) | Tick是操作系统的基本时间单位 < csdn | harmony > v34.xx (原子操作篇) | 是谁在为原子操作保驾护航? < csdn | harmony > v33.xx (消息队列篇) | 进程间如何异步解耦传递大数据 ? < csdn | harmony > v32.xx (CPU篇) | 内核是如何描述CPU的? < csdn | harmony > v31.xx (定时器篇) | 内核最高优先级任务是谁? < csdn | harmony > v30.xx (事件控制篇) | 任务间多对多的同步方案 < csdn | harmony > v29.xx (信号量篇) | 信号量解决任务同步问题 < csdn | harmony > v28.xx (进程通讯篇) | 进程间通讯有哪九大方式? < csdn | harmony > v27.xx (互斥锁篇) | 互斥锁比自旋锁可丰满许多 < csdn | harmony > v26.xx (自旋锁篇) | 真的好想为自旋锁立贞节牌坊! < csdn | harmony > v25.xx (并发并行篇) | 怎么记住并发并行的区别? < csdn | harmony > v24.xx (进程概念篇) | 进程在管理哪些资源? < csdn | harmony > v23.xx (汇编传参篇) | 汇编如何传递复杂的参数? < csdn | harmony > v22.xx (汇编基础篇) | CPU在哪里打卡上班? < csdn | harmony > v21.xx (线程概念篇) | 是谁在不断的折腾CPU? < csdn | harmony > v20.xx (用栈方式篇) | 栈是构建底层运行的基础 < csdn | harmony > v19.xx (位图管理篇) | 为何进程和线程优先级都是32个? < csdn | harmony > v18.xx (源码结构篇) | 内核500问你能答对多少? < csdn | harmony > v17.xx (物理内存篇) | 这样记伙伴算法永远不会忘 < csdn | harmony > v16.xx (内存规则篇) | 内存管理到底在管什么? < csdn | harmony > v15.xx (内存映射篇) | 什么是内存最重要的实现基础 ? < csdn | harmony > v14.xx (内存汇编篇) | 什么是虚拟内存的实现基础? < csdn | harmony > v13.xx (源码注释篇) | 热爱是所有的理由和答案 < csdn | harmony > v12.xx (内存管理篇) | 虚拟内存全景图是怎样的? < csdn | harmony > v11.xx (内存分配篇) | 内存有哪些分配方式? < csdn | harmony > v10.xx (内存主奴篇) | 紫禁城的主子和奴才如何相处? < csdn | harmony > v09.xx (调度故事篇) | 用故事说内核调度 < csdn | harmony > v08.xx (总目录) | 百万汉字注解 百篇博客分析 < csdn | harmony > v07.xx (调度机制篇) | 任务是如何被调度执行的? < csdn | harmony > v06.xx (调度队列篇) | 就绪队列对调度的作用 < csdn | harmony > v05.xx (任务管理篇) | 谁在让CPU忙忙碌碌? < csdn | harmony > v04.xx (任务调度篇) | 任务是内核调度的单元 < csdn | harmony > v03.xx (时钟任务篇) | 触发调度最大的动力来自哪里? < csdn | harmony > v02.xx (进程管理篇) | 进程是内核资源管理单元 < csdn | harmony > v01.xx (双向链表篇) | 谁是内核最重要结构体? < csdn | harmony > 参与贡献 访问注解仓库地址 Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request 新建 Issue 喜欢请「点赞+关注+收藏」 关注「鸿蒙内核源码分析」公众号,百万汉字注解 + 百篇博客分析 => 深挖鸿蒙内核源码 各大站点搜 「鸿蒙内核源码分析」 .欢迎转载,请注明出处.

优秀的个人博客,低调大师

鸿蒙内核源码分析(事件控制篇) | 任务间一对多和多对多的同步方案 | 中文注解HarmonyOS源码 | v30.01

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony > 本篇说清楚事件(Event) 读本篇之前建议先读鸿蒙内核源码分析(总目录)其他篇. 官方概述 先看官方对事件的描述. 事件(Event)是一种任务间通信的机制,可用于任务间的同步。 多任务环境下,任务之间往往需要同步操作,一个等待即是一个同步。事件可以提供一对多、多对多的同步操作。 一对多同步模型:一个任务等待多个事件的触发。可以是任意一个事件发生时唤醒任务处理事件,也可以是几个事件都发生后才唤醒任务处理事件。 多对多同步模型:多个任务等待多个事件的触发。 鸿蒙提供的事件具有如下特点: 任务通过创建事件控制块来触发事件或等待事件。 事件间相互独立,内部实现为一个32位无符号整型,每一位标识一种事件类型。第25位不可用,因此最多可支持31种事件类型。 事件仅用于任务间的同步,不提供数据传输功能。 多次向事件控制块写入同一事件类型,在被清零前等效于只写入一次。 多个任务可以对同一事件进行读写操作。 支持事件读写超时机制。 再看事件图 注意图中提到了三个概念 事件控制块 事件 任务 接下来结合代码来理解事件模块的实现. 事件控制块长什么样? typedef struct tagEvent { UINT32 uwEventID; /**< Event mask in the event control block,//标识发生的事件类型位,事件ID,每一位标识一种事件类型 indicating the event that has been logically processed. */ LOS_DL_LIST stEventList; /**< Event control block linked list *///读取事件任务链表 } EVENT_CB_S, *PEVENT_CB_S; 简单是简单,就两个变量,如下: uwEventID:用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0表示该事件类型未发生、1表示该事件类型已经发生),一共31种事件类型,第25位系统保留。 stEventList,这又是一个双向链表, 双向链表是内核最重要的结构体, 可前往 鸿蒙内核源码分析(总目录) 查看双向链表篇. LOS_DL_LIST像狗皮膏药一样牢牢的寄生在宿主结构体上stEventList上挂的是所有等待这个事件的任务. 事件控制块<>事件<>任务 三者关系 一定要搞明白这三者的关系,否则搞不懂事件模块是如何运作的. 任务是事件的生产者,通过 LOS_EventWrite,向外部广播发生了XX事件,并唤醒此前已在事件控制块中登记过的要等待XX事件发生的XX任务. 事件控制块EVENT_CB_S 是记录者,只干两件事件: 1.uwEventID按位记录哪些事件发生了,它只是记录,怎么消费它不管的. 2.stEventList记录哪些任务在等待事件,但任务究竟在等待哪些事件它也是不记录的 任务也是消费者,通过 LOS_EventRead消费,只有任务自己清楚要以什么样的方式,消费什么样的事件. 先回顾下任务结构体 LosTaskCB 对事件部分的描述如下: typedef struct { //...去掉不相关的部分 VOID *taskEvent; //和任务发生关系的事件控制块 UINT32 eventMask; //对哪些事件进行屏蔽 UINT32 eventMode; //事件三种模式(LOS_WAITMODE_AND,LOS_WAITMODE_OR,LOS_WAITMODE_CLR) } LosTaskCB; taskEvent 指向的就是 EVENT_CB_S eventMask 屏蔽掉 事件控制块 中的哪些事件 eventMode 以什么样的方式去消费事件,三种读取模式 #define LOS_WAITMODE_AND 4U #define LOS_WAITMODE_OR 2U #define LOS_WAITMODE_CLR 1U 所有事件(LOS_WAITMODE_AND):逻辑与,基于接口传入的事件类型掩码eventMask,只有这些事件都已经发生才能读取成功,否则该任务将阻塞等待或者返回错误码。 任一事件(LOS_WAITMODE_OR):逻辑或,基于接口传入的事件类型掩码eventMask,只要这些事件中有任一种事件发生就可以读取成功,否则该任务将阻塞等待或者返回错误码。 清除事件(LOS_WAITMODE_CLR):这是一种附加读取模式,需要与所有事件模式或任一事件模式结合使用(LOS_WAITMODE_AND | LOS_WAITMODE_CLR或 LOS_WAITMODE_OR | LOS_WAITMODE_CLR)。在这种模式下,当设置的所有事件模式或任一事件模式读取成功后,会自动清除事件控制块中对应的事件类型位。 生成和消费同一个事件的多个任务,可以没有任何关系! 函数列表 事件可应用于多种任务同步场景,在某些同步场景下可替代信号量。 其中读懂 OsEventWrite 和 OsEventRead 就明白了事件模块的实现. 事件初始化 -> LOS_EventInit //初始化一个事件控制块 LITE_OS_SEC_TEXT_INIT UINT32 LOS_EventInit(PEVENT_CB_S eventCB) { UINT32 intSave; intSave = LOS_IntLock();//锁中断 eventCB->uwEventID = 0; //其中每一位表示一种事件类型(0表示该事件类型未发生、1表示该事件类型已经发生) LOS_ListInit(&eventCB->stEventList);//事件链表初始化 LOS_IntRestore(intSave);//恢复中断 return LOS_OK; } 代码解读: 事件是共享资源,所以操作期间不能产生中断. 初始化两个记录者 uwEventID stEventList 事件生产过程 -> OsEventWrite LITE_OS_SEC_TEXT VOID OsEventWriteUnsafe(PEVENT_CB_S eventCB, UINT32 events, BOOL once, UINT8 *exitFlag) { LosTaskCB *resumedTask = NULL; LosTaskCB *nextTask = NULL; BOOL schedFlag = FALSE; eventCB->uwEventID |= events;//对应位贴上标签 if (!LOS_ListEmpty(&eventCB->stEventList)) {//等待事件链表判断,处理等待事件的任务 for (resumedTask = LOS_DL_LIST_ENTRY((&eventCB->stEventList)->pstNext, LosTaskCB, pendList); &resumedTask->pendList != &eventCB->stEventList;) {//循环获取任务链表 nextTask = LOS_DL_LIST_ENTRY(resumedTask->pendList.pstNext, LosTaskCB, pendList);//获取任务实体 if (OsEventResume(resumedTask, eventCB, events)) {//是否恢复任务 schedFlag = TRUE;//任务已加至就绪队列,申请发生一次调度 } if (once == TRUE) {//是否只处理一次任务 break;//退出循环 } resumedTask = nextTask;//检查链表中下一个任务 } } if ((exitFlag != NULL) && (schedFlag == TRUE)) {//是否让外面调度 *exitFlag = 1; } } //写入事件 LITE_OS_SEC_TEXT STATIC UINT32 OsEventWrite(PEVENT_CB_S eventCB, UINT32 events, BOOL once) { UINT32 intSave; UINT8 exitFlag = 0; SCHEDULER_LOCK(intSave); //禁止调度 OsEventWriteUnsafe(eventCB, events, once, &exitFlag);//写入事件 SCHEDULER_UNLOCK(intSave); //允许调度 if (exitFlag == 1) { //需要发生调度 LOS_MpSchedule(OS_MP_CPU_ALL);//通知所有CPU调度 LOS_Schedule();//执行调度 } return LOS_OK; } 代码解读: 给对应位贴上事件标签,eventCB->uwEventID |= events; 注意uwEventID是按位管理的.每个位代表一个事件是否写入,例如 uwEventID = 00010010 代表产生了 1,4 事件 循环从stEventList链表中取出等待这个事件的任务判断是否唤醒任务. OsEventResume //事件恢复,判断是否唤醒任务 LITE_OS_SEC_TEXT STATIC UINT8 OsEventResume(LosTaskCB *resumedTask, const PEVENT_CB_S eventCB, UINT32 events) { UINT8 exitFlag = 0;//是否唤醒 if (((resumedTask->eventMode & LOS_WAITMODE_OR) && ((resumedTask->eventMask & events) != 0)) || ((resumedTask->eventMode & LOS_WAITMODE_AND) && ((resumedTask->eventMask & eventCB->uwEventID) == resumedTask->eventMask))) {//逻辑与 和 逻辑或 的处理 exitFlag = 1; resumedTask->taskEvent = NULL; OsTaskWake(resumedTask);//唤醒任务,加入就绪队列 } return exitFlag; } 3.唤醒任务OsTaskWake只是将任务重新加入就绪队列,需要立即申请一次调度 LOS_Schedule . 事件消费过程 -> OsEventRead LITE_OS_SEC_TEXT STATIC UINT32 OsEventRead(PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode, UINT32 timeout, BOOL once) { UINT32 ret; UINT32 intSave; SCHEDULER_LOCK(intSave); ret = OsEventReadImp(eventCB, eventMask, mode, timeout, once);//读事件实现函数 SCHEDULER_UNLOCK(intSave); return ret; } //读取指定事件类型的实现函数,超时时间为相对时间:单位为Tick LITE_OS_SEC_TEXT STATIC UINT32 OsEventReadImp(PEVENT_CB_S eventCB, UINT32 eventMask, UINT32 mode, UINT32 timeout, BOOL once) { UINT32 ret = 0; LosTaskCB *runTask = OsCurrTaskGet(); runTask->eventMask = eventMask; runTask->eventMode = mode; runTask->taskEvent = eventCB;//事件控制块 ret = OsTaskWait(&eventCB->stEventList, timeout, TRUE);//任务进入等待状态,挂入阻塞链表 if (ret == LOS_ERRNO_TSK_TIMEOUT) {//如果返回超时 runTask->taskEvent = NULL; return LOS_ERRNO_EVENT_READ_TIMEOUT; } ret = OsEventPoll(&eventCB->uwEventID, eventMask, mode);//检测事件是否符合预期 return ret; } 代码解读: 事件控制块是给任务使用的, 任务给出读取一个事件的条件 eventMask 告诉系统屏蔽掉这些事件,对屏蔽的事件不感冒. eventMode 已什么样的方式去消费事件,是必须都满足给的条件,还是只满足一个就响应. 条件给完后,自己进入等待状态 OsTaskWait,等待多久 timeout决定,任务自己说了算. OsEventPoll检测事件是否符合预期,啥意思?看下它的代码就知道了 //根据用户传入的事件值、事件掩码及校验模式,返回用户传入的事件是否符合预期 LITE_OS_SEC_TEXT UINT32 OsEventPoll(UINT32 *eventID, UINT32 eventMask, UINT32 mode) { UINT32 ret = 0;//事件是否发生了 LOS_ASSERT(OsIntLocked());//断言不允许中断了 LOS_ASSERT(LOS_SpinHeld(&g_taskSpin));//任务自旋锁 if (mode & LOS_WAITMODE_OR) {//如果模式是读取掩码中任意事件 if ((*eventID & eventMask) != 0) { ret = *eventID & eventMask; //发生了 } } else {//等待全部事件发生 if ((eventMask != 0) && (eventMask == (*eventID & eventMask))) {//必须满足全部事件发生 ret = *eventID & eventMask; //发生了 } } if (ret && (mode & LOS_WAITMODE_CLR)) {//是否清除事件 *eventID = *eventID & ~ret; } return ret; } 编程实例 本实例实现如下流程。 示例中,任务Example_TaskEntry创建一个任务Example_Event,Example_Event读事件阻塞,Example_TaskEntry向该任务写事件。可以通过示例日志中打印的先后顺序理解事件操作时伴随的任务切换。 在任务Example_TaskEntry创建任务Example_Event,其中任务Example_Event优先级高于Example_TaskEntry。 在任务Example_Event中读事件0x00000001,阻塞,发生任务切换,执行任务Example_TaskEntry。 在任务Example_TaskEntry向任务Example_Event写事件0x00000001,发生任务切换,执行任务Example_Event。 Example_Event得以执行,直到任务结束。 Example_TaskEntry得以执行,直到任务结束。 #include "los_event.h" #include "los_task.h" #include "securec.h" /* 任务ID */ UINT32 g_testTaskId; /* 事件控制结构体 */ EVENT_CB_S g_exampleEvent; /* 等待的事件类型 */ #define EVENT_WAIT 0x00000001 /* 用例任务入口函数 */ VOID Example_Event(VOID) { UINT32 ret; UINT32 event; /* 超时等待方式读事件,超时时间为100 ticks, 若100 ticks后未读取到指定事件,读事件超时,任务直接唤醒 */ printf("Example_Event wait event 0x%x \n", EVENT_WAIT); event = LOS_EventRead(&g_exampleEvent, EVENT_WAIT, LOS_WAITMODE_AND, 100); if (event == EVENT_WAIT) { printf("Example_Event,read event :0x%x\n", event); } else { printf("Example_Event,read event timeout\n"); } } UINT32 Example_TaskEntry(VOID) { UINT32 ret; TSK_INIT_PARAM_S task1; /* 事件初始化 */ ret = LOS_EventInit(&g_exampleEvent); if (ret != LOS_OK) { printf("init event failed .\n"); return -1; } /* 创建任务 */ (VOID)memset_s(&task1, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S)); task1.pfnTaskEntry = (TSK_ENTRY_FUNC)Example_Event; task1.pcName = "EventTsk1"; task1.uwStackSize = OS_TSK_DEFAULT_STACK_SIZE; task1.usTaskPrio = 5; ret = LOS_TaskCreate(&g_testTaskId, &task1); if (ret != LOS_OK) { printf("task create failed .\n"); return LOS_NOK; } /* 写g_testTaskId 等待事件 */ printf("Example_TaskEntry write event .\n"); ret = LOS_EventWrite(&g_exampleEvent, EVENT_WAIT); if (ret != LOS_OK) { printf("event write failed .\n"); return LOS_NOK; } /* 清标志位 */ printf("EventMask:%d\n", g_exampleEvent.uwEventID); LOS_EventClear(&g_exampleEvent, ~g_exampleEvent.uwEventID); printf("EventMask:%d\n", g_exampleEvent.uwEventID); /* 删除任务 */ ret = LOS_TaskDelete(g_testTaskId); if (ret != LOS_OK) { printf("task delete failed .\n"); return LOS_NOK; } return LOS_OK; } 运行结果 Example_Event wait event 0x1 Example_TaskEntry write event . Example_Event,read event :0x1 EventMask:1 EventMask:0 喜欢就请收藏吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织. 百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony >

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

用户登录
用户注册