第7章 Linux服务器程序规范
Linux服务器程序规范:
- Linux服务器程序一般以后台进程形式运行。后台进程又称守护进程(daemon)。守护进程的父进程通常是init进程(PID为1的进程)。
- Linux服务器程序通常有一套日志系统,大部分后台进程都在/var/log目录下拥有自己的日志目录。
- Linux服务器程序一般以某个专门的非root身份运行。比如mysqld、httpd、syslogd等后台进程,分别拥有自己的运行账户mysql、apache和syslog。
- Linux服务器程序通常是可配置的。服务器程序通常能处理很多命令行选项,如果一次运行的选项太多,则可以用配置文件来管理。绝大多数服务器程序都有配置文件,并存放在/etc目录下。比如第4章讨论的squid服务器的配置文件是/etc/squid3/squid.conf。
- Linux服务器进程通常会在启动的时候生成一个PID文件并存入/var/run目录中,以记录该后台进程的PID。比如syslogd的PID文件是/var/run/syslogd.pid。
- Linux服务器程序通常需要考虑系统资源和限制,以预测自身能承受多大负荷,比如进程可用文件描述符总数和内存总量等。
Linux系统日志
日志的作用:记录程序运行的过程,以便于程序员对问题进行定位和分析。
日志系统的作用:使得输出的日志更加的规范,这样我们定位程序的问题将更加的迅速。
Linux提供一个守护进程来处理系统日志——syslogd,不过现在的Linux系统上使用的都是它的升级版——rsyslogd。
- 用户进程是通过调用syslog函数生成系统日志到文件/dev/log中
- 内核日志由printk等函数打印至内核的环状缓存 (ring buffer)中。环状缓存的内容直接映射到/proc/kmsg文件中。
rsyslogd守护进程在接收到用户进程或内核输入的日志后,默认情况下,调试信息会保存至/var/log/debug文件,普通信息保存至/var/log/messages文件,内核消息则保存至/var/log/kern.log文件。不过,日志信息具体如何分发,可以在rsyslogd的配置文件中设置。rsyslogd的主配置文件是/etc/rsyslog.conf
rsyslogd中常用的函数:
- syslog():指定日志的类型(等级)和具体的日志。
- openlog():打开一个日志器
- setlogmask():使日志级别大于日志掩码的日志信息被系统忽略,比如运行的时候不需要输出调试的日志
- closelog():关闭日志功能
实例:
#include <syslog.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
openlog("example_app", LOG_PID|LOG_CONS, LOG_USER);
syslog(LOG_NOTICE, "Application started");
syslog(LOG_WARNING, "Warning message");
syslog(LOG_DEBUG, "This is a debug message");
syslog(LOG_ERR, "Error message");
closelog();
return 0;
}
上述代码的输出可以从/var/log/messages和/var/log/debug中找到。
ubuntu系统没有/var/log/messages系统日志文件解决办法,没有debug文件的解决方法是同理的。
UID与EUID
大部分服务器就必须以root身份启动,但不能以root身份运行。【问题】这句话是什么意思?这和下面要讲的东西有什么关系?
一个进程拥有两个用户ID:真实用户ID(UID)、有效用户ID(EUID)、真实组ID(GID)和有效组ID(EGID)
- EUID的作用:如果程序设置了set-user-id标志,那么当某用户使用该程序时,此用户就暂时获取EUID所指向用户的权限。如使用su程序时,EUID为0(root),用户暂时拥有了root用户的权限。利用setuid()、seteuid()方法设置uid和euid。
- EGID的含义与EUID类似:用户暂时获取EGID所指向用户组的权限
测试程序1:获取UID和EUID,test.cpp:
#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);return 0;
}
编译与设置:
g++ test.cpp -o test
sudo chown root:root test # 修改目标文件的所有者和组为root
sudo chmod +s test # 给程序test设置set-user-id标志
./test
输出:
userid is1004,effective userid is:0
测试程序2:切换进程的UID和EUID:
#include<unistd.h>
#include<stdio.h>
bool switch_to_user( uid_t user_id, gid_t gp_id )
{
if ( ( setgid( gp_id ) < 0 ) || ( setuid( user_id ) < 0 ) )
{
return false;
}
return true;
}
进程组与会话
进程组的作用:
终止进程组所有进程;给进程组中的所有进程发送信号从而控制这些进程;限制进程组中进程拥有的权限,提高系统的安全;监视进程组使用的资源总和等。
会话:一些有关联的进程组将形成一个会话。会话中可能有多个进程组。
- ulimit:ulimit命令修改当前shell环境下的资源限制。如CPU数量、内存数量、CPU时间、最大文件名长度等。
- 用于系统资源限制的函数:getrlimit和setrlimit
chdir与chroot
Web服务器的逻辑根目录并非文件系统的根目录“/”,而是站点的根目录(对于Linux的Web服务来说,该目录一般是/var/www/)。所以需要改变进程的根目录。获取进程当前工作目录和改变进程工作目录的函数分别是:getcwd和chdir。
chdir与chroot:
- chdir系统调用用于更改进程的当前工作目录:假设本来是在目录
/home/ubuntu/projects/tests/test11111
下运行程序,使用chdir("/var/txt/")以后,如果访问./text.txt,就是在访问/var/txt/text.txt。 - chroot进程的根目录,限制进程只能访问根目录下的文件:使用chroot("/var/txt")以后,如果访问/text.txt,就是在访问/var/txt/text.txt。
- 区别:chroot("/var/txt/")让进程只能访问/var/txt/目录下文件。如果只使用chdir("/var/txt/"),进程仍然可以访问/var/txt/目录以外的文件。
【注】通过chroot可以改变函数的根目录,chroot并不改变进程的当前工作目录,所以调用chroot之后,我们仍然需要使用chdir(“/”)来将工作目录切换至新的根目录。
下面是一个chroot使用实例:
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <iostream>
int main()
{
// 使用chroot来设置隔离的根目录为 /newroot
if (chroot("/newroot") != 0) {
std::cerr << "chroot failed\n";
return 1;
}
// 在根目录下创建一个新的文件
int fd = open("/testfile", O_CREAT | O_WRONLY, 0644);
if (fd == -1) {
std::cerr << "open failed\n";
return 1;
}
// 关闭文件并输出信息
close(fd);
std::cout << "File created successfully in chroot jail\n";
// 尝试访问 /etc/passwd 目录
fd = open("/etc/passwd", O_RDONLY);
if (fd == -1) {
std::cerr << "open failed: cannot access /etc/passwd\n";
return 1;
}
// 关闭文件并输出信息
close(fd);
std::cout << "Successfully accessed /etc/passwd in chroot jail\n";
return 0;
}
上述代码会/newroot
目录下创建文件testfile
,并且本程序无法访问/etc/passwd
。
【注】运行上述代码需要使用sudo
后台运行的进程,程序中调用daemon()
在程序中调用daemon(),程序在后台运行。下面自己写一个daemon():
bool daemonize()
{
/*创建子进程,关闭父进程,这样可以使程序在后台运行*/
pid_t pid = fork();
if ( pid < 0 )
{
return false;
}
else if ( pid > 0 )
{
exit( 0 );
}
/*设置文件权限掩码。当进程创建新文件(使用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 )
{
/* 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;
}
其他
.d 是 directory 的缩写,表示这是一个目录(文件夹)。是对原有配置的扩展。