第1章 UNIX基础知识
Unix体系结构
不带缓冲的I/O:
函数 open、read、write、lseek、close 提供了不带缓冲的I/O. 这些函数都使用文件描述符。
头文件 <unistd.h> 以及2个常量 STDIN_FILENO 和 STDOUT_FILENO 是 POSIX标准的一部分。
标准I/O:
比如, fgets, fputs, putc, getc, 等等
标准I/O函数为那些不带缓冲I/O的函数提供了一个带缓冲的接口。使用标准I/O函数无需担心如何选取最佳的缓冲区大小。
使用标准I/O函数还简化了对于输入行的处理。比如,fgets 函数读取一个完整的行,而 read 函数只能读取指定的字节数。
进程控制:
3个主要用于进程控制的函数:fork、waitpid, exec(有7种变体)
出错处理:
当UNIX系统出错时,通常会返回一个负值;而整型变量 errno 通常被设置为具有特定信息的值。
也有些函数对于出错会使用自己的约定,而不是返回负值。
文件 <errno.h> 中定义了 errno 以及可以赋予它的各种常量。也可使用 man 3 errno 命令列出出错常量。
另:
多个线程共享进程的地址空间,但每个线程都有属于自己的局部 errno, 以避免一个线程干扰另一个线程。
C标准定义了2个函数,用于打印出错信息:
#include <string.h>
char *strerror(int errnum);
void perror(const char * msg);
strerror 函数将errnum(通常就是errno的值)映射为一个出错消息字符串,并且返回此字符串的指针。
perror 函数基于 errno 当前的值,在标准错误上产生一条出错消息,然后返回。
信号:
进程有以下3种处理信号的方式:
- 忽略信号
- 按系统默认方式处理。比如,对于除数为0,系统默认方式是终止该进程。
- 提供信号处理函数
按Ctrl+C中断键,会产生 SIGINT 信号,系统的默认动作是终止进程。
进程时间:
UNIX系统为一个进程维护了3个时间值:
- 时钟时间: 这是进程运行的时间总量,其值与系统中同时运行的进程数有关
- 用户CPU时间:执行用户指令所用的时间
- 系统CPU时间:为该进程执行内核程序所经历的时间
时钟时间 >= CPU时间 = 用户CPU时间+系统CPU时间
执行命令 time, 就能看到 real, user, sys 三个值,即对应上述的3个值。
其他:
manual 2 介绍的是系统调用接口,Linux 3.2.0 提供380个系统调用
manual 3 介绍的是通用库函数
可以使用命令
- man 2 intro
- man 3 intro
来看到这2个手册的简介。
第2章 UNIX标准及实现
UNIX标准化:
- ISO C
- IEEE POSIX
- Single UNIX Specification (是POSIX.1标准的超集)
- FIPS
UNIX系统实现:
- SVR4
- 4.4BSD
- FreeBSD
- Linux
- Mac OS X
- Solaris
- 其他UNIX系统:AIX(IBM UNIX系统)、HP-UX(HP UNIX系统)、IRIX、UnixWare
APUE主要关注 FreeBSD 8.0、Linux 3.2.0、Mac OS X10.6.8、Solaris 10. 这4种系统都在不同程度符合POSIX标准。
第3章 文件I/O
UNIX系统中大多数的文件I/O只需用到5个函数: open、close、read、write、lseek.
不带缓冲的I/O, 术语“不带缓冲”指的是每个read和write都调用内核的一个系统调用。
文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。
#include <fcntl.h>
int open(const char *path, int oflag, ... /* mode_t mode */); // 返回文件描述符或-1
int openat(int fd, const char *path, int oflag, ... /* mode_t mode */);
int create(const char *path, mode_t mode); // 等价于 open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
#include <unistd.h>
int close(int fd);
off_t lseek(int fd, off_t offset, int whence); // 若成功,返回新的文件偏移量;出错,返回-1
ssize_t read(int fd, void *buf, size_t nbytes); // 返回读到的字节数;若已到文件末尾,返回0;出错,返回-1
ssize_t write(int fd, const void *buf, size_t nbytes); // 若成功,返回已写的字节数;出错,返回-1
大多数文件系统为改善性能会采用某种预读技术(read ahead)。当检测到正进行顺序读取时,系统就试图读入比应用所要求的更多的数据,并假想应用很快就会读这些数据。
图3-7 打开文件的内核数据结构
总结:
- 进程中的进程表项 ,也叫文件描述符表项, 即:文件描述符,文件指针 -->
- 内核中的文件表项: 文件状态标志(读、写、添加、同步、非阻塞)、当前文件偏移量、v节点指针 -->
- v节点: v节点信息、v_data -->
- i 节点: i节点信息(如:文件所有者)、当前文件长度、i_vnode -->v节点
每个进程在进程表中都有一个记录项,该项包含文件描述符和一个指向内核文件表项的指针;内核为所有打开文件维持一张文件表,其表项包括:文件状态标志、当前文件偏移量、指向该文件v节点表项的指针;每个打开的文件都有一个v节点(v-node)结构,它包含了文件类型、对此文件进行各种操作的函数的指针、以及i节点;i节点包含了文件的所有者、文件长度、指向文件实际数据库在磁盘上所在位置的指针。
Linux没有使用v节点,而是使用了通用i节点结构。
dup和dup2函数可以使得多个文件描述符指向同一个文件表项:
-
dup(fd) = fcntl(fd, F_DUPFD, 0);
-
dup2(fd, fd2) = close(fd2) + fcntl(fd, F_DUPFD, fd2);
多个文件表项也可能指向同一个v节点,比如多个进程打开同一个文件。如下图:
原子操作(pread和pwrite)
#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
pread 和 pwrite 原子性地定位并执行I/O.
- pread = lseek + read. 但当调用pread时,无法中断其定位和读操作;并且不更新当前文件偏移量。
- pwrite = lseek + write. 但也有类似的区别。
函数dup和dup2
这2个函数都是用来复制一个现有的文件描述符。
#include <unistd.h>
int dup(int fd);
int dup2(int fd, int fd2);
dup返回的新文件描述符一定是当前可用文件描述符中的最小可用数值。
dup2 可以用 fd2 参数指定新描述符的值。如果fd2已经打开,则先将其关闭在打开;若fd和fd2相等,则dup2返回fd2,而不会关闭它。
- dup(fd) = fcntl(fd, F_DUPFD, 0);
- dup2(fd, fd2) = fcntl(fd, F_DUPFD, fd2);
函数sync、fsync和fdatasync
#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);
void sync(void);
-
sync函数:只是将所有修改过的块缓冲区放入内核缓冲区的写队列,然后就返回,并不等待实际写磁盘操作结束。通常,称为update的系统守护进程周期性地(每30秒)调用sync函数。
-
fsync函数:会等待写磁盘操作结束才返回,但只对由文件描述符fd指定的一个文件起作用: 除了数据部分外,fsync还会同步更新文件的属性。
-
fdatasync函数: 类似于fsync,但它只影响文件的数据部分。
函数 fcntl
#include <fcntl.h>
int fcntl(int fd, int cmd, ../* int arg */);
fcntl函数具有以下5种功能:
- 复制一个已有的描述符
- get/set 文件描述符标志
- get/set 文件状态标志(读、写、添加、同步、非阻塞等)
- get/set 异步I/O所有权
- get/set 记录锁
在UNIX系统中,通常write只是将数据排入内核缓冲区中的队列,而实际的写磁盘的操作可能在以后的某个时刻进行。而数据库系统需要使用O_SYNC, 保证数据当时就写到磁盘上。
函数 ioctl
ioctl是I/O操作的杂物箱。终端I/O是ioctl使用最多的地方。
#include <unistd.h>
#include <sys/ioctl.h>
int ioctl(int fd, int request, ...);
第4章 文件和目录
函数 stat、fstat、fstatat、lstat
UNIX文件类型
- 普通文件
- 目录文件
- 块特殊文件: 这种类型的文件提供对设备(如磁盘)的带缓冲的访问,每次访问以固定长度为单位进行
- 字符特殊文件: 这种类型的文件提供对设备不带缓冲的访问,每次访问长度可变。系统中所有设备要么是字符特殊文件,要么是块特殊文件。
- FIFO:命名管道
- Socket
- 符号链接(symbolic link)
用户权限:
-
实际用户ID、实际组ID:
- 我们实际上是谁
- 在登录时,取自口令文件中的登录项
- 通常,在一个登录会话期间,这些值不会改变;但是超级用户进程有方法改变它们
-
有效用户ID、有效组ID、附属组ID
- 用于文件访问权限检查
-
保存的设置用户ID、保存的设置组ID
- 由exec函数保存
- 在文件模式字(st_mode)中设置一个特殊标志,含义是:当执行此文件时,将进程的有效用户ID设置为文件所有者的用户ID (st_uid)
- 保存的设置组ID与上类似: 当执行此文件时,将进程的有效用户组ID设置为文件所有者的用户组ID (st_gid)
函数:access、faccessat、umask、chmod、fchmod、fchmodat、chown、fchown、fchownat、lchown
文件中的空洞:
空洞是由于设置的偏移量超过文件尾端,并写入了某些数据后造成的。
若使用实用程序(如cat)复制这个文件,那么所有的这些空洞都会被填满,所填内容为0.
文件系统:
每个i节点都有一个链接计数,其值是指向该i节点的目录项数。只有当链接计数减少为0时,才可删除该文件。
函数:link、linkat、unlink、unlinkat、remove、rename、renameat
符号链接:
符号链接是对一个文件的间接指针,而硬链接直接指向文件的i节点。
- 硬链接通常要求链接和文件在同一个文件系统,而符号链接没有这个要求。
- 只有超级用户才能创建指向目录的硬链接(还要在底层文件系统支持的情况下);而符号链接可以允许任何用户创建指向目录的符号链接。
第5章 标准I/O库
当打开一个流时,标准I/O函数 fopen 返回一个指向FILE对象的指针。该对象通常是一个结构,它包含了标准I/O库为管理该流所需要的所有信息,包括用于实际I/O的文件描述符、指向用于该流缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数以及出错标志等。
标准I/O库提供缓冲的目的是,尽可能减少使用 read 和 write 的次数。它也对每个I/O流自动地进行缓冲管理,从而避免了应用程序需要考虑这一点所带来的麻烦。遗憾的是,标准I/O库最令人迷惑的也是它的缓冲。
标准I/O提供了以下3种类型的缓冲:
- 全缓冲: 填满标准I/O缓冲区后才进行实际的I/O操作
- 行缓冲: 当在输入和输出中遇到换行符时,标准I/O库执行I/O操作
- 不缓冲
标准错误流stderr通常是不带缓冲的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个换行符。
标准I/O库的不足之处:
效率低。每使用一次fgets和fputs,通常都要复制2次数据:
- 一次在内核和标准I/O缓冲区之间(当调用read和write时);
- 另一次在标准I/O缓冲区和用户程序的行缓冲区之间。
其他略
(完)
# 第6章 系统数据文件和信息
略