Linux服务器程序规范
- Linux服务器程序一般以后台进程形式运行(守护进程-daemon),它没有控制终端,也不会意外收到用户输入。守护进程的父进程通常是init进程(PID为1);
- Linux服务器程序通常有一套日志系统,至少可以输出到日志文件;
- Linux服务器一般以某个专门的非root身份运行,其分别拥有自己的运行账户;
- Linux服务器程序通常是可配置的,可以使用配置文件来管理;
- Linux服务器进程通常会在启动的时候生成一个PID文件,以记录后台进程的PID;
- Linux服务器通常需要考虑系统资源和限制。
7.1 日志
7.1.1 Linux系统日志
Linux系统上使用守护进程rsylogd来处理系统日志,其既能接受用户进程输出的日志,又能接收内核日志。
用户进程通过syslog函数生成日志系统,将日志输出到一个UNIX本地域socket类型的文件中。
7.1.2 syslog函数
#include <syslog.h>
// priority:设施值与日志级别的按位或
void syslog(int priority, const char* message, ...);
// 结构化日志内容
// ident:指定的字符串被添加到日志消息的日期和之间之后,被设置为程序的名字
// logopt:对syslog调用的行为进行配置
void openlog(const char* ident, int logopt, int facility);
// 日志掩码
int setlogmask(int maskpri);
// 关闭日志
void closelog();
7.2 用户信息
7.2.1 UID、EUID、GID和EGID
#include <sys/types.h>
#include <unistd.h>
uid_t getuid(); // 获得真实用户ID
uid_t geteuid(); // 获得有效用户ID
uid_t getgid(); // 获得真实组ID
uid_t getegid(); // 获得有效组ID
int setuid(uid_t uid); // 设置真实用户ID
int seteuid(uid_t euid); // 设置有效用户ID
int setgid(uid_t gid); // 设置真实组ID
int setegid(uid_t egid); // 设置有效组ID
- 一个进程拥有两个用户ID:UID和EUID。其中EUID的存在是为了方便资源访问。
7.2.2 切换用户
static bool switch_to_user(uid_t user_id, git_t gp_id){
// 确保目标用户不是root
if((user_id==0)&&(gp_id==0)){
return false;
}
// 确保当前用户是合法用户
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 进程间关系
7.3.1 进程组
Linux下每个进程都隶属于一个进程组,因此其除了PID信息外,还有进程组ID(PGID)
#include <unistd.h>
pid_t getpgid(pid_t pid);
// 成功时返回pid所属组的PGID, 失败返回-1并设置errno
// 设置PGID
int setgpid(pid_t pid, pid_t pgid);
7.3.2 会话
一些有关联的进程组将形成一个会话
#include <unistd.h>
// 创建一个会话
pid_t setsid(void);
7.3.3 用ps命令查看进程间关系
$ ps -o pid,ppid,pgid,sid,comm | less
7.4 系统资源限制
Linux系统资源限制可通过函数进行读取和设置
#include <sys/resource.h>
int getrlimit(int resource, rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
struct rlimit{
rlim_t rlim_cur; // 指定资源的软资源
rlim_t rlim_max; // 指定资源的硬资源
};
7.5 改变工作目录和根目录
#include <unistd.h>
// 获取当前工作目录
char* getcwd(char* buf, size_t size);
// 改变进程工作目录
int chdir(const char* path);
// 改变进程根目录
int chroot(const char* path);
7.6 服务器程序后台化
#include <unistd.h>
// nochdir:指定是否改变工作目录
int daemon(int nochdir, int noclose);
实例:将服务器以守护进程的方式运行
bool daemonize(){
// 创建子进程,关闭父进程,使程序可以在后台运行
pid_t pid = fork();
if(pid<0){
return false;
}
else if(pid>0){
exit(0);
}
// 设置文件权限掩码,当进程创建新为念时,文件的权限是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;
}