Linux高性能服务器程序规范(10)

前言

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

Linux服务器程序通常有一套日志系统,它至少能输出日志到文件,有的高级服务器还能输出日志到专门的UDP服务器。大部分后台进程都在**/var/log**目录下拥有自己的日志目录。

Linux服务器程序一般以某个专门的非root身份运行。比如 mysqld、httpd、syslogd等后台进程,分别拥有自己的运行账户mysql、 apache和 syslog。

Linux服务器程序通常是可配置的。服务器程序通常能处理很多命令行选项,如果一次运行的选项太多,则可以用配置文件来管理。绝大多数服务器程序都有配置文件,并存放在/etc目录下

Linux服务器进程通常会在启动的时候生成一个PID文件并存入/var/run目录中,以记录该后台进程的PID。比如 syslogd 的PID文件是/var/run/syslogd.pid.

Linux服务器程序通常需要考虑系统资源和限制,以预测自身能承受多大负荷,比如进程可用文件描述符总数和内存总量等。

日志

日志原理介绍

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

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

void syslog(int priority, const char *format, ...);
//priorty:优先级
 LOG_EMERG      sytem is unusable
 //报警非常严重的情况
 LOG_ALERT      action must be taken immediately

 LOG_CRIT       critical conditions

 LOG_ERR        error conditions

 LOG_WARNING    warning conditions
 LOG_NOTICE     normal, but significant, condition

 LOG_INFO       informational message

 LOG_DEBUG      debug-level message

void openlog(const char* ident,int logopt,int facility)

#define LOG_PID 0x01 //include pid
#define LOG_CONS 0x2
#define LOG_ODELAY 0x04
#define LOG_ODELAY 0x08

//过滤 信息
#include<syslog.h>
int setlogmask(iny maskpri);

void closelog();

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

用户信息

UID EUID GID AND EGUI

用户信息对于服务器程序的安全性来说是很重要的,比如大部分服务器就必须以root身份启动,但不能以root身份运行。下面这一组函数可以获取和真实组ID(GID)和有效组ID (EGID)

  #include <unistd.h>
  #include <sys/types.h>

  uid_t getuid(void);//真实id
  uid_t geteuid(void);
  gid_t getgid(void);
  gid_t getegid(void);
  int setuid(uid_t uid);
  int seteuid(uid_t uid)
  int setgid(gid_t gid);
  int setegid(gid_t gid);

需要指出的是,一个进程拥有两个用户ID:UID和EUID。EUID存在的目的是方便资源访问,可以使用它来修改自己的账户信息,但修改账户时su程序不得不访问/etc/passwd文件,而访问该文件是需要root权限。

problem:su程序?
可以使用它来修改自己的账户信息,但修改账户时su程序不得不访问/etc/passwd文件,而访问该文件是需要root权限的。那么以普通用户身份启动的su程序如何能访问/etc/passwd文件呢?窍门就在EUID。用ls命令可以查看到,su程序的所有者是root,并且它被设置了set-user-id标志。这个标志表示,任何普通用户运行su程序时,其有效用户就是该程序的所有者就是root

区别uid euid

#include<unistd.h>
#include<stdio.h>

int main()
{
    uid_t uid=getuid();
    uid_t euid=geteuid();
    printf("userid is %d,effective userid is: %d\n",uid,euid);
}

[root@localhost LINUX_standard]# vim user.cpp
[root@localhost LINUX_standard]# chmod +s user.cpp
[root@localhost LINUX_standard]# gcc user.cpp 
[root@localhost LINUX_standard]# ./a.out
userid is 0,effective userid is: 0

从测试程序的输出来看**,进程的UID是启动程序的用户的ID**,而EUID则是root账户(文件所有者)的ID.

进程间的关系

进程组

Linux下每个进程都隶属于一个进程组,因此它们除了PID信息外,还有进程组ID(PGID)。我们可以用如下函数来获取指定进程的PGID:

#include<unistd.h>
int setpgid(pid_t pid,pid_t pgid);
该函数将PID为pid 的进程的PGID设置为pgid。
如果pid和 pgid相同,则由pid指定的进程将被设置为
进程组首领﹔如果pid为0,则表示设置当前进程的PGID为pgid ;如果pgid为o,则使用pid作为目标PGID。setpgid函数成功时返回0,失败则返回-1并设置

会话

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

#include<unistd.h>
pid_t setsid(void);

不能有进程组的首领进程调用
对于非首领进程调用如下:
1.调用进程成为会话的首领,此时该进程是新会话的唯一成员。
2.新建一个进程组,其 PGID就是调用进程的PID,
3.调用进程成为该组的首领。调用进程将甩开终端(如果有的话)。

实例 ps查看进程关系

ps -o pid,ppid,sid,comm|less
   PID   PPID    SID   PGID COMMAND
  7953   2402   7953   7953 bash
 10287   7953   7953  10287 ps
 10288   7953   7953  10287 less


我们是在 bash shell下执行ps 和 less命令的,所以ps 和 less命令的父进程是bash命令,这可以从PPID(父进程PID)-列看出。这3条命令创建了1个会话(SID是7953)和2个进程组(PGID分别是7953和10263)。bash命令的PID、PGID和SID都相同,很明显它既是会话的首领,也是组7953的首领。ps命令则是组10287的首领,因为其PID也是1087
下图描述了此三者的关系:
在这里插入图片描述

系统资源限制

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

Linux系统资源限制可以通讨如下一对函数来读取和设置:

#include<sys/resource.h>
int getrlimit(int resource,struct* rlim)
int setrlimit(int resource,struct* rlim)
struct rlimit
{
rlim_t rlim_cur;
rlim_t rlim_max;
}

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

改变工作目录和跟目录

背景:
有些服务器程序还需要改变工作目录和根目录,比如Web服务器。一般来说,Web服务器的逻辑根目录并非文件系统的根目录“/”,而是站点的根目录(对于Linux 的Web 服务来说,该目录一般是/var/www/)。

#include<unistd.h>
char* getcwd(char* buf,size_t size);
int chdir(const char* path);
int chroot(const char* path);

buf参数指向的内存用于存储进程当前工作目录的绝对路径名,其大小由size参数指定。如果当前工作目录的绝对路径的长度(再加上一个空结束字符“0”〉)超过了size,则getcwd将返回NULL,并设置errno为ERANGE。如果 buf为NULL并且size非o,则getcwd可能在内部使用malloc 动态分配内存,并将进程的当前工作目录存储在其中。如果是这种情况,则我们必须自己来释放getcwd在内部创建的这块内存。getcwd函数成功时返回一个指向目标存储区(buf指向的缓存区或是getcwd在内部动态创建的缓存区〉的指针,失败则返回NULL
path参数指定要切换到的目标根目录。它成功时返回0,失败时返回-1并设置errno。chroot并不改变进程的当前工作目录,所以调用chroot之后,我们仍然需要使用chdir(“” )来将工作目录切换至新的根目录。改变进程的根目录之后,程序可能无法访问类似**/dev的文件(和目录),因为这些文件(和目录〉并非处于新的根目录之下。不过好在调用chroot之后,进程原先打开的文件描述符依然生效**,所以我们可以利用这些早先打开的文件描述符来访问调用chroot之后不能直接访问的文件(和目录),尤其是一些日志文件。此外,只有特权进程才能改变根目录。

服务器后台化

#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
bool deamonize()
{
  pid_t pid=fork();
  if(pid<0)
{
    return false;
}
else if(pid>0)
{
    return 1;
}
/*设置文件权限掩码。当进程创建新文件
(使用open( const char *pathname, int flags,mode_t mode )系统调用)时,
文件的权限将是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);

   open("/dev/null",O_RDONLY);
   open("/dev/null",O_RDWR);
   open("/dev/null",O_RDWR);
    
   return true;

}

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值