Linux 服务器程序规范——《Linux高性能服务器编程》第7章——读书笔记
1、概述
2、日志
2.1 Linux 系统日志
Linux使用一个守护进程(后台进程)rsyslogd
来处理系统日志。
Linux系统产生的日志大概可以分为两种,内核日志 和 用户进程输出的日志。
- ① 用户进程调用
syslog
函数生成(系统)日志,该函数把日志输出到/dev/log
文件中,rsyslogd
监听该文件 并获取用户进程的输出,把他们输出到特定的日志文件(/var/log/*
文件)。 - ② 内核日志由
printk
等函数打印至内核的环状缓存中,环状缓存的内容直接映射到/proc/kmsg
文件中。rsyslog
通过读取该文件获得内核日志,把他们输出到特定的日志文件(/var/log/*
文件)。
2.2 syslog
、openlog
、closelog
函数
① 用途
记录日志。Linux C提供一套日志写入接口,就是他们仨。
openlog
是可选的,若不调用openlog
,则在第一次调用syslog
时,自动调用openlog
。closelog
也是可选的,它用于关闭syslog
日志功能(关闭与syslog
守护进程通信的描述符)。
② openlog
函数
openlog
用于打开一个到系统日志记录程序的连接,打开之后就可以用syslog
向系统日志里添加信息了。此外,该函数还能改变syslog
的默认输出方式(其facility
参数)。而closelog
就是用于关闭此连接。openlog
定义如下:
#include<syslog.h>
void openlog(const char* ident, int logopt, int facility);
ident
所表示的字符串通常就是当前程序的名字,它固定的加在每行日志消息的日期和时间之后,以作为标记。调用openlog
指定一个ident
后,此ident
会被加到每行日志消息中。logopt
参数对后续调用的syslog
函数的行为进行控制(或配置),它可取下列值的按位或与:facility
指明记录日志的程序(后续的syslog
函数)的类型。
另一种解释:facility
参数用来修改syslog
函数中的默认设施值。
我们在讨论syslog
函数的时候讨论设施值的含义及其具体取值。
③ syslog
函数
开启日志功能。syslog
函数用于把日志消息发送给系统程序rsyslogd
去记录。也就是:用于程序使用syslog
函数与 rsyslogd
守护进程通信。syslog
函数的定义如下:
#include<syslog.h>
void syslog(int priority, const char* message, ...); 该函数采用可变参数(第二个'message'和第三个参数'...')
priority
表示消息的紧急级别。它的值是设施值 与 日志级别的按位或与。其中,设施值的默认值是LOG_USER
,下面的讨论也只限于这一种设施值。日志级别的值如下:- 第二个参数是一个格式化表达式,第三个参数是格式化表达式对应的参数,两个参数共同结构化输出。(这两个参数的关系,就类似于
printf
函数的两个参数那样)
④ closelog
函数
#include<syslog.h>
void close();
关闭日志功能。
⑤ 日志过滤——setlogmask
函数
3、用户信息
什么是用户ID,什么是组ID
其他用户:陌生的用户,不属于当前的用户组。
3.2 UID、EUID、GID 和 EGID 都是啥
① UID:实际用户ID
登陆时候的ID。当一个用户登陆到这个系统后就唯一确定了他的UID。(实际用户组ID类似)
该值在login
函数执行时,取自口令文件/etc/passwd
中,一般由用户标识号(第三列)标识。这个ID在登陆期间一般不会改变,除非有root
权限用setuid
改变。
② EUID:有效用户ID
有效ID就是用于权限检查的ID。
③ GID:实际组ID
一个用户登录时其所属的组ID。
④ EGIO:有效组ID
有效组ID就是用于权限检查的组ID。
3.3 获取和设置当前进程的 上面四种ID
3.4 特权进程
有效用户为root
的进程称为特权进程。
3.5 set-user-id
标志
这个标志表示,任何普通用户运行 设置了该标志的程序时,其有效用户就是该程序的所有者。
sudo chmod +s 文件名 设置文件的 set-user-id 标志
4、切换用户
下面的代码将以root身份启动的进程切换为 普通用户身份运行:
static bool switch_to_user(uid_t user_id, git_t gp_id)
{
/* 先确保目标用户不是root */
if (user_id == 0 && gp_id == 0)
{
return false;
}
/* 确保当前用户是合法淞沪:root 或者目标用户 */
git_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;
}
这个代码等以后用到的时候再来看吧,现在不明所以,先不管了。
5、进程间关系
5.1 进程组
5.2 会话
5.3 用ps
命令查看进程关系
6、系统资源限制
Linux上运行的程序都会受到资源限制的影响,比如物理设备闲置(CPU数量、内存数量等)、系统策略限制(CPU时间等),以及具体实现的限制(比如文件名的最大长度)。Linux系统资源限制可以通过如下一对函数来读取和设置:
#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;
};
7、改变工作目录 和 根目录
获取进程当前工作目录和改变进程工作目录的函数分别是:
#include<unistd.h>
char* getcwd(char* buf, size_t size);
int chdir(const char* path);
① getcwd
参数:
buf
参数指向的字符串 存储了进程当前工作目录的绝对路径名,其大小由size
参数指定。
- ——若绝对路径长度(还要加上空字符
\0
)超过了size
,则getcwd
将返回NULL,并设置errno
位ERANGE
。 - ——若
buf
为NULL,并且size
非0,则getcwd
可能在内部使用malloc
动态分配内存,并将当前工作目录的绝对路径存在其中。如果是这种情况,我们必须自己释放getcwd
在内部创建的这块内存。
返回值:
getcwd
函数成功时返回一个指向目标存储区的指针(buf
指向的缓存区或是getcwd
动态创建的缓存区),失败返回BULL并设置errno
。
② chdir
chdir
函数的path
参数指定要切换到的目标目录。成功时返回0,失败返回-1并设置errno
。
改变进程根目录的函数是chroot
:
#include<unistd.h>
int chroot(const char* path);
path
参数指定要切换到的目标根目录。成功返回0,失败返回-1并设置errno
。
chroot
就是把当前程序所在的路径整个 直接变成新的根目录。举个例子:现在有个.c
源文件,他的路径是/home/xl22/桌面/trash/chroot.c
,其中trash
目录的其他内容如下:
.
|-- chroot.c
|-- chroot.out
`-- test
`-- 111.txt
文件111.txt
的路径是:/home/xl22/桌面/trash/test/111.txt
如果在chroot.c
中进行了如下调用:
chroot("./test");
那么新的111.txt
的路径变为:/111.txt
,也就是包括test
在内的前面多有路径一起被替换为了根目录,即/
。
如果只想把test
文件夹之前的部分更改为根目录,那么chroot.c
文件中的调用如下:
chroot("./");
注:使用chroot
函数的程序必须是特权进程,否则该函数会调用失败,所以,当该函数调用失败的时候先检查你有没有超级权限。
8、服务器程序后台化
上面的函数能够让一个程序在后台运行。
Linux提供了完成同样功能的函数:
#include<ubistd.h>
int deamon(int nochdir, int noclose);
nochdir
参数用于指定是否改变工作目录。
——若给他传递0,则工作目录被设置为根目录/
,否则继续使用当前工作目录。noclose
参数为0时,标准输入、标准输出和标准错误输出都被重定向到dev/null
文件,也就是不输出任何信息,否则仍然照样输出。
deamon
调用了fork
,如果fork
成功,那么父进程就调用_exit(2)
退出,所以看到的错误信息 全部是子进程产生的。
deamon
如果成功返回0,失败返回-1并设置errno
。