Linux服务器程序规范——Linux高性能服务器

日志

Linux提供一个守护进程来处理系统日志【syslogd】,现在用的是它的升级版【rsyslogd】

守护进程:Linux服务器程序一般以后台程序的形式运行,后台进程也叫守护进程。它没有控制终端,所以不会意外接收到用户输入。
守护进程的父进程通常是init进程【PID为1的进程】

rsyslogd守护进程既能接收用户进程输出的日志,又能接收内核日志

用户进程通过调用syslog函数生成系统日志。该函数将日志输出到一个UNIX本地域socket类型(AF_UNIX)的文件/dev/log中,rsyslogd监听该文件以获取用户进程的输出。
内核日志在老的系统上是通过另外一个守护进程rklogd来管理的,rsyslogd利用额外的模块实现了相同的功能。内核日志由printk等函数打印至内核的环状缓存(ring buffer)中。环状缓存的内容直接映射到/proc/kmsg文件中,rsyslogd通过读取该文件获得内核日志

rsyslogd守护进程在接收到用户进程或内核输人的日志后,会把它们输出至某些特定的日志文件。

默认情况下:
调试信息保存至/varlog/debug文件
普通信息保存至/var/log/messages文件
内核消息保存至/var/log/ker.log文件
日志信息具体如何分发,可以在rsyslogd的配置文件中设置。rsyslogd 的主配置文件是/etc/rsyslog.conf
主要可以设置的项包括:内核日志输入路径,是否接收UDP日志及其监听端口(默认是514,/etc/services文件),是否接收TCP日志及其监听端口,日志文件的权限,包含哪些子配置文件(比如/etc/rsyslog.d/* .conf)。rsyslogd的子配置文件则指定各类日志的目标存储文件

日志体系

syslog函数

应用程序使用syslog函数和rsyslogd守护进程通信:

#include<syslog.h>
void syslog(int priorty,const char* massage,...);

该函数采用可变参数结构化输出

priority参数是所谓的设施值与日志级别的按位或
设施值的默认值是LOG_USER
日志级别:
- #define LOG_EMERG 0//系统不可用
- #define LOG_ALERT 1//报警,需立刻采取动作
- #define LOG_CRIT 2//非常严重的情况
- #define LOG_ERR 3//错误
- #define LOG_WARNING 4//警告
- #deine LOG_NOTICE 5//通知
- #deinfe LOG_INFO 6//信息
- #define LOG_DEBUG 7//调试

改变syslog的默认输出方式,进一步结构化日志内容:

#include<syslog.h>
void openlog(const char* ident,int logpot,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函数中的默认设施值

日志的过滤也很重要:程序在开发阶段可能需要输出很多调试信息,而发布之后需要将这些调试信息关闭。解决这个问题的方法并不是在程序发布之后删除调试代码(日后可能要用),而是设置日志掩码,让日志级别大于日志掩码的日志信息被系统忽略掉:

#include<syslog.h>
int setlogmask(int maskpri);

maskpri参数指定日志掩码值。始终会成功,它返回调用进程先前的日志掩码值

操作完成后记得关闭日志功能:

#include<syslog.h>
void closelog();
用户信息

用户信息对于服务器程序的安全性来说是很重要的,比如大部分服务器就必须以root身份启动,但不能以root身份运行

UID、EUID、GID、EGID

UID:真实用户ID
EUID:有效ID
GID:真实组ID
EGID:有效组ID
获取/设置当前进程的各ID:

一个进程拥有两个用户ID: UID和EUID
UID是启动程序的用户的ID

EUID是root(文件所有者)账户的ID,存在的目的是方便资源访问:它使得运行程序的用户拥有该程序的有效用户的权限。比如su程序,任何用户都可以使用它来修改自己的账户信息,但修改账户时su程序不得不访问/etc/passwd文件,而访问该文件是需要root权限的。那么以普通用户身份启动的su程序如何能访问/etc/passwd文件呢?窍门就在EUID。用Is命令可以查看到,su程序的所有者是root,并且它被设置了set-user-id标志。这个标志表示,任何普通用户运行su程序时,其有效用户就是该程序的所有者root。那么,根据有效用户的含义,任何运行su程序的普通用户都能够访问etc//passd文件。有效用户为root的进程称为特权进程

EGID的含义与EUID类似:给运行目标程序的组用户提供有效组的权限。

切换用户:

将以root身份启动运行的进程切换为以普通用户身份运行

static bool switch_to_user(uid_t user_id,gid_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;
}
进程间关系
进程组

Linux下每个进程都隶属于一个进程组,因此有进程组ID(PGID)


获取进程的PGID:
#include<unistd.h>
pid_t getpgid(pid_t pid);成功时返回进程pid所属进程组的PGID,失败则返回-1并设置errno


每个进程组都有一个首领进程,其PGID和PID相同。进程组将一直存在,直到其中所有进程都退出,或者加入到其他进程组


设置PGID:
int setpgid(pid_t pid,pid_t pgid);将PID为pid的进程的PGID设置为pgid

  • 如果pid和pgid相同,则由pid指定的进程将被设置为进程组首领
  • 如果pid为0,则表示设置当前进程的PGID为pgid
  • 如果pgid为0,则使用pid作为目标PGID
  • setpgid 函数成功时返回o,失败则返回1并设置errno

一个进程只能设置自己或者其子进程的PGID,当子进程调用exee系列函数后,不能再在父进程中对它设置PGID

会话

一些有关联的进程组将形成一个会话


创建会话:#include <unistd.h> pid_t setsid(void)
该函数不能由进程组的首领进程调用,否则会产生错误。对于非组首领的进程,调用该函数不仅创建新会话,还会:

  • 调用进程成为会话的首领,此时该进程是新会话的唯一成员
  • 新建一进程组,其PGID就是调用进程的PID,调用进程成为该组的首领
  • 调用进程将甩开终端
    该函数成功返回新的进程组PGID,失败返回-1并设置errno

Linux进程没有提供会话ID(SID)的概念,但Linux系统认为它等于会话首领所在的进程组的PGID,并提供函数读取SID:pid_t getsid(pid_t pid);

ps命令查看进程关系

ps -o pid,ppid,pgid,sid,comm|less

PPID:父进程ID
在bash shell下运行ps和less命令,所以ps、less的父进程是bash命令
三条命令创建一个会话【SID:1943】两个进程组:【PGID:1943、2298】
bash:PIDPGIDSID—>bash命令既是会话的首领也是组1943的首领
ps:PID==PGID---->ps命令是组2298的首领
关系图

系统资源限制

Linux上运行的程序都会受资源限制的影响,eg:物理设备限制(CPU数量,内存数量)、系统策略限制(CPU时间等)、具体实现的限制(文件名的最大长度)


Linux系统资源限制可通过下列函数读取、设置:

#include<sys/resource.h>
int getrlimit(int resource,struct rlimit *rlim);
int setrlimit(int resource,const struct rlimit *rlim);

//rlim是rlimit结构体类型的指针,定义:
struct rlimit
{
	rlim_t rlim_cur;
	rlim_t rlim_max;
};
  • rlim_t:整数类型,描述资源级别
  • rlim_cur:指定资源的软限制
  • rlim_max:制定资源的硬限制

软限制是一个建议性的、最好不要超越的限制,如果超越的话,系统可能向进程发送信号以终止其运行。例如,当进程CPU时间超过其软限制时,系统将向进程发送SIGXCPU信号;当文件尺寸超过其软限制时,系统将向进程发送SIGXFSZ信号
硬限制一般是软限制的上限。普通程序可以减小硬限制,而只有以root身份运行的程序才能增加硬限制。此外,我们可以使用ulimit命令修改当前shell环境下的资源限制(软限制或/和硬限制),这种修改将对该shell启动的所有后续程序有效。我们也可以通过修改配置文件来改变系统软限制和硬限制,而且这种修改是永久的

  • resource:指定资源限制类型


setlimit、getlimit成功返回0,失败返回-1并设置errno

改变工作目录和根目录

有的服务器需要改变工作目录和根目录
获取进程当前目录和改变进程工作目录:

#include<unistd.h>

char *getcwd(char *buf,size_t size);
int chdir(const char *path);

buf:指向的内存用于存储进程当前工作目录的绝对路径名,其大小由size参数指定
如果当前工作目录的绝对路径的长度(再加上一个空结束字符“\0”)超过了size,则getewd将返回NULL,并设置errno为ERANGE
如果buf为NULL并且size非0,则getewd可能在内部使用malloc动态分配内存,并将进程的当前工作目录存储在其中,此时必须自己释放getcwd在内部创建的这块内存
getcwd 函数成功时返回一个指向目标存储区(buf指向的缓存区或是getcwd在内部动态创建的缓存区)的指针,失败则返回NULL并设置errnoo

chdir函数的path参数指定要切换到的目标目录。它成功时返回0,失败时返回-1并设置errno.


改变进程根目录的函数是chroot:

int chroot(const char* path);

path:指定要切换到的目标根目录
它成功时返回0,失败时返回-1并设置errno
chroot并不改变进程的当前工作目录,所以调用chroot之后,仍然要用chdir(“/”)来将工作目录切换至新的根目录。改变进程的根目录之后,程序可能无法访问类似/dev的文件(目录),因为这些文件(目录)并非处于新的根目录之下。不过在调用chroot之后,进程原先打开的文件描述符依然生效,所以我们可以利用这些早先打开的文件描述符来访问调用chroot之后不能直接访问的文件(和目录),尤其是一些日志文件
注意:此外,只有特权进程才能改变根目录

服务器程序后台化

即让服务器程序以守护进程的方式运行:

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

	//创建新会话,设本进程为进程组首领
	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 提供了同样功能的库函数:

#include<unistd.h>
int daemon(int nochdir,int noclose);

nochdir用于指定是否改变工作目录,如果给它传递0,则工作目录将被设置为“1”(根目录),否则继续使用当前工作目录
noclose 参数为0时,标准输入、标准输出、标准错误输出都被重定向到/dev/null文件,否则依然使用原来的设备
该函数成功时返回0,失败则返回-1并设置errno

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值