@IT·互联网鸿蒙(HarmonyOS)开发知识

鸿蒙内核源码分析(控制台篇) | 一个让很多人模糊的概念

2025-06-08  本文已影响0人  迪士尼在逃程序员

Shell | 控制台模型

下图为看完鸿蒙内核Shell和控制台源码后整理的模型图

模型说明

代码实现

每个模块都有一个核心结构体,控制台则是

结构体 | CONSOLE_CB

/**
 * @brief 控制台控制块(描述符)
 */
typedef struct {
    UINT32 consoleID;   ///< 控制台ID    例如 : 1 | 串口 , 2 | 远程登录
    UINT32 consoleType; ///< 控制台类型
    UINT32 consoleSem;  ///< 控制台信号量
    UINT32 consoleMask; ///< 控制台掩码
    struct Vnode *devVnode; ///< 索引节点
    CHAR *name; ///< 名称 例如: /dev/console1 
    INT32 fd;   ///< 系统文件句柄, 由内核分配
    UINT32 refCount;    ///< 引用次数,用于判断控制台是否被占用
    UINT32 shellEntryId; ///<  负责接受来自终端信息的 "ShellEntry"任务,这个值在运行过程中可能会被换掉,它始终指向当前正在运行的shell客户端
    INT32 pgrpId;   ///< 进程组ID
    BOOL isNonBlock; ///< 是否无锁方式        
#ifdef LOSCFG_SHELL
    VOID *shellHandle;  ///< shell句柄,本质是 shell控制块 ShellCB
#endif
    UINT32 sendTaskID;  ///< 创建任务通过事件接收数据, 见于OsConsoleBufInit
    /*--以下为 一家子 start---------*/
    CirBufSendCB *cirBufSendCB; ///< 循环缓冲发送控制块
    UINT8 fifo[CONSOLE_FIFO_SIZE]; ///< 控制台缓冲区大小 1K
    UINT32 fifoOut; ///< 对fifo的标记,输出位置
    UINT32 fifoIn;  ///< 对fifo的标记,输入位置
    UINT32 currentLen;  ///< 当前fifo位置
    /*---以上为 一家子 end------- https://man7.org/linux/man-pages/man3/tcflow.3.html */
    struct termios consoleTermios; ///< 行规程
} CONSOLE_CB;

解析

发送数据给终端的任务 | ConsoleSendTask

ConsoleSendTask只干一件事,将数据发送给串口或远程登录,任务优先级与shell同级,为9,它由系统初始化任务SystemInit创建, 具体可翻看系列篇之内核启动篇

/// 控制台缓存初始化,创建一个 发送任务
STATIC UINT32 OsConsoleBufInit(CONSOLE_CB *consoleCB)
{
    UINT32 ret;
    TSK_INIT_PARAM_S initParam = {0};
    consoleCB->cirBufSendCB = ConsoleCirBufCreate();//创建控制台
    if (consoleCB->cirBufSendCB == NULL) {
        return LOS_NOK;
    }
    initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)ConsoleSendTask;//控制台发送任务入口函数
    initParam.usTaskPrio   = SHELL_TASK_PRIORITY;   //优先级9
    initParam.auwArgs[0]   = (UINTPTR)consoleCB;    //入口函数的参数
    initParam.uwStackSize  = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;   //16K
    if (consoleCB->consoleID == CONSOLE_SERIAL) {//控制台的两种方式
        initParam.pcName   = "SendToSer";   //任务名称(发送数据到串口) 
    } else {
        initParam.pcName   = "SendToTelnet";//任务名称(发送数据到远程登录)
    }
    initParam.uwResved     = LOS_TASK_STATUS_DETACHED; //使用任务分离模式
    ret = LOS_TaskCreate(&consoleCB->sendTaskID, &initParam);//创建task 并加入就绪队列,申请立即调度
    if (ret != LOS_OK) { //创建失败处理
        ConsoleCirBufDelete(consoleCB->cirBufSendCB);//释放循环buf
        consoleCB->cirBufSendCB = NULL;//置NULL
        return LOS_NOK;
    }//永久等待读取 CONSOLE_SEND_TASK_RUNNING 事件,CONSOLE_SEND_TASK_RUNNING 由 ConsoleSendTask 发出.
    (VOID)LOS_EventRead(&consoleCB->cirBufSendCB->sendEvent, CONSOLE_SEND_TASK_RUNNING,
                        LOS_WAITMODE_OR | LOS_WAITMODE_CLR, LOS_WAIT_FOREVER);
    // ... 读取到 CONSOLE_SEND_TASK_RUNNING 事件才会往下执行  
    return LOS_OK;
}

任务的入口函数ConsoleSendTask实现也很简单,此处全部贴出来,死循环等待事件的发送.说到死循环多说两句,不要被while (1)吓倒,认为内核会卡死在这里玩不下去,那是应用程序员看待死循环的视角,其实在内核当等待的事件没有到来的时,这个任务并不会往下执行,而是处于挂起状态,当事件到来时才会切换回来继续往下走,那如何知道事件到来了呢? 可翻看系列篇之事件控制篇

STATIC UINT32 ConsoleSendTask(UINTPTR param)
{
    CONSOLE_CB *consoleCB = (CONSOLE_CB *)param;
    CirBufSendCB *cirBufSendCB = consoleCB->cirBufSendCB;
    CirBuf *cirBufCB = &cirBufSendCB->cirBufCB;
    UINT32 ret, size;
    UINT32 intSave;
    CHAR *buf = NULL;
    (VOID)LOS_EventWrite(&cirBufSendCB->sendEvent, CONSOLE_SEND_TASK_RUNNING);//发送一个控制台任务正在运行的事件
    while (1) {//读取 CONSOLE_CIRBUF_EVENT | CONSOLE_SEND_TASK_EXIT 这两个事件
        ret = LOS_EventRead(&cirBufSendCB->sendEvent, CONSOLE_CIRBUF_EVENT | CONSOLE_SEND_TASK_EXIT,
                            LOS_WAITMODE_OR | LOS_WAITMODE_CLR, LOS_WAIT_FOREVER);//读取循环buf或任务退出的事件
        if (ret == CONSOLE_CIRBUF_EVENT) {//控制台循环buf事件发生
            size =  LOS_CirBufUsedSize(cirBufCB);//循环buf使用大小
            if (size == 0) {
                continue;
            }
            buf = (CHAR *)LOS_MemAlloc(m_aucSysMem1, size + 1);//分配接收cirbuf的内存
            if (buf == NULL) {
                continue;
            }
            (VOID)memset_s(buf, size + 1, 0, size + 1);//清0
            LOS_CirBufLock(cirBufCB, &intSave);
            (VOID)LOS_CirBufRead(cirBufCB, buf, size);//读取循环cirBufCB至  buf
            LOS_CirBufUnlock(cirBufCB, intSave);

            (VOID)WriteToTerminal(consoleCB, buf, size);//将buf数据写到控制台终端设备
            (VOID)LOS_MemFree(m_aucSysMem1, buf);//清除buf
        } else if (ret == CONSOLE_SEND_TASK_EXIT) {//收到任务退出的事件, 由 OsConsoleBufDeinit 发出事件.
            break;//退出循环
        }
    }
    ConsoleCirBufDelete(cirBufSendCB);//删除循环buf,归还内存
    return LOS_OK;
}

上面提到了控制台和终端,是经常容易搞混的又变得越来越模糊两个概念,简单说明下.

传统的控制台和终端

控制台(console)和终端(terminal)有什么区别? 看张古老的图

这个不陌生吧,实现中虽很少看到,可电影里可没少出现.

据说是NASA航天飞机控制台,满满的科技感.

这就是控制台.早期控制台其实是给系统管理人员使用的.因为机器很大,价格很贵,不可能让每个人都拥有一个真正物理上属于自己的计算机,但是只让一个人用那其他人怎么办? 效率太低,就出现了多用户多任务计算机,让一台计算机多个人同时登录使用的情况, 给每个人面前放个简单设备(只有键盘和屏幕)连接到主机上,如图所示

这个就叫终端 ,注意别看那么大,长得很像一体机,但其实它只是一台显示器.这是给普通用户使用,权限也有限,核心功能权限还是在操作控制台的系统管理员手上.

综上所述,用图表列出二者早期差异

区别 终端(terminal) 控制台(console)
设备属性 外挂的附加设备 自带的基本设备
数量 多个 一个
主机信任度
输出内容 主机处理的信息 主机核心/自身信息
操作员 普通用户 管理员

现在的控制台和终端

由于时代的发展计算机的硬件越来越便宜,现在都是一个人独占一台计算机(个人电脑),已经不再需要传统意义上的硬件终端。现在终端和控制台都由硬件概念,逐渐演化成了软件的概念。终端和控制台的界限也慢慢模糊了,复杂了,甚至控制台也变成了终端, 现在要怎么理解它们,推荐一篇文章,请自行前往搜看.
<< 彻底理解Linux的各种终端类型以及概念 >>

本篇内容与图中右上角的/dev/console那部分相关. 从鸿蒙内核视角来看,控制台和终端还是有很大差别的.

写在最后

上一篇 下一篇

猜你喜欢

热点阅读