APUE笔记之1-5章:UNIX基础、标准、文件I/O、文件与目录、标准I/O库

版权声明:本文为博主原创文章,可以转载但必须注明出处。 https://blog.csdn.net/nirendao/article/details/88144754

第1章 UNIX基础知识

Unix体系结构

不带缓冲的I/O:

函数 open、read、write、lseek、close 提供了不带缓冲的I/O. 这些函数都使用文件描述符。

头文件 <unistd.h> 以及2个常量 STDIN_FILENOSTDOUT_FILENO 是 POSIX标准的一部分。

标准I/O:

比如, fgets, fputs, putc, getc, 等等

标准I/O函数为那些不带缓冲I/O的函数提供了一个带缓冲的接口。使用标准I/O函数无需担心如何选取最佳的缓冲区大小。

使用标准I/O函数还简化了对于输入行的处理。比如,fgets 函数读取一个完整的行,而 read 函数只能读取指定的字节数。

扫描二维码关注公众号,回复: 5434673 查看本文章

进程控制:

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种处理信号的方式:

  1. 忽略信号
  2. 系统默认方式处理。比如,对于除数为0,系统默认方式是终止该进程。
  3. 提供信号处理函数 

按Ctrl+C中断键,会产生 SIGINT 信号,系统的默认动作是终止进程。

进程时间:

UNIX系统为一个进程维护了3个时间值:

  1. 时钟时间: 这是进程运行的时间总量,其值与系统中同时运行的进程数有关
  2. 用户CPU时间:执行用户指令所用的时间
  3. 系统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种功能:

  1. 复制一个已有的描述符
  2. get/set 文件描述符标志
  3. get/set 文件状态标志(读、写、添加、同步、非阻塞等)
  4. get/set 异步I/O所有权
  5. 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文件类型

  1. 普通文件
  2. 目录文件
  3. 块特殊文件: 这种类型的文件提供对设备(如磁盘)的带缓冲的访问,每次访问以固定长度为单位进行
  4. 字符特殊文件: 这种类型的文件提供对设备不带缓冲的访问,每次访问长度可变系统中所有设备要么是字符特殊文件,要么是块特殊文件。
  5. FIFO:命名管道
  6. Socket
  7. 符号链接(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种类型的缓冲:

  1. 全缓冲: 填满标准I/O缓冲区后才进行实际的I/O操作
  2. 行缓冲: 当在输入和输出中遇到换行符时,标准I/O库执行I/O操作
  3. 不缓冲

标准错误流stderr通常是不带缓冲的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个换行符。

标准I/O库的不足之处

效率低。每使用一次fgets和fputs,通常都要复制2次数据

  • 一次在内核和标准I/O缓冲区之间(当调用read和write时);
  • 另一次在标准I/O缓冲区和用户程序的行缓冲区之间。

其他略

(完)


# 第6章 系统数据文件和信息


猜你喜欢

转载自blog.csdn.net/nirendao/article/details/88144754