在多進程編程中,根據業務需要會通過fork+exec執行shell腳本或其它程序,在fork后父、子進程對於每一個打開的文件描述符共享同一個文件表項,此時可能有多個文件描述符項指向同一文件表項。有時子進程不需要繼承父進程的文件描述符,並且在exec后子進程繼承下來的文件描述符成了耗費系統資源的一個累贅,此時應該怎么處理呢?接下來我將分享下我在工作中遇到的問題以及解決方案。
1、首先介紹一下fcntl函數,以下摘自Unix環境高級編程:
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
fcntl函數有五種功能:
• 復制一個現存的描述符(cmd=F_DUPFD)。
• 獲得/設置文件描述符標記(cmd = F_GETFD或F_SETFD)。
• 獲得/設置文件狀態標志(cmd = F_GETFL或F_SETFL)。
• 獲得/設置異步I/O有權(cmd = F_GETOW N或F_SETOWN)。
• 獲得/設置記錄鎖(cmd = F_GETLK , F_SETL K或F_ SETLKW)
• F_DUPFD 復制文件描述符f i l e d e s,新文件描述符作為函數值返回。它是尚未打開的各
描述符中大於或等於第三個參數值(取為整型值)中各值的最小值。新描述符與 filedes共享同
一文件表項(見圖3-3)。但是,新描述符有它自己的一套文件描述符標志,其 FD_CLOEXEC
文件描述符標志則被清除(這表示該描述符在 exec 時仍保持開放,我們將在第 8章對此進行
討論)。
• F_GETFD 對應於filedes 的文件描述符標志作為函數值返回。當前只定義了一個文件描
述符標志FD_CLOEXEC。
• F_SETFD 對於filedes 設置文件描述符標志。新標志值按第三個參數(取為整型值)設置。
應當了解很多現存的涉及文件描述符標志的程序並不使用常數FD_CLOEXEC,而是將此
標志設置為0 (系統默認,在exec時不關閉)或1 (在exec時關閉)。
• F_GETFL 對應於filedes 的文件狀態標志作為函數值返回。在說明open函數時,已說明
了文件狀態標志。它們列於表3 - 2中。
不幸的是,三個存取方式標志( O_RDONLY, O_WRONLY,以及O_RDWR )並不各占1位。(正
如前述,這三種標志的值各是 0、1和2,由於歷史原因。這三種值互斥 — 一個文件只能有這
三種值之一。)因此首先必須用屏蔽字O_ACCMODE取得存取方式位,然后將結果與這三種值
相比較。
• F_SETFL 將文件狀態標志設置為第三個參數的值(取為整型值)。可以更改的幾個標志是:
O_APPEND,O_NONBLOCK,O_SYNC和O_ASYNC。
• F_GETOWN 取當前接收SIGIO和SIGURG信號的進程ID或進程組ID。12.6.2節將論述這
兩種4.3 + BSD異步I/O信號。
• F_SETOWN 設置接收SIGIO和SIGURG信號的進程ID或進程組ID。正的arg指定一個進
程ID,負的a rg表示等於arg絕對值的一個進程組ID。
fcntl的返回值與命令有關。如果出錯,所有命令都返回- 1,如果成功則返回某個其他值。
下列三個命令有特定返回值:F_DUPFD,F_GETFD, F_GETFL以及F_GETOWN。第一個返回新
的文件描述符,第二個返回相應標志,最后一個返回一個正的進程 ID或負的進程組ID。
2、對一個文件描述符設置\清除一個或多個文件狀態標志
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
void set_fl(int fd, int flags)
{
int val;
if ((val = fcntl(fd, F_GETFL, 0)) < 0)
printf("fcntl F_GETFL error!\n");
val |= flags;
if (fcntl(fd, F_SETFL, val) < 0)
printf("fcnt F_SETFL error!\n");
}
void clr_fl(int fd, int flags)
{
int val;
if ((val = fcntl(fd, F_GETFL, 0)) < 0)
printf("fcntl F_GETFL error!\n");
val &= ~flags;
if (fcntl(fd, F_SETFL, val) < 0)
printf("fcnt F_SETFL error!\n");
}
3、fork后exec子進程繼承父進程文件描述符保持開放的問題
由於工作原因,需要通過fork后exec執行ffmpeg推流,於是發現fork后子進程繼承了父進程文件描述符,導致大量系統資源被占用。查看進程打開的文件描述符可以通過以下命令查看:
ps -ef | grep [pid];
lsof -p [pid];
也可以通過
ll proc/[pid]/fd
來查看進程打開的文件描述符選項。
4、解決方案
1、用getrlimit函數獲取最大文件描述符,系統資源的最大使用量,此處參考APUE的做法
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
int close_all_fd(void)
{
struct rlimit lim;
unsigned int i;
if (getrlimit(RLIMIT_NOFILE, &lim) < 0)
return -1;
if (lim.rlim_cur == RLIM_INFINITY)
lim.rlim_cur = 1024;
for (i = 0; i < lim.rlim_cur; i++) {
#ifdef MYPERF
if (i == 1)
continue;
#endif
if (close(i) < 0 && errno != EBADF)
return -1;
}
return 0;
}
由於ffmpeg子進程需要讀取我的fifofd 還需要將fifofd排除在外,並且個人覺得這種方式不優雅,在開啟守護進程的時候可以用這套方案
2、通過設置文件描述符標志FD_CLOEXEC,則子進程不會繼承父進程文件描述符
int flags = fcntl(iSockFd, F_GETFD);
flags |= FD_CLOEXEC;
fcntl(iSockFd, F_SETFD, flags);
在多進程中個人比較推薦這種方式操作文件描述符,個人水平有限,如果有錯誤還請指正
由於之前個人比較懶散,並且寫作水平很差,於是沒有將一些問題記錄下來,最近重讀Unix環境高級編程一書,發現了很多之前沒有注意的問題,下定決心將各種問題再次記錄下來,以便以后查閱。
之前讀書一直都沒有記筆記的習慣,導致讀完就忘,這次需要重讀的書籍有TCP/IP詳解卷一卷二,設計模式,算法第四版,大話數據結構,數據結構C語言描述,Effective C++ ,More Effective C++,Unix網絡編程卷一卷二。為了支撐自己讀完這些書籍,一定要多些博客記筆記啊。