1. 高级 I/O 函数
1.1 pipe
#include <unistd.h>
/*
成功返回0,失败返回-1设置error
*/
int pipe( int fd[2] );
(1)fd[0] 只能从管道读,fd[1] 只能写,默认情况下这一对文件描述符都是阻塞的(读空管道 或 写满管道 会被阻塞);
(2)写端 fd[1] 引用计数减为 0,即没有进程往管道写入数据,对 fd[0] 的 read
操作将返回 0(EOF);同理,读端 fd[0] 引用计数减为 0,对 fd[1] 的 write
操作将失败,并引发 SIGPIPE 信号;
(3)管道大小默认为 65536 字节,可以使用 fcntl
函数修改;
创建双向管道;
#include <sys/types.h>
#include <sys/socket.h>
/*
成功返回0,失败返回-1设置error
*/
int socketpair( int domain, int type, int protocol, int fd[2] );
domain 只能设置为 AF_UNIX;
1.2 dup 和 dup2
创建新的文件描述符,与 file_descriptor 指向相同的文件、管道或网络连接;
#include <unistd.h>
/*
失败返回-1设置error
*/
int dup( int file_descriptor );
int dup2( int file_descriptor_one, int file_descriptor_two );
dup
返回的文件描述符总是取当前系统可用的最小整数值(一般方式是关闭标准输入或输出,其返回的就是之前关闭标准输入或输出的值),dup2
返回第一个不小于 file_descriptor_two 的整数值;
注意:dup
和 dup2
创建的新文件描述符不会继承原本文件描述符的属性;
1.3 readv 和 writev
不同内存位置上的数据,不需要拼接到一块再进行写入
#include <sys/uio.h>
/*
成功时返回读出/写入 fd 的字节数
失败返回-1设置error
*/
ssize_t readv( int fd, const struct iovec* vector, int count );
ssize_t writev( int fd, const struct iovec* vector, int count );
count 是 vector 数组的长度
1.4 sendfile
在两个文件描述符之间直接传递数据(完全在内核中操作),称为零拷贝(没有内核缓冲区和用户缓冲区之间的数据拷贝)
#include <sys/sendfile.h>
/*
成功返回传输的字节数
失败返回-1设置error
*/
ssize_t sendfile( int out_fd, int in_fd, off_t* offset, size_t count );
offset 指定从读入文件流的哪个位置开始读,如果为空,使用默认的起始位置;
count 指定在文件描述符 in_fd 和 out_fd 之间传输的字节数;
注意:in_fd 必须指向真实的文件(不能是管道和 socket),即必须支持 mmap 函数;out_fd必须是一个 socket;
1.5 mmap 和 munmap
mmap
申请一段内存空间,可作为进程间通信的共享内存,也可将文件直接映射到其中;
munmap
释放这段内存;
#include <sys/mman.h>
/*
成功返回指向目标内存的指针
失败返回 MAP_FAILED((void*)-1)并设置errno
*/
void* mmap( void *start, size_t length, int prot, int flags, int fd,
off_t offset );
/*
成功返回 0
失败返回-1设置error
*/
int munmap( void *start, size_t length );
start 允许用户指定某个特定地址作为这段内存的起始地址,如果被设置为 NULL,系统自动分配;
length 指明需要内存的长度;
prot 设置访问权限,取值为 PROT_READ(可读)、PROT_WRITE(可写)、PROT_EXEC(可执行)、PROT_NONE(不能被访问) 这几个值的按位或;
flags 控制内存段内容被修改后程序的行为,是下表某些值的按位或(MAP_SHARED 和 MAP_PRIVATE 是互斥的);
fd 是被映射文件的文件描述符,一般通过 open
获得;
offset 设置从文件的何处开始映射;
1.6 splice
用于在两个文件描述符之间移动数据,也是零拷贝;
#include <fcntl.h>
/*
成功返回移动字节的数量,可能返回0
失败返回 -1 并设置errno
*/
ssize_t splice( int fd_in, loff_t* off_in, int fd_out, loff_t* off_out,
size_t len, unsigned int flags );
(1)fd_in 是待输入数据的文件描述符;如果其为管道,off_in 必须为 NULL;否则,off_in 表示从输入数据流何处开始读数据,此时 off_in 若被设置为 NULL,表示从当前偏移位置读入;
fd_out / off_out 与之类似,用于输出数据流;
fd_in 和 fd_out 必须至少有一个是管道;
len 指定移动数据的长度
(2)flags 控制数据如何移动;被设置为下表某些值的按位或;
1.7 tee
两个管道之间复制数据,也是零拷贝操作,不消耗数据,管道上的数据仍可用于后续的读操作;
#include <fcntl.h>
/*
成功返回在两个fd之间复制的数量,可能返回0
失败返回 -1 并设置errno
*/
ssize_t tee( int fd_in, int fd_out, size_t len, unsigned int flags );
fd_in 和 fd_out 必须都是管道;flags 与 splice
相同
1.8 fcntl(file control)
#include <fcntl.h>
/*
失败返回 -1 并设置errno
*/
int fcntl( int fd, int cmd, ... );
cmd 指定执行何种类型的操作;常用操作和参数如下所示;
SIGIO 和 SIGURG 必须与某个文件描述符关联后才能使用,关联的方法为:使用 fcntl
为文件描述符指定宿主进程或进程组。宿主进程或进程组将捕获这两个信号;
被关联的文件描述符可读或可写时,系统触发 SIGIO 信号;其上有带外数据可读时,触发 SIGURG 信号;
使用 SIGIO 时,还需要利用 fcntl
设置其 O_ASYNC 标志;
2. Linux 服务器程序规范
2.1 日志
2.1.1 Linux 系统日志
(1)Linux 提供一个守护进程来处理系统日志 —— rsyslogd;
其能接收用户日志和内核日志;
用户进程调用 syslog
生成系统日志,将日志输出到 /dev/log 中(AF_UNIX 类型),rsyslogd 监听该文件获得用户进程输出;
内核日志由 printk
等打印到内核的环状缓冲(ring buffer)中,该缓冲直接映射到 /proc/kmsg 文件中,rsyslogd 读取该文件获得内核日志;
(2)输出,默认情况下,调试信息会保持到 /var/log/debug 中,普通信息到 /var/log/messages,内核消息到 /var/log/kern.log;
rsyslogd 的主配置文件是 /etc/rsyslog.conf,可以配置内核日志输入路径、包含哪些子配置文件等;
2.1.1 syslog 函数
(1)
#include <syslog.h>
void syslog( int priority, const char* message, ... );
priority 是设施值(默认为 LOG_USER)和日志级别的按位或;
日志级别如下所示:
#define LOG_EMEGE 0 /* 系统不可用 */
#define LOG_ALERT 1 /* 报警,需要立即采取动作 */
#define LOG_CRIT 2 /*非常严重情况 */
#define LOG_ERR 3 /* 错误 */
#define LOG_WARNING 4 /* 警告 */
#define LOG_NOTICE 5 /* 通知 */
#define LOG_INFO 6 /* 信息 */
#define LOG_DEBUG 7 /* 调试 */
(2)改变 syslog
的默认输出方式,进一步结构化日志内容
void openlog( const char* ident, int logopt, int facility );
ident 指定的字符串将被添加到日志消息的日期和时间之后,通常被设定为程序的名字;
logopt 对后续的 syslog
调用的行为进行配置,可取下列值的按位或:
#define LOG_PID 0x01 /* 日志信息中包含程序PID */
#define LOG_CONS 0x02 /* 如果消息不能记录到日志文件,则打印到终端 */
#define LOG_ODELAY 0x04 /* 延迟打开日志功能直到第一次调用 syslog */
#define LOG_NDELAY 0x08 /* 不延迟打开日志功能 */
facility 可用来修改 syslog
中的默认设施值;
(3)设置 syslog
日志掩码,日志级别大于日志掩码的日志信息被系统忽略;
/* 返回先前的日志掩码值 */
int setlogmask( int maskpri );
(4)关闭日志功能
void closelog();
2.2 用户信息
2.2.1 UID、EUID、GID 和 EGID
#include <sys/types.h>
#include <unistd.h>
uid_t getuid(); /* 获取真实用户ID */
uid_t geteuid(); /* 获取有效用户ID */
gid_t getgid(); /* 获取真实组ID */
gid_t getegid(); /* 获取有效组ID */
int setuid( uid_t uid );
int seteuid( uid_t uid );
int setgid( gid_t gid );
int setegid( gid_t gid );
一个进程拥有两个用户 ID:UID 和 EUID;EUID 存在的目的是方便资源访问:使得运行程序的用户拥有该程序的有效用户的权限,比如 su;
文件如果由 set-user-id(s) 标志,运行该文件时,其有效用户就是该程序的所有者;
root 的 UID 为 0;有效用户为 root 的进程称为特权进程;
2.2.2 切换用户
从 root 身份启动进程切换到普通用户身份运行,调用 setgid
和 setgid
实现;
2.3 进程间关系
2.3.1 进程组
每个进程都隶属于一个进程组,除了 PID 外,还有 PGID;
#include <unistd.h>
/*
成功返回进程 PID 所属进程组的 PGID
失败返回-1并设置errno
*/
pid_t getpgid( pid_t pid );
/*
成功返回 0
失败返回-1并设置errno
*/
int setpgid( pid_t pid, pid_t pgid );
(1)每个进程组都有一个首领进程,其 PID 和 PGID 相同;进程组将一直存在,直到其中所有进程都退出或加入其他进程组;
(2)setpgid
中 pid 为 0,表示设置当前进程的 PGID 为 pgid;如果 pgid 为 0,则使用 pid 作为 PGID;
(3)一个进程只能设置自己或其子进程的 PGID,当子进程调用 exec
系列函数后,不能在父进程对它设置 PGID;
2.3.2 会话
有关联的进程组将形成一个会话,下面函数用于创建一个会话;
#include <unistd.h>
/*
成功返回新的进程组的 PGID
失败返回-1并设置errno
*/
pid_t setsid( void );
pid_t getsid( pid_t pid );
不能由进程组的首领进程创建会话,会产生错误;对非首领进程,调用 setsid
,会使得调用进程成为会话的首领,会新建一个进程组,调用进程成为该组首领,调用进程将甩开终端;
会话ID:会话首领所在的进程组的 PGID;
2.4 改变工作目录和根目录
#include <unistd.h>
/*获取和改变进程工作目录*/
char* getcwd( char* buf, size_t size );
int chdir( const char* path );
/*改变进程根目录*/
int chroot( const char* path );
(1)buf 指向的内存用于存储进程当前工作目录的绝对路径,其大小由 size 指定;
如果当前工作目录的绝对路径长度(加上最后一个’\0’)大于 size,返回 NULL,并设置 errno 为 ERANGE;
(2)chdir
和 chroot
成功返回 0,失败返回 -1 并设置 error;
chroot
不改变进程当前的工作目录,调用其之后,仍需要使用 chdir(”/“)
来将工作目录切换至新的根目录,只有特权进程才能改变根目录;
2.5 进程后台化
#include <unistd.h>
/*成功返回 0, 失败返回-1并设置errno*/
int daemon( int nochdir, int noclose );
nochdir 为 0,工作目录被设置为 “/”,否则为当前工作目录;
noclose 为 0 时,标准输入、输出、错误输出都被重定向到 /dev/null 文件,否则,继续用原先设备;
参考
《Linux 高性能服务器》