簡介
守護進程是一種運行在後臺的特殊進程,它獨立於控制終端並且週期性地執行某種任務或等待處理某些發生的事件。
其實,本質上守護進程和普通的進程並沒有什麼區別,只是我們規定了一種進程的編寫規則,將其叫做守護進程,僅此而已。
特點
1. 在後臺運行
爲了不讓其阻塞終端,我們用fork()創建子進程,然後退出父進程,就可以完成在後臺運行的目的。
2. 脫離控制終端,創建新的會話組和進程組
簡單說一下會話、進程、進程組以及控制終端的關係。多個進程構成進程組,多個進程組構成一個會話,會話可以有一個控制終端
此時我們的會話進程組等都是由父進程繼承來的,我們要與之脫離,不受其影響。
方法是調用setsid();成功返回0,失敗返回-1。
執行成功後會爲當前進程創建會話組併成爲該會話組的組長,當然也是新的進程組的組長了。
當調用者本身就是會話組長時會失敗。
3. 使其不再是會話組長,用以禁止其打開終端
仍然是通過fork()產生子進程,然後退出父進程來做到這一點
4. 關閉已經打開文件描述符
這裏的作用是避免對資源的佔用
for(int i=0; i<NOFILE; i++)
close(i);
//NOFILE所屬頭文件是 <sys/param.h>
5. 更改當前工作目錄
原因很簡單,比如你講工作目錄設在了/home/aaa下,現在你要刪掉aaa,是刪不掉的,因爲守護進程在運行過程中,是依賴於aaa這個目錄的。
通常需要將工作目錄設爲根,根據特定情況而定。
chdir("/")
6. 重設文件掩碼
使文件操作權限不再受父進程影響
直接將其設爲0即可。
umask(0);
這裏強調一點,umask函數裏的參數,我們平時使用的比如0666,第一位的0只是表示0666是一個八進制的數,沒有別的含義。與umask命令略有區別
7. 處理信號(非必須)
signal(SIGCHLD, SIG_IGN);
忽略SIGCHLD信號,常用於併發服務器的性能提升。因爲併發服務器常常fork很多子進程,子進程終結之後需要服務器進程去wait清理資源。如果將此信號的處理方式設爲忽略,內核就會把殭屍子進程轉交給init進程去處理,避免了大量殭屍進程對系統資源的佔用。
代碼
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <stdlib.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>
#define DIR "/Users/shiyi/Desktop"
#define TMP_TXT "/Users/shiyi/Desktop/tmp.txt"
void init_daemon()
{
//轉爲後臺進程
int pid = fork();
if(pid < 0)
exit(1);
else if(pid != 0)
exit(0);
//開啓新的會話組,成爲會話組長和進程組長
setsid();
//使其不再是會話組長,不能開啓終端
pid = fork();
if(pid < 0)
exit(1);
else if(pid != 0)
exit(0);
//關閉已經打開的文件描述符,避免浪費系統資源
for(int i=0; i<NOFILE; i++)
close(i);
//更改工作目錄
chdir(DIR);
//重設文件掩碼,使文件操作權限不再受父進程影響
umask(0000);
//重定向輸入輸出
int fd = open(TMP_TXT, O_CREAT | O_RDWR, 0644);
if(fd < 0)
exit(2);
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
close(fd);
//忽略SIGCHLD信號,避免大量殭屍進程佔用系統資源
signal(SIGCHLD, SIG_IGN);
}
int main()
{
//創建守護進程
init_daemon();
time_t t;
char str[30];
for(int i=0; i<10; i++)
{
time(&t);
strcpy(str, ctime(&t));
str[strlen(str)-1] = '\0';
printf("%s : 當前是第%d次循環\n", str, i);
fflush(NULL);
sleep(1);
}
return 0;
}