APUE读书笔记(一)

1 UNIX基础

登录

登录UNIX时,依次键入登录名与口令。
口令文件(通常是/etc/passwd文件)的登录项的7个字段(冒号分隔):登录名、加密口令、数字用户ID、数字组ID、注释字段、起始目录(/home/sar)以及shell程序。

文件和目录

目录是包含目录项的文件。
POSIX.1限制文件名在此些字符集中:字母(a~z、A~Z)、数字(0~9)、句点(.)、短横线(-)和下划线(_)。
绝对路径名的开头为斜线(/),反之为相对路径名。
每个进程有一个(当前)工作目录,相对路径名从此处开始解释。
登录时,工作目录设置为起始目录(从口令文件中相应登录项中取得)。

输入和输出

内核用文件描述符(一个小的非负整数)标识一个特定进程正在访问的文件。
每当运行一个新程序,shell会为其打开标准输入、标准输出、标准错误三个文件描述符。

程序和进程

程序为存储在磁盘中的某个可执行文件,而进程是程序的执行实例。
UNIX确保每个进程都有进程ID(唯一的数字标识符、非负整数)。
同一进程中的线程也有线程ID,它们共享同一地址空间、文件描述符、栈以及与进程相关属性。

出错处理

整型变量errno可以用来存储各种出错信息。
<errno.h>定义两类出错:致命性的(无法执行恢复动作)与非致命性的。

用户标识

用户ID(在口令文件登录项中)是一个向系统标识各个不同用户的数值,用户ID为0的用户是根用户(超级用户)。
组ID(口令文件登录项中)是一个向系统标识各个不同用户组的数值(同组的各个成员共享资源),组文件(通常是/etc/group)将组名映射为组ID。

信号

信号用于通知进程发生了什么情况(进程可以忽略它或按系统默认方式处理或提供处理函数)。

时间值

  1. 日历时间:为1970年1月1日00:00:00以来累计的秒数,保存在time_t类型中;
  2. 进程时间(CPU时间):用以度量进程使用CPU资源,保存在clock_t类型中。

UNIX为一个进程维护3个进程时间值:时钟时间(墙上时钟时间,进程运行的时间总量,包括阻塞与就绪时间)、用户CPU时间(执行用户指令所用的时间量)、系统CPU时间(在内核执行的时间量)。
运行时间 = 用户CPU时间 + 系统CPU时间。
时钟时间 = 运行时间 + 阻塞时间 + 就绪时间。

2 UNIX标准及实现

UNIX标准化

  1. ISO C
  2. IEEE POSIX
  3. Single UNIX Specification(SUS)
  4. FIPS

UNIX系统实现

  1. SVR4
  2. 4.4BSD
  3. FreeBSD
  4. Linux
  5. Mac OS X
  6. Solaris

限制

UNIX提供的3种限制:

  1. 编译时限制
  2. 与文件或目录无关的运行时限制
  3. 与文件或目录有关的运行时限制

ISO C限制在<limits.h>中。
POSIX限制包括:数值限制、最小值、最大值、运行时可以增加的值、运行时不变值、其他不变值、路径名可变值。
XSI限制包括:最小值、运行时不变值。

确定限制值以及特征选项:

#include <unistd.h>
long sysconf(int name);
long pathconf(const char *pathname, int name);
long fpathconf(int fd, int name);
//返回值:成功则返回相应值,出错则返回-1
//name为限制名对应参数

选项

运行一些程序需要先判断当前环境是否支持其依赖的可选选项。
POSIX定义了3种处理选项的方法:

  1. 编译时选项定义在<unistd.h>中;
  2. 与文件或目录无关的运行时选项用sysconf来判断;
  3. 与文件或目录无关的选项用pathconffpathconf来判断。

功能测试宏

常量_POSIX_C_SOURCE_XOPEN_SOURCE被称为功能测试宏。

基本系统数据类型

<sys/types.h>中定义了基本系统数据类型。

3 文件I/O

文件描述符

打开文件并读写:将opencreat返回的文件描述符作为参数传递给readwrite
标准输入、标准输出、标准错误的文件描述符分别为STDIN_FILENOSTDOUT_FILENOSTDERR_FILENO

函数open和openat

打开或创建一个文件:

#include <fcntl.h>
int open(const char *path, int oflag, ... /* mode_t mode */);
int openat(int fd, const char *path, int oflag, ... /* mode_t mode */);
//返回值:成功则返回文件描述符,出错则返回-1
//fd为文件描述符
//若path指定的是绝对路径名则fd被忽略,若path指定的是相对路径名则fd指出了相对路径名在文件系统中的开始地址,若path指定了相对路径名且fd为AT_FDCWD则路径名在当前工作目录获取(同之后类似情况)
//oflag可以用来说明此函数的多个选项(如读写方式、追加方式、是否存在、检测文件类型、同步读写、是否更新文件属性等,常量定义在<fcntl.h>)

函数creat

创建一个新文件:

#include <fcntl.h>
int creat(const char *path, mode_t mode);
//返回值:成功则返回为只写打开的文件描述符,出错则返回-1
//等效于:open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);

creat以只写方式打开所创建的文件,若打开后既要写也要读,便需要先调用close后再调用open

函数close

关闭一个打开文件(会释放该进程在该文件上的所有锁记录):

#include <unistd.h>
int close(int fd);
//返回值:成功则返回0,出错则返回-1

当一个进程终止时,内核自动关闭它所有的打开文件。

函数lseek

为一个打开文件设置偏移量:

#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
//返回值:成功则返回新的文件偏移量,出错则返回-1
//偏移量offset为字节
/*
whence值:
SEEK_SET为距文件开始处;
SEEK_CUR为其当前值处;
SEEK_END为文件长度处
*/

如果文件描述符指向的是一个管道、FIFO或网络套接字,则返回-1。
lseek仅将当前的文件偏移量记录在内核中,不引起任何I/O操作。
文件偏移量可以大于文件的当前长度,对该文件的下一次写将加长该文件,并在文件中构成一个空洞(文件中没有写过的字节都被读为0),文件的空洞并不要求在磁盘上占用存储区。

函数read

从打开文件中读数据:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);
//返回值:读到的字节数,已到文件尾则返回0,出错则返回-1

函数write

向打开文件写数据:

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);
//返回值:成功则返回已写的字节数,出错则返回-1

write的返回值通常与nbytes的值相同,否则表示出错。

I/O的效率

对于UNIX系统内核,文本文件与二进制代码文件没区别。
为改善系统,文件系统使用预读技术,所以要选取合适大小的缓冲区。

文件共享

UNIX系统支持在不同进程间共享打开文件。
内核使用3种数据结构表示打开文件:

  1. 一个进程在进程表中有一个存放文件描述符(即文件描述符标志与文件指针的映射)的表;
  2. 文件指针指向文件表项(包含文件状态标志、当前文件偏移量、V节点指针);
  3. v节点指针指向v节点表项(包含文件相关信息及i节点)。

打开一个文件就会拥有一个独立的文件描述符、文件表项,但是v节点以及i节点对于同一文件来说只有一份。

原子操作

原子操作指的是由多步组成的一个操作,若执行则要么执行完所有步骤要么一步也不执行。
“先定位到文件尾端然后写”的原子操作是用open时设置O_APPEND
原子性地定位并执行I/O:

#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
//返回值:读到的字节数,已到文件尾则返回0,出错则返回-1
ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
//返回值:成功则返回已写的字节数,出错则返回-1

调用pread/pwrite相当于调用lseek后调用read/write,但是有区别:1)调用pread/pwrite时无法中断其定位和读/写操作,2)不更新当前文件偏移量。
“检查文件是否存在”的原子操作是用open时设置O_CRAET | O_EXCL

函数dup和dup2

复制一个现有文件的文件描述符(使得文件指针指向同一文件表项):

#include <unistd.h>
int dup(int fd);//新文件描述符一定是当前可用文件描述符中的最小数值
int dup2(int fd, int fd2);//若fd2已经打开则先将其关闭,若fd与fd2不相等则不关闭fd2且其FD_CLOEXEC被清除(fd2在exec时为打开状态)
//返回值:成功则返回新的文件描述符,出错则返回-1

复制一个描述符的另一种方法是使用fcntl

dup(fd);
//即
fcntl(fd, F_DUPFD, 0);

dup2(fd, fd2);
//即
close(fd2);
fcmtl(fd, F_DUPFD, fd2);
//dup2是原子操作,且其有些不同的errno

函数sync、fsync和fdatasync

延迟写:向文件写入数据时,内核通常先将数据复制到缓冲区中然后排入队列,晚些时候再写入磁盘。
刷新缓冲区(保证磁盘上实际文件系统与缓冲区中内容的一致性):

#include <unistd.h>
int fsync(int fd);//刷新磁盘中特定文件,等待实际写磁盘结束才返回
int fdatasync(int fd);//与fsync类似但不更新文件属性
//返回值:成功则返回0,出错则返回-1
void sync(void);//将修改过的块缓冲区排入写队列,不等待实际写磁盘结束就返回,通常被update守护进程周期性调用

函数fcntl

关于文件的瑞士军刀(复制一个已有的描述符、获取/设置文件描述符标志或文件状态标志或异步I/O所有权或记录锁):

#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* int arg */);
//返回值:若成功,则依赖于cmd;若出错,返回-1

oldFlag |= addFlag可以开启addFlagoldFlag &= ~deleteFlag可以关闭deleteFlag

函数ioctl

I/O操作的杂物箱(设备驱动程序中对设备的I/O通道进行管理):

#include <unistd.h>/* System V */
#include <sys/ioctl.h>/* BSD and Linux */
int ioctl(int fd, int request, ...);
//返回值:出错则返回-1,成功则返回其他值

/dev/fd

打开文件/dev/fd/n等效于复制描述符n

fd = open("/dev/fd/0", mode);
//即
fd = dup(0);

/dev/fd文件主要由shell使用。

posted @ 2020-09-27 18:09  SIGMA711  阅读(152)  评论(0编辑  收藏  举报