Linux高性能服务器编程---服务器程序规范

【摘自《linux高性能服务器编程》】

日志

Syslog函数

应用程序使用syslog函数与rsyslogd守护进程通信。

#include<syslog.h>

Void syslog(int priority, const char* message,...);

Priority参数是设施值与日志级别的按位或。设施值得默认值是LOG_USER

日志级别:

#include<syslog.h>

LOG_EMERG 0 系统不可用

LOG_ALERT  1 报警,需要立即采取动作

LOG_CRIT    2 非常严重的情况

LOG_ERR    3 错误

LOG_WARNING 4 警告

LOG_NOTICE  5 通知

LOG_INFO   6 信息

LOG_DEBUG 7 调试

 

改变syslog的默认输出方式

#include<syslog.h>

Void openlog(const char* ident, int logopt, int facility);

Ident参数指定的字符串将被添加到日志消息的日期和时间之后,它通常被设置为程序的名字。Logopt参数对后续syslog调用的行为进行设置。

可选值为按位或:

LOG_PID 0x01  在日志消息中包括程序PID

LOG_CONS 0x02 如果消息不能记录到日志文件,则打印至终端

LOG_ODELAY 0x04 延迟打开日志功能知道第一次调用syslog

LOG_NDELAY 0x08 不延迟打开日志功能

Facility参数可用来修改syslog函数中的默认设施值。

日志过滤功能,设置日志掩码,而不需要删除调试代码,简单设置日志掩码,使日志级别大于日志掩码的日志信息被系统忽略。

#include<syslog.h>

Int setlogmask(int maskpri);

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

最后关闭日志功能。

#include<syslog.h>

Void closelog();

 

用户信息:

UIDEUIDGIDEGID

#include<sys/types.h>

#include<unistd.h>

Uid_t getuid();//获取真实用户ID

Uid_t geteuit();//获取有效用户ID

Gid_t getgid();//获取有真实组ID

Gid_t getegid();//获取有效组ID

Int setuid(uid_t uid);//设置真实用户ID

Int seteuid(uid_t uid);//设置有效用户ID

Int setgid(gid_t gid);//设置真实组ID

Int setegid(gid_t gid);//设置有效组ID

 

切换用户:

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

Static bool switch_to_user(uid_t user_id,gid_t gp_id)

{

   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 || setuid(user_id) < 0))

      Return false;

   Return true;

}

 

进程间关系

Linux下每个进程都隶属于一个进程组,因此它们除了PID信息外,还有进程组IDPID)。

#include<unistd.h>

Pid_t getpgid(pid_t pid);

该函数成功时返回进程PID所属进程组的PGID,失败则返回-l并设置errno.

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

#include<unistd.h>

Int setpgid(pid_t pidpid_t pgit);

该函数将PIDPID的进程的PGID设置为pgid.如果pidpgid相同,则由pid指定的进程将被设置为进程组首领。如果pid0,则表示设置当前进程PGIDpgid;如果pgid额为0,则使用pid作为目标PGIDSetpgid函数成功时返回0,失败则返回-1并设置errno.

 

会话:

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

#include<unistd.h>

Pid_t setsid(void);

该函数不能由进程组的首领进程调用,否则将产生一个错误。

对于非组首领的进程,调用该函数不仅创建新会话,而且有如下额外效果:

1,调用进程成为会话的首领,此时该进程是新会话的唯一成员。

2,新建一个进程组,其PGID就是调用进程的PID,调用进程成为该组的首领

3,调用进程将甩开终端,如果有的话。

调用成功返回新的进程组的PGID,失败则返回-1并设置errno

可以通过SID来获取会话ID

#include<unistd.h>

Pid_t getsid(pid_t pid);

 

系统资源限制:

读取限制信息

#include<sys/resource.h>

Int getrlimit(int resource, struct rlimit *rlim);

Int setrlimit(int resource, const struct rlimit *rlim);

Rlim参数是rlimit结构体类型的指针,rlimit结构体的定义如下:

Struct rlimit

{

  Rlim_t rlim_cur;

  Rlim_t rlim_max;

};

硬限制一般是软限制的上限。普通程序可以减小硬限制,而只有以root身份运行的程序才能增加硬限制。此外,我们可以使用ulimit命令修改当前shell环境下的资源限制,这种修改将对该shell启动的所有后续程序有效。也可以通过修改配置文件来改变系统软件限制和硬限制,而且这种修改是永久的。

Resource参数指定的资源限制类型。

RLIMIT_AS ,进程虚拟内存总量限制,超过该限制将使得某些函数比如mmap产生ENOMEM错误。

RLIMIT_CORE,进程核心转储文件的大小限制,其值为0表示不产生核心转储文件

RLIMIT_DATA 进程数据段(初始化数据data段,末初始化数据bss段和堆)限制。

RLIMIT_FSIZE 文件大小限制,超过该限制将使得某些函数产生EFBIG错误。

RLIMIT_NOFILE 文件描述符数量限制,超过该限制将使得某些函数(比如pipe)产生EMFILE错误

RLIMIT_NPROC 用户能创建的进程数限制,超过该限制将使得某些函数(比如fork)产生EAGAIN错误。

RLIMIT_SIGPENDING 用户能够挂起的信号数量限制

RLIMIT_STACK  进程栈内存限制,超过该限制将引起SIGSEGV信号。

 

改变工作目录和根目录

获取进程当前工作目录和改变进程工作目录的函数分别是:

#include<unistd.h>

char* getcwd(char* buf, size_t size);

Int chdir(const char* path);

改变进程根目录的函数时chroot,其定义如下:

#include<unistd.h>

int chroot(const char* path);

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

 

服务器程序后台化

守护进程的编写遵循一定的步骤:

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 )

    {

        /* Log the failure */

        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,则工作目录将被设置为“/”(根目录),否则继续使用当前目录,noclose参数为0时,标准输入,标准输出和标准错误输出都被重定向到/dev/null文件,否则依然使用原来的设备。该函数成功时返回0,失败则返回-1并设置errno

你可能感兴趣的:(Linux高性能服务器编程---服务器程序规范)