HelloX操作系統應用編程指南
HelloX應用開發概述
可以通過三種方式,在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目錄下。