【摘自《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();
用户信息:
UID,EUID,GID,EGID
#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信息外,还有进程组ID(PID)。
#include<unistd.h>
Pid_t getpgid(pid_t pid);
该函数成功时返回进程PID所属进程组的PGID,失败则返回-l并设置errno.
每个进程组都有一个首领进程,其PGID和PID相同。进程组将一直存在,直到其中所有进程都退出,或者加入到其他进程组。
#include<unistd.h>
Int setpgid(pid_t pid,pid_t pgit);
该函数将PID为PID的进程的PGID设置为pgid.如果pid和pgid相同,则由pid指定的进程将被设置为进程组首领。如果pid为0,则表示设置当前进程PGID为pgid;如果pgid额为0,则使用pid作为目标PGID。Setpgid函数成功时返回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。