《Linux高性能服务器编程》学习笔记——第七章 Linux服务器程序规范

对Linux服务器程序,有许多模板式的要求,我们称之为程序规范:
1、一般以后台进程形式运行。后台进程又称为守护进程(daemon),没有控制终端,因而不会意外接收到用户输入。父进程通常为init进程(PID为1)。
2、通常有一套日志系统,至少能输出日志到文件,有的高级服务器能输出到专门的udp服务器。大部分后台进程都在/var/log目录下有自己的日志目录。
3、一般以某个专门的非root身份运行。如mysqld,httpd,syslogd等后台进程,分别拥有自己的运行账户mysql,apache,syslog。
4、通常是可配置的。服务器程序通常能处理很多命令行选项,如果一次运行的选项太多,则可以用配置文件来管理。绝大多数服务器程序都有配置文件,并存放在/etc目录下。
5、通常会在启动的时候生成一个pid文件并存放入/var/run目录中,以记录该后台进程的pid。比如syslogd的pid文件是/var/run/syslogd.pid。
6、通常需要考虑系统资源和限制,以预测自身能承受多大负荷,比如进程可用文件描述符总数和内存总量。

一、日志
Linux提供一个守护进程syslogd来处理系统日志。现在Linux都使用其升级版本rsyslogd。
rsyslogd守护进程既能接收用户进程输出的日志,又能接收内核日志。用户进程是通过调用syslogd函数生成系统日志的。该函数将日志输出到一个unix本地域socket类型(AF_UNIX)的文件/dev/log中,rsyslogd监听该文件以获取用户进程的输出。内核日志由printk等函数打印到内核的环状缓存(ring buffer)中。环状缓存的内容直接映射到/proc/kmsg文件中。rsyslogd则通过读取该文件获得内核日志。

rsyslogd在收到用户进程或内核输入的日志后,会把它们输出到某些特定的日志文件中。默认情况下,调试信息保存至/val/log/debug文件,普通信息保存到/var/log/message文件中,内核消息则保存到/var/log/kern.log文件。不过日志如何分发,可以通过rsyslogd的配置文件设置。其主要配置文件是/etc/rsyslog.conf,能配置的项包括内核日志输入路径、是否接收UDP日志及其监听端口(默认514),是否接收TCP日志及其监听接口、日志文件权限、包含哪些子配置文件。rsyslogd的子配置文件则指定各类日志的目标存储文件。

应用程序使用syslog系统调用与rsyslogd守护进程通信。
#include <syslog.h>
void syslog(int priority, const char* message, ...);
日志级别:
#include <syslog.h>
#define LOG_EMERG //系统不可用
#define LOG_ALERT //报警,需要立即采取动作
#define LOG_CRIT //非常严重的错误
#define LOG_ERR //错误
#define LOG_WARNING
#define LOG_NOTICE
#define LOG_INFO
#define LOG_DEBUF

改变syslog的默认输出方式,进一步结构化日志内容:
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函数中的默认值。

开发阶段要输出很多调试信息,而程序发布后需要将这些信息关闭。解决的方式并不是在发布程序后删除调试代码,而是简单的设置日志掩码,使日志级别大于日志掩码的日志信息被系统忽略。设置日志掩码:
int setlogmask(int maskpri);
参数指定日志掩码值,始终会成功,返回程序之前设置的掩码。

关闭日志功能:
void closelog();

二、用户信息:
当前进程的真实用户ID(UID)、有效用户ID(EUID)、真实组(GID)、有效组(EGID)。
#include <sys/types.h>
#include <unistd.h>
uid_t getuid();
uid_t geteuid();
gid_t getgid();
gid_t getegid();
int setuid();
int seteuid();
int setgid();
int setegid();
一个进程拥有两个用户ID,EUID存在的目的是方便资源访问,它使得运行程序的用户拥有该程序有效用户的权限。

三、进程间关系:
Linux下每个进程都隶属于一个进程组,进程组ID——PGID
#include <unistd.h>
pid_t getpgid(pid_t pid);
int setpgid(pid_t pid, pid_t pgid);

每个进程组都有一个首领进程,其PID和PGID相同。进程组将一直存在,直到其所有进程退出或加入其它进程组。
setpgid函数将PID为pid的进程的PGID设置为pgid,若pid与pgid相同,则该进程被设置为进程组的首领进程。如果pid为0,则表示将当前进程的PGID设置为pgid;如果pgid为0,则使用pid作为目标PGID。成功返回0,失败返回-1。
一个进程只能设置自己或子进程的PGID。并且当子进程调用exec系列的函数后,父进程也不能再设置其PGID。

一些有关联的进程组将形成一个会话(session),创建一个会话:
#include<unistd.h>
pid_t setsid(void);
该函数不能由进程组的首领进程调用,否则将产生一个错误。对于非组首领的进程,调用该函数不仅创建新会话,而且有如下额外效果:
1、调用进程成为会话的首领,此时该进程是新会话的唯一成员。
2、新建一个进程组,其PGID就是调用进程的PID,调用进程成为该组首领。
3、调用进程将甩开终端。
成功返回新进程组的PGID,失败返回-1。
Linux进程并未提供所谓的会话ID,但Linux系统认为它等于会话首领所在进程组的PGID,读取会话ID:
pid_t getsid(pid_t pid);


执行ps命令可以查看进程、进程组、会话之间的关系:
如:ps -o pid,ppid,pgid,sid,comm | less




4、系统资源限制:
Linux上的程序都会受到资源限制的影响,比如物理设备限制(cpu数量、内存数量等)、系统策略限制(cpu时间等),以及具体实现的限制(比如文件名的最大长度)。
读取和设置系统资源限制:
#include <sys/resource.h>
int getrlimit(int resource, struct 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成员指定资源的硬限制。软限制是一个建设性的、最好不要超越的限制,如果超越的话,系统可能向进程发送信号以终止其运行。硬限制是软限制的上限。普通程序可以减少应限制,而只有以root身份运行的程序才能增加硬限制。我们可以使用ulimit命令修改当前shell环境下的资源限制(软限制和硬限制),这种修改将对该shell启动的所有后续程序有效。我们也可以通过修改配置文件来改变系统软限制和硬限制,而且这种修改是永久的。
资源类型:
RLIMIT_AS//进程虚拟内存总量限制(单位是字节)
RLIMIT_CORE//进程核心转储文件(core dump)的大小限制
RLIMIT_CPU
RLIMIT_DATA//进程数据段(初始化数据data段、未初始化数据bss段和堆)限制(单位字节)
RLIMIT_FSIZE//文件大小限制(单位字节)
RLIMIT_NOFILE//文件描述符数量限制
RLIMIT_NPROC//用户能够创建的进程数量限制
RLIMIT_SIGPENDING//用户能够挂起的信号数量限制
RLIMIT_STACK//进程栈内存限制(单位字节)


5、改变工作目录和根目录:
获取和改变进程当前工作目录:
#include <unistd.h>
char* getcwd(char* buf, size_t size);
int chdir(const char* path);
如果工作目录的绝对路径名长度超过size,getcwd返回NULL,并设置errno为ERANGE。如果buf为NULL,getcwd在内部通过malloc分配内存,我们需要手动释放。

int chroot(const char* path);
chroot并不改变进程的当前工作目录,所以调用chroot之后,我们仍然需要使用chdir("/")来将工作目录切换至新的根目录。改变进程的根目录后,程序可能无法访问类似/dev的文件(和目录),因为这些文件(和目录)并非处于新的根目录之下。不过进程原来打开的文件描述符依然生效,我们可以使用之前打开的文件描述符来访问chroot之后不能直接访问的文件(和目录),尤其是一些日志文件。此外,只有特权进程才能改变根目录。

6、服务器程序后台化:
创建子进程关闭父进程,可以使进程在后台运行。

Linux提供了完成同样功能的库函数:
#include <unistd.h>
int daemon(int nochdir, int noclose);
nochdir参数用于指定是否改变工作目录,如果给它传递0,则工作目录将被设置为"/",否则继续使用当前工作目录。noclose参数为1时,标准输入、标准输出、标准错误都被重定向到/dev/null文件,否则依然使用原来的设备。成功返回0,失败返回-1。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值