1 Linux服务器规范
Linux服务器程序一般以后台进程形式运行(守护进程-daemon),它没有控制终端,也不会意外收到用户输入。守护进程的父进程通常是init进程(PID为1);
Linux服务器程序通常有一套日志系统,至少可以输出到日志文件;
Linux服务器一般以某个专门的非root身份运行,其分别拥有自己的运行账户;
Linux服务器程序通常是可配置的,可以使用配置文件来管理;
Linux服务器进程通常会在启动的时候生成一个PID文件,以记录后台进程的PID;
Linux服务器通常需要考虑系统资源和限制。
2 日志
2.1Linux系统日志
Linux系统上使用守护进程rsylogd来处理系统日志,其既能接受用户进程输出的日志,又能接收内核日志。
用户进程通过syslog函数生成日志系统,将日志输出到一个UNIX本地域socket类型的文件中。
2.2syslog函数
#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();
日志级别
logopt 对于后续syslog调用行为进行配置
3 用户信息
3.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的存在是为了方便资源访问。
-
getuid() 获取进程的实际用户ID,geteuid() 获取进程的有效用户ID.
有效用户ID(EUID)表示该ID是程序的所有者,一般为程序的实际执行者。 真实用户ID(UID) 最初运行该程序的用户ID。 当程序未设置SUID的时候,两者是相同的,但是当程序设置了SUID时,两者可能不同。
3.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; }
3.3 进程间关系
进程组
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);
会话
#include <unistd.h> // 创建一个会话 pid_t setsid(void); //读取 pid_t getsid(pid_t pid);
3.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; // 指定资源的硬资源 };
rlim_t是一个整数类型,它描述资源级别。 rlim_cur成员指定资源的软限制,rlim_max成员指定资源的硬限制。 软限制是一个建议性的、最好不要超越的限制,如果超越的话,系统可能向进程发送信号以终止其运行。例如,当进程CPU时间超过其软限制时,系统将向进程发送SIGXCPU信号﹔当文件尺寸超过其软限制时,系统将向进程发送SIGXFSZ信号(见第10章)。 硬限制一般是软限制的上限。普通程序可以减小硬限制,而只有以root身份运行的程序才能增加硬限制。 此外,我们可以使用ulimit命令修改当前shell环境下的资源限制(软限制或/和硬限制),这种修改将对该shell启动的所有后续程序有效。 我们也可以通过修改配置文件来改变系统软限制和硬限制,而且这种修改是永久的,
3.5 改变工作目录和根目录
#include <unistd.h> // 获取当前工作目录 char* getcwd(char* buf, size_t size); // 改变进程工作目录 int chdir(const char* path); // 改变进程根目录 int chroot(const char* path);
切换根目录的利弊
path参数指定要切换到的目标根目录。它成功时返回0,失败时返回-1并设置errno。
chroot并不改变进程的当前工作目录,所以调用chroot 之后,我们仍然需要使用chdir(“I”)来将工作目录切换至新的根目录。
改变进程的根目录之后,程序可能无法访问类似ldev的文件(和目录),因为这些文件(和目录〉并非处于新的根目录之下。
不过好在调用chroot之后,进程原先打开的文件描述符依然生效,所以我们可以利用这些早先打开的文件描述符来访问调用chroot之后不能直接访问的文件(和目录),尤其是一些日志文件。此外,只有特权进程才能改变根目录。
4服务器程序后台化
Linux提供了和守护进程相同作用的函数
#include <unistd.h> // nochdir:指定是否改变工作目录 int daemon(int nochdir, int noclose);
实例:将服务器以守护进程的方式运行
bool daemonisze() { // 创建子进程,关闭父进程,使程序可以在后台运行 pid_t pid = fock(); if(pid < 0) { return false; } else if(pid > 0) { exit(0); } // 设置文件权限掩码,当进程创建新为念时,文件的权限是mode&0777 umask(0); // 创建新的对话,设置本进程为进程组的首领 pid_t pid = setsid(); if(sid < 0) { return false; } // 切换工作目录 if((chdir("/"))<0){ return false; } // 关闭标准输入黑色被、标准输出设备和标准错误输出设备 close(STDIN_FILENO); close(SRDOUT_FILENO); close(STDERR_FILENO); // 关闭其他已经打开的文件描述符 // 将标准输入、标准输出、和标准错误输出都定向到/dev/null文件 open("/dev/null", O_RDONLY); open("/dev/null", O_RDWR); open("/dev/null", O_RDWR); return true; }