首页 文章 精选 留言 我的

精选列表

搜索[全场景互联],共10000篇文章
优秀的个人博客,低调大师

GEO 核心技术名词全解

生成式引擎正在重塑信息分发逻辑,内容被'看见'的方式从链接列表转向 AI 合成答案。本文穷尽梳理 GEO(Generative Engine Optimization,生成式引擎优化)领域的核心技术名词,按'检索与生成基础架构—引用与归因机制—实体与知识表示—内容质量与可信度信号—模型训练、对齐与治理'五条主线归类,每项给出定义、原理、GEO 意义与实操要点,供从业者建立完整的方法论框架。

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

HarmonyOS NEXT:使用全自研内核

在本周五的 2023 年华为开发者大会(HDC.Together)上,除了面向消费者的HarmonyOS4 之外,华为还推出了面向开发者的 HarmonyOS NEXT 开发者预览版。 华为表示,HarmonyOS NEXT 即日起面向合作企业开发者开放,2024 年第一季度面向所有开发者开放! 据介绍,HarmonyOS NEXT 系统底座全线自研,去掉了传统的 AOSP 代码,仅支持鸿蒙内核和鸿蒙系统的应用,减少了 40% 的冗余代码,使系统的流畅度、能效、纯净安全特性大为提升。 华为终端BG软件部总裁龚体表示,截止当前,鸿蒙底座已有1亿多行代码,2万多个API,超过7亿的鸿蒙生态设备,鸿蒙底座已经基本成熟。HarmonyOS经过了四个大版本的迭代,已经构建了具有差异化特征的用户体验,接下来最重要的工作就是构建繁荣的鸿蒙应用生态。这是一项需要广大开发者一起参与的伟大事业。 以下内容来自官方关于 HarmonyOS NEXT 的技术介绍。 鸿蒙内核 鸿蒙内核从万物智联时代需求出发,具备天生流畅、极致能效、纯净安全三大特征。 天生流畅 极致能效 极致能效 纯净安全 华为方舟图形引擎 现在的智能终端设备,屏幕越来越大,帧率越来越高,带给图形技术的挑战也越来越大。如何用更低的能耗完成渲染,并实现更好的动画效果? 针对这两个挑战,华为推出了全新的方舟图形引擎,高能效统一渲染,有效降低渲染功耗和提升图形渲染效果。 详情:https://mp.weixin.qq.com/s/JBx-uejet2nDqHPWhuc9sg

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

Hive解析Json数组超全讲解

2 --> 在Hive中会有很多数据是用Json格式来存储的,如开发人员对APP上的页面进行埋点时,会将多个字段存放在一个json数组中,因此数据平台调用数据时,要对埋点数据进行解析。接下来就聊聊Hive中是如何解析json数据的。 本文首发于公众号【五分钟学大数据】 Hive自带的json解析函数 1. get_json_object 语法:get_json_object(json_string, '$.key') 说明:解析json的字符串json_string,返回path指定的内容。如果输入的json字符串无效,那么返回NULL。这个函数每次只能返回一个数据项。 示例: selectget_json_object('{"name":"zhangsan","age":18}','$.name'); 结果: name zhangsan 如果既要解析name字段,也解析age字段,则可以这样写: selectget_json_object('{"name":"zhangsan","age":18}','$.name'),get_json_object('{"name":"zhangsan","age":18}','$.age'); 但是如果要解析的字段有很多,再这样写就太麻烦了,所以就有了 json_tuple 这个函数。 2. json_tuple 语法: json_tuple(json_string, k1, k2 ...) 说明:解析json的字符串json_string,可指定多个json数据中的key,返回对应的value。如果输入的json字符串无效,那么返回NULL。 示例: selectb.name,b.agefromtableNamealateralviewjson_tuple('{"name":"zhangsan","age":18}','name','age')basname,age; 结果: name age zhangsan 18 注意:上面的json_tuple函数中没有$. 如果在使用json_tuple函数时加上$.就会解析失败: selectb.name,b.agefromtableNamealateralviewjson_tuple('{"name":"zhangsan","age":18}','$.name','$.age')basname,age; 结果: name age NULL NULL 字段全是NULL,所以json_tuple函数不需要加$.了,否则会解析不到。 总结:json_tuple相当于get_json_object的优势就是一次可以解析多个json字段。但是如果我们有个json数组,这两个函数都无法处理。 Hive解析json数组 一、嵌套子查询解析json数组 如果有一个hive表,表中 json_str 字段的内容如下: json_str [{"website":"baidu.com","name":"百度"},{"website":"google.com","name":"谷歌"}] 我们想把这个字段解析出来,形成如下的结构: website name baidu.com 百度 google.com 谷歌 要解析这个json数组,仅用上面介绍的两个函数就解析不出来了,还需用到如下介绍的几个函数: explode函数 语法: explode(Array OR Map) 说明:explode()函数接收一个array或者map类型的数据作为输入,然后将array或map里面的元素按照每行的形式输出,即将hive一列中复杂的array或者map结构拆分成多行显示,也被称为列转行函数。 示例: --解析arrayhive>selectexplode(array('A','B','C'));OKABC--解析maphive>selectexplode(map('A',10,'B',20,'C',30));OKA10B20C30 regexp_replace函数 语法: regexp_replace(string A, string B, string C) 说明:将字符串A中的符合java正则表达式B的部分替换为C。注意,在有些情况下要使用转义字符,类似oracle中的regexp_replace函数。 示例: hive>selectregexp_replace('foobar','oo|ar','');OKfb 上述示例将字符串中的 oo 或 ar 替换为''。 有了上述几个函数,接下来我们来解析json_str字段的内容: 先将json数组中的元素解析出来,转化为每行显示: hive>SELECTexplode(split(regexp_replace(regexp_replace('[{"website":"baidu.com","name":"百度"},{"website":"google.com","name":"谷歌"}]','\\[|\\]',''),'\\}\\,\\{','\\}\\;\\{'),'\\;'));OK{"website":"baidu.com","name":"百度"}{"website":"google.com","name":"谷歌"} 对上述sql进行简要说明: SELECTexplode(split(regexp_replace(regexp_replace('[{"website":"baidu.com","name":"百度"},{"website":"google.com","name":"谷歌"}]','\\[|\\]',''),将json数组两边的中括号去掉'\\}\\,\\{','\\}\\;\\{'),将json数组元素之间的逗号换成分号'\\;')以分号作为分隔符(split函数以分号作为分隔)); 为什么要将json数组元素之间的逗号换成分号?因为元素内的分隔也是逗号,如果不将元素之间的逗号换掉的话,后面用split函数分隔时也会把元素内的数据给分隔,这不是我们想要的结果。 上步已经把一个json数组转化为多个json字符串了,接下来结合son_tuple函数来解析json里面的字段: selectjson_tuple(explode(split(regexp_replace(regexp_replace('[{"website":"baidu.com","name":"百度"},{"website":"google.com","name":"谷歌"}]','\\[|\\]',''),'\\}\\,\\{','\\}\\;\\{'),'\\;')),'website','name'); 执行上述语句,结果报错了:FAILED: SemanticException [Error 10081]: UDTF's are not supported outside the SELECT clause, nor nested in expressions 意思是UDTF函数不能写在别的函数内,也就是这里的explode函数不能写在json_tuple里面。 既然explode函数不能写在别的json_tuple里面,那我们可以用子查询方式,如下所示: selectjson_tuple(json,'website','name')from(selectexplode(split(regexp_replace(regexp_replace('[{"website":"baidu.com","name":"百度"},{"website":"google.com","name":"谷歌"}]','\\[|\\]',''),'\\}\\,\\{','\\}\\;\\{'),'\\;'))asjson)t; 执行上述语句,没有报错,执行结果如下: www.baidu.com百度google.com谷歌 二 使用 lateral view 解析json数组 hive表中 goods_id 和 json_str 字段的内容如下: goods_id json_str 1,2,3 [{"source":"7fresh","monthSales":4900,"userCount":1900,"score":"9.9"},{"source":"jd","monthSales":2090,"userCount":78981,"score":"9.8"},{"source":"jdmart","monthSales":6987,"userCount":1600,"score":"9.0"}] 目的:把 goods_id 字段和 json_str 字段中的monthSales解析出来。 下面我们就开始解析: 拆分goods_id字段及将json数组转化成多个json字符串: selectexplode(split(goods_id,','))asgood_id,explode(split(regexp_replace(regexp_replace(json_str,'\\[|\\]',''),'\\}\\,\\{','\\}\\;\\{'),'\\;'))assale_infofromtableName; 执行上述语句,结果报错:FAILED: SemanticException 3:0 Only a single expression in the SELECT clause is supported with UDTF's. Error encountered near token 'sale_info' 意思是用UDTF的时候,SELECT 只支持一个字段。而上述语句select中有两个字段,所以报错了。 那怎么办呢,要解决这个问题,还得再介绍一个hive语法: lateral view lateral view用于和split、explode等UDTF一起使用的,能将一行数据拆分成多行数据,在此基础上可以对拆分的数据进行聚合,lateral view首先为原始表的每行调用UDTF,UDTF会把一行拆分成一行或者多行,lateral view在把结果组合,产生一个支持别名表的虚拟表。 示例: 假设我们有一张用户兴趣爱好表 hobbies_table,它有两列数据,第一列是name,第二列是用户兴趣爱好的id_list,是一个数组,存储兴趣爱好的id值: name id_list zhangsan [1,2,3] lisi [3,4,5] 我们要统计所有兴趣id在所有用户中出现的次数: 对兴趣id进行解析: SELECTname,hobby_idFROMhobbies_tableLATERALVIEWexplode(id_list)tmp_tableAShobby_id; 上述sql执行结果: name hobby_id zhangsan 1 zhangsan 2 zhangsan 3 lisi 3 lisi 4 lisi 5 将id_list拆分完之后统计在用户中出现的次数就简单了,按照hobby_id进行分组聚合即可: SELECThobby_id,count(name)client_numFROMhobbies_tableLATERALVIEWexplode(id_list)tmp_tableAShobby_idgroupbyhobby_id; 结果: hobby_id client_num 1 1 2 1 3 2 4 1 5 1 介绍完 lateral view 之后,我们再来解决上面遇到的用UDTF的时候,SELECT 只支持一个字段的问题: selectgood_id,get_json_object(sale_json,'$.monthSales')asmonthSalesfromtableNameLATERALVIEWexplode(split(goods_id,','))goodsasgood_idLATERALVIEWexplode(split(regexp_replace(regexp_replace(json_str,'\\[|\\]',''),'\\}\\,\\{','\\}\\;\\{'),'\\;'))salesassale_json; 注意:上述语句是三个表笛卡尔积的结果,所以此方式适用于数据量不是很大的情况。 上述语句执行结果如下: goods_id monthSales 1 4900 1 2090 1 6987 2 4900 2 2090 2 6987 3 4900 3 2090 3 6987 如果表中还有其他字段,我们可以根据其他字段筛选出符合结果的数据。 总结:lateral view通常和UDTF一起出现,为了解决UDTF不允许在select存在多个字段的问题。 公众号【五分钟学大数据】,大数据领域原创技术号,专注于大数据技术研究

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

init模块全解析 | 解读鸿蒙源码

从系统启动流程来看,init位于kernel启动之后,user程序启动以前。user程序,是指用户可交互的程序(比如Home、Shell、智能快递柜的交互程序等),也指最终的业务程序(比如智能音箱的业务程序、扫地机器人的工作程序等)。 init进程是系统的第一号用户空间进程,所有的系统进程和user进程都是由它fork()而来,即都是它的子进程。 init模块负责解析系统引导配置文件,并执行里面的命令,完成系统的引导操作。鸿蒙OS的引导配置文件使用JSON格式。系统开发人员会在这里接触到鸿蒙系统的第一个配置文件。这一点应该是借鉴Linux系操作系统。我们知道Android系统也是基于Linux内核开发的,也有自己实现的init引导程序和自己的Android initial language编写的init.rc引导配置文件。这些都是遵从或借鉴Linux操作系统,Linux操作系统就是通过init引导程序解析执行不同目录的shell脚本来进行系统引导的。 下面直接撸代码,来看一下鸿蒙init引导程序的实现。(源码部分会有些枯燥,操作代码+注释的形式开展) init模块源码分析 code-1.0/base/startup/services/init_lite/ 上面这个是init模块的代码目录。这个模块很小巧,整个目录所有文件加起来只有108KB,源文件只有8个。最大的源文件代码不超过350行。 init主流程 code-1.0/base/startup/services/init_lite/src/main.c int main(int argc, char * const argv[]) { // 1. print system info PrintSysInfo(); // 2. signal register SignalInitModule(); // 3. read configuration file and do jobs InitReadCfg(); // 4. keep process alive printf("[Init] main, entering wait.\n"); while (1) { // pause only returns when a signal was caught and the signal-catching function returned. // pause only returns -1, no need to process the return value. (void)pause(); } return 0; } 以上就是init进程进行系统引导的主流程: 打印系统信息 注册信号 读取系统引导配置文件并执行相应的任务 init进程进入无限循环状态 这个流程很清晰简洁。看过Android操作系统init模块源代码的人,应该会很有感触,这份代码短小精悍,阅读起来很轻松。或许,毕竟这只是HarmonyOS 2.0,不知道经过几个版本的迭代会不会也变得很臃肿呢。 下面详细分析每一步的原理和代码实现。 打印系统信息 这一步是把系统信息输出到控制台,系统信息是由多个字段拼接而成的。这个系统信息类似Android操作系统的fingerprint,是一个很长的字符串,里面包含厂商、品牌、编译类型等。 code-1.0/base/startup/services/init_lite/src/main.c static void PrintSysInfo() { char* sysInfo = GetVersionId(); if (sysInfo != NULL) { printf("[Init] %s\n", sysInfo); // 看这两行代码,主动释放内存,用完立刻释放。 // 鸿蒙OS对内存管理的很好,阅读系统源码的时候,随处可以看到这个设计信条 free(sysInfo); sysInfo = NULL; return; } printf("[Init] main, GetVersionId failed!\n"); } code-1.0/base/startup/frameworks/syspara_lite/parameter/src/parameter_common.c char* GetVersionId(void) { char* value = (char*)malloc(VERSION_ID_LEN); if (value == NULL) { return NULL; } if (memset_s(value, VERSION_ID_LEN, 0, VERSION_ID_LEN) != 0) { free(value); value = NULL; return NULL; } char* productType = GetProductType(); char* manufacture = GetManufacture(); char* brand = GetBrand(); char* productSerial = GetProductSeries(); char* productModel = GetProductModel(); char* softwareModel = GetSoftwareModel(); if (productType == NULL || manufacture == NULL || brand == NULL || productSerial == NULL || productModel == NULL || softwareModel == NULL) { free(productType); free(manufacture); free(brand); free(productSerial); free(productModel); free(softwareModel); free(value); value = NULL; return NULL; } int len = sprintf_s(value, VERSION_ID_LEN, "%s/%s/%s/%s/%s/%s/%s/%s/%s/%s", productType, manufacture, brand, productSerial, g_roBuildOs, productModel, softwareModel, g_roSdkApiLevel, INCREMENTAL_VERSION, BUILD_TYPE); free(productType); free(manufacture); free(brand); free(productSerial); free(productModel); free(softwareModel); if (len < 0) { free(value); value = NULL; return NULL; } return value; } 这里涉及到10个参数,分别是:产品类型、制造商、品牌、产品串号、产品型号、软件型号、操作系统名称、SDK版本号、软件版本、编译类型。用“/”隔开各个字段,形成的字符串就是打印的系统信息。 其中前面6个参数是设备厂商定义的,这包代码在下面的文件进行配置: code-1.0/vendor/huawei/camera/hals/utils/sys_param/hal_sys_param.c static const char OHOS_PRODUCT_TYPE[] = {"****"}; static const char OHOS_MANUFACTURE[] = {"****"}; static const char OHOS_BRAND[] = {"****"}; static const char OHOS_PRODUCT_SERIES[] = {"****"}; static const char OHOS_PRODUCT_MODEL[] = {"****"}; static const char OHOS_SOFTWARE_MODEL[] = {"****"}; 第7、8个参数是在下面的文件中定义的,标示操作系统名称和SDK版本: code-1.0/base/startup/frameworks/syspara_lite/parameter/src/parameter_common.c static char g_roBuildOs[] = {"OpenHarmony"}; static char g_roSdkApiLevel[] = {"3"}; 第9个参数是在产品配置的json脚本中配置的,然后通过编译选项传给源代码使用: code-1.0/base/startup/frameworks/syspara_lite/parameter/src/BUILD.gn code-1.0/build/lite/product/ipcamera_hi3518ev300.json 第10个参数是编译系统时,传入的参数,然后再通过编译选项传给源代码使用: code-1.0/base/startup/frameworks/syspara_lite/parameter/src/BUILD.gn defines = [ "INCREMENTAL_VERSION=\"${ohos_version}\"", "BUILD_TYPE=\"${ohos_build_type}\"", "BUILD_USER=\"${ohos_build_user}\"", "BUILD_TIME=\"${ohos_build_time}\"", "BUILD_HOST=\"${ohos_build_host}\"", "BUILD_ROOTHASH=\"${ohos_build_roothash}\"", ] 没有开发板,没办法把系统跑起来,就不贴实际输出效果图了。 注册信号 code-1.0/base/startup/services/init_lite/src/init_signal_handler.c void SignalInitModule() { struct sigaction act; act.sa_handler = SigHandler; act.sa_flags = SA_RESTART; (void)sigfillset(&act.sa_mask); sigaction(SIGCHLD, &act, NULL); sigaction(SIGTERM, &act, NULL); } 当信号SIGCHLD和SIGTERM发生的时候,会回调函数SigHandler()。 static void SigHandler(int sig) { switch (sig) { case SIGCHLD: { pid_t sigPID; int procStat = 0; printf("[Init] SigHandler, SIGCHLD received.\n"); while (1) { // 非阻塞状态下,等待任意子进程结束返回 sigPID = waitpid(-1, &procStat, WNOHANG); if (sigPID <= 0) { break; } ReapServiceByPID((int)sigPID); } break; } case SIGTERM: { printf("[Init] SigHandler, SIGTERM received.\n"); StopAllServices(); break; } default: printf("[Init] SigHandler, unsupported signal %d.\n", sig); break; } } SIGCHLD:当子进程停止或退出时通知父进程。 SIGTERM:程序结束信号,与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出。 杀死所有服务 code-1.0/base/startup/services/init_lite/src/init_service_manager.c void StopAllServices() { for (size_t i = 0; i < g_servicesCnt; i++) { if (ServiceStop(&g_services[i]) != SERVICE_SUCCESS) { printf("[Init] StopAllServices, service %s stop failed!\n", g_services[i].name); } } } code-1.0/base/startup/services/init_lite/src/init_service.c int ServiceStop(Service *service) { service->attribute &= ~SERVICE_ATTR_NEED_RESTART; service->attribute |= SERVICE_ATTR_NEED_STOP; if (service->pid <= 0) { return SERVICE_SUCCESS; } // 直接向服务进程发送SIGKILL信号,杀死进程 if (kill(service->pid, SIGKILL) != 0) { printf("[Init] stop service %s pid %d failed! err %d.\n", service->name, service->pid, errno); return SERVICE_FAILURE; } printf("[Init] stop service %s, pid %d.\n", service->name, service->pid); return SERVICE_SUCCESS; } 如果收到程序结束信号SIGTERM,会遍历服务列表,服务列表里面保存着所有服务的pid,通过向pid发送SIGKILL信号,来杀死进程。 Reap Service 如果收到子进程停止或退出的信号SIGCHLD code-1.0/base/startup/services/init_lite/src/init_service_manager.c void ReapServiceByPID(int pid) { for (size_t i = 0; i < g_servicesCnt; i++) { if (g_services[i].pid == pid) { if (g_services[i].attribute & SERVICE_ATTR_IMPORTANT) { // important process exit, need to reboot system g_services[i].pid = -1; StopAllServices(); RebootSystem(); } ServiceReap(&g_services[i]); break; } } } 这里分两种情况:如果死掉的是一个important process,则需要杀死所有服务进程,然后重启系统;否则,进行服务收割。 code-1.0/base/startup/services/init_lite/src/init_service.c void ServiceReap(Service *service) { // 首先将服务pid设置为-1 service->pid = -1; // init设置了服务属性NEED_STOP,所以不需要重启,直接返回 if (service->attribute & SERVICE_ATTR_NEED_STOP) { service->attribute &= (~SERVICE_ATTR_NEED_STOP); service->crashCnt = 0; return; } // 具有ONCE属性的服务 if (service->attribute & SERVICE_ATTR_ONCE) { // no need to restart if (!(service->attribute & SERVICE_ATTR_NEED_RESTART)) { service->attribute &= (~SERVICE_ATTR_NEED_STOP); return; } // the service could be restart even if it is one-shot service } // the service that does not need to be restarted restarts, indicating that it has crashed if (!(service->attribute & SERVICE_ATTR_NEED_RESTART)) { // crash time and count check time_t curTime = time(NULL); // 记录崩溃次数和时间 if (service->crashCnt == 0) { service->firstCrashTime = curTime; ++service->crashCnt; } else if (difftime(curTime, service->firstCrashTime) > CRASH_TIME_LIMIT) { service->firstCrashTime = curTime; service->crashCnt = 1; } else { ++service->crashCnt; // 崩溃超过4次,就不在尝试重启 if (service->crashCnt > CRASH_COUNT_LIMIT) { printf("[Init] reap service %s, crash too many times!\n", service->name); return; } } } // 重启服务 int ret = ServiceStart(service); if (ret != SERVICE_SUCCESS) { printf("[Init] reap service %s start failed!\n", service->name); } // 清除服务的NEED_RESTART属性 service->attribute &= (~SERVICE_ATTR_NEED_RESTART); } 服务属性的状态变化似有点绕,没太看懂!!! 启动服务 code-1.0/base/startup/services/init_lite/src/init_service.c int ServiceStart(Service *service) { // 首先检查服务属性,如果是无效属性,不执行服务启动 if (service->attribute & SERVICE_ATTR_INVALID) { printf("[Init] start service %s invalid.\n", service->name); return SERVICE_FAILURE; } struct stat pathStat = {0}; service->attribute &= (~(SERVICE_ATTR_NEED_RESTART | SERVICE_ATTR_NEED_STOP)); // 检查服务可执行文件路径,如果文件不存在,则不执行服务启动 if (stat(service->path, &pathStat) != 0) { service->attribute |= SERVICE_ATTR_INVALID; printf("[Init] start service %s invalid, please check %s.\n", service->name, service->path); return SERVICE_FAILURE; } // 调用fork(),创建子进程 int pid = fork(); if (pid == 0) { // permissions if (SetPerms(service) != SERVICE_SUCCESS) { printf("[Init] service %s exit! set perms failed! err %d.\n", service->name, errno); _exit(0x7f); // 0x7f: user specified } char* argv[] = {service->name, NULL}; char* env[] = {NULL}; // 启动服务的可执行文件,传入文件名称参数 if (execve(service->path, argv, env) != 0) { printf("[Init] service %s execve failed! err %d.\n", service->name, errno); } _exit(0x7f); // 0x7f: user specified } else if (pid < 0) { // 子进程创建失败 printf("[Init] start service %s fork failed!\n", service->name); return SERVICE_FAILURE; } // 将得到的pid保存在服务的数据结构里面 service->pid = pid; printf("[Init] start service %s succeed, pid %d.\n", service->name, service->pid); return SERVICE_SUCCESS; } 启动服务,采用fork+execve。 重启系统 code-1.0/base/startup/services/init_lite/src/init_adapter.c void RebootSystem() { #ifdef __LINUX__ int ret = reboot(RB_DISABLE_CAD); #else int ret = syscall(__NR_shellexec, "reset", "reset"); #endif if (ret != 0) { printf("[Init] reboot failed! syscall ret %d, err %d.\n", ret, errno); } } 读取配置文件并执行任务 这是init模块的重点。 code-1.0/base/startup/services/init_lite/src/init_read_cfg.c void InitReadCfg() { // 读取json格式的引导配置文件 char* fileBuf = ReadFileToBuf(); if (fileBuf == NULL) { printf("[Init] InitReadCfg, read file %s failed! err %d.\n", INIT_CONFIGURATION_FILE, errno); return; } // 解析json文件 cJSON* fileRoot = cJSON_Parse(fileBuf); free(fileBuf); fileBuf = NULL; if (fileRoot == NULL) { printf("[Init] InitReadCfg, parse failed! please check file %s format.\n", INIT_CONFIGURATION_FILE); return; } // 得到服务数据 ParseAllServices(fileRoot); // 得到任务数据 ParseAllJobs(fileRoot); // 释放内存 cJSON_Delete(fileRoot); // 执行任务 DoJob("pre-init"); DoJob("init"); DoJob("post-init"); // 释放Jobs数据结构占据的内存 ReleaseAllJobs(); } 读取配置文件 #define INIT_CONFIGURATION_FILE "/etc/init.cfg" static char* ReadFileToBuf() { char* buffer = NULL; FILE* fd = NULL; struct stat fileStat = {0}; // ??? do...while...0 看不懂,不知道在搞啥? do { // 检查文件有效性 if (stat(INIT_CONFIGURATION_FILE, &fileStat) != 0 || fileStat.st_size <= 0 || fileStat.st_size > MAX_JSON_FILE_LEN) { break; } // 以只读方式打开文件 fd = fopen(INIT_CONFIGURATION_FILE, "r"); if (fd == NULL) { break; } // 分配文件size+1的空间 buffer = (char*)malloc(fileStat.st_size + 1); if (buffer == NULL) { break; } // 从文件读取数据到buffer if (fread(buffer, fileStat.st_size, 1, fd) != 1) { free(buffer); buffer = NULL; break; } // buffer最后一个字节写空字符 buffer[fileStat.st_size] = '\0'; } while (0); if (fd != NULL) { fclose(fd); fd = NULL; } return buffer; } 解析JSON文件 workspace/code-1.0/third_party/cJSON/cJSON.c CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) { return cJSON_ParseWithOpts(value, 0, 0); } CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) { size_t buffer_length; if (NULL == value) { return NULL; } /* Adding null character size due to require_null_terminated. */ buffer_length = strlen(value) + sizeof(""); return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); } 这里使用开源的cJSON库来进行JSON文件解析。 得到服务数据 code-1.0/base/startup/services/init_lite/src/init_read_cfg.c static void ParseAllServices(const cJSON* fileRoot) { int servArrSize = 0; cJSON* serviceArr = GetArrItem(fileRoot, &servArrSize, SERVICES_ARR_NAME_IN_JSON); if (serviceArr == NULL) { printf("[Init] InitReadCfg, get array %s failed.\n", SERVICES_ARR_NAME_IN_JSON); return; } // 限制配置服务的最大数量是100个 if (servArrSize > MAX_SERVICES_CNT_IN_FILE) { printf("[Init] InitReadCfg, too many services[cnt %d] detected, should not exceed %d.\n",\ servArrSize, MAX_SERVICES_CNT_IN_FILE); return; } // 申请空间存放服务数据 Service* retServices = (Service*)malloc(sizeof(Service) * servArrSize); if (retServices == NULL) { printf("[Init] InitReadCfg, malloc for %s arr failed! %d.\n", SERVICES_ARR_NAME_IN_JSON, servArrSize); return; } if (memset_s(retServices, sizeof(Service) * servArrSize, 0, sizeof(Service) * servArrSize) != EOK) { free(retServices); retServices = NULL; return; } // 遍历服务队列,读取数据到`retServices` for (int i = 0; i < servArrSize; ++i) { // 取得一个JSON格式的服务数据 cJSON* curItem = cJSON_GetArrayItem(serviceArr, i); // 获取服务name和path if (GetServiceString(curItem, &retServices[i], "name", MAX_SERVICE_NAME) != SERVICE_SUCCESS || GetServiceString(curItem, &retServices[i], "path", MAX_SERVICE_PATH) != SERVICE_SUCCESS) { retServices[i].attribute |= SERVICE_ATTR_INVALID; printf("[Init] InitReadCfg, bad string values for service %d.\n", i); continue; } // 获取服务uid、gid、once、importance、 if (GetServiceNumber(curItem, &retServices[i], UID_STR_IN_CFG) != SERVICE_SUCCESS || GetServiceNumber(curItem, &retServices[i], GID_STR_IN_CFG) != SERVICE_SUCCESS || GetServiceNumber(curItem, &retServices[i], ONCE_STR_IN_CFG) != SERVICE_SUCCESS || GetServiceNumber(curItem, &retServices[i], IMPORTANT_STR_IN_CFG) != SERVICE_SUCCESS) { retServices[i].attribute |= SERVICE_ATTR_INVALID; printf("[Init] InitReadCfg, bad number values for service %d.\n", i); continue; } // 获取服务caps if (GetServiceCaps(curItem, &retServices[i]) != SERVICE_SUCCESS) { retServices[i].attribute |= SERVICE_ATTR_INVALID; printf("[Init] InitReadCfg, bad caps values for service %d.\n", i); } } // 赋值给全局变量`g_services` RegisterServices(retServices, servArrSize); } // All serivce processes that init will fork+exec. static Service* g_services = NULL; static int g_servicesCnt = 0; void RegisterServices(Service* services, int servicesCnt) { g_services = services; g_servicesCnt = servicesCnt; } 得到任务数据 code-1.0/base/startup/services/init_lite/src/init_jobs.c void ParseAllJobs(const cJSON* fileRoot) { if (fileRoot == NULL) { printf("[Init] ParseAllJobs, input fileRoot is NULL!\n"); return; } // 取得`jobs`的JSON格式的队列 cJSON* jobArr = cJSON_GetObjectItemCaseSensitive(fileRoot, JOBS_ARR_NAME_IN_JSON); int jobArrSize = 0; if (cJSON_IsArray(jobArr)) { jobArrSize = cJSON_GetArraySize(jobArr); } // 最大支持10个任务(组) if (jobArrSize <= 0 || jobArrSize > MAX_JOBS_COUNT) { printf("[Init] ParseAllJobs, jobs count %d is invalid, should be positive and not exceeding %d.\n",\ jobArrSize, MAX_JOBS_COUNT); return; } // 分配内存 Job* retJobs = (Job*)malloc(sizeof(Job) * jobArrSize); if (retJobs == NULL) { printf("[Init] ParseAllJobs, malloc failed! job arrSize %d.\n", jobArrSize); return; } if (memset_s(retJobs, sizeof(Job) * jobArrSize, 0, sizeof(Job) * jobArrSize) != EOK) { printf("[Init] ParseAllJobs, memset_s failed.\n"); free(retJobs); retJobs = NULL; return; } for (int i = 0; i < jobArrSize; ++i) { cJSON* jobItem = cJSON_GetArrayItem(jobArr, i); ParseJob(jobItem, &(retJobs[i])); } // 赋值给全局变量`g_jobs` g_jobs = retJobs; g_jobCnt = jobArrSize; } static void ParseJob(const cJSON* jobItem, Job* resJob) { // 取得任务名称。 // 任务名称为pre-init/init/post-init三个中一个 if (!GetJobName(jobItem, resJob)) { (void)memset_s(resJob, sizeof(*resJob), 0, sizeof(*resJob)); return; } // 获取任务对应的cmd的JSON数据 cJSON* cmdsItem = cJSON_GetObjectItem(jobItem, CMDS_ARR_NAME_IN_JSON); if (!cJSON_IsArray(cmdsItem)) { return; } // 获取cmd的数量 int cmdLinesCnt = cJSON_GetArraySize(cmdsItem); if (cmdLinesCnt <= 0) { // empty job, no cmd return; } // 一个任务组的cmd不能超过30个 if (cmdLinesCnt > MAX_CMD_CNT_IN_ONE_JOB) { printf("[Init] ParseAllJobs, too many cmds[cnt %d] in one job, it should not exceed %d.\n",\ cmdLinesCnt, MAX_CMD_CNT_IN_ONE_JOB); return; } // 分配内存 resJob->cmdLines = (CmdLine*)malloc(cmdLinesCnt * sizeof(CmdLine)); if (resJob->cmdLines == NULL) { return; } if (memset_s(resJob->cmdLines, cmdLinesCnt * sizeof(CmdLine), 0, cmdLinesCnt * sizeof(CmdLine)) != EOK) { free(resJob->cmdLines); resJob->cmdLines = NULL; return; } resJob->cmdLinesCnt = cmdLinesCnt; for (int i = 0; i < cmdLinesCnt; ++i) { char* cmdLineStr = cJSON_GetStringValue(cJSON_GetArrayItem(cmdsItem, i)); ParseCmdLine(cmdLineStr, &(resJob->cmdLines[i])); } } code-1.0/base/startup/services/init_lite/src/init_cmds.c void ParseCmdLine(const char* cmdStr, CmdLine* resCmd) { if (cmdStr == NULL || strlen(cmdStr) == 0 || resCmd == NULL) { return; } // 取得cmd line字符串长度 size_t cmdLineLen = strlen(cmdStr); // 获得支持的命令数量 size_t supportCmdCnt = sizeof(g_supportedCmds) / sizeof(g_supportedCmds[0]); // 声明并初始化标志位:是否找到命令并解析成功 int foundAndSucceed = 0; // 遍历支持的命令列表,判断这个命令是否在支持的列表里面 for (size_t i = 0; i < supportCmdCnt; ++i) { size_t curCmdNameLen = strlen(g_supportedCmds[i]); // 如果cmd line的长度比比较的这个命令长,并且这个命令+max_cmd_content_len的长度小 // 并且cmd line中的命令和这个命令一样 if (cmdLineLen > curCmdNameLen && cmdLineLen <= (curCmdNameLen + MAX_CMD_CONTENT_LEN) && strncmp(g_supportedCmds[i], cmdStr, curCmdNameLen) == 0) { // 写入cmd_name,并把尾字符写入一个空字符 if (memcpy_s(resCmd->name, MAX_CMD_NAME_LEN, cmdStr, curCmdNameLen) != EOK) { break; } resCmd->name[curCmdNameLen] = '\0'; // 写入cmd_content,并把尾字符写入一个空字符 const char* cmdContent = cmdStr + curCmdNameLen; size_t cmdContentLen = cmdLineLen - curCmdNameLen; if (memcpy_s(resCmd->cmdContent, MAX_CMD_CONTENT_LEN, cmdContent, cmdContentLen) != EOK) { break; } resCmd->cmdContent[cmdContentLen] = '\0'; // 设置标志位:找到命令并解析成功 foundAndSucceed = 1; break; } } // 如果没有找到或解析失败,则向其中全部写入0 if (!foundAndSucceed) { (void)memset_s(resCmd, sizeof(*resCmd), 0, sizeof(*resCmd)); } } 纯字符串操作,看着就是这么舒服! 执行任务 任务的执行分三个阶段,按照时间顺序,依次是:pre-init、init、post-init。 根据init_liteos_a_3518ev300.cfg配置来看: pre-init阶段主要进行目录创建、文件权限设置、分区挂载等。 init阶段主要进行服务程序启动 post-init阶段主要进行设备文件权限更改 code-1.0\base\startup\services\init_lite\src\init_jobs.c void DoJob(const char* jobName) { if (jobName == NULL) { printf("[Init] DoJob, input jobName NULL!\n"); return; } for (int i = 0; i < g_jobCnt; ++i) { if (strncmp(jobName, g_jobs[i].name, strlen(g_jobs[i].name)) == 0) { CmdLine* cmdLines = g_jobs[i].cmdLines; for (int j = 0; j < g_jobs[i].cmdLinesCnt; ++j) { DoCmd(&(cmdLines[j])); } break; } } } void DoCmd(const CmdLine* curCmd) { if (curCmd == NULL) { return; } if (strncmp(curCmd->name, "start ", strlen("start ")) == 0) { DoStart(curCmd->cmdContent); } else if (strncmp(curCmd->name, "mkdir ", strlen("mkdir ")) == 0) { DoMkDir(curCmd->cmdContent); } else if (strncmp(curCmd->name, "chmod ", strlen("chmod ")) == 0) { DoChmod(curCmd->cmdContent); } else if (strncmp(curCmd->name, "chown ", strlen("chown ")) == 0) { DoChown(curCmd->cmdContent); } else if (strncmp(curCmd->name, "mount ", strlen("mount ")) == 0) { DoMount(curCmd->cmdContent); } else { printf("[Init] DoCmd, unknown cmd name %s.\n", curCmd->name); } } 目前鸿蒙2.0支持的命令还很少(或是为了简介),只有5个命令:start、mkdir、chmod、chown、mount。start命令指,启动services配置的服务。其它四个命令就是linux系统的同名命令的功能。 DoStart()展开一下,其它四个命令,感兴趣的可以自己跟一下代码。 static void DoStart(const char* cmdContent) { StartServiceByName(cmdContent); } void StartServiceByName(const char* servName) { // 从全局的服务数据结构里面,通过名字查找服务 int servIdx = FindServiceByName(servName); if (servIdx < 0) { printf("[Init] StartServiceByName, cannot find service %s.\n", servName); return; } // 调用ServiceStart()函数启动服务,前面已经展开过 if (ServiceStart(&g_services[servIdx]) != SERVICE_SUCCESS) { printf("[Init] StartServiceByName, service %s start failed!\n", g_services[servIdx].name); } // ??? 这个有啥作用? sleep(SLEEP_DURATION); return; } service属性 code-1.0/base/startup/services/init_lite/include/init_service.h #define SERVICE_ATTR_INVALID 0x001 // option invalid #define SERVICE_ATTR_ONCE 0x002 // do not restart when it exits #define SERVICE_ATTR_NEED_RESTART 0x004 // will restart in the near future #define SERVICE_ATTR_NEED_STOP 0x008 // will stop in reap #define SERVICE_ATTR_IMPORTANT 0x010 // will reboot if it crash service一共有5种属性,每个属性占据一个bit位,依次为: INVALID 服务不存在 ONCE 服务退出后,不进行重启 NEED_RESTART 服务退出后,需要(在不就的将来进行)重启 NEED_STOP 如果是init进程强行杀死的服务,会设置服务的这个bit位为1 IMPORTANT 重要服务,如果服务退出(异常),会导致系统重启 关键数据结构 Service code-1.0\base\startup\services\init_lite\include\init_service.h typedef struct { uid_t uID; gid_t gID; unsigned int *caps; unsigned int capsCnt; } Perms; typedef struct { char name[MAX_SERVICE_NAME + 1]; char path[MAX_SERVICE_PATH + 1]; int pid; int crashCnt; time_t firstCrashTime; unsigned int attribute; Perms servPerm; } Service; Service数据结构,定义一个结构体,然后在结构体里面写入服务的每个数据字段。 Job code-1.0\base\startup\services\init_lite\include\init_jobs.h // one job, could have many cmd lines typedef struct { char name[MAX_JOB_NAME_LEN + 1]; int cmdLinesCnt; CmdLine* cmdLines; } Job; code-1.0\base\startup\services\init_lite\include\init_cmds.h // one cmd line typedef struct { char name[MAX_CMD_NAME_LEN + 1]; char cmdContent[MAX_CMD_CONTENT_LEN + 1]; } CmdLine; Job数据结构,一个Job可能含有多个cmd,所以结构体设计了三个字段,分别是:Job名字、cmd数量、cmd指针。 全局变量 code-1.0\base\startup\services\init_lite\src\init_service_manager.c static Service* g_services = NULL; static int g_servicesCnt = 0; 全局变量g_services保存了所有配置的服务。 引导配置文件 code-1.0\vendor\huawei\camera\init_configs\init_liteos_a_3516dv300.cfg { "jobs" : [{ "name" : "pre-init", "cmds" : [ "mkdir /sdcard", "chmod 0777 /sdcard", "mount vfat /dev/mmcblk1 /sdcard rw,umask=000" ] }, { "name" : "init", "cmds" : [ "start foundation", "start appspawn" ] }, { "name" : "post-init", "cmds" : [ "chown 0 99 /dev/dev_mgr", "chown 0 99 /dev/hdfwifi" ] } ], "services" : [{ "name" : "foundation", "path" : "/bin/foundation", "uid" : 7, "gid" : 7, "once" : 0, "importance" : 1, "caps" : [10, 11, 12, 13] }, { "name" : "appspawn", "path" : "/bin/appspawn", "uid" : 1, "gid" : 1, "once" : 0, "importance" : 0, "caps" : [2, 6, 7, 8, 23] } ] } 目前一共支持两种类型的定义,一类是services,一类是jobs。services配置的服务可以在jobs中调用start命令来启动。 services有7个配置项,分别是:name、path、uid、gid、once、importance、caps。其中比较重要的是服务名称、可执行文件路径、挂掉之后要不要重启、重要性。可配置的服务最大数量是100个。 name: 服务名称 path: 可执行文件路径 uid: 用户ID gid: 组ID once: 挂掉之后需不需要重新拉起来 importance: 服务的重要性 caps: 暂不明确有什么作用 jobs是设置一些要执行的命令。目前支持5个命令,分别是start、mkdir、chown、chmod、mount。start命令就是用来启动services中定义的服务。 这个JSON配置文件是有很多限制条件的: 文件大小不能超过100KB 配置的服务数量不能超过100个 服务的名字不能超过32个字符 服务的路径不能超过64个字符 配置的任务(任务组)数量不能超过10个 一个任务组配置的cmd不能超过30个 一行一个cmd,包括cmd_name和cmd_content cmd_name长度不能超过10个字符 cmd_content长度不能超过128个字符 3518开发板默认配置开机引导都做了什么 创建log文件夹,创建软总线softbus文件夹,创建sdcard文件夹,挂载sdcard,依次启动服务shell、apphilogcat、foundation、bundle_daemon、media_server、appspawn,修改一些设备权限。 后续学习计划 研究完了init模块,后续该如何继续学习呢? 其实根据系统的启动流程来学习就是一个不错的学习路径。init进程启动之后,接着就是这6大服务的启动,后续会根据shell、apphilogcat、foundation、bundle_daemon、media_server、appspawn,这个顺序,依次研究每一个服务。这只是大概的计划,关键模块肯定会非常庞大,遇到的时候需要分而学之。 本文参与了「解读鸿蒙源码」技术征文,欢迎正在阅读的你也加入。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

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

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应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册