linux系统日志
服务器的调试和维护都需要一个专业的日志系统。linux提供一个守护进程(syslogd)来处理系统日志,现在linux系统上使用的是它的升级版(rsyslogd)。
rsyslogd守护进程既能接收用户进程输出的日志,又能接收内核日志。
用户进程是通过调用syslog函数生成系统日志的。该函数将日志输出到一个UNIX本地域socket类型(AF_UNIX)的文件/dev/log中,rsyslogd则监听该文件以获取用户进程的输出。
内核日志由printk等函数打印至内核的环状缓存(ring buffer)中。环状缓存的内容直接映射到/proc/kmsg文件中。rsyslogd通过读取该文件获得内核日志。
rsyslogd守护进程在接收到用户进程或内核输入的日志后,会把它们输出至某些特定的日志文件。默认情况下,调试信息会保存至/var/log/debug文件,普通信息保存至/var/log/messages文件,内核信息保存至/var/log/kern.log文件。日志信息具体如何分发,可以在rsyslogd的配置文件(/etc/rsyslog.conf)中设置。
#include <syslog.h>
void openlog(const char *ident, int option, int facility);
此函数用于改变syslog的默认输出方式,进一步结构化日志内容。
调用openlog是可选择的。如果不调用openlog,则在第一次调用syslog时,自动调用openlog。调用closelog也是可选择的,它只是关闭被用于与syslog守护进程通信的描述符。
1.ident指定的字符串将被添加到日志消息的日期和时间之后,通常被设置为程序的名称。
2.option参数对后续syslog调用的行为进行配置,它可取下列值的按位或:
#define LOG_PID 0x01 //在日志消息中包含程序PID
#define LOG_CONS 0x02 //如果消息不能记录到日志文件,则打印至终端
#define LOG_ODELAY 0x04 //延迟打开日志功能,知道第一次调用syslog
#define LOG_NDELAY 0x08 //不延迟打开日志功能
3.facility参数用来修改syslog函数中的默认设施值。
LOG_AUTH //security/authorization messages (DEPRECATED Use LOG_AUTHPRIV instead)
LOG_AUTHPRIV //security/authorization messages (private)
LOG_CRON //clock daemon (cron and at)
LOG_DAEMON //system daemons without separate facility value
LOG_FTP //ftp daemon
LOG_KERN //kernel messages
LOG_LOCAL0 through LOG_LOCAL7 //reserved for local use
LOG_LPR //line printer subsystem
LOG_MAIL //mail subsystem
LOG_NEWS //USENET news subsystem
LOG_SYSLOG //messages generated internally by syslogd
LOG_USER (default) //generic user-level messages
LOG_UUCP //UUCP subsystem
void closelog(void);
openlog函数用来打开一个到系统日志记录程序的连接,打开之后就可以用syslog或vsyslog函数向系统日志里添加信息了。closelog函数就是用来关闭此连接的。
void syslog(int priority, const char *format, ...);
应用程序使用syslog函数与rsyslogd守护进程通信。
priority(级别)参数是设施值(facility)与日志级别的按位或。设施值(facility)的默认值是LOG_USER
日志级别有如下几个:
#define LOG_EMERG: 0 //系统不可用
#define LOG_ALERT: 1 //报警,需要立即采取动作
#define LOG_CRIT: 2 //非常严重的情况
#define LOG_ERR: 3 //错误
#define LOG_WARNING:4 //警告
#define LOG_NOTICE: 5 //通知
#define LOG_INFO: 6 //信息
#define LOG_DEBUG: 7 //调试
程序在开发阶段可能需要输出很多调试信息,发布之后又需要将这些调试信息关闭。解决这个问题得方法并不是在程序发布之后删除调试代码(以后可能还用的到),而是简单的设置日志掩码,使日志级别大于掩码的日志信息被系统忽略。
int setlogmask(int maskpri);
进程间关系
linux下每个进程都隶属于一个进程组,因此除了有PID信息外,还有进程组ID(PGID)。
使用如下函数获取指定进程的PGID:
pid_t getpgid(pid_t pid);
每个进程组都有一个首领进程,其PGID和PID相同。进程组将一直存在,直到其中所有进程都退出,或者加入到其他进程组。
使用如下函数设置PGID:
int setpgid(pid_t pid, pid_t pgid);
一个进程只能设置自己或者其子进程的PGID。
会话
一些有关联的进程组将形成一个会话(session)。
下面的函数用于创建一个会话:
pid_t setsid(void);
该函数不能由进程组的首领进程调用,否则将产生一个错误。对于非进程组首领的进程,调用该函数不仅创建新会话,而且有如下额外效果:
1、调用进程成为会话的首领,此时该进程是新会话的唯一成员。
2、新建一个进程组,其PGID就是调用进程的PID,调用进程成为该组的首领。
3、调用进程将甩开终端(如果有的话)。
linux进程并未提供所谓会话ID的概念,但linux系统认为它等于会话首领所在的进程组的PGID,提供如下函数来读取SID:
pid_t getsid(pid_t pid);
用户信息
UID、EUID、GID、EGID
用户信息对于服务器程序的安全性来说是很重要的。下面这一组函数可以获取和设置当前进程的真实用户ID、有效用户ID、真实组ID、有效组ID。
uid_t getuid(); //获取真实用户ID
uid_t geteuid(); //获取有效用户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
系统资源限制
linux上运行的程序都会受到资源限制的影响。可通过如下一对函数读取和设置:
#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成员指定资源的硬限制。
改变工作目录和根目录
有些服务器程序需要改变工作目录和根目录。
获取进程当前工作目录和改变进程工作目录的函数:
#include <unistd.h>
char* getcwd(char *buf, size_t size);
int chdir(const char *path);
改变进程根目录的函数:
int chroot(const char *path);
chroot并不改变进程的当前工作目录,所以调用chroot之后,仍需要使用chdir("/")将工作目录切换至新的根目录。改变进程的根目录之后,程序可能无法访问类似/dev的文件(和目录),因为这些文件并非处于新的根目录之下。不过好在调用chroot之后,进程原先打开的文件描述符依然生效。
服务器程序后台化
bool daemonize()
{
int fd;
pid_t sid;
//创建子进程,关闭父进程,这样可以使程序在后台运行
pid_t pid = fork();
if(pid < 0)
{
return false;
}else if(pid > 0)
{
exit(0);
}
//将标准输入、标准输出和标准错误输出都定向到/dev/null文件
fd = open("/dev/null", O_RDWR);
if (fd >= 0)
{
if (fd != STDIN_FILENO)
dup2(fd, STDIN_FILENO);
if (fd != STDOUT_FILENO)
dup2(fd, STDOUT_FILENO);
if (fd != STDERR_FILENO)
dup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO)
close(fd);
}
umask(022); //设置文件权限掩码
chdir("/"); //切换工作目录
//创建新的会话,设置本进程为进程组的首领
sid = setsid();
if(sid < 0)
{
return false;
}
return true;
}
linux提供了完成同样功能的库函数:
int daemon(int nochdir, int noclose);