第7章 Linux服务器程序规范
7.1 日志
Linux提供rsyslogd守护进程接收用户进程输出的日志和内核日志。
应用程序使用syslog函数与rsyslogd守护进程通信。
void syslog(int priority, const char* message, …);
openlog函数:改变syslog的默认输出方式。
setlogmask函数:设置syslog的日志掩码(日志过滤,使日志级别大于日志掩码的日志信息被系统忽略)。
closelog函数:关闭日志功能。
7.2 用户信息
真实用户ID(UID)、有效用户ID(EUID)、真实组ID(GID)、有效组ID(EGID)。
一个进程拥有两个用户ID:UID和EUID。EUID存在的目的是方便资源访问:它使得运行程序的用户拥有该程序的有效用户的权限。
有效用户为root的进程称为特权进程。EGID的含义与EUID类似:给运行目标程序的组用户提供有效组的权限。
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
uid_t uid = getuid();
uid_t euid = geteuid();
printf("userid is %d, effective userid is:%d \n", uid, euid);
return 0;
}
进程的UID是启动程序的用户的ID,而EUID则是root账户(文件所有者)的ID。
static bool switch_to_user( uid_t user_id, gid_t gp_id )
{
//先确保目标用户不是root
if ( ( user_id == 0 ) && ( gp_id == 0 ) )
{
return false;
}
//确保当前用户是合法用户:root或者目标用户
gid_t gid = getgid();
uid_t uid = getuid();
if ( ( ( gid != 0 ) || ( uid != 0 ) ) && ( ( gid != gp_id ) || ( uid != user_id ) ) )
{
return false;
}
//如果不是root,则已经是目标用户
if ( uid != 0 )
{
return true;
}
//切换到目标用户
if ( ( setgid( gp_id ) < 0 ) || ( setuid( user_id ) < 0 ) )
{
return false;
}
return true;
}
7.3 进程间关系
每个进程都隶属于一个进程组,因此它们除了PID信息外,还有进程组ID(PGID)。
每个进程组都有一个首领进程,其PGID和PID相同。进程组将一直存在,直到其中所有进程都退出,或者加入到其他进程组。
一个进程只能设置自己或者其子进程的PGID,并且,当子进程调用exec系列函数后,也不能再在父进程中对它设置PGID。
一些有关联的进程组将形成一个会话(session),通过setsid函数实现。调用进程成为会话的首领,新建一个进程组,其PGID就是调用进程的PID,调用进程成为该组的首领。调用进程将甩开终端。
7.4 系统资源限制
物理设备限制(CPU 数量、内存数量等)、系统策略限制(CPU 时间等),以及具体实现的限制(比如文件名的最大长度)。
getrlimit、setrlimit函数:读取和设置系统资源限制。
7.5 改变工作目录和根目录
getcwd函数:获取进程当前工作目录。
chdir函数:改变进程工作目录。
chroot函数:改变进程根目录。并不改变进程的当前工作目录,仍需要用chdir函数将工作目录切换至新的根目录。
7.6 服务器程序后台化
bool daemonize()
{
//创建子进程,关闭父进程,这样可以使程序在后台运行
pid_t pid = fork();
if (pid < 0)
return false;
else if (pid > 0)
exit(0);
//设置文件权限掩码。当进程创建新文件(使用open(const char *pathname, int flags,
// mode_t mode)系统调用)时,文件的权限将是 mode & 0777
umask(0);
//创建新的会话,设置本进程为进程组的首领
pid_t sid = setsid();
if (sid < 0)
return false;
//切换工作目录
if ((chdir("/")) < 0)
return false;
//关闭标准输入设备、标准输出设备和标准错误输出设备
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
//关闭其他已经打开的文件描述符,代码省略
//将标准输入、标准输出和标准错误输出都定向到 /dev/null 文件
open("/dev/null", O_RDONLY);
open("/dev/null", O_RDWR);
open("/dev/null", O_RDWR);
return true;
}
int daemon(int nochdir, int noclose);
nochdir是否改变工作目录,noclose是否将标准输入、标准输出和标准错误输出都定向到 /dev/null 文件。