守護進(jìn)程:
守護進(jìn)程最重要的特性是后臺運行;其次,守護進(jìn)程必須與其運行前的環(huán)境隔離開(kāi)來(lái)。
這些環(huán)境包括未關(guān)閉的文件描述符,控制終端,會(huì )話(huà)和進(jìn)程組,工作目錄以及文件創(chuàng )建掩模等。
這些環(huán)境通常是守護進(jìn)程從執行它的父進(jìn)程(特別是shell)中繼承下來(lái)的;
最后,守護進(jìn)程的啟動(dòng)方式有其特殊之處------它可以在 Linux系統啟動(dòng)時(shí)從啟動(dòng)腳本/etc/rc.d中啟動(dòng),
可以由作業(yè)規劃進(jìn)程crond啟動(dòng),還可以由用戶(hù)終端(通常是shell)執行。
總之,除開(kāi)這些特殊性以外,守護進(jìn)程與普通進(jìn)程基本上沒(méi)有什么區別,
因此,編寫(xiě)守護進(jìn)程實(shí)際上是把一個(gè)普通進(jìn)程按照上述的守護進(jìn)程的特性改造成為守護進(jìn)程。
守護進(jìn)程的編程要點(diǎn)
1. 在后臺運行。
為避免掛起控制終端將Daemon放入后臺執行。方法是在進(jìn)程中調用fork使
父進(jìn)程終止, 讓Daemon在子進(jìn)程中后臺執行。
if(pid=fork()) exit(0); //是父進(jìn)程,結束父進(jìn)程,子進(jìn)程繼續
2. 脫離控制終端,登錄會(huì )話(huà)和進(jìn)程組
有必要先介紹一下Linux中的進(jìn)程與控制終端,登錄會(huì )話(huà)和進(jìn)程組之間的關(guān)系:
進(jìn)程屬于 一個(gè)進(jìn)程組,進(jìn)程組號(GID)就是進(jìn)程組長(cháng)的進(jìn)程號(PID)。
登錄會(huì )話(huà)可以包含多個(gè)進(jìn)程組。這些進(jìn)程組共享一個(gè)控制終端。這個(gè)控制終端通常是創(chuàng )建進(jìn)程的登錄終端。
控制終端,登錄會(huì )話(huà)和進(jìn)程組通常是從父進(jìn)程繼承下來(lái)的。我們的目的就是要擺脫它們 ,使之不受它們的影響。
方法是在第1點(diǎn)的基礎上,調用setsid()使進(jìn)程成為會(huì )話(huà)組長(cháng):
setsid();
說(shuō)明:當進(jìn)程是會(huì )話(huà)組長(cháng)時(shí)setsid()調用失敗。但第一點(diǎn)已經(jīng)保證進(jìn)程不是會(huì )話(huà)組長(cháng)。
setsid()調用成功后,進(jìn)程成為新的會(huì )話(huà)組長(cháng)和新的進(jìn)程組長(cháng),并與原來(lái)的登錄會(huì )話(huà)和進(jìn)程組脫離。
由于會(huì )話(huà)過(guò)程對控制終端的獨占性,進(jìn)程同時(shí)與控制終端脫離。
3. 禁止進(jìn)程重新打開(kāi)控制終端
現在,進(jìn)程已經(jīng)成為無(wú)終端的會(huì )話(huà)組長(cháng)。但它可以重新申請打開(kāi)一個(gè)控制終端。
可以通過(guò)使進(jìn)程不再成為會(huì )話(huà)組長(cháng)來(lái)禁止進(jìn)程重新打開(kāi)控制終端:
if(pid=fork()) exit(0); //結束第一子進(jìn)程,第二子進(jìn)程繼續(第二子進(jìn)程不再是會(huì )話(huà)組長(cháng))
4. 關(guān)閉打開(kāi)的文件描述符
進(jìn)程從創(chuàng )建它的父進(jìn)程那里繼承了打開(kāi)的文件描述符。
如不關(guān)閉,將會(huì )浪費系統資源, 造成進(jìn)程所在的文件系統無(wú)法卸下以及引起無(wú)法預料的錯誤。
按如下方法關(guān)閉它們:
for(i=0;i 關(guān)閉打開(kāi)的文件描述符close(i);
5. 改變當前工作目錄
進(jìn)程活動(dòng)時(shí),其工作目錄所在的文件系統不能卸下。一般需要將工作目錄
改變到根目錄 。對于需要轉儲核心,寫(xiě)運行日志的進(jìn)程將工作目錄改變到特定目錄如/tmp
chdir("/")
6. 重設文件創(chuàng )建掩模
進(jìn)程從創(chuàng )建它的父進(jìn)程那里繼承了文件創(chuàng )建掩模。它可能修改守護進(jìn)程所創(chuàng )建的文件的存取位。
為防止這一點(diǎn),將文件創(chuàng )建掩模清除:
umask(0);
7. 處理SIGCHLD信號
處理SIGCHLD信號并不是必須的。但對于某些進(jìn)程,特別是服務(wù)器進(jìn)程往往在請求到來(lái)時(shí)生成子進(jìn)程處理請求。
如果父進(jìn)程不等待子進(jìn)程結束,子進(jìn)程將成為僵尸進(jìn)程(zombie )從而占用系統資源。
如果父進(jìn)程等待子進(jìn)程結束,將增加父進(jìn)程的負擔,影響服務(wù)器 進(jìn)程的并發(fā)性能。
在Linux下可以簡(jiǎn)單地將SIGCHLD信號的操作設為SIG_IGN。
signal(SIGCHLD,SIG_IGN);
這樣,內核在子進(jìn)程結束時(shí)不會(huì )產(chǎn)生僵尸進(jìn)程。
這一點(diǎn)與BSD4不同,BSD4下必須顯式等待子進(jìn)程結束才能釋放僵尸進(jìn)程.
關(guān)于/dev/null及用途
把/dev/null看作"黑洞". 它非常等價(jià)于一個(gè)只寫(xiě)文件.
所有寫(xiě)入它的內容都會(huì )永遠丟失. 而嘗試從它那兒讀取內容則什么也讀不到.
然而, /dev/null對命令行和腳本都非常的有用.
禁止標準輸出.
1 cat $filename >/dev/null
2 # 文件內容丟失,而不會(huì )輸出到標準輸出.
禁止標準錯誤
1 rm $badname 2>/dev/null
2 # 這樣錯誤信息[標準錯誤]就被丟到太平洋去了.
禁止標準輸出和標準錯誤的輸出.
1 cat $filename 2>/dev/null >/dev/null
2 # 如果"$filename"不存在,將不會(huì )有任何錯誤信息提示.
3 # 如果"$filename"存在, 文件的內容不會(huì )打印到標準輸出.
4 # 因此Therefore, 上面的代碼根本不會(huì )輸出任何信息.
5 # 當只想測試命令的退出碼而不想有任何輸出時(shí)非常有用。
6 #-----------測試命令的退出 begin ----------------------#
7 # ls dddd 2>/dev/null 8
8 # echo $? //輸出命令退出代碼:0為命令正常執行,1-255為有出錯。
9 #-----------測試命令的退出 end-----------#
10# cat $filename &>/dev/null
11 #也可以, 由 Baris Cicek 指出.
清除日志文件內容
1 cat /dev/null > /var/log/messages
2 # : > /var/log/messages 有同樣的效果, 但不會(huì )產(chǎn)生新的進(jìn)程.(因為:是內建的)
3
4 cat /dev/null > /var/log/wtmp
例子 28-1. 隱藏cookie而不再使用
1 if [ -f ~/.netscape/cookies ] # 如果存在則刪除.
2 then
3 rm -f ~/.netscape/cookies
4 fi
5
6 ln -s /dev/null ~/.netscape/cookies
7 # 現在所有的cookies都會(huì )丟入黑洞而不會(huì )保存在磁盤(pán)上了.
--------------------------------------------------------------------------------------------------
首先,分析該軟件的結構體:
--------------------------------------------------------------------------------------------------
globals結構體:
--------------------------------------------------------------------------------------------------
typedef struct _globals globals; /* mjpg-streamer只支持一個(gè)輸入插件,多個(gè)輸出插件 */
struct _globals {
int stop; /* 一個(gè)全局標志位 */
pthread_mutex_t db; /* 互斥鎖,數據鎖 */
pthread_cond_t db_update; /* 條件變量,數據更新的標志 */
unsigned char *buf; /* 全局JPG幀的緩沖區的指針 */
int size; /* 緩沖區的大小 */
input in; /* 輸入插件,一個(gè)輸入插件可對應多個(gè)輸出插件 */
output out[MAX_OUTPUT_PLUGINS]; /* 輸出插件,以數組形式表示 */
int outcnt; /* 輸出插件的數目 */
};
--------------------------------------------------------------------------------------------------
input結構體:
/* structure to store variables/functions for input plugin */
typedef struct _input input;
struct _input {
char *plugin; /* 動(dòng)態(tài)鏈接庫的名字,或者是動(dòng)態(tài)鏈接庫的地址 */
void *handle; /* 動(dòng)態(tài)鏈接庫的句柄,通過(guò)該句柄可以調用動(dòng)態(tài)庫中的函數 */
input_parameter param; /* 插件的參數 */
int (*init)(input_parameter *); /* 四個(gè)函數指針 */
int (*stop)(void);
int (*run)(void);
int (*cmd)(in_cmd_type, int); /* 處理命令的函數 */
};
/* parameters for input plugin */
typedef struct _input_parameter input_parameter;
struct _input_parameter {
char *parameter_string;
struct _globals *global;
};
--------------------------------------------------------------------------------------------------
output結構體:
/* structure to store variables/functions for output plugin */
typedef struct _output output;
struct _output {
char *plugin; /* 插件的名字 */
void *handle; /* 動(dòng)態(tài)鏈接庫的句柄,通過(guò)該句柄可以調用動(dòng)態(tài)庫中的函數 */
output_parameter param; /* 插件的參數 */
int (*init)(output_parameter *); /* 四個(gè)函數指針 */
int (*stop)(int);
int (*run)(int);
int (*cmd)(int, out_cmd_type, int); /* 處理命令的函數 */
};
/* parameters for output plugin */
typedef struct _output_parameter output_parameter;
struct _output_parameter {
int id; /* 用于標記是哪一個(gè)輸出插件的參數 */
char *parameter_string;
struct _globals *global;
};
--------------------------------------------------------------------------------------------------
現在開(kāi)始分析main()函數:
--------------------------------------------------------------------------------------------------
默認情況下,程序會(huì )將video0作為輸入,http的8080端口作為輸出 (fps = frames per second)
char *input = "input_uvc.so --resolution 640x480 --fps 5 --device /dev/video0";
char *output[MAX_OUTPUT_PLUGINS]; /* 一個(gè)輸入最大可以對應10個(gè)輸出 */
output[0] = "output_http.so --port 8080"; /* 將video0作為輸入,http的8080端口作為輸出 */
--------------------------------------------------------------------------------------------------
下面是一個(gè)while()循環(huán),來(lái)解析main()函數后面所帶的參數
/* parameter parsing */
while(1) {
int option_index = 0, c=0;
static struct option long_options[] = \ /* 長(cháng)選項表,進(jìn)行長(cháng)選項的比對 */
{
{"h", no_argument, 0, 0}, /* 第一個(gè)參數為選項名,前面沒(méi)有短橫線(xiàn)。譬如"help"、"verbose"之類(lèi) */
{"help", no_argument, 0, 0}, /* 第二個(gè)參數描述了選項是否有選項參數 |no_argument 0 選項沒(méi)有參數|required_argument 1 選項需要參數|optional_argument 2 選項參數可選|*/
{"i", required_argument, 0, 0}, /* 第三個(gè)參數指明長(cháng)選項如何返回,如果flag為NULL,則getopt_long返回val。
{"input", required_argument, 0, 0}, * 否則返回0,flag指向一個(gè)值為val的變量。如果該長(cháng)選項沒(méi)有發(fā)現,flag保持不變.
{"o", required_argument, 0, 0}, */
{"output", required_argument, 0, 0}, /* 第四個(gè)參數是發(fā)現了長(cháng)選項時(shí)的返回值,或者flag不是NULL時(shí)載入*flag中的值 */
{"v", no_argument, 0, 0},
{"version", no_argument, 0, 0},
{"b", no_argument, 0, 0}, /* 每個(gè)長(cháng)選項在長(cháng)選項表中都有一個(gè)單獨條目,該條目里需要填入正確的數值。數組中最后的元素的值應該全是0。
{"background", no_argument, 0, 0}, *數組不需要排序,getopt_long()會(huì )進(jìn)行線(xiàn)性搜索。但是,根據長(cháng)名字來(lái)排序會(huì )使程序員讀起來(lái)更容易.
{0, 0, 0, 0} */
};
--------------------------------------------------------------------------------------------------
c = getopt_long_only(argc, argv, "", long_options, &option_index);
--------------------------------------------------------------------------------------------------
下面重點(diǎn)分析一下getopt_long_only函數:
int getopt_long_only(int argc, char * const argv[],const char *optstring,const struct option *longopts, int *longindex);
該函數每解析完一個(gè)選項,就返回該選項字符。
如果選項帶參數,參數保存在optarg中。如果選項帶可選參數,而實(shí)際無(wú)參數時(shí),optarg為NULL。
當遇到一個(gè)不在optstring指明的選項時(shí),返回字符'?'。如果在optstring指明某選項帶參數而實(shí)際沒(méi)有參數時(shí),返回字符'?'或者字符':',視optstring的第一個(gè)字符而定。這兩種情況選項的實(shí)際值被保存在optopt中。
當解析錯誤時(shí),如果opterr為1則自動(dòng)打印一條錯誤消息(默認),否則不打印。
當解析完成時(shí),返回-1。
每當解析完一個(gè)argv,optind就會(huì )遞增。如果遇到無(wú)選項參數,getopt默認會(huì )把該參數調后一位,接著(zhù)解析下一個(gè)參數。如果解析完成后還有無(wú)選項的參數,則optind指示的是第一個(gè)無(wú)選項參數在argv中的索引。
最后一個(gè)參數longindex在函數返回時(shí)指向被搜索到的選項在longopts數組中的下標。longindex可以為NULL,表明不需要返回這個(gè)值
--------------------------------------------------------------------------------------------------
/* no more options to parse */
if (c == -1) break;
/* unrecognized option */
if(c=='?'){ help(argv[0]); return 0; }
switch (option_index) {
/* h, help */
case 0:
case 1:
help(argv[0]);
return 0;
break;
/* i, input */
case 2:
case 3:
input = strdup(optarg);
break;
/* o, output */
case 4:
case 5:
output[global.outcnt++] = strdup(optarg);
break;
/* v, version */
case 6:
case 7:
printf("MJPG Streamer Version: %s\n" \
"Compilation Date.....: %s\n" \
"Compilation Time.....: %s\n", SOURCE_VERSION, __DATE__, __TIME__);
return 0;
break;
/* b, background */
case 8:
case 9:
daemon=1;
break;
default:
help(argv[0]);
return 0;
}
}
--------------------------------------------------------------------------------------------------
好,現在分析一下該程序是否需要成為守護進(jìn)程:
/* fork to the background */
if ( daemon ) { /* 當命令后面設置了b命令時(shí),daemon就會(huì )被置為1 */
LOG("enabling daemon mode");
daemon_mode();
}
現在看一看daemon_mode()時(shí)如何創(chuàng )建守護進(jìn)程的
void daemon_mode(void) {
int fr=0;
fr = fork();
if( fr < 0 ) { /* fork失敗 */
fprintf(stderr, "fork() failed\n");
exit(1);
}
if ( fr > 0 ) { /* 結束父進(jìn)程,子進(jìn)程繼續 */
exit(0);
}
if( setsid() < 0 ) { /* 創(chuàng )建新的會(huì )話(huà)組,子進(jìn)程成為組長(cháng),并與控制終端分離 */
fprintf(stderr, "setsid() failed\n");
exit(1);
}
fr = fork(); /* 防止子進(jìn)程(組長(cháng))獲取控制終端 */
if( fr < 0 ) { /* fork錯誤,退出 */
fprintf(stderr, "fork() failed\n");
exit(1);
}
if ( fr > 0 ) { /* 父進(jìn)程,退出 */
fprintf(stderr, "forked to background (%d)\n", fr);
exit(0);
} /* 第二子進(jìn)程繼續執行 , 第二子進(jìn)程不再是會(huì )會(huì )話(huà)組組長(cháng)*/
umask(0); /* 重設文件創(chuàng )建掩碼 */
chdir("/"); /* 切換工作目錄 */
close(0); /* 關(guān)閉打開(kāi)的文件描述符*/
close(1);
close(2);
open("/dev/null", O_RDWR); /* 將0,1,2重定向到/dev/null */
dup(0);
dup(0);
}
--------------------------------------------------------------------------------------------------
初始化global全局變量
global.stop = 0;
global.buf = NULL;
global.size = 0;
global.in.plugin = NULL;
--------------------------------------------------------------------------------------------------
同步全局圖像緩沖區:
pthread_mutex_init(&global.db, NULL);
pthread_cond_init(&global.db_update, NULL);
--------------------------------------------------------------------------------------------------
忽略SIGPIPE信號(當關(guān)閉TCP sockets時(shí),OS會(huì )發(fā)送該信號)
signal(SIGPIPE, SIG_IGN);
--------------------------------------------------------------------------------------------------
注冊<CTRL>+C信號處理函數,來(lái)結束該程序
signal(SIGINT, signal_handler);
------------------------------------------------------------------------------------------
void signal_handler(int sig)
{
int i;
/* signal "stop" to threads */
LOG("setting signal to stop\n");
global.stop = 1;
usleep(1000*1000);
/* clean up threads */
LOG("force cancelation of threads and cleanup ressources\n");
global.in.stop();
for(i=0; i<global.outcnt; i++) {
global.out[i].stop(global.out[i].param.id);
}
usleep(1000*1000);
/* close handles of input plugins */
dlclose(&global.in.handle);
for(i=0; i<global.outcnt; i++) {
dlclose(global.out[i].handle);
}
DBG("all plugin handles closed\n");
pthread_cond_destroy(&global.db_update);
pthread_mutex_destroy(&global.db);
LOG("done\n");
closelog();
exit(0);
return;
}
------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------
打開(kāi)輸入插件:
tmp = (size_t)(strchr(input, ' ')-input);
global.in.plugin = (tmp > 0)?strndup(input, tmp):strdup(input); /* 在命令中獲得動(dòng)態(tài)庫 */
global.in.handle = dlopen(global.in.plugin, RTLD_LAZY); /* 打開(kāi)動(dòng)態(tài)鏈接庫 */
global.in.init = dlsym(global.in.handle, "input_init"); /* 獲得動(dòng)態(tài)庫內的input_init()函數 */
global.in.stop = dlsym(global.in.handle, "input_stop"); /* 獲得動(dòng)態(tài)庫內的input_stop()函數 */
global.in.run = dlsym(global.in.handle, "input_run"); /* 獲得動(dòng)態(tài)庫內的input_run()函數 */
/* try to find optional command */
global.in.cmd = dlsym(global.in.handle, "input_cmd"); /* 獲得動(dòng)態(tài)庫內的input_cmd()函數 */
global.in.param.parameter_string = strchr(input, ' '); /* 將命令參數的起始地址賦給para.parameter_string,已經(jīng)去掉前賣(mài)弄的動(dòng)態(tài)庫 */
global.in.param.global = &global; /* 將global結構體的地址賦給param.global */
global.in.init(&global.in.param); /* 傳遞global.in.param給init,進(jìn)行初始化 */
}
--------------------------------------------------------------------------------------------------
打開(kāi)輸出插件:
for (i=0; i<global.outcnt; i++) { /* 因為是一個(gè)輸入對應多個(gè)輸出,所以輸出采用了for循環(huán) */
tmp = (size_t)(strchr(output[i], ' ')-output[i]);
global.out[i].plugin = (tmp > 0)?strndup(output[i], tmp):strdup(output[i]);
global.out[i].handle = dlopen(global.out[i].plugin, RTLD_LAZY);
global.out[i].init = dlsym(global.out[i].handle, "output_init");
global.out[i].stop = dlsym(global.out[i].handle, "output_stop");
global.out[i].run = dlsym(global.out[i].handle, "output_run");
/* try to find optional command */
global.out[i].cmd = dlsym(global.out[i].handle, "output_cmd");
global.out[i].param.parameter_string = strchr(output[i], ' ');
global.out[i].param.global = &global;
global.out[i].param.id = i;
global.out[i].init(&global.out[i].param);
}
--------------------------------------------------------------------------------------------------
開(kāi)始運行輸入插件的run()函數:
global.in.run();
--------------------------------------------------------------------------------------------------
開(kāi)始運行輸出插件的run()函數:
for(i=0; i<global.outcnt; i++)
global.out[i].run(global.out[i].param.id);
--------------------------------------------------------------------------------------------------
運行完以上函數,該進(jìn)程進(jìn)入休眠狀態(tài),等待用戶(hù)按下<CTRL>+C結束所有的進(jìn)程:
pause();
--------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------
聯(lián)系客服