syslogd和klogd是很有意思的守护进程,syslogd是一个分发器,它将接收到的所有日志按照/etc/syslog.conf的配置策略发送到这些日志应该去的地方,当然也包括从klogd接收到的日志,klogd首先接收内核的日志,然后将之发送给syslogd,klogd是怎么接收内核的日志,接收到的日志又是怎么发给syslogd的呢?过程其实很简单,klogd通过读取/proc/kmsg来接收内核的日志,通过strace -p可以看到:
read(0, "<6>ACPI: PCI interrupt 0000:05:0"..., 4095) = 95
time([1278013550]) = 1278013550
write(1, "<6>Jul 1 15:45:50 kernel: ACPI:"..., 97) = 97
为了看清楚0和1分别代表什么,看一下/proc文件系统:
ll /proc/XX/fd/
total 3
lr-x------ 1 root root 64 2010-07-01 15:43 0 -> /proc/kmsg
lrwx------ 1 root root 64 2010-07-01 15:43 1 -> socket:[7974]
lr-x------ 1 root root 64 2010-07-01 15:43 2 -> /boot/System.map...
或者通过lsof -p XX查看也是可以的。经过解析命令的输出,发现klogd从/proc/kmsg中读取了内核信息,然后写入了一个套接字,这个套接字的另一端肯定是syslogd使用的套接字,并且syslogd肯定从该套接字的另一端接收了数据,用strace和lsof/ll /proc/YY/fd/证实一下:
ll /proc/YY/fd
total 18
lrwx------ 1 root root 64 2010-07-01 15:21 0 -> socket:[4146]
l-wx------ 1 root root 64 2010-07-01 15:21 1 -> /var/log/auth.log
...
l-wx------ 1 root root 64 2010-07-01 15:21 8 -> /var/log/uucp.log
l-wx------ 1 root root 64 2010-07-01 15:21 9 -> /var/log/mail.info
确实是这样的,syslogd中确实使用了一个套接字,为了证实确实从该套接字接收到了klogd发来的数据,在两个终端上同时strace -p syslogd和klogd,然后加载一个驱动或者自己写一个printk的内核模块,然后观察两个终端的输出,确实klogd从0读出了信息abc,然后写入了1,syslogd从0读出了abc,然后写入了一个配置好的日志文件,它们之间是通过套接字来完成通信的。同样的,库函数syslog将klogd的大致过程进行了封装,这样任何程序都可以调用syslog函数进行日志记录了,syslogd成了整个系统统一的一个日志记录器,下面是在syslog的manpage上得到的函数原型:
void openlog(const char *ident, int option, int facility); //ident设置为一个可被过滤的特征字符串,一般为应用程序的名字
void syslog(int priority, const char *format, ...);
void closelog(void);
此处的syslog库函数内部实现的就是套接字发送过程,没有什么好说的,可是千万不要将这个syslog和linux的系统调用sys_syslog搞混淆了,后者的原型是:
asmlinkage long sys_syslog(int type, char __user *buf, int len);
而dmesg调用的正是这个函数,该函数内部通过type分成了若干个类型,其实klogd也是可以通过直接调用这个函数来实现的(/proc/kmsg的read方法就是调用do_syslog,而sys_syslog也是调用do_syslog)。
syslogd和syslog库函数中使用的套接字就是unix族套接字,是unix本地进程间通信的一种好办法,这种套接字必须有一对,本质上实现了一个管道,在接口和语义上又和bsd套接字相统一,实际上bsd套接字没有网络的概念,它只承认自己可以实现进程间通信,它将网络通信也统一看作了进程间通信,只是后来的AF_INET等实现的不同机器间的进程间通信,而原始的AF_UNIX实现的是本机的进程间通信。既然AF_UNIX实现了本机的进程间通信,那么它的地址只需要本机内唯一即可,因此采用普通文件是一个不错的选择,其地址就是文件的全路径:
struct sockaddr_un {
sa_family_t sun_family;
char sun_path[UNIX_PATH_MAX]; //文件路径
};
比如syslogd使用的/dev/log就是一个处于一般文件系统的套接字。套接字创建函数socket的第二个参数是类型,AF_UNIX族的套接字也分stream类型和datagram类型,只是在语义约束上松了很多,datagram仅仅在数据边界等不多的方面和stream类型的套接字有区别,有无连接的概念已经退化了。不管是stream还是datagram类型的AF_UNIX套接字,send的本质就是将数据放入接收者的接收队列,recv则相反。
read(0, "<6>ACPI: PCI interrupt 0000:05:0"..., 4095) = 95
time([1278013550]) = 1278013550
write(1, "<6>Jul 1 15:45:50 kernel: ACPI:"..., 97) = 97
为了看清楚0和1分别代表什么,看一下/proc文件系统:
ll /proc/XX/fd/
total 3
lr-x------ 1 root root 64 2010-07-01 15:43 0 -> /proc/kmsg
lrwx------ 1 root root 64 2010-07-01 15:43 1 -> socket:[7974]
lr-x------ 1 root root 64 2010-07-01 15:43 2 -> /boot/System.map...
或者通过lsof -p XX查看也是可以的。经过解析命令的输出,发现klogd从/proc/kmsg中读取了内核信息,然后写入了一个套接字,这个套接字的另一端肯定是syslogd使用的套接字,并且syslogd肯定从该套接字的另一端接收了数据,用strace和lsof/ll /proc/YY/fd/证实一下:
ll /proc/YY/fd
total 18
lrwx------ 1 root root 64 2010-07-01 15:21 0 -> socket:[4146]
l-wx------ 1 root root 64 2010-07-01 15:21 1 -> /var/log/auth.log
...
l-wx------ 1 root root 64 2010-07-01 15:21 8 -> /var/log/uucp.log
l-wx------ 1 root root 64 2010-07-01 15:21 9 -> /var/log/mail.info
确实是这样的,syslogd中确实使用了一个套接字,为了证实确实从该套接字接收到了klogd发来的数据,在两个终端上同时strace -p syslogd和klogd,然后加载一个驱动或者自己写一个printk的内核模块,然后观察两个终端的输出,确实klogd从0读出了信息abc,然后写入了1,syslogd从0读出了abc,然后写入了一个配置好的日志文件,它们之间是通过套接字来完成通信的。同样的,库函数syslog将klogd的大致过程进行了封装,这样任何程序都可以调用syslog函数进行日志记录了,syslogd成了整个系统统一的一个日志记录器,下面是在syslog的manpage上得到的函数原型:
void openlog(const char *ident, int option, int facility); //ident设置为一个可被过滤的特征字符串,一般为应用程序的名字
void syslog(int priority, const char *format, ...);
void closelog(void);
此处的syslog库函数内部实现的就是套接字发送过程,没有什么好说的,可是千万不要将这个syslog和linux的系统调用sys_syslog搞混淆了,后者的原型是:
asmlinkage long sys_syslog(int type, char __user *buf, int len);
而dmesg调用的正是这个函数,该函数内部通过type分成了若干个类型,其实klogd也是可以通过直接调用这个函数来实现的(/proc/kmsg的read方法就是调用do_syslog,而sys_syslog也是调用do_syslog)。
syslogd和syslog库函数中使用的套接字就是unix族套接字,是unix本地进程间通信的一种好办法,这种套接字必须有一对,本质上实现了一个管道,在接口和语义上又和bsd套接字相统一,实际上bsd套接字没有网络的概念,它只承认自己可以实现进程间通信,它将网络通信也统一看作了进程间通信,只是后来的AF_INET等实现的不同机器间的进程间通信,而原始的AF_UNIX实现的是本机的进程间通信。既然AF_UNIX实现了本机的进程间通信,那么它的地址只需要本机内唯一即可,因此采用普通文件是一个不错的选择,其地址就是文件的全路径:
struct sockaddr_un {
sa_family_t sun_family;
char sun_path[UNIX_PATH_MAX]; //文件路径
};
比如syslogd使用的/dev/log就是一个处于一般文件系统的套接字。套接字创建函数socket的第二个参数是类型,AF_UNIX族的套接字也分stream类型和datagram类型,只是在语义约束上松了很多,datagram仅仅在数据边界等不多的方面和stream类型的套接字有区别,有无连接的概念已经退化了。不管是stream还是datagram类型的AF_UNIX套接字,send的本质就是将数据放入接收者的接收队列,recv则相反。
上述套接字和管道的区别是什么?仅仅是使用时的语义不同,管道的约束很少,也不很规范,很难扩展,而套接字拥有地址空间的概念,有协议的概念,很容易进行扩展,并且语义也很清晰。也正是这种统一,使得syslog很容易支持远程日志记录,本机日志记录和远程记录都使用套接字,所不同的只是套接字的地址族不同,这样很多编码都可以参数化,配置化,不必再写冗余的代码了。
本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1271806