首页 文章 精选 留言 我的

精选列表

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

| 百篇博客分析HarmonyOS源码 | v47.01

百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< 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所有汇编代码全都在这,拿走不谢 | 百篇博客分析鸿蒙源码 | v40.03

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< oschina | csdn | weharmony > 汇编其实很可爱 绝大部分IT从业人员终生不用触碰到的汇编,它听着像上古时代遥远的呼唤,总觉得远却又能听到声,汇编再往下就真的是01110011了,汇编指令基本是一一对应了机器指令. 所谓内核是对硬件的驱动,对驱动之后资源的良序管理,这里说的资源是CPU(单核/多核),内存,磁盘,i/o设备.层层封装,步步遮蔽,到了应用层,不知有汉,无论魏晋才好.好是好,但有句话,其实哪有什么岁月静好,只是有人替你负重前行.难道就不想知道别人是怎么负重前行的? 越高级的语言是越接近人思维模式的,越低级的语言就是越贴近逻辑与非门的高低电平的起伏.汇编是贴着硬件飞行的,要研究内核就绕不过汇编,觉得神秘是来源于不了解,恐惧是来自于没接近. 其实深入分析内核源码之后就会发现,汇编其实很可爱,很容易,比c/c++/java容易太多了,真的是很傻很单纯. 鸿蒙内核源码分析系列篇至少已经有五篇涉及到了汇编,请自行翻看,但还是远远不够,要写十五篇,彻底摸透,现在才刚刚开始,本篇先整理鸿蒙内核所有汇编文件和大概说明文件的作用,后续一块一块来剥,不把这些汇编剥个精光不罢休. 汇编目录 鸿蒙所有汇编文件如下: 直接点击可以查看注解源码,有些站点会把链接去除,没办法,可直接去各大站点搜"鸿蒙内核源码分析",找到源码注解. \arch\arm\arm\src startup 启动相关 reset_vector_mp.S 多核CPU下启动代码,大文件 reset_vector_up.S 单核CPU下启动代码,大文件 armv7a cache.S 缓存相关的两个函数 los_dispatch.S 异常分发处理,大文件. los_hw_exc.S 硬件异常相关,大文件. los_hw_runstop.S OsSRSaveRegister 和 OsSRRestoreRegister 汇编实现 jmp.S 两个简单的跳转函数 hw_user_get.S 拷贝用户空间数据到内核空间 hw_user_put.S 拷贝内核空间数据到用户空间 hw_user_get.S 将用户空间数据src 拷贝到内核空间 dst // errno_t _arm_get_user(void *dst, const void *src, size_t dstTypeLen, size_t srcTypeLen) FUNCTION(_arm_get_user) stmdb sp!, {r0, r1, r2, r3, lr} @四个参数入栈,保存LR cmp r2, #0 @r2 和 0比较 beq .Lget_user_return @相等 跳到Lget_user_return 直接返回 cmp r2, r3 @r2 和 r3比较 bne .Lget_user_err @不等,说明函数要返回错误 cmp r2, #1 @r2 和 1比较 bhi .Lget_user_half @if(dstTypeLen>1) 跳转到Lget_user_half .Lget_user_byte: @按字节拷贝数据 0: ldrbt r3, [r1], #0 @r3=*r1 1: strb r3, [r0], #0 @*r0=r3 b .Lget_user_return .Lget_user_half: cmp r2, #2 @r2 和 2比较 bhi .Lget_user_word @if(dstTypeLen>2) Lget_user_word 2: ldrht r3, [r1], #0 @完成最后一个字节的拷贝 3: strh r3, [r0], #0 @完成最后一个字节的拷贝 b .Lget_user_return .Lget_user_word: cmp r2, #4 @r2 和 4比较 bhi .Lget_user_err @if(dstTypeLen>4) 跳转到Lget_user_err 4: ldrt r3, [r1], #0 5: str r3, [r0], #0 .Lget_user_return: @返回锚点 ldmia sp!, {r0, r1, r2, r3, lr} @保存的内容出栈,恢复各寄存器值 mov r0, 0 @r0保存返回值为0 bx lr @跳回调用函数继续执行,_arm_get_user到此结束! .Lget_user_err: ldmia sp!, {r0, r1, r2, r3, lr} @保存的内容出栈,恢复各寄存器值 mov r0, #-14 @r0保存返回值为-14 bx lr @跳回调用函数继续执行,_arm_get_user到此结束! .pushsection __exc_table, "a" .long 0b, .Lget_user_err .long 1b, .Lget_user_err .long 2b, .Lget_user_err .long 3b, .Lget_user_err .long 4b, .Lget_user_err .long 5b, .Lget_user_err .popsection 解读 用户空间和内核空间的数据为什么需要拷贝? 这是个经典问题,看了网上的一些回答,没毛病: 内核不能信任任何用户空间的指针。必须对用户空间的指针指向的数据进行验证。如果只做验证不做拷贝的话,那么在随后的运行中要随时受到其它进/线程可能修改用户空间数据的威胁。所以必须做拷贝。 在内存系列篇中已经反复的说过,每个用户进程都有自己独立的用户空间,但这个用户空间是通过MMU映射出来的,是表面上繁花似锦,背后都共用着真正的物理内存,所以在高频率的任务切换过程中,原有的用户空间地址内容很容易被覆盖掉.举个例子说明下: 用户A有个美女西施放在万聪酒店21号房说要献给内核大佬,如果内核不直接把美女接回家,而仅仅是做个记录,写着西施在万聪酒店21号房,内核大佬立马跑去过,还不会错能拿对人,但如果被其他事给耽搁了呢? 耽搁的这回功夫,调度算法把万聪酒店21号房给了用户B使用,当然用户B使用之前,酒店管理人员会把西施置换个地方(以至于用户A再回到酒店时,原来的东西该怎样还咋样还原). 等21号房空出来了,B肯定不知道原来的房间是A在用,而且里面曾经还过有个美女西施,更不可能晓得A把西施献给内核大佬这回事了.因为B的业务需要,很可能往21号房整了个东施进来. 此时如果内核大佬事忙完了,想起用户A献美女的事了,是时候了.因为只记录了地址,直接去万聪酒店21号房抓人,可这会抓出来那是咱东施小姐呀.这可不把事给搞砸啦. 所以需要跨空间拷贝,直接把美女接回家找个地方关起来先. reset_vector_mp.S 和 reset_vector_up.S 鸿蒙开机代码根据 CPU多核还是单核分成了两个独立文件处理. mp就是多处理器(multiprocessing)的意思: 多CPU核的操作系统3种处理模式(SMP+AMP+BMP) 鸿蒙实现的是 SMP 的方式 非对称多处理(Asymmetric multiprocessing,AMP)每个CPU内核 运行一个独立的操作系统或同一操作系统的独立实例(instantiation)。 对称多处理(Symmetric multiprocessing,SMP)一个操作系统的实例 可以同时管理所有CPU内核,且应用并不绑定某一个内核。 混合多处理(Bound multiprocessing,BMP)一个操作系统的实例可以 同时管理所有CPU内核,但每个应用被锁定于某个指定的核心。 up(unit processing )的意思,单个CPU,虽然没mp的复杂,但文件也很大 500行汇编,一小节讲不完,需要单独的一篇专讲 reset_vector 这里只列出up情况下的开机代码 reset_vector: @鸿蒙单核cpu 开机代码 /* do some early cpu setup: i/d cache disable, mmu disabled */ mrc p15, 0, r0, c1, c0, 0 bic r0, #(1<<12) bic r0, #(1<<2 | 1<<0) mcr p15, 0, r0, c1, c0, 0 /* r11: delta of physical address and virtual address */ adr r11, pa_va_offset ldr r0, [r11] sub r11, r11, r0 /* if we need to relocate to proper location or not */ adr r4, __exception_handlers /* r4: base of load address */ ldr r5, =SYS_MEM_BASE /* r5: base of physical address */ subs r12, r4, r5 /* r12: delta of load address and physical address */ beq reloc_img_to_bottom_done /* if we load image at the bottom of physical address */ /* we need to relocate image at the bottom of physical address */ ldr r7, =__exception_handlers /* r7: base of linked address (or vm address) */ ldr r6, =__bss_start /* r6: end of linked address (or vm address) */ sub r6, r7 /* r6: delta of linked address (or vm address) */ add r6, r4 /* r6: end of load address */ los_dispatch.S 和 los_hw_exc.S 异常模式处理入口和统一分发现实,之前也有提到过,很复杂,1000多行,后续单独细说实现过程. jmp.S 两个简单的函数longjmp setjmp 的实现,加注解部分请前往 鸿蒙内核源码注解分析 查看 FUNCTION(longjmp) ldmfd r0,{r4-r12} add r0,#(4 * 9) ldr r13,[r0] add r0,#4 ldr r14,[r0] cmp r1,#0 moveq r1,#1 mov r0,r1 mov pc,lr FUNCTION(setjmp) stmea r0,{r4-r12} add r0,#(4 * 9) str r13,[r0] add r0,#4 str r14,[r0] mov r0,#0 mov pc,lr los_hw_runstop.S .global OsSRSaveRegister .global OsSRRestoreRegister 两个函数的汇编现实,有点复杂,后续单独说明. cache.S 这是缓存部分的两个函数实现,此处没有加注解,试着看明白这两个函数的实现.加注解部分请前往 鸿蒙内核源码注解分析 查看 .macro DCACHE_LINE_SIZE, reg, tmp mrc p15, 0, \tmp, c0, c0, 1 lsr \tmp, \tmp, #16 and \tmp, \tmp, #0xf mov \reg, #4 mov \reg, \reg, lsl \tmp .endm FUNCTION(arm_inv_cache_range) push {r2, r3} DCACHE_LINE_SIZE r2, r3 sub r3, r2, #1 tst r0, r3 bic r0, r0, r3 mcrne p15, 0, r0, c7, c14, 1 tst r1, r3 bic r1, r1, r3 mcrne p15, 0, r1, c7, c14, 1 1: mcr p15, 0, r0, c7, c6, 1 add r0, r0, r2 cmp r0, r1 blo 1b dsb pop {r2, r3} mov pc, lr FUNCTION(arm_clean_cache_range) push {r2, r3} DCACHE_LINE_SIZE r2, r3 sub r3, r2, #1 bic r0, r0, r3 1: mcr p15, 0, r0, c7, c10, 1 add r0, r0, r2 cmp r0, r1 blo 1b dsb pop {r2, r3} mov pc, lr 参与贡献 访问注解仓库地址 Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request 新建 Issue 喜欢请大方 点赞+关注+收藏 吧 各大站点搜 "鸿蒙内核源码分析" .欢迎转载,请注明出处. 进入 >> 百万汉字注解 百篇博客分析 精读鸿蒙源码 深挖地基工程 < gitee | csdn | oschina >

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

| 中文注解HarmonyOS源码 | v27.01

鸿蒙内核源码中文注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆, 四大源码仓每日同步更新< Gitee | Github | CSDN | Coding > 鸿蒙内核源码分析博客 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新< OSCHINA | CSDN | WeHarmony > 本篇说清楚互斥锁 读本篇之前建议先读鸿蒙内核源码分析(总目录)之自旋锁篇. 内核中哪些模块会用到互斥锁?看图: 图中是内核有关模块对互斥锁初始化,有文件,有内存,用消息队列等等,使用面非常的广.其实在给内核源码加注的过程中,会看到大量的自旋锁和互斥锁,它们的存在有序的保证了内核和应用程序的正常运行.是非常基础和重要的功能. 概述 自旋锁 和 互斥锁 虽都是锁,但解决的问题不同, 自旋锁解决用于CPU核间共享内存的竞争,而互斥锁解决线程(任务)间共享内存的竞争. 自旋锁的特点是死守共享资源,拿不到锁,CPU选择睡眠,等待其他CPU释放资源.所以共享代码段不能太复杂,否则容易死锁,休克. 互斥锁的特点是拿不到锁往往原任务阻塞,切换到新任务运行.CPU是会一直跑的.这样很容易会想到几个问题: 第一:会出现很多任务在等同一把锁的情况出现,因为切换新任务也可能因要同一把锁而被阻塞,CPU又被调去跑新新任务了.这样就会出现一个等锁的链表. 第二:持有锁的一方再申请同一把锁时还能成功吗? 答案是可以的,这种锁叫递归锁,是鸿蒙内核默认方式. 第三:当优先级很高的A任务要锁失败,主动让出CPU进入睡眠,而如果持有锁的B任务优先级很低, 迟迟等不到调度不到B任务运行,无法释放锁怎么办? 答案是会临时调整B任务的优先级,调到A一样高,这样B能很快的被调度到,等B释放锁后其优先级又会被打回原形.所以一个任务的优先级会看情况时高时低. 第四:B任务释放锁之后要主动唤醒等锁的任务链表,使他们能加入就绪队列,等待被调度.调度算法是一视同仁的,它只看优先级. 带着这些问题,进入鸿蒙内核互斥锁的实现代码,本篇代码量较大, 每行代码都一一注解说明. 互斥锁长什么样? enum { LOS_MUX_PRIO_NONE = 0, //线程的优先级和调度不会受到互斥锁影响,先来后到,普通排队. LOS_MUX_PRIO_INHERIT = 1, //当高优先级的等待低优先级的线程释放锁时,低优先级的线程以高优先级线程的优先级运行。 //当线程解锁互斥量时,线程的优先级自动被将到它原来的优先级 LOS_MUX_PRIO_PROTECT = 2 //详见:OsMuxPendOp中的注解,详细说明了LOS_MUX_PRIO_PROTECT的含义 }; enum { LOS_MUX_NORMAL = 0, //非递归锁 只有[0.1]两个状态,不做任何特殊的错误检,不进行deadlock detection(死锁检测) LOS_MUX_RECURSIVE = 1, //递归锁 允许同一线程在互斥量解锁前对该互斥量进行多次加锁。递归互斥量维护锁的计数,在解锁次数和加锁次数不相同的情况下,不会释放锁,别的线程就无法加锁此互斥量。 LOS_MUX_ERRORCHECK = 2, //进行错误检查,如果一个线程企图对一个已经锁住的mutex进行relock或对未加锁的unlock,将返回一个错误。 LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE //鸿蒙系统默认使用递归锁 }; typedef struct { //互斥锁的属性 UINT8 protocol; //协议 UINT8 prioceiling; //优先级上限 UINT8 type; //类型属性 UINT8 reserved; //保留字段 } LosMuxAttr; typedef struct OsMux { //互斥锁结构体 UINT32 magic; /**< magic number */ //魔法数字 LosMuxAttr attr; /**< Mutex attribute */ //互斥锁属性 LOS_DL_LIST holdList; /**< The task holding the lock change */ //当有任务拿到本锁时,通过holdList节点把锁挂到该任务的锁链表上 LOS_DL_LIST muxList; /**< Mutex linked list */ //等这个锁的任务链表,上面挂的都是任务,注意和holdList的区别. VOID *owner; /**< The current thread that is locking a mutex */ //当前拥有这把锁的任务 UINT16 muxCount; /**< Times of locking a mutex */ //锁定互斥体的次数,递归锁允许多次 } LosMux; 这互斥锁长的明显的比自旋锁丰满多啦,还记得自旋锁的样子吗,就一个变量,单薄到令人心疼. 初始化 LITE_OS_SEC_TEXT UINT32 LOS_MuxInit(LosMux *mutex, const LosMuxAttr *attr) { //... SCHEDULER_LOCK(intSave); //拿到调度自旋锁 mutex->muxCount = 0; //锁定互斥量的次数 mutex->owner = NULL; //持有该锁的任务 LOS_ListInit(&mutex->muxList); //初始化等待该锁的任务链表 mutex->magic = OS_MUX_MAGIC; //固定标识,互斥锁的魔法数字 SCHEDULER_UNLOCK(intSave); //释放调度自旋锁 return LOS_OK; } 留意mutex->muxList,这又是一个双向链表, 双向链表是内核最重要的结构体,不仅仅是鸿蒙内核,在linux内核中(list_head)又何尝不是,牢牢的寄生在宿主结构体上.muxList上挂的是未来所有等待这把锁的任务. 三种申请模式 申请互斥锁有三种模式:无阻塞模式、永久阻塞模式、定时阻塞模式。 无阻塞模式:即任务申请互斥锁时,入参timeout等于0。若当前没有任务持有该互斥锁,或者持有该互斥锁的任务和申请该互斥锁的任务为同一个任务,则申请成功,否则立即返回申请失败。 永久阻塞模式:即任务申请互斥锁时,入参timeout等于0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则,任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,直到有其他任务释放该互斥锁,阻塞任务才会重新得以执行。 定时阻塞模式:即任务申请互斥锁时,0<timeout<0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,超时前如果有其他任务释放该互斥锁,则该任务可成功获取互斥锁继续执行,若超时前未获取到该互斥锁,接口将返回超时错误码。 如果有任务阻塞于该互斥锁,则唤醒被阻塞任务中优先级最高的,该任务进入就绪态,并进行任务调度。 如果没有任务阻塞于该互斥锁,则互斥锁释放成功。 申请互斥锁主函数 OsMuxPendOp //互斥锁的主体函数,由OsMuxlockUnsafe调用,互斥锁模块最重要的几个函数之一 //最坏情况就是拿锁失败,让出CPU,变成阻塞任务,等别的任务释放锁后排到自己了接着执行. STATIC UINT32 OsMuxPendOp(LosTaskCB *runTask, LosMux *mutex, UINT32 timeout) { UINT32 ret; LOS_DL_LIST *node = NULL; LosTaskCB *owner = NULL; if ((mutex->muxList.pstPrev == NULL) || (mutex->muxList.pstNext == NULL)) {//列表为空时的处理 /* This is for mutex macro initialization. */ mutex->muxCount = 0;//锁计数器清0 mutex->owner = NULL;//锁没有归属任务 LOS_ListInit(&mutex->muxList);//初始化锁的任务链表,后续申请这把锁任务都会挂上去 } if (mutex->muxCount == 0) {//无task用锁时,肯定能拿到锁了.在里面返回 mutex->muxCount++; //互斥锁计数器加1 mutex->owner = (VOID *)runTask; //当前任务拿到锁 LOS_ListTailInsert(&runTask->lockList, &mutex->holdList);//持有锁的任务改变了,节点挂到当前task的锁链表 if ((runTask->priority > mutex->attr.prioceiling) && (mutex->attr.protocol == LOS_MUX_PRIO_PROTECT)) {//看保护协议的做法是怎样的? LOS_BitmapSet(&runTask->priBitMap, runTask->priority);//1.priBitMap是记录任务优先级变化的位图,这里把任务当前的优先级记录在priBitMap OsTaskPriModify(runTask, mutex->attr.prioceiling);//2.把高优先级的mutex->attr.prioceiling设为当前任务的优先级. }//注意任务优先级有32个, 是0最高,31最低!!!这里等于提高了任务的优先级,目的是让其在下次调度中继续提高被选中的概率,从而快速的释放锁. return LOS_OK; } //递归锁muxCount>0 如果是递归锁就要处理两种情况 1.runtask持有锁 2.锁被别的任务拿走了 if (((LosTaskCB *)mutex->owner == runTask) && (mutex->attr.type == LOS_MUX_RECURSIVE)) {//第一种情况 runtask是锁持有方 mutex->muxCount++; //递归锁计数器加1,递归锁的目的是防止死锁,鸿蒙默认用的就是递归锁(LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE) return LOS_OK; //成功退出 } //到了这里说明锁在别的任务那里,当前任务只能被阻塞了. if (!timeout) {//参数timeout表示等待多久再来拿锁 return LOS_EINVAL;//timeout = 0表示不等了,没拿到锁就返回不纠结,返回错误.见于LOS_MuxTrylock } //自己要被阻塞,只能申请调度,让出CPU core 让别的任务上 if (!OsPreemptableInSched()) {//不能申请调度 (不能调度的原因是因为没有持有调度任务自旋锁) return LOS_EDEADLK;//返回错误,自旋锁被别的CPU core 持有 } OsMuxBitmapSet(mutex, runTask, (LosTaskCB *)mutex->owner);//设置锁位图,尽可能的提高锁持有任务的优先级 owner = (LosTaskCB *)mutex->owner; //记录持有锁的任务 runTask->taskMux = (VOID *)mutex; //记下当前任务在等待这把锁 node = OsMuxPendFindPos(runTask, mutex);//在等锁链表中找到一个优先级比当前任务更低的任务 ret = OsTaskWait(node, timeout, TRUE);//task陷入等待状态 TRUE代表需要调度 if (ret == LOS_ERRNO_TSK_TIMEOUT) {//这行代码虽和OsTaskWait挨在一起,但要过很久才会执行到,因为在OsTaskWait中CPU切换了任务上下文 runTask->taskMux = NULL;// 所以重新回到这里时可能已经超时了 ret = LOS_ETIMEDOUT;//返回超时 } if (timeout != LOS_WAIT_FOREVER) {//不是永远等待的情况 OsMuxBitmapRestore(mutex, runTask, owner);//恢复锁的位图 } return ret; } 释放锁的主体函数 OsMuxPostOp //是否有其他任务持有互斥锁而处于阻塞状,如果是就要唤醒它,注意唤醒一个任务的操作是由别的任务完成的 //OsMuxPostOp只由OsMuxUnlockUnsafe,参数任务归还锁了,自然就会遇到锁要给谁用的问题, 因为很多任务在申请锁,由OsMuxPostOp来回答这个问题 STATIC UINT32 OsMuxPostOp(LosTaskCB *taskCB, LosMux *mutex, BOOL *needSched) { LosTaskCB *resumedTask = NULL; if (LOS_ListEmpty(&mutex->muxList)) {//如果互斥锁列表为空 LOS_ListDelete(&mutex->holdList);//把持有互斥锁的节点摘掉 mutex->owner = NULL; return LOS_OK; } resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(mutex->muxList)));//拿到等待互斥锁链表的第一个任务实体,接下来要唤醒任务 if (mutex->attr.protocol == LOS_MUX_PRIO_INHERIT) {//互斥锁属性协议是继承会怎么操作? if (resumedTask->priority > taskCB->priority) {//拿到锁的任务优先级低于参数任务优先级 if (LOS_HighBitGet(taskCB->priBitMap) != resumedTask->priority) {//参数任务bitmap中最低的优先级不等于等待锁的任务优先级 LOS_BitmapClr(&taskCB->priBitMap, resumedTask->priority);//把等待任务锁的任务的优先级记录在参数任务的bitmap中 } } else if (taskCB->priBitMap != 0) {//如果bitmap不等于0说明参数任务至少有任务调度的优先级 OsMuxPostOpSub(taskCB, mutex);// } } mutex->muxCount = 1;//互斥锁数量为1 mutex->owner = (VOID *)resumedTask;//互斥锁的持有人换了 resumedTask->taskMux = NULL;//resumedTask不再等锁了 LOS_ListDelete(&mutex->holdList);//自然要从等锁链表中把自己摘出去 LOS_ListTailInsert(&resumedTask->lockList, &mutex->holdList);//把锁挂到恢复任务的锁链表上,lockList是任务持有的所有锁记录 OsTaskWake(resumedTask);//resumedTask有了锁就唤醒它,因为当初在没有拿到锁时处于了pend状态 if (needSched != NULL) {//如果不为空 *needSched = TRUE;//就走起再次调度流程 } return LOS_OK; } 总结 1.互斥锁解决的是任务间竞争共享内存的问题. 2.申请锁失败的任务会进入睡眠OsTaskWait,内核会比较持有锁的任务和申请锁任务的优先级,把持有锁的任务优先级调到尽可能的高,以便更快的被调度执行,早日释放锁. 3.释放锁的任务会在等锁链表中找一个高优先级任务,通过OsTaskWake唤醒它,并向调度算法申请调度.但要注意,调度算法只是按优先级来调度,并不保证调度后的任务一定是要唤醒的任务. 4.互斥锁篇关键是看懂 OsMuxPendOp 和 OsMuxPostOp 两个函数. 喜欢就请收藏吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织. 鸿蒙内核源码中文注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆, 四大源码仓每日同步更新< Gitee | Github | CSDN | Coding > 鸿蒙内核源码分析博客 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新< OSCHINA | CSDN | WeHarmony >

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

web开发模式

Model1 显示层,控制层,数据层,统一交给jsp或者javabean处理. 处理流程 客户端发送request 到 jsp jsp 执行javabean javabean读取databases 返回 databases 返回给javabean 在返回给jsp 在response 给客户端 问题 代码杂乱即 jsp + dao 方式 model - view - controller 客户端发送request 到servlet 然后servlet 执行javabean javabean用于读取databases 控制器,获取到javabean读取的数据以后,再次返回给jsp,jso生成html文件,response 给客户端分为 显示层 控制层 模型层 EJB 属于SUN提供的分布式组件服务 分为会话bean 实体bean 消息驱动bean 实栗 一个登录程序用户提交登录信息,发送给servlet servlet数据验证失败将会返回给登录页,同时servlet将会调用数据层操作dao,dao到数据库databases进行验证,结果返回给servlet 然后返回两个结果,登录成功,登录失败. 代码如下 创建数据库 no 列名称 描述 1 userid 保存用户的登录id 2 name 用户真实姓名 3 password 用户密码 目录结构如下 ![](https://melovemingming-1253878077.cos.ap-chengdu.myqcloud.com/blog-image/2019/03/2019-03-17%2002-08-04%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE.png) jdbc层 package com.ming.dbc; import java.sql.Connection; import java.sql.DriverManager; public class DatabaseConnection { private static final String DBDRIVER = "com.mysql.cj.jdbc.Driver"; private static final String DBURL = "jdbc:mysql://47.94.95.84:32786/test"; private static final String DBUSER = "test"; private static final String DBPASSWORD = "ABCcba20170607"; private Connection connection = null; // 连接数据库 public DatabaseConnection() throws Exception{ try{ Class.forName(DBDRIVER); connection = DriverManager.getConnection(DBURL, DBUSER, DBPASSWORD); }catch (Exception e){ throw e; } } // 获得数据库连接 public Connection getConnection(){ return this.connection; } // 数据库关闭 public void close() throws Exception{ if(this.connection != null){ try{ this.connection.close(); }catch (Exception e){ throw e; } } } } dao层 定义接口 package com.ming.dao; import com.ming.vo.User; public interface IUserDAO { /** * 用户登录验证 * @param user 传入VO对象 * @return 验证操作结果 * @throws Exception 抛出错误 */ public boolean findLogin(User user) throws Exception; } 实现类 package com.ming.dao; import com.ming.vo.User; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; public class UserDAOImpl implements IUserDAO { // 数据库连接对象 private Connection connection = null; // 操作对象 private PreparedStatement preparedStatement = null; // 数据库连接 public UserDAOImpl(Connection _connection){ this.connection = _connection; } /** * 用户登录验证 * * @param user 传入VO对象 * @return 验证操作结果 * @throws Exception 抛出错误 */ @Override public boolean findLogin(User user) throws Exception { boolean flag = false; try{ String sql = "SELECT name FROM user WHERE userid = ? AND password = ?"; // 获得实例化对象 this.preparedStatement = this.connection.prepareStatement(sql); // 设置id this.preparedStatement.setString(1, user.getUserid()); this.preparedStatement.setString(2, user.getPassword()); ResultSet resultset = this.preparedStatement.executeQuery(); if(resultset.next()){ user.setName(resultset.getString(1)); flag = true; } }catch (Exception e){ throw e; }finally { if(this.preparedStatement != null){ try{ this.preparedStatement.close(); }catch (Exception e){ throw e; } } } return flag; } } 代理类 package com.ming.dao; import com.ming.dbc.DatabaseConnection; import com.ming.vo.User; public class UserDAOProxy implements IUserDAO { private DatabaseConnection databaseConnection = null; private IUserDAO dao = null; public UserDAOProxy(){ try{ this.databaseConnection = new DatabaseConnection(); }catch (Exception e){ e.printStackTrace(); } this.dao = new UserDAOImpl(this.databaseConnection.getConnection()); } /** * 用户登录验证 * * @param user 传入VO对象 * @return 验证操作结果 * @throws Exception 抛出错误 */ @Override public boolean findLogin(User user) throws Exception { boolean flag = false; try{ flag = this.dao.findLogin(user); }catch (Exception e){ throw e; }finally { this.databaseConnection.close(); } return flag; } } 定义代理工厂 package com.ming.factory; import com.ming.dao.IUserDAO; import com.ming.dao.UserDAOProxy; public class DAOFactory { public static IUserDAO getIuserDAOInstance(){ return new UserDAOProxy(); } } 实体关系映射 package com.ming.vo; // 对user表进行映射 public class User { private String userid; private String name; private String password; public String getUserid() { return userid; } public String getName() { return name; } public String getPassword() { return password; } public void setUserid(String userid) { this.userid = userid; } public void setName(String name) { this.name = name; } public void setPassword(String password) { this.password = password; } } 视图层 <%@ page import="java.util.List" %> <%@ page import="java.util.Iterator" %><%-- Created by IntelliJ IDEA. User: ming Date: 19-3-16 Time: 下午11:07 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h2>用户登录程序</h2> <% List<String> info = (List<String>)request.getAttribute("info"); if(info != null){ Iterator<String> iterator = info.iterator(); while(iterator.hasNext()){ %> <h4><%=iterator.next()%></h4> <% } } %> <form action="loginServlet" method="post"> 用户id <input type="text" name="userid" id="uname"/> 密码 <input type="password" name="userpass" id="password"/> <input type="submit" value="登录" id="submit"/> <input type="reset" value="重置"/> </form> <script> let submit = document.getElementById("submit"); submit.onclick = (event) => { let uname = document.getElementById("uname").value; let password = document.getElementById("password").value; if(!(/^\w{5,15}/.test(uname))){ alert("用户id为5-15位"); return false; } if(!(/^\w{5,15}/.test(password))){ alert("密码必须为5-15位"); return false; } return true; } </script> </body> </html> 配置文件 <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>login</servlet-name> <servlet-class>com.ming.servlrt.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>login</servlet-name> <url-pattern>/loginServlet</url-pattern> </servlet-mapping> </web-app> mvc运行流程 表单提交到servlet,servlet调用dao进行表单验证,然后dao连接数据库进行验证,验证结果返回给业务层,即servlet,在业务层servlet中获取info等日志信息,然后服务器端跳转到运行结果页面即view层.

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

IOS开发-KVO

一、什么是kvo? key-value observing,观察者模式 观察者,观察对象属性的变化,当被观察者该属性发生变化时,观察者会接收到通知,可以在回调函数中做相应的处理 二、有什么作用? 变化处理操作可以在同一个函数中进行,先前本人都会在每次修改属性值的地方调用后续操作,比较繁琐,修改的地方也比较多,现在只要在同一个函数中操作就可以 用kvo只要做监控就行,更加方便易用,减少代码逻辑 三、使用场景: 当一个控件某个属性变化需要做别的相应操作时,比较适合用kvo,只要当该属性发生变化时,会发消息给观察者,在回调函数中做相应的操作 四、实际例子: 一)解释方法: typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) { NSKeyValueObservingOptionNew = 0x01,//改变后的值 NSKeyValueObservingOptionOld = 0x02,//改变前的值 NSKeyValueObservingOptionInitial NS_ENUM_AVAILABLE(10_5, 2_0) = 0x04, //addobserving之后会马上调用observeValueForKeyPath,不会等到值改变 NSKeyValueObservingOptionPrior NS_ENUM_AVAILABLE(10_5, 2_0) = 0x08 //分2次调用。在值改变之前和值改变之后 }; NSKeyValueObservingOptionNew =0x01,//改变后的值NSKeyValueObservingOptionOld =0x02,//改变前的值这两个用到的比较多 NSObject(NSKeyValueObserving) //一旦被观察者属性发生改变,就会调用此方法后续操作在这个方法中进行 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context; keyPath:是被观察对象的属性,字符串表示 object:被观察对象 change:属性改变的值,字典,通过objectForKey (key为 FOUNDATION_EXPORTNSString*constNSKeyValueChangeKindKey; FOUNDATION_EXPORTNSString*constNSKeyValueChangeNewKey; FOUNDATION_EXPORTNSString*constNSKeyValueChangeOldKey; FOUNDATION_EXPORTNSString*constNSKeyValueChangeIndexesKey; FOUNDATION_EXPORTNSString*constNSKeyValueChangeNotificationIsPriorKeyNS_AVAILABLE(10_5,2_0); 对应addobserving指定的NSKeyValueObservingOptions ) context:需要传输的数据(void *:任意指针类型),一般传(__bridgevoid*)self 或者 nil,用户也能传别的 for example: - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == (__bridge void*)self) { if ([keyPath isEqualToString:kKeyPathForNavigationItemRightBarButtonItems]) { //取值 NSArray *rightBarButtonItems = [change objectForKey:NSKeyValueChangeNewKey]; //需要做操作 self.navigationItem.rightBarButtonItems = rightBarButtonItems; } } else { [super observeValueForKeyPath:keyPath ofObject:objectchange:changecontext:context]; } } -------------------------------------------- 二)接口方法 NSObject(NSKeyValueObserverRegistration) - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context; - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context NS_AVAILABLE(10_7, 5_0); - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath; NSArray(NSKeyValueObserverRegistration) - (void)addObserver:(NSObject *)observer toObjectsAtIndexes:(NSIndexSet *)indexes forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context; - (void)removeObserver:(NSObject *)observer fromObjectsAtIndexes:(NSIndexSet *)indexes forKeyPath:(NSString *)keyPath context:(void *)contextNS_AVAILABLE(10_7,5_0); - (void)removeObserver:(NSObject *)observer fromObjectsAtIndexes:(NSIndexSet *)indexes forKeyPath:(NSString *)keyPath; - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context; - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context NS_AVAILABLE(10_7, 5_0); - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath; NSOrderedSet(NSKeyValueObserverRegistration) - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context; - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context NS_AVAILABLE(10_7, 5_0); - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath; NSSet(NSKeyValueObserverRegistration) - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context; - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context NS_AVAILABLE(10_7, 5_0); - (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath; NSObject(NSKeyValueObserverNotification) //这些方法都为了手动通知用到 - (void)willChangeValueForKey:(NSString *)key; - (void)didChangeValueForKey:(NSString *)key; - (void)willChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key; - (void)didChange:(NSKeyValueChange)changeKind valuesAtIndexes:(NSIndexSet *)indexes forKey:(NSString *)key; - (void)willChangeValueForKey:(NSString *)key withSetMutation:(NSKeyValueSetMutationKind)mutationKind usingObjects:(NSSet *)objects; - (void)didChangeValueForKey:(NSString *)key withSetMutation:(NSKeyValueSetMutationKind)mutationKind usingObjects:(NSSet *)objects; NSObject(NSKeyValueObservingCustomization) + (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)keyNS_AVAILABLE(10_5,2_0); + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key; **addObserver和removeObserver要成对出现 ----------------------------------------------------------------------- **手动通知: 有两种通知观察者的方式,自动通知和手动通知。顾名思义,手动通知需要在值变化时调用 willChangeValueForKey:和didChangeValueForKey: 方法通知调用者。为求简便,我们一般使用自动通知。 要使用手动通知,需要在 automaticallyNotifiesObserversForKey方法中明确告诉cocoa,哪些键值要使用手动通知: forExample: [self willChangeValueForKey:@"frame"]; self.frame = CGRectMake(0,0,320,100); [self didChangeValueForKey:@"frame"]; 这时候就会调用 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context; //重新实现NSObject类中的automaticallyNotifiesObserversForKey:方法,返回yes表示自动通知。 + (BOOL)automaticallyNotifiesObserversForKey:(NSString*)key { //当这两个值改变时,使用自动通知已注册过的观察者,观察者需要实现observeValueForKeyPath:ofObject:change:context:方法 if ([key isEqualToString:@"frame"]) { return NO; } return [super automaticallyNotifiesObserversForKey:key]; } 这时候frame就必须要手动通知 *手动通知一般不用,为了方便,都自动通知,所以这部分知道就可以了 ----------------------------------------------------------------------- 上面一些接口方法说明NSObject,NSArray,NSSet均实现了以上方法,因此我们不仅可以观察普通对象,还可以观察数组或结合类对象。 一般用的都是观察NSObject的某个属性 对NSArray进行观察是观察NSArray中每个model的属性 NSSet和NSArray差不多,只不过NSSet是无序集合 本文转自 咖啡机(K.F.J) 博客园博客,原文链接:http://www.cnblogs.com/strick/p/4036663.html ,如需转载请自行联系原作者

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

Hive UDF开发

HIVE允许用户使用UDF(user defined function)对数据进行处理。用户可以使用‘show functions’ 查看function list,可以使用'describe function function-name'查看函数说明。 [plain]view plaincopy hive>showfunctions; OK ! != ...... Timetaken:0.275seconds hive>descfunctionsubstr; OK substr(str,pos[,len])-returnsthesubstringofstrthatstartsatposandisoflengthlenorsubstr(bin,pos[,len])-returnsthesliceofbytearraythatstartsatposandisoflengthlen Timetaken:0.095seconds hive提供的build-in函数包括以下几类:1. 关系操作符:包括 = 、 <> 、 <= 、>=等2. 算数操作符:包括 + 、 - 、 *、/等3. 逻辑操作符:包括AND 、 && 、 OR 、 || 等4. 复杂类型构造函数:包括map、struct、create_union等5. 复杂类型操作符:包括A[n]、Map[key]、S.x6. 数学操作符:包括ln(double a)、sqrt(double a)等7. 集合操作符:包括size(Array<T>)、sort_array(Array<T>)等8. 类型转换函数:binary(string|binary)、cast(expr as <type>)9. 日期函数:包括from_unixtime(bigint unixtime[, string format])、unix_timestamp()等10.条件函数:包括if(boolean testCondition, T valueTrue, T valueFalseOrNull)等11. 字符串函数:包括acat(string|binary A, string|binary B...)等12. 其他:xpath、get_json_objectscii(string str)、con 编写Hive UDF有两种方式: 1. extends UDF ,重写evaluate方法 2. extends GenericUDF,重写initialize、getDisplayString、evaluate方法 编写UDF代码实例(更多例子参考https://svn.apache.org/repos/asf/hive/tags/release-0.8.1/ql/src/java/org/apache/hadoop/hive/ql/udf/):功能:大小转小写ToLowerCase.java: [plain]view plaincopy packagetest.udf; importorg.apache.hadoop.hive.ql.exec.UDF; importorg.apache.hadoop.io.Text; publicclassToLowerCaseextendsUDF{ publicTextevaluate(finalTexts){ if(s==null){returnnull;} returnnewText(s.toString().toLowerCase()); } } 功能:计算array中去重后元素个数 UDFArrayUniqElementNumber.java [java]view plaincopy packagetest.udf; importorg.apache.hadoop.hive.ql.exec.Description; importorg.apache.hadoop.hive.ql.exec.UDFArgumentException; importorg.apache.hadoop.hive.ql.exec.UDFArgumentTypeException; importorg.apache.hadoop.hive.ql.metadata.HiveException; importorg.apache.hadoop.hive.ql.udf.generic.GenericUDF; importorg.apache.hadoop.hive.serde2.objectinspector.ListObjectInspector; importorg.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; importorg.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorUtils; importorg.apache.hadoop.hive.serde2.objectinspector.ObjectInspector.Category; importorg.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory; importorg.apache.hadoop.io.IntWritable; /** *UDF: *Getnubmerofobjectswithduplicateelementseliminated *@authorxiaomin.zhou */ @Description(name="array_uniq_element_number",value="_FUNC_(array)-Returnsnubmerofobjectswithduplicateelementseliminated.",extended="Example:\n" +">SELECT_FUNC_(array(1,2,2,3,3))FROMsrcLIMIT1;\n"+"3") publicclassUDFArrayUniqElementNumberextendsGenericUDF{ privatestaticfinalintARRAY_IDX=0; privatestaticfinalintARG_COUNT=1;//NumberofargumentstothisUDF privatestaticfinalStringFUNC_NAME="ARRAY_UNIQ_ELEMENT_NUMBER";//ExternalName privateListObjectInspectorarrayOI; privateObjectInspectorarrayElementOI; privatefinalIntWritableresult=newIntWritable(-1); publicObjectInspectorinitialize(ObjectInspector[]arguments) throwsUDFArgumentException{ //Checkiftwoargumentswerepassed if(arguments.length!=ARG_COUNT){ thrownewUDFArgumentException("Thefunction"+FUNC_NAME +"accepts"+ARG_COUNT+"arguments."); } //CheckifARRAY_IDXargumentisofcategoryLIST if(!arguments[ARRAY_IDX].getCategory().equals(Category.LIST)){ thrownewUDFArgumentTypeException(ARRAY_IDX,"\"" +org.apache.hadoop.hive.serde.Constants.LIST_TYPE_NAME +"\""+"expectedatfunctionARRAY_CONTAINS,but" +"\""+arguments[ARRAY_IDX].getTypeName()+"\"" +"isfound"); } arrayOI=(ListObjectInspector)arguments[ARRAY_IDX]; arrayElementOI=arrayOI.getListElementObjectInspector(); returnPrimitiveObjectInspectorFactory.writableIntObjectInspector; } publicIntWritableevaluate(DeferredObject[]arguments) throwsHiveException{ result.set(0); Objectarray=arguments[ARRAY_IDX].get(); intarrayLength=arrayOI.getListLength(array); if(arrayLength<=1){ result.set(arrayLength); returnresult; } //elementcompare;Algorithmcomplexity:O(N^2) intnum=1; inti,j; for(i=1;i<arrayLength;i++) { ObjectlistElement=arrayOI.getListElement(array,i); for(j=i-1;j>=0;j--) { if(listElement!=null){ Objecttmp=arrayOI.getListElement(array,j); if(ObjectInspectorUtils.compare(tmp,arrayElementOI,listElement, arrayElementOI)==0){ break; } } } if(-1==j) { num++; } } result.set(num); returnresult; } publicStringgetDisplayString(String[]children){ assert(children.length==ARG_COUNT); return"array_uniq_element_number("+children[ARRAY_IDX]+")"; } } 生成udf.jarhive有三种方法使用自定义的UDF函数 1. 临时添加UDF如下: [plain]view plaincopy hive>select*fromtest; OK Hello wORLD ZXM ljz Timetaken:13.76seconds hive>addjar/home/work/udf.jar; Added/home/work/udf.jartoclasspath Addedresource:/home/work/udf.jar hive>createtemporaryfunctionmytestas'test.udf.ToLowerCase'; OK Timetaken:0.103seconds hive>showfunctions; ...... mytest ...... hive>selectmytest(test.name)fromtest; ...... OK hello world zxm ljz Timetaken:38.218seconds 这种方式在会话结束后,函数自动销毁,因此每次打开新的会话,都需要重新add jar并且create temporary function2. 进入会话前自动创建使用hive -i参数在进入hive时自动初始化 [plain]view plaincopy $cathive_init addjar/home/work/udf.jar; createtemporaryfunctionmytestas'test.udf.ToLowerCase'; $hive-ihive_init Logginginitializedusingconfigurationinfile:/home/work/hive/hive-0.8.1/conf/hive-log4j.properties Hivehistoryfile=/tmp/work/hive_job_log_work_201209200147_1951517527.txt hive>showfunctions; ...... mytest ...... hive>selectmytest(test.name)fromtest; ...... OK hello world zxm ljz 方法2和方法1本质上是相同的,区别在于方法2在会话初始化时自动完成3. 自定义UDF注册为hive内置函数可参考:hive利器自定义UDF+重编译hive 和前两者相比,第三种方式直接将用户的自定义函数作为注册为内置函数,未来使用起来非常简单,但这种方式也非常危险,一旦出错,将是灾难性的,因此,建议如果不是特别通用,并且固化下来的函数,还是使用前两种方式比较靠谱。 本文转自 yntmdr 51CTO博客,原文链接:http://blog.51cto.com/yntmdr/1716940,如需转载请自行联系原作者

资源下载

更多资源
Mario

Mario

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

腾讯云软件源

腾讯云软件源

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

Spring

Spring

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

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册