《Linux高性能服務器編程》學習總結(七)——Linux服務器程序規範
第七章 Linux服務器程序規範
服務器程序除了需要網絡通信外,還應該考慮很多其他的細節,而這些細節很多很雜,但又基本是模板式的。1)服務器程序基本都是以後臺形式運行的,沒有控制終端,不能接受用戶輸入,其父進程通常是init。2)服務器程序有一套日誌系統。3)服務器程序以某個專門的非root身份運行。4)服務器通常是可配置的。5)服務器進程啟動時通常會生成一個PID文件以記錄後臺進程的PID。6)服務器程序同城需要考慮系統資源和限制。
服務器一般使用syslog函數與rsyslogd守護進程通信,其參數需要指示日誌級別和格式化輸出,openlog函數可以改變默認輸出方式,setlogmask函數可以設置日誌掩碼,如果日誌級別大於掩碼的日誌信息被忽略。
一個進程有兩個用戶ID:UID和EUID,UID一般是進程創建者的ID,而EUID則是進程對文件和資源訪問的權限,舉個例子:/etc/passwd文件需要root權限才能訪問,但是當普通用戶想要訪問時需要在命令前加上sudo,其原理就是sudo改變了EUID,使其能夠訪問passwd文件。
我們使用一個能更改當前進程的運行權限的例子來說明一下,下面的程序可以將以root身份啟動的進程變成以普通用戶身份運行。
1 /************************************************************************* 2 > File Name: 7-2.cpp3 > Author: Torrance_ZHANG 4 > Mail: [email protected] 5 > Created Time: Fri 02 Feb 2018 07:17:19 PM PST 6 ************************************************************************/ 7 8 #include"head.h" 9 using namespace std; 10 11 static bool switch_to_user(uid_t user_id, gid_t gp_id) {12 //確保目標用戶不是root 13 if((user_id == 0) && (gp_id == 0)) return false; 14 gid_t gid = getgid(); 15 uid_t uid = getuid(); 16 //確保當前用戶是合法用戶,或者是root或者已經是目標用戶 17 if(((gid != 0) || (uid != 0)) && ((gid != gp_id) || (uid != user_id))) return false; 18 //如果不是root用戶則已經是目標用戶 19 if(uid != 0) return true; 20 //設置為目標用戶 21 if((setgid(gp_id) < 0) || (setuid(user_id) < 0)) return false; 22 return true; 23 } 24 25 int main() { 26 uid_t user_id; 27 gid_t gp_id; 28 user_id = getuid(); 29 gp_id = getgid(); 30 printf("uid = %d, gid = %d\n", user_id, gp_id); 31 if(switch_to_user(1000, 1000) == false) { 32 perror("switch_to_user"); 33 return 1; 34 } 35 user_id = getuid(); 36 gp_id = getgid(); 37 printf("uid = %d, gid = %d\n", user_id, gp_id); 38 }
root用戶的uid和gid都是0,我們成功將其改為了非0的數。
可以說進程的uid和gid都是其所屬用戶的相關信息,接下來我們來看一下進程本身都有什麽屬性信息及進程之間都有什麽關系。在Linux下每個進程都隸屬於一個進程組,所以進程除了pid信息外還有pgid,而每個進程組都有一個首領進程,其pgid和pid相同,值得一提的是,一個進程只能設置自己和其子進程的pgid,並且當子進程進行exec函數族調用後就不能在父進程中改變其pgid。而多個進程組可以形成一個會話,但是創建會話的進程不能是某個進程組的首領進程,否則將產生錯誤。對於其余進程創建會話,則有如下效果:1)調用進程成為會話的首領,此時該進程是新會話的唯一成員。2)新建一個進程組,其pgid就是調用進程的pid,調用進程成為該組首領。3)調用進程將甩開終端。
最後我看一下如何將進程以守護進程的形式運行,代碼如下:
1 /************************************************************************* 2 > File Name: 7-3.cpp 3 > Author: Torrance_ZHANG 4 > Mail: [email protected] 5 > Created Time: Fri 02 Feb 2018 07:59:21 PM PST 6 ************************************************************************/ 7 8 #include"head.h" 9 using namespace std; 10 11 bool daemonize() { 12 //創建新進程並將父進程退出可以使子進程後臺運行 13 pid_t pid = fork(); 14 if(pid < 0) return false; 15 else if(pid > 0) exit(0); 16 17 //清空文件掩碼,這樣新建的文件權限為mode&0777 18 umask(0); 19 20 //創建新會話並設置本進程為進程組首領 21 pid_t sid = setsid(); 22 if(sid < 0) return false; 23 24 //改變工作目錄 25 if((chdir("/")) < 0) return false; 26 27 //關閉標準設備 28 close(STDIN_FILENO); 29 close(STDOUT_FILENO); 30 close(STDERR_FILENO); 31 32 //將其重定向 33 open("/dev/null", O_RDONLY); 34 open("/dev/null", O_RDWR); 35 open("/dev/null", O_RDWR); 36 } 37 38 int main() { 39 daemonize(); 40 while(1) sleep(1); 41 }
我們發現程序確實以守護進程形式運行,有意思的是,第一次運行時發現其父進程不是我們熟知的托管孤兒進程的init,而是另外一個/sbin/upstart --user,通過查詢資料發現這個進程是Ubuntu圖形化界面的一個守護進程,完成了init進程的一部分功能,當切換到命令行界面執行程序後,父進程就變成了init。此外,Linux系統還提供了一個daemon函數完成這個功能。
《Linux高性能服務器編程》學習總結(七)——Linux服務器程序規範