快速上手popen()
?
該函數(shù)用于運(yùn)行指定命令,并且讓剛啟動的程序看起來像文件一樣可以被讀寫。
2 個 demo
1) 從外部程序中讀數(shù)據(jù):
int?main(int?argc,?char?**argv)
{
????FILE?*fp;
????char?buf[100];
????int?i?=?0;
????fp?=?popen("ls?-1X",?"r");
????if?(fp?!=?NULL)?{
????????while(fgets(buf,?100,?fp)?!=?NULL)?{
????????????printf("%d:?%s",?i++,?buf);
????????}
????????pclose(fp);
????????return?0;
????}
????return?1;
}
運(yùn)行效果:
$?./001_popen_r?
0:?001_popen_r
1:?002_popen_w
2:?001_popen_r.c
3:?002_popen_w.c
4:?004_popen_intern.c
2) 寫數(shù)據(jù)到外部程序:
int?main(int?argc,?char?*argv)
{
????FILE?*fp?=?NULL;
????char?buffer[BUFSIZE];
????sprintf(buffer,?"hello?worldn");
????fp?=?popen("od?-tcx1",?"w");
????if?(fp?!=?NULL)?{
????????fwrite(buffer,?sizeof(char),?strlen(buffer),?fp);
????????pclose(fp);
????????return?0;
????}
????return?1;
}
運(yùn)行效果:
0000000???h???e???l???l???o???????w???o???r???l???d??n
?????????68??65??6c??6c??6f??20??77??6f??72??6c??64??0a
0000014
相關(guān)要點(diǎn)
函數(shù)原型
FILE?*popen(const?char?*command,?const?char?*type);
popen() 會先執(zhí)行 fork,然后調(diào)用 exec 執(zhí)行 command,并且返回一個標(biāo)準(zhǔn) I/O 文件指針。
type = "r":
- 文件指針連接到 command 的標(biāo)準(zhǔn)輸出。
type = "w":
- 文件指針連接到 command 的標(biāo)準(zhǔn)輸入。
點(diǎn)擊查看大圖
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 由于調(diào)用了 shell,所以可以支持通配符 (例如*.c) 等各種 shell 擴(kuò)展特性;減少了代碼量;
缺點(diǎn):
- 要啟動 2 個程序:shell 和 目標(biāo)程序,調(diào)用成本略高,比起直接 exec 某個程序來說要慢一些;
內(nèi)部實(shí)現(xiàn)
popen() 的內(nèi)部實(shí)現(xiàn)思路如下:
FILE?*_popen(const?char?*command,?const?char?*type)
{
????pipe()
????fork();
????if?(pid?>?0)
????????close()?child's?fd
????????return?fdopen()?parent's?fd
????else
????????close(parent's?fd)
????????dup2()?child's?data?fd?to?stdin?or?stdout
????????close()?child's?fd
????????exec("/bin/sh?-c")?command
}
- 創(chuàng)建一個管道,用于父子進(jìn)程間的通訊;父進(jìn)程:
- 關(guān)閉未使用的管道端;返回父進(jìn)程數(shù)據(jù)管道端的 FILE *, 它可能連接父進(jìn)程的 stdin / stdout;
子進(jìn)程:
- 關(guān)閉未使用的管道端;重定位子進(jìn)程的數(shù)據(jù)管道端到 stdin / stdout;執(zhí)行目標(biāo)命令;
初步的代碼實(shí)現(xiàn):
FILE?*_popen(const?char?*command,?const?char?*type)
{
????int?pfp[2];
????int?parent_end,?child_end;
????int?pid;
????if?(*type?==?'r')?{
????????parent_end?=?READ;
????????child_end?=?WRITE;
????}?else?if?(*type?==?'w')?{
????????parent_end?=?WRITE;
????????child_end?=?READ;
????}?else?{
????????return?NULL;
????}
????pipe(pfp);
????pid?=?fork();
????if?(pid?>?0?)?{
????????close(pfp[child_end]);
????????return?fdopen(pfp[parent_end],?type);
????}?else?{
????????close(pfp[parent_end]);
????????dup2(pfp[child_end],?child_end);
????????close(pfp[child_end]);
????????execl("/bin/sh",?"sh",?"-c",?command,?NULL);
????????exit(0);
????}
????return?NULL;
}
這里的實(shí)現(xiàn)有一些不足的地方,例如:
為了便于閱讀,省略了錯誤檢查;
沒有保存子進(jìn)程的 pid,后續(xù)無法使用 wait() 進(jìn)行收尸;
一個進(jìn)程可能調(diào)用 popen() 多次,需要用數(shù)組 / 鏈表來存儲所有子進(jìn)程的 pid;
更完善的實(shí)現(xiàn)可以參考:
https://android.googlesource.com/platform/bionic/+/3884bfe9661955543ce203c60f9225bbdf33f6bb/libc/unistd/popen.c
應(yīng)用案例
MJPG-streamer 是什么?
https://github.com/jacksonliam/mjpg-streamer
通過 mjpg-streamer,你可以通過 PC 瀏覽器訪問到板子上的攝像頭圖像。
MJPG-streamer 就是通過 popen() 來支持 CGI 功能的:
CGI 是早期出現(xiàn)的一種簡單、流行的服務(wù)端應(yīng)用程序執(zhí)行接口,http server 通過運(yùn)行 CGI 程序來完成更復(fù)雜的處理工作,在 MJPG-streamer . 里的相關(guān)代碼如下:
plugins/output_http/httpd.c
void?execute_cgi(int?id,?int?fd,?char?*parameter,?char?*query_string)
{
????//?prepare
????//?執(zhí)行瀏覽器指定的?CGI?程序
????f?=?popen(buffer,?"r");
????//?獲得?CGI?程序的輸出
????while((i?=?fread(buffer,?1,?sizeof(buffer),?f))?>?0)?{
????????if?(write(fd,?buffer,?i)?<?0)?{
????????????fclose(f);
????????????free(buffer);
????????????close(lfd);
????????????return;
????????}
????}
}
這里只是簡單地了解一下 MJPG-Streamer,有興趣的小伙伴們自行閱讀更多的代碼吧。
相關(guān)參考
Unix-Linux 編程實(shí)踐教程 / 11.4 popen: 讓進(jìn)程看似文件
Linux 程序設(shè)計(jì)(第 4 版) / 13.3 將輸出送往 popen
Unix 環(huán)境高級編程第 3 版 / 15.3 函數(shù) popen 和 pclose
HTTP 權(quán)威指南
思考技術(shù),也思考人生
要學(xué)習(xí)技術(shù),更要學(xué)習(xí)如何生活。
你和我各有一個蘋果,如果我們交換蘋果的話,我們還是只有一個蘋果。但當(dāng)你和我各有一個想法,我們交換想法的話,我們就都有兩個想法了。
對 嵌入式系統(tǒng) (Linux、RTOS、OpenWrt、Android) 和 開源軟件 感興趣,關(guān)注公眾號:嵌入式 Hacker。
覺得文章對你有價值,還請多多 轉(zhuǎn)發(fā)。