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
之后不能直接访问的文件(和目录),尤其是一些日志文件。此外,只有特权进程才能改变根目录。