说明
本文主要介绍某嵌入式产品中DSL用户态驱动模块作为守护进程时,如何接收终端输入的变通性方法。
出于信息安全考虑,文中涉及系统方面的接口函数未给出实现细节,但不影响表述的完整性。
相关性文章参见《GNU Readline库函数的应用示例》。
一 背景知识
init进程(如Busybox init)是嵌入式系统内核自举时启动的第一个也是惟一的用户进程。init进程是后续所有其他进程的父进程(其进程ID为1),在系统运行期间以守护进程的形式一直存在。它主要负责启动各运行层次特定的系统服务(如执行某些脚本、启动shell及运行用户指定的程序等),这些服务通常在其拥有的守护进程的帮助下实现。
Busybox init程序对应的代码在init/init.c文件中,首先启动init_main函数,以设置信号的处理函数、初始化控制台以及解析inittab内容。
守护进程没有控制终端。因此编写守护进程时,通常关闭标准输入、标准输出和标准出错等文件描述符(若不关闭会浪费系统资源,造成进程所在文件系统无法卸载甚至引起无法预料的错误)。这样,守护进程不再持有从其父进程继承而来的文件描述符,任何一个试图读标准输入、写标准输出和标准出错的库例程将不会产生任何效果。因为守护进程并不与终端设备相关联,所以不能在终端设备上显示其输出,也无处从交互式用户那里接收输入。
但某产品中因调试排障需要,未关闭标准输出描述符。事实上,该产品中所有进程均作为pc守护进程的子进程启动,但启动时并未执行关闭文件描述符的步骤,而是在Busybox的init_main函数关闭标准输入和标准出错。虽然守护进程已脱离控制终端,但仍可使用从父进程继承的文件描述符来输出到“原”终端。
此外,嵌入式系统中守护进程通常启动后一直运行直至系统关机,而不受用户输入的影响。若不关闭标准输入,虽然各守护进程可以不处理这些输入,或按需选择性地处理,但当多个进程试图读取标准输入时将引起混乱。
二 需求提出
BCM厂家SDK为该产品DSL驱动模块提供了丰富的配置、查询及调测命令集,入口函数为dispatchingTask。该函数行为类似多级Shell,内部通过BCM_GETLINE宏(循环+getchar实现)等待和捕获用户输入。其模型可简化为:
1 dispatchingTask 2 { 3 while getchar 4 若输入 == "doSomething" 5 do something 6 若输入 == "-4" 7 break 8 }
DSL驱动模块作为守护进程没有控制终端,无法通过getchar捕获终端输入。因此,必须创建一个普通的前台进程,由该进程捕获终端输入并转发给DSL驱动。但这种消息驱动的异步方式无法实现BCM_GETLINE的“同步等待”特性,还需开辟一块读写缓存。
具体实现如下图所示:
前台进程dsltst捕获用户在终端输入的命令行,并转发给DslDriver(DSL驱动)进程下的dsldbg线程。dsldbg线程若接收到BcmShell启动命令行(“StartShell”),则向dsldrv线程发送Shell启动消息;否则将命令行输入进行缓存。dsldrv线程启动BcmShell后将“轮询”该缓存,若缓存非空则读取其中的命令行并做相应处理。
注意,同一进程下线程间的消息收发通过读写一个由指向消息体数据的指针组成的循环缓冲区实现,并非传统意义上的进程间消息队列。
基于命令行交互模式的考虑,无需使用互斥机制保护缓存:
1) 每次执行单条输入并输出,等待下条输入;
2) 本次输入(写)必须等待上次输入被执行(读)并输出。
三 代码实现
为描述清晰,本节将按照代码文件组织各小节内容。
3.1 Readline.c/h
Readline.c/h为基本的命令行输入函数集。考虑到终端设备可能不支持GNU Readline库,本函数集可在需要时用来替代库的行为。
首先定义一组私有函数,如下:
1 /****************************************************************************** 2 * 函数名称: TerminateStr 3 * 功能说明: 对给定字符数组添加结束符 4 ******************************************************************************/ 5 static VOID TerminateStr(CHAR *pszStr, INT32U dwCharsNum) 6 { 7 if(0 == dwCharsNum) 8 return; 9 pszStr[dwCharsNum-1] = '