Linux服务器规范
学习《Linux高性能服务器编程》第七章Linux服务器规范,为了印象深刻一些,多动手多实践,所以记下这个笔记。这一篇主要记录Linux中日志、用户信息、进程关系、改变工作目录和根目录。
日志
Linux系统日志
Linux上使用rsyslogd守护进程接收用户进程输出的日志和接收内核日志。
用户进程是通过syslogd函数生成系统日志。该函数将日志输出到一个UNIX本地域socket类型(AF_UNIX)的文件/dev/log中,rsyslogd则监听该文件以获取用户进程的输出。
内核日志是如何进行管理的,在这里我们不进行关系。
rsyslogd守护进程在接收到用户进程或内核输入的日志后,会把它们输出至某些特定的日志文件。默认情况下,调试信息会保存至/var/log/debug文件,普通信息保存至/var/log/messages文件,内核消息则保存至/var/log/kern.log文件。
不过,日志信息具体如何分发,可以在rsyslogd的配置文件中设置。rsyslogd的主配置文件是/etc/rsyslog.conf,其中主要可以设置的项包括:内核日志输入路径,是否接收UDP日志及其监听端口(默认是514,见/etc/services文件),是否接收TCP日志及其监听端口,日志文件的权限,包含哪些子配置文件(比如 /etc/rsyslog.d/*.conf)。rsyslogd的子配置文件则指定各类日志的目标存储文件。

rsyslogd系统日志功能比较复杂,有facility、priority、action等概念。还有Input模块、Filetr模块、Output模块等模块内容,目前还未弄清楚相关的知识。
查看了一下/etc/rsyslog.conf的配置文件
1 | /etc/rsyslog.conf configuration file for rsyslog |
我们可以看到模块module(load="imuxsock"),这是一个输入模块
然后看到默认规则在/etc/rsyslog.d/50-default.conf,查看一下
1 | Default rules for rsyslog. |
其中auth这种就是facility(设施),mail.err这种就是priority(等级),而这种设置日志记录的位置就是action
1 | *.*;auth,authpriv.none -/var/log/syslog |
从上面这一个action可以看出普通的日志("."),设置日志记录位置是/var/log/syslog,前面-表示异步写入
我们使用logger命令测试一下
1 | logger -i -t "my_test" "test_log" |
使用vim查看/var/log/syslog可以看到我们的logger的结果

rsyslogd的内容比较多,后续再补。
syslog函数
应用程序使用syslog函数和rsyslogd守护进程进行通讯。
1 |
|
openlog用于改变syslog默认的输出方式,进行日志结构化
ident参数指定的字符串将被添加到日志消息的日期和时间之后,它通常被设置为程序的名字。
option参数对后续syslog调用的行为进行配置,它可取下列的值
1 |
facility参数可以修改syslog函数中的默认设施值
1 | /* facility codes */ |
syslog用于输出日志。
priority参数是设施值和日志级别的按位与,默认是LOG_USER。日志级别有下面几个
1 |
第二个参数message和第三个参数...来结构化输出。
closelog用于关闭日志
小例子:
1 |
|
结果

用户信息
UID、EUID、GID和EGID
Linux中id真是太多了进程有pid,然后用户还有UID这种,真是有点绕。
在Linux当中一个进程(程序)拥有四个ID:真实用户UID、有效用户EUID、真实组GID和有效组EGID。
这里以真实用户UID和有效用户EUID为例,真实组GID和有效组EGID道理是相同的。
EUID存在的目的是方便资源访问:它使得运行程序的用户拥有该程序的有效用户的权限(太过官方这种说法感觉)。EUID确定进程对某些资源和文件的访问权限。在大多数情况下,进程的UID和EUID是一样的,但是对于一些程序如su、passwd这种set-user-id程序,它们有可能是不相同的。对于set-user-id程序而言,程序的EUID会变成程序的所有者的UID,也就是说程序执行时,是以程序的所有者身份进行运行的。
以passwd为例。passwd允许用户修改自己的登录密码,这个程序的所有者是root,passwd权限中有s,表明这是一个set-user-id程序。passwd命令需要修改/etc/shadow文件,对于/etc/shadow文件,普通用户是不可写(只有读权限)的,那么用户怎么能够通过passwd修改自己的密码呢,set-user-id程序的标志s就起到了作用,它在程序运行时将EUID会变成程序的所有者的UID,那么程序有效的用户就会变成程序的所有者,在这里是root用户,理所当然的可以进行/etc/shadow文件的修改。

再比如su程序允许任何用户都可以使用它来修改自己的账户信息,但修改账户时程序不得不访问文件/etc/passwd文件,而访问该文件是需要root权限的。那么以普通用户身份启动的su程序如何能访问/etc/passwd文件呢?

su程序的所有者是root,并且它被设置了set-user-id标志。和上面passwd一样,set-user-id标志表示任何普通用户运行su程序时,其有效用户就是该程序的所有者root。
获取和设置真实用户UID、有效用户EUID、真实组GID和有效组EGID的函数如下
1 |
|
为了测试上面所说,我们先创建一个普通用户bugcat,目前已经有普通用户ubuntu。
可以看到bugcat的uid是1002

我们写下读取程序uid和euid的代码如下:
1 |
|
将其编译一下,然后查看查看文件属性,再运行程序,可以看到uid和euid输出相同,表示真实用户和有效用户都是ubuntu

接着再将程序的所有者改为root,再加上s权限,再运行程序,可以看到uid和euid输出不相同,表示真实用户是ubuntu,有效用户是root(符合set-user-id程序特点)

然后将程序的所有者改为bugcat(s权限不知道为啥自动取消了),再加上s权限,再运行程序,可以看到uid和euid输出不相同,表示真实用户是ubuntu,有效用户是bugcat

最后我们去掉s权限,运行程序,可以看到uid和euid输出相同,表示真实用户和有效用户都是ubuntu,也从反面说明s权限的作用。

进程间关系
进程组
Linux下每个进程都隶属于一个进程组,因此它们除了PID信息外,还有进程组ID(PGID)。我们可以用如下函数来获取指定进程PGID:
1 |
|
该函数成功时返回进程pid所属进程组的PGID,失败则返回-1并设置errno。
每个进程组都有一个首领进程,其PGID和PID相同。进程组将一直存在,直到其中所有进程都退出,或者加入到其他进程组。下面的函数用于设置PGID:
1 | int setpgid(pid_t pid, pid_t pgid); |
该函数将PID为pid的进程的PGID设置为pgid。
如果pid和 pgid相同,则由pid指定的进程将被设置为进程组首领;
如果pid为0,则表示设置当前进程的PGID为pgid;
如果pgid为0,则使用pid作为目标PGID。
setpgid函数成功时返回0,失败则返回-1并设置errno.
一个进程只能设置自己或者其子进程的PGID。并且,当子进程调用exec系列函数后,我们也不能再在父进程中对它设置PGID。
会话
一些有关联的进程组将形成一个会话(session)。下面的函数用于创建一个会话:
1 |
|
该进程不能由进程组的首领进程进行调用,会报错。
对于非进程组首领的进程,调用该函数不仅创建新会话,还会:
- 调用进程成为会话的首领,此时该进程是新会话的唯一成员。
- 新建一个进程组,其
PGID就是调用进程的PID,调用进程成为该组的首领。 - 调用进程将失去终端
该函数成功时返回新的进程组的PGID,失败则返回-1并设置errno。
Linux进程并未提供所谓会话ID (SID)的概念,但Linux系统认为它等于会话首领所在的进程组的PGID,并提供了如下函数来读取SID:
1 |
|
使用ps命令查看进程之间的关系
在终端输入
1 | ps -o pid,ppid,pgid,sid,comm | less |

它们之间的关系如下图
从单独的进程角度看,zsh是ps和less的父进程
从组的角度看,zsh是一个组(组里面只有zsh,所以zsh是进程组首领),ps和less是一个组(ps是进程组首领)
从会话的角度看,会话里面有两个关联的进程组,其实zsh是会话的首领

改变工作目录和根目录
进程有工作目录和根目录。
工作目录:进程在哪个路径下被运行起来哪个路径就是进程的工作目录(Current Woring Directory, CWD)
根目录:就是”/“
工作目录和根目录可以通过/proc/PID/cwd和/proc/PID/root进行查看

工作目录和根目录都可以进行更改,获取进程当前工作目录和改变进程工作目录的函数分别是:
1 |
|
buf参数指向的内存用于存储进程当前工作目录的绝对路径名,其大小由size参数指定。
如果当前工作目录的绝对路径的长度(再加上一个空结束字符“\0”)超过了size,则getcwd将返回NULL,并设置errno为ERANGE。
如果buf为NULL并且size非0,则getcwd可能在内部使用malloc动态分配内存,并将进程的当前工作目录存储在其中。如果是这种情况,则我们必须自己来释放getcwd在内部创建的这块内存。
getcwd函数成功时返回一个指向目标存储区(buf指向的缓存区或是getcwd在内部动态创建的缓存区)的指针,失败则返回NULL并设置errno。
chdir函数的path参数指定要切换到的目标目录。它成功时返回0,失败时返回-1并设置errno。
改变进程根目录可以使用chroot:
1 |
|
path参数指定要切换到的目标根目录。它成功时返回0,失败时返回-1并设置errno。
chroot并不改变进程的当前工作目录。
改变进程的根目录之后,程序可能无法访问类似/dev的文件(和目录),因为这些文件(和目录〉并非处于新的根目录之下。不过好在调用chroot之后,进程原先打开的文件描述符依然生效,所以我们可以利用这些早先打开的文件描述符来访问调用chroot之后不能直接访问的文件(和目录),尤其是一些日志文件。此外,只有特权进程才能改变根目录。