可以通过三种方式,在HelloX操作系统基础上开发应用:
1. 以内部命令方式实现应用,直接编译链接到HelloX的内核shell中。这时候应用代码的执行上下文,是shell线程的上下文。应用程序代码不会单独成为一个独立的线程;
2. 以外部命令方式实现应用。直接编译链接到HelloX的内核中,通过shell来启动应用。这时候的应用,内核会创建一个独立的线程来承载;
3. 以外部应用方式实现应用。应用代码单独编译链接,完成后存放在安装了HelloX操作系统的计算机存储设备中。在shell下,使用loadapp命令,加载可执行的应用模块。这种方式下,应用代码也是在一个独立的线程中执行的。需要注意的是,在这种方式下,应用代码只能调用HelloX SDK提供的API接口函数和C库函数。
本文主要对第一种和第二种实现方式进行说明。
内部命令采用映射表的方式,使系统功能的添加和删除变得比较容易。比如要添加一个功能,只需要写一个处理函数,然后在HelloX的内部命令射表内添加一个项目即可。下面我们通过一个示例来说明如何往系统中添加新的功能。
第一步:假设新增的功能命令为mycommand,首先编写一个功能函数,可以直接在SHELL.CPP文件中添加,也可以通过另外的模块实现,然后在SHELL.CPP中,包含实现的命令函数的头文件。假设mycommand命令的实现函数如下。
VOID mycommand(LPSTR)
{
ChangeLine();
PrintLine("Hello,World!");
ChangeLine();
}
该函数的功能十分简单,打印出“Hello,World!”字符串,这也是大多数编程语音的一个入门示例。
第二步:把该命令字符串和命令函数添加到内部命令列表中,并更改CMD_OBJ_NUM宏为原来的值加一,因为新增加了一个内部命令。代码如下(黑体部分是修改内容):
//#define CMD_OBJ_NUM 21
#defineCMD_OBJ_NUM 22
__CMD_OBJ CmdObj[CMD_OBJ_NUM] = {
{"version" , VerHandler},
{"memory" , MemHandler},
{"sysinfo" , SysInfoHandler},
{"sysname" , SysNameHandler},
{"help" , HlpHandler},
{"cpuinfo" , CpuHandler},
{"support" , SptHandler},
{"runtime" , RunTimeHandler},
{"test" , TestHandler},
{"untest" , UnTestHandler},
{"memview" , MemViewHandler},
{"ktview" , KtViewHandler},
{"ioctrl" , IoCtrlApp},
{"sysdiag" , SysDiagApp},
{"loadapp" , LoadappHandler},
{"gui" , GUIHandler},
{"reboot" , Reboot},
{"poff" , Poweroff},
{"cls" , ClsHandler},
{“mycommand”, mycommand}
};
第三步:重新编译连接(rebuild)整个操作系统核心,并重新制作引导盘引导系统。成功启动后,在命令行提示符下,输入mycommand并回车,就可以看到mycommand的输出了。
需要注意的是,内部命令是直接在shell线程上下文中被调用的,没有自己独立的执行环境。因此只适合于实现较为简单的功能。
外部命令则会有独立的执行上下文,被内核以独立线程方式加载运行。因此,外部命令的功能非常强大,可以进一步以子命令的方式,实现更进一步的功能。比如,HelloX的网络诊断功能,就是以外部命令方式实现的。用户在shell界面下,输入network后回车,即可进入network诊断命令。这时候系统提示符会改变。在network命令下,输入help命令,可以看到该命令模式下所有可用的子命令,如下图:
其中iflist,ping等就是network应用下的子命令。在执行子命令的时候,用户可以指定参数,HelloX会以一种统一的方式,把用户指定的参数,传递给命令处理函数。
下面以network诊断外部命令的实现为例,说明外部命令的实现方式。
第一步:编写外部命令的入口处理函数。
以network命令为例,要编写类似下列形式的处理函数:
DWORD networkEntry(LPVOID p)
{
return Shell_Msg_Loop(NETWORK_PROMPT_STR,CommandParser,QueryCmdName);
}
具体的处理函数的实现,就是开发者大显神通的地方。比如要实现子命令,则需要定义一些子命令映射列表等信息。在上面的实现中,是一个消息处理循环,根据用户的输入,来调用特定的子命令。
注意,外部命令下的子命令,既可以直接在外部命令的线程上下文中执行,也可以单独创建执行线程,取决于开发者的判断。
需要注意的是,外部命令的字命令处理函数,必须以下列格式来定义:
static DWORD ping(__CMD_PARA_OBJ* lpCmdObj)
{
__PING_PARAM PingParam;
ip_addr_t ipAddr;
int count = 3; //Ping counter.
int size = 64; //Ping packet size.
BYTE index = 1;
DWORD dwRetVal = SHELL_CMD_PARSER_FAILED;
__CMD_PARA_OBJ* pCurCmdObj = lpCmdObj;
if(pCurCmdObj->byParameterNum<= 1)
{
return dwRetVal;
}
while(index <lpCmdObj->byParameterNum)
{
if(strcmp(pCurCmdObj->Parameter[index],"/c")== 0)
{
index++;
if(index>= lpCmdObj->byParameterNum)
{
break;
}
count = atoi(pCurCmdObj->Parameter[index]);
}
elseif(strcmp(pCurCmdObj->Parameter[index],"/l") == 0)
{
index++;
if(index>= lpCmdObj->byParameterNum)
{
break;
}
size = atoi(pCurCmdObj->Parameter[index]);
}
else
{
ipAddr.addr= inet_addr(pCurCmdObj->Parameter[index]);
}
index ++;
}
if(ipAddr.addr != 0)
{
dwRetVal = SHELL_CMD_PARSER_SUCCESS;
}
PingParam.count = count;
PingParam.targetAddr =ipAddr;
PingParam.size = size;
//Call ping entry routine.
ping_Entry((void*)&PingParam);
return dwRetVal;
}
子命令处理函数必须返回一个DWORD类型的值,用来表示子命令的执行情况,比如成功或者是失败。同时,子命令处理函数的参数,也必须是__CMD_PARA_OBJ* 类型。这是个内部定义的参数传递数据结构,如下:
typedef struct tag__CMD_PARA_OBJ
{
BYTE byParameterNum; //How many parameters followed.
WORD wReserved;
CHAR* Parameter[CMD_PARAMETER_COUNT];
}__CMD_PARA_OBJ;
byParameterNum指明了这个结构体中包含的参数个数,而Parameter则是一个字符串数组,包含了每个字符串参数的首地址。这与标准的C入口函数main(intargc,char* argv[])的参数是一致的。其中byParameterNum与argc对应,而Parameter则与argv数组对应。需要注意的是,数组中的第一个参数,就是子命令字符串本身。这与C的argv数组中,第一个是应用程序文件名字符串的情况一致。
子命令函数就可以通过分析__CMD_PARA_OBJ对象,来获取每个参数。
第二步:在外部命令数组中,增加入口函数信息。
外部命令数组在kernel/shell/extcmd.c文件中,在这个数组中增加一项,如下:
__EXTERNAL_COMMAND ExtCmdArray[] = {
{"fs",NULL,FALSE,fsEntry},
{"fdisk",NULL,FALSE,fdiskEntry},
{"hedit",NULL,FALSE,heditEntry},
{"fibonacci",NULL,FALSE,Fibonacci},
{"hypertrm",NULL,FALSE,Hypertrm},
{"hyptrm2",NULL,FALSE,Hyptrm2},
#if defined(__CFG_NET_IPv4) || defined(__CFG_NET_IPv6)
{"network",NULL,FALSE,networkEntry},
#endif
//Add your externalcommand/application entry here.
//{"yourcmd",NULL,FALSE,cmdentry},
//The last entry of thisarray must be the following one,
//to indicate theterminator of this array.
{NULL,NULL,FALSE,NULL}
};
数组元素的第一个参数,就是定义的外部命令字符串。用户在shell下,输入该字符串,shell就会以这个字符串为索引,搜索这个数组,找到对应的执行函数,创建一个内核线程并执行。数组元素中的最后一个参数,就是对应的外部命令入口函数。数组元素的第二和第三个参数,主要是指明了外部命令的入口参数,以及是否已阻塞方式执行。如果设置为TRUE,则以非阻塞方式执行,也就是与shell一起并行执行。否则的话,shell会阻塞,等待外部命令执行完毕后,再继续执行。一般设置为FALSE,这样可以确保shell能够及时的释放掉相关资源。
第三步:在帮助数组中增加一行,确保外部命令能够在help输出中可见。
在shell下,用户输入help命令,可以列出系统中所有可用的内部和外部命令。为了确保新增加的外部命令在help命令中可见,需要在下列数组(kernel/shell/shell1.c)中增加相关的帮助描述和输出信息:
//Handler for help command.
DWORD HlpHandler(__CMD_PARA_OBJ* pCmdParaObj) //Command 'help' 's handler.
{
LPSTR strHelpTitle = " The following commands are available currently:";
LPSTR strHelpVer = " version : Print out theversion information.";
LPSTR strHelpMem = " memory : Print out currentversion's memory layout.";
LPSTR strHelpSysInfo =" sysinfo : Print out the system context.";
LPSTR strSysName = " sysname : Change the systemhost name.";
LPSTR strHelpHelp = " help : Print out thisscreen.";
LPSTR strSupport = " support : Print out technicalsupport information.";
LPSTR strTime = " time : Show system date and time.";
LPSTR strRunTime = " runtime : Display the totalrun time since last reboot.";
LPSTR strIoCtrlApp =" ioctrl : Start IO control application.";
LPSTR strSysDiagApp = " sysdiag : System or hardwarediag application.";
LPSTR strFsApp = " fs : File system operating application.";
LPSTR strFdiskApp = " fdisk : Hard disk operating application.";
LPSTR strNetApp = " network : Network diagnostic application.";
LPSTR strLoadappApp = " loadapp : Load applicationmodule and execute it.";
LPSTR strGUIApp = " gui : Load GUI module and enter GUI mode.";
#ifdef __CFG_APP_JVM
LPSTR strJvmApp = " jvm : Start Java VM to run Java Application.";
#endif //__CFG_APP_JVM
LPSTR strReboot = " reboot : Reboot the system.";
LPSTR strCls = " cls : Clear the whole screen.";
PrintLine(strHelpTitle); //Print out the help informationline by line.
PrintLine(strHelpVer);
PrintLine(strHelpMem);
PrintLine(strHelpSysInfo);
PrintLine(strSysName);
PrintLine(strHelpHelp);
PrintLine(strSupport);
PrintLine(strTime);
PrintLine(strRunTime);
PrintLine(strIoCtrlApp);
PrintLine(strSysDiagApp);
PrintLine(strFsApp);
PrintLine(strNetApp);
PrintLine(strFdiskApp);
PrintLine(strLoadappApp);
PrintLine(strGUIApp);
#ifdef __CFG_APP_JVM
PrintLine(strJvmApp);
#endif //__CFG_APP_JVM
PrintLine(strReboot);
PrintLine(strCls);
return S_OK;
}
需要注意的是,不仅仅要增加一个LPSTR(char*)类型的字符串定义,还要在下面增加对应的PrintLine输出,否则不会有信息书出来。
完成上述三个步骤之后,重新编译HelloX内核,然后用最新的内核引导计算机。进入shell后,执行help命令,应该可以看到新增加的外部命令了。输入对应的外部命令字符串,即可看到外部命令的执行结果。
实际上,外部命令的复杂之处,主要还是在于如何处理用户输入,以及如何根据用户的输入,调用对应的子命令处理函数。HelloX实现了很多外部命令,比如本文讲到的network命令,系统诊断sysdiag命令,输入输出控制ioctrl命令,以及文件系统操作命令fs等。开发者可以在这些外部实现的基础上,利用已有的框架,修改特定的部分即可。比如,对于外部命令的入口函数,可以直接在现有的基础上,修改一下函数名。对于子命令处理函数,可以根据需要,进行修改或定义。完成后,要增加到本地子命令映射数组中。比如network命令的子命令映射数组如下:
static struct __NETWORK_CMD_MAP{
LPSTR lpszCommand;
DWORD (*CommandHandler)(__CMD_PARA_OBJ*);
LPSTR lpszHelpInfo;
}SysDiagCmdMap[] = {
{"iflist", iflist, " iflist : Show all network interface(s) insystem."},
{"ping", ping, " ping : Check a specifiedhost's reachbility."},
{"route", route, " route : List all route entry(ies) insystem."},
{"exit", _exit, " exit : Exit theapplication."},
{"help", help, " help : Print out thisscreen."},
{"showint", showint, " showint : Display ethernet interface's statisticsinformation."},
{"assoc", assoc, " assoc : Associate to a specified WiFiSSID."},
{"scan", scan, " scan : Scan WiFi networks andshow result."},
{"setif", setif, " setif : Set IP configurations to a giveninterface."},
{NULL, NULL, NULL}
};
数组元素的第一个参数,就是子命令字符串,第二个参数,是该子命令的处理函数。最后一个字符串信息,则是该子命令的帮助信息。在外部命令提示符下,输入help,就会显示出所有可用的子命令帮助信息。
开发者可以在此基础上,根据自己的实现,修改或删除特定的映射表项即可。
需要注意的是,为了确保整体代码的整洁,建议外部命令的组织,遵循下列原则:
1. 所有新增外部命令的代码,放在一个新创建的代码文件中,不要与现有的shell源文件放在一起;
2. 新增加的外部命令源文件,放在shell目录下。