文章目錄
- 服務器程序規范
- 日志
- rsyslogd 守護進程
- syslog函數
- openlog函數
- setlogmask函數
- closelog函數
- 用戶
- 進程間的關系
- 進程組
- 會話
- 系統資源限制
- 改變工作目錄和根目錄
- 服務器程序后臺化
服務器程序規范
Linux
服務器程序一般以后臺進程(守護進程[daemon
])形式運行。它沒有控制終端,因此不會意外接受到用戶輸入,守護進程的父進程通常是init
進程(PID為1)。- 服務器的調試和維護都需要一個專業的日志系統,
Linux
常用一個守護進程rsyslogd(syslogd的升級版)
來處理系統日志。 Linux
服務器程序一般以非root
的身份運行。如:mysqld
、httpd
、syslogd
等后臺進程分別擁有自己的運行賬戶mysql
、apache
和syslog
。Linux
服務器程序通常能處理很多命令行選項,如果一次運行的選項太多,那么可以用配置文件來管理。配置文件一般放在/etc
目錄下。Linux
服務器程序通常在啟動時會生成一個記錄該后臺進程PID
的文件并存入/var/run
目錄中。例如:syslogd
的PID
文件是/var/run/syslogd.pid
。
日志
rsyslogd 守護進程
既能接受用戶進程輸出的日志,也能接受內核日志。
- 用戶進程調用
syslog函數
將日志輸出到一個UNIX
本地域socket
類型(AF_UNIX
)的文件/dev/log
中,rsyslogd
則監聽該文件以獲得用戶進程的輸出。 - 內核日志由
printk
等函數打印至內核的環狀緩存(ring buffer)
中,環狀緩存的內容直接映射到/proc/kmsg
中,rsyslogd
讀取該文件以獲得內核日志。
syslog函數
應用程序通過 syslog函數
與 rsyslogd
守護進程通信:
#include<syslog.h>
void syslog( int priority, const char* message, ...);
// 采用可變參數(message 和 第三個參數……)來結構化輸出
// priority:設施值與日志級別的按位或,設施值的默認值是 LOG_USER。
限于 LOG_USER
設施值對應的日志級別有:
#include<syslog.h>
#define LOG_EMERG 0 // 系統不可用
#define LOG_ALERT 1 // 報警,需要立即采取動作
#define LOG_CRIT 2 // 非常嚴重的狀況
#define LOG_ERR 3 // 錯誤
#define LOG_WARNING 4 // 警告
#define LOG_NOTICE 5 // 通知
#define LOG_INFO 6 // 信息
#define LOG_DEBUG 7 // 調試
openlog函數
改變 syslog
的默認輸出方式,進一步結構化日志內容:
#include<syslog.h>
void openlog( const char* ident, int logopt, int facility );
// ident指定的字符串被添加到日志消息的日期和時間之后,通常被設置為程序的名字
// logopt對 syslog 調用的行為進行配置,為下列值的按位或:
#define LOG_PID 0x01 // 在日志消息中博阿寒程序 PID
#define LOG_CONS 0x02 // 如果消息不能記錄到日志文件,則打印至終端
#define LOG_ODELAY 0X04 // 延遲打開日志功能知道第一次調用 syslog
#define LOG_NDELAY 0x08 // 不延遲打開日志功能
// facility用來修改 syslog 函數中的默認設施值
setlogmask函數
程序在開發階段可能需要輸出很多調試信息,而發布之后又需要將這些調試信息關閉。
解決這個問題的方法并非是在發布后刪除調試代碼(日后可能還需要用到),而有更簡單的做法——設置日志掩碼,使日志級別大于掩碼的日志信息被系統忽略。
setlogmask函數
就用以設置日志掩碼:
#include<syslog.h>
int setlogmask( int maskpri );
// maskpri指定日志掩碼值
// 該函數始終會成功,返回調用進程舊的日志掩碼值
closelog函數
用以關閉日志功能:
#include<syslog.h>
void closelog();
用戶
服務器中不同的用戶有不同的權限,因此 獲取or設置 當前進程的 真實用戶ID(UID)、有效用戶ID(EUID)、真實組ID(GID)、有效組(EGID) 就尤為重要:
#include <sys/types.h>
#include <unistd.h>
uid_t getuid();
uid_t geteuid();
gid_t getgid();
gid_t getegid();
int setuid( uid_t uid );
int seteuid( uid_t uid );
int setgid( gid_t gid );
int setegid( gid_t gid );
一個進程有兩個 用戶ID: UID
和 EUID
。EUID
方便了資源訪問:使得 運行程序的用戶 擁有 該程序的有效用戶 的權限。如:任何用戶都可以通過 su程序
修改自己的賬戶信息,這就不得不訪問需要 root
權限的 /etc/passwd
文件。那么以普通用戶身份啟動的 su程序
如何能訪問/etc/passwd
文件呢?
用 ls
命令可以查看到,su程序
的所有者是 root
,且被設置了 set-user-id標志
,即任何普通用戶運行 su程序
時,su程序
的有效用戶為 root
。有效用戶為 root
的進程被稱為 特權進程(privileged processes)。
類似的,EGID
為運行目標程序的組用戶提供有效組的權限。
進程間的關系
進程組
Linux
下每個進程都隸屬于一個進程組,PGID
即為它的 進程組ID。進程組將一直存在,知道其中所有進程都退出或者加入到其他線程組。每個進程組都有一個首領進程,其 PGID
和 PID
相同。
#include< unistd.h>
pid_t getpgid( pid_t pid );
// 成功返回 ID,失敗返回 -1 并設置 errno
int setpgid( pid_t pid, pid_t pgid );
// 將 PID 為 pid 的進程的 PGID 設置為 pgid,若 pid 和 pgid 相等,則 pid 指定的進程將被設定為進程組首領
// 若 pid=0,則表示設置 當前進程 的 PGID 為 pgid
// 若 pgid=0,則使用 pid 作為目標進程的 PGID
// 成功時返回 0,失敗返回 -1 并設置 errno
一個進程只能設置自己或者子進程的 PGID
,子進程調用 exec
系列函數后,父進程不再能設置它的 PGID
。
會話
一些有關聯的進程組能形成一個會話(session
),下面的函數用以創建一個會話:
#include<unistd.h>
pid_t setsid( void );
// 成功時返回新的進程組的 PGID,失敗則返回 -1 并設置 errno
該函數不能由進程組的首領進程調用,否則產生一個錯誤。對于非組首領的進程,創建新會話的同時且有如下額外效果:
- 調用進程成為會話的首領,此時該進程是新會話的唯一成員。
- 新建一個進程組,其
PGID
就是調用進程的PID
,調用進程成為該組的首領。 - 調用進程將甩開終端(如果有的話)。
Linux
進程并未提供所謂 會話ID(SID)
的概念,但將會話首領所在的進程組的 PGID
視為 SID
,并提供了如下函數來讀取 SID
:
#include<unistd.h>
pid_t getsid( pid_t pid );
系統資源限制
Linux 上運行的程序都會受到資源限制的影響,比如物理設備限制(CPU數量、內存數量等)、系統策略限制(CPU時間等),以及具體實現的限制(文件名的最大長度)。這些系統資源限制可以通過下面的函數來讀取和設置:
#include <sys/resource.h>
int getrlimit( int resource, struct rlimit* rlim );
int setrlimit( int resource, const struct rlimit* rlim );
// 成功時返回 0,失敗時返回 -1 并設置 errno。
struct rlimit{rlim_t rlim_cur; // 指定資源的軟限制,是一個建議性的,最好不要超越的限制,若超越限制,則系統可能向進程發送信號以終止其運行。rlim_t rlim_max; // 指定資源的硬限制,一般是軟限制的上限。普通程序可以減小硬限制,只有以 root 身份運行的程序才能增加硬限制。// rlim_t是一個整數類型,描述資源級別。
};
除上述外:
- 還可以使用
ulimit
命令修改當前shell
環境下的資源限制(軟限制或/和硬限制),這種修改將對該shell
啟動的所有后續程序有效。 - 還可以通過修改配置文件來改變系統軟限制和硬限制,這種修改是永久的。
改變工作目錄和根目錄
#include<unistd.h>
/* 獲取進程當前工作目錄 */
char* getcwd( char* buf, size_t size );
// buf指向的內存用于存儲進程當前工作目錄的絕對路徑名,大小由 size 參數指定
// 如果當前工作的絕對路徑長度(加上末尾的“\0”)超過了 size,則返回 NULL 并設置 errno 為 ERANGE。
// 若 buf 為 NULL 并且 size 非 0,則 getcwd 可能在內部使用 malloc 動態分配內存,并將進程的當前工作目錄存儲在其中,
// 此時我們需要自己釋放 getcwd 在內部創建的這塊內存。
// 成功返回一個指向目標存儲區(buf指向的緩存區或者getcwd在內部動態創建的緩存區)的指針,失敗返回 NULL 并設置 errno。/* 改變進程的工作目錄 */
int chdir( const char* path );
// path 指定要切換到的目標目錄
// 成功返回 0,失敗返回 -1 并設置 errno/* 改變進程根目錄 */
int chroot(const char* path);
// 參數和返回值同上,調用本函數后,仍需使用 chdir("/") 來將工作目錄切換至新的根目錄。
// 改變進程的根目錄后,程序可能無法訪問處于舊路徑的文件or目錄,
// 不過可以利用進程已經打開的文件描述符來訪問調用 chroot 之后不能直接訪問的文件。
// 只有特權進程才能改變根目錄
服務器程序后臺化
讓一個進程以守護進程的方式運行:
#include<unistd.h>
int daemon(int nochdir, int noclose);
// nochdir 用于指定是否改變工作目錄,為 0 則工作目錄被設置為“/”(根目錄),否則繼續使用當前目錄
// noclose 為 0 時,標準輸入、輸出、錯誤輸出都被重定向到 /dev/null 文件,否則依然使用原來的設備
// 成功返回 0,失敗返回 -1 并設置 errno