鸿蒙内核源码分析(Shell解析篇) | 应用窥视内核的窗口

系列篇从内核视角用一句话概括shell的底层实现为:两个任务,三个阶段。其本质是独立进程,因而划到进程管理模块。每次创建shell进程都会再创建两个任务。

  • 客户端任务(ShellEntry): 负责接受来自终端(控制台)敲入的一个个字符,字符按VT规范组装成一句句的命令。
  • 服务端任务(ShellTask): 对命令进行解析并执行,将结果输出到控制台。
    而按命令生命周期可分三个阶段.
  • 编辑: 鸿蒙在这个部分实现了一个简单的编辑器功能,处理控制台输入的每个字符,主要包括了对控制字符 例如 <ESC>,\t,\b,\n,\r,四个方向键0x41 ~ 0x44 的处理。
  • 解析: 对编辑后的字符串进行解析,解析出命令项和参数项,找到对应的命令项执行函数。
  • 执行: 命令可通过静态和动态两种方式注册到内核,解析出具体命令后在注册表中找到对应函数回调。将结果输出到控制台。

编辑部分由客户端任务完成,后两个部分由服务端任务完成,命令全局注册由内核完成。

  • 本篇主要说 服务端任务 和 解析/执行过程.
  • 客户端任务 和 编辑过程 已在 (Shell编辑篇) 中说明,请自行翻看.
总体过程
  • 第一步: 将支持的shell命令注册进全局链表,支持静态和动态两种方式,内容包括命令项,参数信息和回调函数.
  • 第二步: 由独立任务解析出用户输入的命令行,拆分出命令项和参数内容
  • 第三步: 通过命令项在全局链表中遍历找到已注册的回调函数,并执行.
结构体

鸿蒙对命令的注册用了三个结构体,个人感觉前两个可以合成一个,降低代码阅读难度.

STATIC CmdModInfo g_cmdInfo;//shell 命令模块信息,上面挂了所有的命令项(ls,cd ,cp ==)typedef struct {//命令项CmdType cmdType;	//命令类型//CMD_TYPE_EX:不支持标准命令参数输入,会把用户填写的命令关键字屏蔽掉,例如:输入ls /ramfs,传入给注册函数的参数只有/ramfs,而ls命令关键字并不会被传入。//CMD_TYPE_STD:支持的标准命令参数输入,所有输入的字符都会通过命令解析后被传入。const CHAR *cmdKey;	//命令关键字,例如:ls 函数在Shell中访问的名称。UINT32 paraNum;		//调用的执行函数的入参最大个数,暂不支持。CmdCallBackFunc cmdHook;//命令执行函数地址,即命令实际执行函数。
} CmdItem;
typedef struct {	//命令节点LOS_DL_LIST list;	//双向链表CmdItem *cmd;	//命令项
} CmdItemNode;/* global info for shell module */
typedef struct {//shell 模块的全局信息CmdItemNode cmdList;	//命令项节点UINT32 listNum;//节点数量UINT32 initMagicFlag;//初始魔法标签 0xABABABABLosMux muxLock;	//操作链表互斥锁CmdVerifyTransID transIdHook;//暂不知何意
} CmdModInfo;

解读

  • CmdItem为注册的内容载体结构体,cmdHook为回调函数,是命令的真正执行体.
  • 通过双向链表CmdItemNode.list将所有命令穿起来
  • CmdModInfo记录命令数量和操作的互斥锁,shell的魔法数字为 0xABABABAB
第一步 | Shell 注册
  • 静态宏方式注册,链接时处理
    静态注册命令方式一般用在系统常用命令注册,鸿蒙已支持以下命令.
        arp           cat           cd            chgrp         chmod         chown         cp            cpup          date          dhclient      dmesg         dns           format        free          help          hwi           ifconfig      ipdebug       kill          log           ls            lsfd          memcheck      mkdir         mount         netstat       oom           partinfo      partition     ping          ping6         pwd           reset         rm            rmdir         sem           statfs        su            swtmr         sync          systeminfo    task          telnet        test          tftp          touch         umount        uname         watch         writeproc  
例如注册 `ls`命令
    SHELLCMD_ENTRY(ls_shellcmd,  CMD_TYPE_EX, "ls", XARGS,  (CMD_CBK_FUNC)osShellCmdLs)

需在链接选项中添加链接该新增命令项参数,具体在liteos_tables_ldflags.mk文件的LITEOS_TABLES_LDFLAGS项下添加-uls_shellcmd。至于SHELLCMD_ENTRY是如何实现的在链接阶段的注册,请自行翻看 (内联汇编篇) ,有详细说明实现细节.

  • 动态命令方式,运行时处理
    动态注册命令方式一般用在用户命令注册,具体实现代码如下:
    osCmdReg(CMD_TYPE_EX, "ls", XARGS,  (CMD_CBK_FUNC)osShellCmdLs){// ....//5.正式创建命令,挂入链表return OsCmdItemCreate(cmdType, cmdKey, paraNum, cmdProc);//不存在就注册命令}//创建一个命令项,例如 chmodSTATIC UINT32 OsCmdItemCreate(CmdType cmdType, const CHAR *cmdKey, UINT32 paraNum, CmdCallBackFunc cmdProc){CmdItem *cmdItem = NULL;CmdItemNode *cmdItemNode = NULL;//1.构造命令节点过程cmdItem = (CmdItem *)LOS_MemAlloc(m_aucSysMem0, sizeof(CmdItem));if (cmdItem == NULL) {return OS_ERRNO_SHELL_CMDREG_MEMALLOC_ERROR;}(VOID)memset_s(cmdItem, sizeof(CmdItem), '\0', sizeof(CmdItem));cmdItemNode = (CmdItemNode *)LOS_MemAlloc(m_aucSysMem0, sizeof(CmdItemNode));if (cmdItemNode == NULL) {(VOID)LOS_MemFree(m_aucSysMem0, cmdItem);return OS_ERRNO_SHELL_CMDREG_MEMALLOC_ERROR;}(VOID)memset_s(cmdItemNode, sizeof(CmdItemNode), '\0', sizeof(CmdItemNode));cmdItemNode->cmd = cmdItem;			//命令项 cmdItemNode->cmd->cmdHook = cmdProc;//回调函数 osShellCmdLscmdItemNode->cmd->paraNum = paraNum;//`777`,'/home'cmdItemNode->cmd->cmdType = cmdType;//关键字类型cmdItemNode->cmd->cmdKey = cmdKey;	//`chmod`//2.完成构造后挂入全局链表(VOID)LOS_MuxLock(&g_cmdInfo.muxLock, LOS_WAIT_FOREVER);OsCmdAscendingInsert(cmdItemNode);//按升序方式插入g_cmdInfo.listNum++;//命令总数增加(VOID)LOS_MuxUnlock(&g_cmdInfo.muxLock);return LOS_OK;}
第二步 解析 | ShellTask
//shell 服务端任务初始化,这个任务负责解析和执行命令
LITE_OS_SEC_TEXT_MINOR UINT32 ShellTaskInit(ShellCB *shellCB)
{CHAR *name = NULL;TSK_INIT_PARAM_S initParam = {0};//输入Shell命令的两种方式if (shellCB->consoleID == CONSOLE_SERIAL) {	//通过串口工具name = SERIAL_SHELL_TASK_NAME;} else if (shellCB->consoleID == CONSOLE_TELNET) {//通过远程工具name = TELNET_SHELL_TASK_NAME;} else {return LOS_NOK;}initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)ShellTask;//任务入口函数,主要是解析shell命令initParam.usTaskPrio   = 9; /* 9:shell task priority */initParam.auwArgs[0]   = (UINTPTR)shellCB;initParam.uwStackSize  = 0x3000;initParam.pcName       = name;initParam.uwResved     = LOS_TASK_STATUS_DETACHED;(VOID)LOS_EventInit(&shellCB->shellEvent);//初始化事件,以事件方式通知任务解析命令return LOS_TaskCreate(&shellCB->shellTaskHandle, &initParam);//创建任务
}
LITE_OS_SEC_TEXT_MINOR UINT32 ShellTask(UINTPTR param1,UINTPTR param2,UINTPTR param3,UINTPTR param4)
{UINT32 ret;ShellCB *shellCB = (ShellCB *)param1;(VOID)param2;(VOID)param3;(VOID)param4;while (1) {PRINTK("\nOHOS # ");//读取shell 输入事件 例如: cat weharmony.net 命令ret = LOS_EventRead(&shellCB->shellEvent,0xFFF, LOS_WAITMODE_OR | LOS_WAITMODE_CLR, LOS_WAIT_FOREVER);if (ret == SHELL_CMD_PARSE_EVENT) {//获得解析命令事件ShellCmdProcess(shellCB);//处理命令 } else if (ret == CONSOLE_SHELL_KEY_EVENT) {//退出shell事件break;}}OsShellKeyDeInit((CmdKeyLink *)shellCB->cmdKeyLink);//OsShellKeyDeInit((CmdKeyLink *)shellCB->cmdHistoryKeyLink);(VOID)LOS_EventDestroy(&shellCB->shellEvent);//注销事件(VOID)LOS_MemFree((VOID *)m_aucSysMem0, shellCB);//释放shell控制块return 0;
}

解读

  • 任务优先级和 客户端任务 一样同为 9

  • 指定内核栈大小为0x3000 = 12K ,因任务负责命令的解析和执行,所以需要更大的内核空间.

  • 任务的入口函数ShellTask,一个死循环在以LOS_WAIT_FOREVER方式死等事件发生.

    • SHELL_CMD_PARSE_EVENT 通知开始解析事件,该事件由 客户端任务ShellEntry检测到回车键时发出.
      STATIC VOID ShellNotify(ShellCB *shellCB){(VOID)LOS_EventWrite(&shellCB->shellEvent, SHELL_CMD_PARSE_EVENT);}
*   `CONSOLE_SHELL_KEY_EVENT` 收到 `exit`命令时将发出该事件,退出`shell`回收资源       
  • 层层跟进ShellCmdProcess,解析出命令项和参数内容,最终跑到OsCmdExec中遍历 已注册的命令表,找出命令对应的函数完成回调.
   LITE_OS_SEC_TEXT_MINOR UINT32 OsCmdExec(CmdParsed *cmdParsed, CHAR *cmdStr){UINT32 ret;CmdCallBackFunc cmdHook = NULL;CmdItemNode *curCmdItem = NULL;UINT32 i;const CHAR *cmdKey = NULL;if ((cmdParsed == NULL) || (cmdStr == NULL) || (strlen(cmdStr) == 0)) {return (UINT32)OS_ERROR;}ret = OsCmdParse(cmdStr, cmdParsed);//解析出命令关键字,参数if (ret != LOS_OK) {goto OUT;}//遍历命令注册全局链表LOS_DL_LIST_FOR_EACH_ENTRY(curCmdItem, &(g_cmdInfo.cmdList.list), CmdItemNode, list) {cmdKey = curCmdItem->cmd->cmdKey;if ((cmdParsed->cmdType == curCmdItem->cmd->cmdType) &&(strlen(cmdKey) == strlen(cmdParsed->cmdKeyword)) &&(strncmp(cmdKey, (CHAR *)(cmdParsed->cmdKeyword), strlen(cmdKey)) == 0)) {//找到命令的回调函数 例如: ls <-> osShellCmdLscmdHook = curCmdItem->cmd->cmdHook;break;}}ret = OS_ERROR;if (cmdHook != NULL) {//执行命令,即回调函数ret = (cmdHook)(cmdParsed->paramCnt, (const CHAR **)cmdParsed->paramArray);}OUT:for (i = 0; i < cmdParsed->paramCnt; i++) {//无效的命令要释放掉保存参数的内存if (cmdParsed->paramArray[i] != NULL) {(VOID)LOS_MemFree(m_aucSysMem0, cmdParsed->paramArray[i]);cmdParsed->paramArray[i] = NULL;}}return (UINT32)ret;}
第三步 | 执行

想知道有哪些系统shell命令,可以搜索关键词SHELLCMD_ENTRY拿到所有通过静态方式注册的命令.

其中有网络的,进程的,任务的,内存的 等等,此处列出几个常用的shell命令的实现.

ls 命令
SHELLCMD_ENTRY(ls_shellcmd, CMD_TYPE_EX, "ls", XARGS, (CmdCallBackFunc)osShellCmdLs);/*******************************************************
命令功能
ls命令用来显示当前目录的内容。命令格式
ls [path]path为空时,显示当前目录的内容。
path为无效文件名时,显示失败,提示:
ls error: No such directory。
path为有效目录路径时,会显示对应目录下的内容。使用指南
ls命令显示当前目录的内容。
ls可以显示文件的大小。
proc下ls无法统计文件大小,显示为0。*******************************************************/
int osShellCmdLs(int argc, const char **argv)
{char *fullpath = NULL;const char *filename = NULL;int ret;char *shell_working_directory = OsShellGetWorkingDirtectory();//获取当前工作目录if (shell_working_directory == NULL){return -1;}ERROR_OUT_IF(argc > 1, PRINTK("ls or ls [DIRECTORY]\n"), return -1);if (argc == 0)//木有参数时 -> #ls {ls(shell_working_directory);//执行ls 当前工作目录return 0;}filename = argv[0];//有参数时 -> #ls ../harmony  or #ls /no such file or directoryret = vfs_normalize_path(shell_working_directory, filename, &fullpath);//获取全路径,注意这里带出来fullpath,而fullpath已经在内核空间ERROR_OUT_IF(ret < 0, set_err(-ret, "ls error"), return -1);ls(fullpath);//执行 ls 全路径free(fullpath);//释放全路径,为啥要释放,因为fullpath已经由内核空间分配return 0;
}
task 命令
SHELLCMD_ENTRY(task_shellcmd, CMD_TYPE_EX, "task", 1, (CmdCallBackFunc)OsShellCmdDumpTask);LITE_OS_SEC_TEXT_MINOR UINT32 OsShellCmdDumpTask(INT32 argc, const CHAR **argv)
{UINT32 flag = 0;
#ifdef LOSCFG_KERNEL_VMflag |= OS_PROCESS_MEM_INFO;
#endifif (argc >= 2) { /* 2: The task shell name restricts the parameters */goto TASK_HELP;}if (argc == 1) {if (strcmp("-a", argv[0]) == 0) {flag |= OS_PROCESS_INFO_ALL;} else if (strcmp("-i", argv[0]) == 0) {if (!OsShellShowTickRespo()) {return LOS_OK;}goto TASK_HELP;} else if (strcmp("-t", argv[0]) == 0) {if (!OsShellShowSchedParam()) {return LOS_OK;}goto TASK_HELP;} else {goto TASK_HELP;}}return OsShellCmdTskInfoGet(OS_ALL_TASK_MASK, NULL, flag);TASK_HELP:PRINTK("Unknown option: %s\n", argv[0]);PRINTK("usage: task or task -a\n");return LOS_NOK;
}
cat 命令
SHELLCMD_ENTRY(cat_shellcmd, CMD_TYPE_EX, "cat", XARGS, (CmdCallBackFunc)osShellCmdCat);/*****************************************************************
cat用于显示文本文件的内容。cat [pathname]
cat weharmony.txt
*****************************************************************/
int osShellCmdCat(int argc, const char **argv)
{char *fullpath = NULL;int ret;unsigned int ca_task;struct Vnode *vnode = NULL;TSK_INIT_PARAM_S init_param;char *shell_working_directory = OsShellGetWorkingDirtectory();//显示当前目录 pwdif (shell_working_directory == NULL){return -1;}ERROR_OUT_IF(argc != 1, PRINTK("cat [FILE]\n"), return -1);ret = vfs_normalize_path(shell_working_directory, argv[0], &fullpath);//由相对路径获取绝对路径ERROR_OUT_IF(ret < 0, set_err(-ret, "cat error"), return -1);VnodeHold();ret = VnodeLookup(fullpath, &vnode, O_RDONLY);if (ret != LOS_OK){set_errno(-ret);perror("cat error");VnodeDrop();free(fullpath);return -1;}if (vnode->type != VNODE_TYPE_REG){set_errno(EINVAL);perror("cat error");VnodeDrop();free(fullpath);return -1;}VnodeDrop();(void)memset_s(&init_param, sizeof(init_param), 0, sizeof(TSK_INIT_PARAM_S));init_param.pfnTaskEntry = (TSK_ENTRY_FUNC)osShellCmdDoCatShow;init_param.usTaskPrio   = CAT_TASK_PRIORITY;	//优先级10init_param.auwArgs[0]   = (UINTPTR)fullpath;	//入口参数init_param.uwStackSize  = CAT_TASK_STACK_SIZE;//内核栈大小init_param.pcName       = "shellcmd_cat";	//任务名称init_param.uwResved     = LOS_TASK_STATUS_DETACHED | OS_TASK_FLAG_SPECIFIES_PROCESS;init_param.processID    = 2; /* 2: kProcess */ //内核任务ret = (int)LOS_TaskCreate(&ca_task, &init_param);//创建任务显示cat内容if (ret != LOS_OK){free(fullpath);}return ret;
}

你能看明白这些命令的底层实现吗? 如果看明白了,可能会不由得发出 原来如此 的感叹!

鸿蒙全栈开发全新学习指南

也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线【包含了大厂APP实战项目开发】

本路线共分为四个阶段:

第一阶段:鸿蒙初中级开发必备技能

第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:gitee.com/MNxiaona/733GH

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:gitee.com/MNxiaona/733GH

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH

鸿蒙入门教学视频:

美团APP实战开发教学:gitee.com/MNxiaona/733GH

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:gitee.com/MNxiaona/733GH

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/836166.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【云原生】 Kubernetes核心概念

目录 引言 一、部署方式回溯 &#xff08;一&#xff09;传统部署时代 &#xff08;二&#xff09;虚拟化部署时代 &#xff08;三&#xff09;容器部署时代 二、Kubernetes基本介绍 &#xff08;一&#xff09;为什么使用k8s &#xff08;二&#xff09;主要功能 &am…

乡村振兴与数字乡村建设:加强农村信息化建设,推动数字乡村发展,提升乡村治理和服务水平,构建智慧化的美丽乡村

目录 一、引言 二、数字乡村建设的必要性 1、推动农村经济转型升级 2、提升乡村治理水平 3、改善乡村民生福祉 三、数字乡村建设的现状与挑战 1、现状 2、挑战 四、数字乡村建设的未来发展路径 1、加强农村信息化基础设施建设 2、提升农民信息素养和技能水平 3、制…

py黑帽子学习笔记_环境准备

1 下载os装os 下载一个kali虚机镜像然后用虚机管理软件创虚机&#xff0c;装完如下图&#xff0c;我用的版本是2024.1的版本kali-linux-2024.1-installer-amd64&#xff0c;可以从镜像站下载&#xff0c;官网下的慢还断网Index of /kali-images/kali-2024.1/ | 清华大学开源软…

C++高精度算法-加法

引子 在C++的运算中,难免会出现很大很大的数,下面是各个关键字的表示范围 但是如果要表示的数超过了long long可以表示的最大值( 2 64 2^{64} 264-1) 怎么办呢? 如果强制表示,就会溢出,这里的溢出大家可以自行百度,反正就是会出一些-5665434之类的数 现在,就要切入正…

网络基础-Telnet协议

Telnet&#xff08;Telecommunication Network&#xff09;是一种基于文本的远程终端协议&#xff0c;允许用户通过网络连接到远程计算机&#xff0c;并在远程计算机上执行命令&#xff1b;它使用TCP作为传输层协议&#xff0c;并依赖于网络连接在客户端和服务器之间进行通信&a…

MySQL 身份认证漏洞 CVE-2012-2122

漏洞影响版本 MariaDB versions from 5.1.62, 5.2.12, 5.3.6, 5.5.23 are not.MySQL versions from 5.1.63, 5.5.24, 5.6.6 are not.演示 开启靶场 进入漏洞目录 cd /root/vulhub/mysql/CVE-2012-2122开启漏洞靶场 docker-compose up -d攻击 直接 运行 这个命令 for i i…

分布式与一致性协议之PBFT算法(二)

PBFT算法 如何替换作恶的主节点 虽然PBFT算法可以防止备份节点作恶&#xff0c;因为这个算法是由主节点和备份节点组成的&#xff0c;但是&#xff0c;如果主节点作恶(比如主机点接收到了客户端的请求&#xff0c;但就是默不作声&#xff0c;不执行三阶段协议)&#xff0c;那…

Line Buffer概述

buffer在芯片物理上一般指的是SRAM&#xff0c;也可以指寄存器组。buffer的作用是用来在逻辑芯片上暂时存储数据&#xff0c;但不会是大量的数据。如果是大量数据一般会使用DRAM&#xff08;典型的指DDR&#xff09;作为存储芯片&#xff0c;用来存储大密度数据。line buffer可…

优化资源利用,用C++内存池点亮编程之路

内存池介绍(Memory Pool): 它是一种内存分配方式&#xff0c;通过预先分配和复用内存块。 在真正使用内存之前&#xff0c;先申请一大块内存备用。当有新的内存需求时&#xff0c;就从内存池中分出一部分内存块&#xff0c; 若内存块不够再继续申请新的内存。如果我们不需要…

环形链表(判断链表中是否有环)的讲解

一&#xff1a;题目 二&#xff1a;思路讲解 1&#xff1a;采用快慢指针的方法&#xff0c;一个fast指针一次移动两个节点&#xff0c;一个slow指针一次移动一个节点。 2&#xff1a;两个指针从头指针开始往后遍历&#xff0c;如果fast指针或者fast->next 有一个为空&…

5款可用于LLMs的爬虫工具/方案

5款可用于LLMs的爬虫工具/方案 Crawl4AI 功能: 提取语义标记的数据块为JSON格式&#xff0c;提供干净的HTML和Markdown文件。 用途: 适用于RAG&#xff08;检索增强生成&#xff09;、微调以及AI聊天机器人的开发。 特点: 高效数据提取&#xff0c;支持LLM格式&#xff0c;多U…

c++ 入门2

目录 五. 函数重载 1、参数类型不同 2、参数个数不同 3、参数类型顺序不同 C支持函数重载的原理--名字修饰(name Mangling&#xff09; 为什么C支持函数重载&#xff0c;而C语言不支持函数重载呢&#xff1f; 六. 引用 6.1 概念 6.2 引用特性 6.3 常引用 6.4 使用场景 …

数据结构之排序(上)

片头 嗨&#xff0c;小伙伴们&#xff0c;大家好&#xff01;我们今天来学习数据结构之排序&#xff08;上&#xff09;&#xff0c;今天我们先讲一讲3个排序&#xff0c;分别是直接插入排序、冒泡排序以及希尔排序。 1. 排序的概念及其应用 1.1 排序的概念 排序&#xff1a…

图书馆APP开发解决方案

uni-app框架&#xff1a;使用Vue.js开发跨平台应用的前端框架&#xff0c;编写一套代码&#xff0c;可编译到Android、小程序等平台。 框架支持:springboot/Ssm/thinkphp/django/flask/express均支持 前端开发:vue.js 可选语言&#xff1a;pythonjavanode.jsphp均支持 运行软件…

百度云防护如何开启CC攻击防护

百度云防护的最重要的功能是可以CC攻击防护&#xff0c;针对CC攻击&#xff0c;百度云防护有被动的CC攻击拦截规则&#xff0c;也有主动自定义访问策略拦截。 今天百度云来教大家如何开启百度云防护的CC攻击防御功能。 1.进入防护模板功能-创建模板 2.开启CC攻击防御功能&…

李飞飞首次创业!

B站&#xff1a;啥都会一点的研究生公众号&#xff1a;啥都会一点的研究生 最近AI又有啥进展&#xff1f;一起看看吧~ 中国独角兽企业已达369家&#xff0c;六成以上与AI、芯片等硬科技赛道有关 2024中关村论坛“全球独角兽企业大会”上发布全新《中国独角兽企业发展报告&am…

探索互联网医院系统源码:开发在线药房小程序实战教学

今天&#xff0c;笔者将与大家一同深入探讨互联网医院系统的源码结构&#xff0c;并通过开发在线药房小程序的实战教学&#xff0c;为读者提供一种学习和理解这一领域的途径。 一、互联网医院系统源码解析 1.技术选型 互联网医院系统的开发离不开合适的技术选型&#xff0c;…

类和对象-Python-第二部分

师从黑马程序员 多态 抽象类&#xff08;接口&#xff09; #演示抽象类 class AC:def cool_wind(self):"""制冷"""passdef hot_wind(self):"""制热"""def swing_l_r(self):"""左右摆风""…

Cloudflare国内IP地址使用教程

Cloudflare国内IP地址使用教程 加速网站&#xff1a; 首先我们添加一个 A 记录解析&#xff0c;解析 IP 就是我们服务器真实 IP&#xff1a; 然后侧边栏 SSL/TLS - 自定义主机名&#xff1a; 回退源这里填写你刚刚解析的域名&#xff0c;保存后回退源状态为有效再来接下的操作…

第十二篇:数据库系统导论 - 探索数据管理的基石

数据库系统导论 - 探索数据管理的基石 1 引言 数据的力量&#xff1a;揭秘数据库系统的核心 在信息时代&#xff0c;数据无处不在&#xff0c;它们成为了企业和社会运作的基础。我们如何储存、检索、更新和维护这些数据&#xff0c;决定了我们能否从这些数据中获得力量。数据…