Unix/Linux学习记录——I/O篇

基础操作

《Unix环境高级编程》一书中的样例代码用到了"apue.h",该库在Advanced Programming in the UNIX® Environment, Third Edition中可以下载。详见:Linux安装apue.3e以及运行第一个例子报错undefined reference to ‘err_quit’
编译:gcc test.c (-o test) -l apue/g++ test.cpp -l apue
运行:./a.out (./test)

基础I/O

文件描述符

当打开或者创建文件时,内核向进程返回一个非负整数作为文件描述符,用于表示一个文件。
用法:open或者create函数返回一个文件描述符,read或者write函数使用文件描述符作为参数。文件描述符上限63,即一个进程最多打开63个文件。
惯例:0作为进程的标准输入,1作为标准输出,2作为标准错误。其对应的符号常量:STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO(在<unistd.h>中定义)。

open & openat

int open(const char *path, int oflag, …, mode_t mode);
int openat(int fd, const char *path, int oflag, …, mode_t mode)
…表示余下的参数类型和数量是可变的。mode表示权限位,只有当open用作创建文件时才有用

/* oflag作为文件选项,用下列常量或多个常量的'或运算'构成*/
O_RDONLY	//只读,0
O_WRONLY	//只写,1
O_RDWR		//只读写,2
O_EXEC			//只执行打开
O_SEARCH	//只搜索打开
/* 上述常量必须指定一个且只能指定一个*/
// 下列oflag常量是可选的
O_CREAT		//文件不存在则创建它,同时说明mode权限位
O_TRUNC		//文件存在则为只写或读写打开,且清空文件
/* ... */

重定向

open & openat函数返回的文件描述符一定为最小的未用描述符,这一点可以用来在标准输入、标准输出、标准错误上打开另一个文件。例如,一个进程可以先关闭标准输出(文件描述符1),然后打开新文件,这样标准输出则重定向到该新文件。

create

int create()const char *path, mode_t mode;
= open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);

close

int close(int fd);

lseek

每个打开的文件都关联一个“当前文件偏移量”,新打开的文件的偏移量一般为0。

#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);
  • whence = SEEK_SET,设置为距文件开始处offset个字节;
  • whence = SEEK_CUR,设置为当前文件偏移量加offset,可正可负;
  • whence = SEEK_END,设置为文件长度加offset,可正可负;

lseek返回新的文件偏移量,可用以获取当前文件偏移量:

//获取文件当前偏移量
off_t currpos = lseek(fd, 0, SEEK_CUR);
//也可用于测试文件是否可设置偏移量,如pipe、socket、signal就不能设置,将返回-1;
//lseek有时也能正常返回负整数,所以不要测试它是否小于0,而是测试它是否==-1

具有空洞的文件:《Unix环境高级编程》(P68)

read

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);

读取成功则返回已读取的字节数。如果到达文件末尾则返回0,nbytes为要求读取的字节数,以下情况可以使返回字节数小于nbytes:

  • 读普通文件时到达文件尾端
  • 终端读取时,一般一次只读取一行;
  • 网络读取时,网络的缓冲机制可能造成;
  • 管道或FIFO读取时,如果管道内字节少于所需
  • 信号中断

write

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);

nbytes为要求写入的字节数,一般返回的字节数等于要求的字节数,除非出错。

i/O效率

一般将buf设置为4096个字节

文件共享

Unix支持不同进程同时共享打开文件,先说明内核用于所有I/O的数据结构,内核使用三种数据结构表示打开的文件:

  1. 每个进程进程表中有一个记录项,记录项包含一张打开文件的文件描述符表,其关联的是:
    (a) 文件描述符
    (b) 指向文件表的指针
  2. 内核为所有打开文件维护一张文件表,它包含:
    (a) 文件状态标志位
    (b) 当前文件偏移量
    © 指向该文件v节点表项的指针
  3. 每个打开文件具有一个v节点结构,v节点结构包含了i节点结构
    一个进程打开不同的文件:
    请添加图片描述
    不同独立进程打开同一个文件:
    请添加图片描述
    注意:对于一个给定文件,内核只维护一个v节点表,但为每个进程维护单独的文件表,之所以每个进程都获得自己的文件表项,是因为每个进程都有它自己对该文件的当前偏移量

原子操作

//追加文件的非原子操作:
// 先定位、再写入
if (lseek(fd, OL, 2) < 0)
	err_sys("lseek error");
if (write(fd, buf, 100) != 100)
	err_sys("write error");

// 创建文件一般需要检查+创建,非原子操作如下:
if ((fd = open(path, O_WRONLY)) < 0){
	if (errno == ENOENT){
		if ((fd = creat(path, mode)) < 0)
			err_sys("creat error");
	} else{
		err_sys("open error");
	}
}

例如追加文件:假定有两个独立的进程 A 和 B 都对同一文件进行追加写操作。每个进程都已打开了该文件,但未使用O_APPEND 标志。此时,各数据结构之间的关系如图3-8中所示。每个进程都有它自己的文件表项,但是共享一个 v 节点表项。假定进程 A 调用了工seek,它将进程 A 的该文件当前偏移量设置为1500 字节(当前文件尾端处)。然后内核切换进程,进程 B 运行。进程 B 执行 lseek, 也将其对该文件的当前偏移量设置为 1500 字节(当前文件尾端处)。然后 B 调用 write,它将 B 的该文件当前文件偏移量增加至1600。因为该文件的长度己经增加了,所以内核将 v 节点中的当前文件长度更新为1600.然后,内核又进行进程切换,使进程 A 恢复运行。当 A 调用 write 时,就从其当前文件偏移量(1500)处开始将数据写入到文件。这样也就覆盖了进程 B 刚才写入到该文件中的数据。
文件创建中:A进程处于open和creat之间,此时B进程创建了该文件并写入完成,然后A走到creat将造成B进程写入的数据被擦除。
原子操作

// 将定位和操作融合到一起,并且是原子操作
#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);

函数dup和dup2

都可用来复制一个现有的文件描述符

#include <unistd.h>
int dup(int fd);			// 返回的新的文件描述符是当前可用文件描述符的最小数值
int dup2(int fd, int fd2);	// 指定返回fd2参数指定的文件描述符。若fd2一打开则关闭它。若fd==fd2,则返回fd2。

dup返回的新文件描述符与fd的关系:
请添加图片描述
fcntl也可用于复制文件描述符
dup(fd); = fcntl(fd, F_DUPFD, 0);
dup2(fd, fd2); = close(fd); fcntl(fd, F_DUPFD, fd2);

sync、fsync、fdatasync

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

  • sync 只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。(通常,称为 update 的系统守护进程周期性地调用sync 函数.这就保证了定期冲洗(flush)内核的块缓冲区)。
  • fsync 函数只对由文件描述符 fd 指定的一个文件起作用,并且等待写磁盘操作结束才返回. fsync 可用于数据库这样的应用程序,这种应用程序需要确保修改过的块立即写到磁盘上。
  • fdatasync 函数类似于 fsync ,但它只影响文件的数据部分.而除数据外, fsync 还会同步更新文件的属性。

fcntl函数

#include <fcntl.h>
int fcntl(int fd, int cmd, ..., /* int arg */);

fcntl 函数有以下 5 种功能(参考《Unix环境高级编程》(P81))。

  • 复制一个已有的描述符(cmd = F _ DUPFD 或F_DUPFD_CLOEXEC ) .
  • 获取/设置文件描述符标志( cmd = F_GETFD 或 F_ SETFD ) .
  • 获取/设置文件状态标志( cmd = F_GETFL 或F_SETFL .
  • 获取/设置异步 I/O 所有权( cmd = F_GETOWN 或 F_SETOWN )。
  • 获取/设置记录锁( cmd = F_GETLK 、 F_SETLK 或 F_SETLKW )。

ioctl

《Unix环境高级编程》(P87)

/dev/fd

系统提供/dev/fd的目录

  • 我们知道目录本质是一个包含目录项的文件,每个目录项包含一个文件名文件属性
  • 文件属性:文件类型(目录还是文件)、文件大小、文件所有者、文件权限、文件最后修改时间等

/dev/fd的目录项是名为0、1、2等的文件,打开文件/dev/fd/n等效于复制描述符n

fd = open("/dev/fd/0", mode);
// 等效于:
fd = dup(0);

我们也可以用/dev/fd作为路径名参数,调用creat,这与open时O_CREAT作为第二参数作用相同。

/dev/fd主要由shell使用,/dev/fd/0表示标准输入、/dev/fd/1表示标准输出、/dev/fd/2表示标准错误。
filter file1 | cat file2 /dev/fd/0 file3 | lpr
cat先读取file2,然后读取标准输入(也就是filter file1的输出),然后读取file3.

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值