概述
-
守护进程
-
Linux服务器程序一般以后台进程形式运行。后台进程又称为守护进程(daemon)。
-
它没有控制终端,因而也不会意外接收到用户收入。
-
守护进程的父进程通常是init进程(PID为1的进程)。
-
-
日志
- Linux服务器程序通常有一套日志系统,它至少能输出日志到文件,有的高级服务器还能输出日志到专门的UDP服务器。
- 大部分后台进程都在
/var/log
目录下拥有字节的日志目录。
-
配置性
- Linux服务器程序通常都是可配置的。
- 服务器程序通常能处理很多命令选项,如果一次运行的选项太多,则可以通过配置文件来管理。
- 绝大多数服务器程序都有配置文件,并存放在
/etc
目录下。
-
PID
- Linux服务器程序通常会在启动的时候生成一个PID文件并存入
/var/run
目录中,以记录该后台进程的PID。
- Linux服务器程序通常会在启动的时候生成一个PID文件并存入
-
Linux服务器程序通常需要考虑资源和限制,以预测自身能承受多大负荷,比如进程可用文件描述符总数和内存总量等。
日志
系统日志
-
Linux提供一个守护进程来处理系统日志,那就是
syslogd
,不过现在的Linux系统上使用的都是它的升级版rsyslogd
。 -
rsyslogd
守护进程既能接收用户进程输出的日志,又能接收内核日志。- 用户进程是通过调用
syslog
系统生成日志的。该函数将日志输出到一个UNIX本地域socket类型的文件/dev/log
中,rsyslogd
则监听该文件以获取用户进程的输出。 - 内核日志在老的系统上是通过另一个守护进程
rklogd
来管理的,rsyslogd
利用额外的模块实现了相同的功能。
内核日志由prink等函数打印至内核的环状缓存中。
环状缓存的内容直接映射到/proc/kmsg
文件中。rsyslogd
则通过读取该文件获得内核日志。
- 用户进程是通过调用
-
rsyslogd
守护进程在接收到用户进程或内核进程输入的日志后,会把它们输出至某些特定的日志文件。
默认情况下,调试信息会保存至/var/log/kern.log
文件中。不过日志信息具体如何分发,可以在rsyslogd
的配置文件中设置。 -
rsyslogd
的主要配置文件是/etc/rsyslog.conf
,其中,主要可以设置的项包括:- 内核日志输入路径
- 是否接收UDP日志及其监听端口。
- 是否接收TCP日志及其监听端口。
- 日志文件的权限
- 包含哪些子配置文件。
syslog
- 应用程序使用
syslog
函数与rsyslogd
守护进程通信。 - priority的等级见下图
1 2 3 |
#include <syslog.h> void syslog(int priority, const char* message, ...); |
- openlog函数可以改变syslog的默认输出方式,进一步结构化日志内容。
logopt选项见下图:
1 2 3 |
#include <syslog.h> void openlog(const char* ident, int logopt, int facility); |
- setlogmask也可以设置日志掩码
1 2 3 |
#include <syslog.h> int setlogmask(int maskpri); |
- closelog可以关闭日志功能
1 2 3 |
#include <syslog.h> void closelog(); |
用户信息
UID EUID GID EGID
- 用户信息对于服务器程序的安全性来说是很重要的,比如大部分服务器就必须以root身份启动,但不能以root身份运行。
- uid:真实用户id。
- euid:有效用户id。
- gid:真实组id。
- egid:有效组id。
- 需要注意的是,一个进程拥有两个用户id,uid和euid。euid存在的目的是为了方便访问资源:它使得运行程序的用户拥有该程序的有效用户的权限。
- 有效用户为root的进程称为特权进程。
- 相关函数如下图:
切换用户
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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) || (set_uid(user_id) < 0)) { return false; } return true; } |
进程间关系
进程组
- Linux下的每个进程都隶属于一个进程组,因此它们除了PID信息外,还有进程组ID即PGID。
1 2 3 |
#include <unistd.h> pid_t getpgid(pid_t pid); |
- 该函数成功返回进程pid所属进程组的PGID,失败则返回-1并设置errno。
- 每个进程组都有一个首领进程,其PGID和PID相同。
进程组将一直存在,直到其中所有进程都退出,或者加入到其他进程组。
1 2 3 |
#include <unistd.h> int setpgid(pid_t pid, pid_t pgid); |
- 上面这个函数将PID为pid的进程的PGID设置为pgid。该函数成功时返回0,失败返回-1并设置errno。
如果pid和pgid相同,则由pid指定的进程将被设置为进程组首领;
如果pid为0,则表示设置当前进程的PGID为pgid;
如果pgid为0,则使用pid作为目标PGID。 - 一个进程只能设置自己或者其子进程的PGID。并且,当子进程调用exec系列函数后,我们也不能再在父进程中对它设置PGID。
会话
- 一些有关联的进程组将组成一个会话,
session
。
1 2 3 |
#include <unistd.h> pid_t setsid(void); |
- 上面函数用于创建一个会话,但该函数不能由进程组的首领进程调用否则将会产生一个错误。
对于非组首领的进程,调用该函数不仅创建新会话,而且有如下额外效果:- 调用进程称为会话的首领,此时该进程是新会话的唯一成员。
- 新建一个进程组,其PGID就是调用进程的PID,调用进程成为该组的首领。
- 调用进程将甩开终端(如果有的话)。
- 该函数成功时返回新的进程组的PGID,失败则返回-1并设置errno。
- Linux进程并未提供所谓会话ID的概念,但Linux系统认为它等于会话首领所在的进程组的PGID,并提供了读取SID的函数:
1 2 3 |
#include <unistd.h> pid_t getsid(pid_t pid); |
ps
- 可以用ps命令查看进程、进程组和会话之间的关系。
系统资源限制
- Linux上运行的程序都会受到资源限制的影响,比如物理设备限制(CPU数量、内存数量等)。系统策略限制(CPU时间等)、以及具体实现的限制(比如文件名的最大长度)。
- 可以使用getrlimit和setrlimit来读取或设置这些相关内容:
1 2 3 4 5 6 7 8 9 10 11 |
#include <sys/resource.h> struct rlimit { rlim_t rlim_cur; rlim_t rlim_max; }; int getrlimit(int resource, struct rlimit* rlim); int setrlimit(int resource, const struct rlimit* rlim); |
- rlim_t描述资源级别。rlim_cur指定资源的软限制,rlim_max指定资源的硬限制。
- 所谓软限制,是一个建议性、最好不要超过的限制,超过的话,系统可能会向进程发送信号以终止其运行。
- 所谓硬限制,一般指软限制的上限,普通程序可以减小硬限制,而只有root身份运行的程序才能增加硬限制。
工作目录和根目录
- 有些服务器程序还需要改变工作目录和根目录。
- 比如Web服务器的逻辑根目录并非文件系统的根目录“/”,而是站点的根目录,对于Linux的Web服务来说,该目录一般是
/var/www/
。
- 比如Web服务器的逻辑根目录并非文件系统的根目录“/”,而是站点的根目录,对于Linux的Web服务来说,该目录一般是
- 获取当前工作目录和改变进程工作目录的函数:
1 2 3 4 |
#include <unistd.h> char* getcwd(char* buf, size_t size); int chdir(const char* path); |
- 改变进程根目录的函数是chroot:
1 2 3 |
#include <unistd.h> int chroot(const char* path); |
服务器程序后台化
- 将服务器程序以守护进程的方式运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
bool daemonize() { // 创建子进程,关闭父进程,可以使得程序在后台运行 pid_t pid = fork(); if (pid < 0 ) { return false; } else if (pid > 0) { exit(0); } // 设置文件掩码 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; } |
- Linux创建守护进程的函数是daemon
- nochdir指定是否改变工作目录,如果传递0,则工作目录将被设置为"/"。否则继续使用当前工作目录。
- noclose参数为0时,标准输入、标准输出和标准错误输出都被重定向到
/dev/null
文件,否则依然使用原来的设备。 - 该函数成功时返回0,失败返回-1并设置errno。
1 2 3 |
#include <unistd.h> int daemon(int nochdir, int noclose); |
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Linux_ 命令大全 文件管理03/16
- ♥ Linux 高性能服务器编程:I/O复用二12/12
- ♥ Linux下网络及其他配置相关记述08/12
- ♥ Linux 高性能服务器编程:高级I/O函数11/28
- ♥ Linux高性能服务器编程:TCP/IP协议族09/02
- ♥ X86_64汇编学习记述四08/09