但使书种多,会有岁稔时。这篇文章主要讲述:系统级I?O相关的知识,希望能为你提供帮助。
0. 学习原因大多时候,高级别I/O函数工作良好,没必要直接用Unix I/O,为何需学习?
- 了解Unix I/O将帮助理解其他系统概念。I/O是系统操作不可或缺的部分,因此经常遇到I/O和其他系统概念之间的循环依赖
- 有时必须用Unix I/O,用高级I/O不太可能或不合适,如标准I/O库没提供读取文件元数据的方式,此外I/O库存在一些问题
?open()?
??/??close()?
?? 打开/关闭文件,??read()?
??/ ??write()?
?? 读/写文件。??seek()?
?改变当前文件位置。Unix I/O主要分为两大类:为区分不同文件的类型,会有一个 ?
?type?
? 来进行区别:- 普通文件:包含任意数据
- 目录:相关文件组的索引
- Socket:用于另一台机器上的进程通信
普通文件
普通文件包含任意数据,应用程序通常需区分文本文件和二进制文件。前者只包含 ASCII 或 Unicode 字符。除此之外的都是二进制文件(对象文件, JPEG 图片, 等等)。内核不能区分出区别。
文本文件是文本行的序列,每行以 ?
?\\n?
?? 结尾,新行是 ??0xa?
??,和 ASCII 码中LF一样。不同系统判断行结束的符号不同(End of line, EOL),Linux &
Mac OS是??\\n?
??(0xa)等价line feed(LF),而Windows &
网络协议是??\\r\\n?
? (0xd 0xa)等价Carriage return(CR) followed by line feed(LF)目录
目录包含一个链接(link)数组,且每个目录至少包含两记录:?
?.?
??(dot) 当前目录、??..?
?(dot dot) 上层目录操作命令主要有 ?
?mkdir?
??, ??ls?
??, ??rmdir?
??。目录以树状结构组织,根目录是 ??/?
?(slash)。内核会为每个进程保存当前工作目录(cwd, current working directory),可用 ?
?cd?
? 命令进行更改。通过路径名来确定文件的位置,分为绝对路径和相对路径。2. 文件操作2.1 打开文件打开文件会通知内核已准备好访问该文件
int fd; // 文件描述符 file descriptor
if ((fd = open("/etc/hosts", O_RDONLY)) < 0)
perror("open");
exit(1);
返回值是一个小的整型称为文件描述符(file descriptor),若该值等于 -1 则说明发生错误。每个由 Linux shell创建的进程都会默认打开三个文件(注意这里的文件概念):
- 0: standard input(stdin)
- 1: standard output(stdout)
- 2: standar error(stderr)
int fd; // 文件描述符
int retval; // 返回值
int ((retval = close(fd)) < 0)
perror("close");
exit(1);
关闭一个已经关闭的文件是线程程序中的灾难(稍后会详细介绍),所以一定要检查返回值,哪怕是看似良好的函数如 ?
?close()?
?2.3 读取文件读取文件将字节从当前文件位置复制到内存,然后更新文件位置
char buf[512];
int fd;
int nbytes
// 打开文件描述符,并从中读取 512 字节的数据
if ((nbytes = read(fd, buf, sizeof(buf))) < 0)
perror("read");
exit(1);
返回值是读取的字节数量,是一个 ?
?ssize_t?
?? 类型(其实就是一个有符号整型),如果 ??nbytes <
0?
?? 那么表示出错。??nbytes <
sizeof(buf)?
? 这种情况(short counts) 是可能发生的,而且并不是错误。2.4 写入文件【(系统级I?O)】写入文件将字节从内存复制到当前文件位置,然后更新当前文件位置
char buf[512];
int fd;
int nbytes;
// 打开文件描述符,并向其写入 512 字节的数据
if ((nbytes = write(fd, buf, sizeof(buf)) < 0)
perror("write");
exit(1);
返回值是写入的字节数量,如果 ?
?nbytes <
0?
?? 表示出错。??nbytes <
sizeof(buf)?
? 这种情况(short counts) 是可能发生的,且不是错误。2.5 读取目录可用readdir系列函数读取目录的内容,每次对readdir的调用返回的都是指向流dirp中下一个目录项的指针,或没有更多目录项则返回NULL,每个目录项都有结构体:
struct dirent
ino_t d_ino; /* inode number */
char d_name[256]; /* filename */
;
2.6 简单Unix I/O 例子拷贝文件到标准输出,一次一个字节:
#include "csapp.h"
int main(int argc, char *argv[])
char c;
int infd = STDIN_FILENO;
if (argc == 2)
infd = Open(argv[1], O_RDONLY, 0);
while(Read(infd, & c, 1) != 0)
Write(STDOUT_FILENO, & c, 1);
exit(0);
前面提到的 short count 会在下面的情形下发生:
- 读取的时遇到 EOF(end-of-file)
- 从终端中读取文本行
- 读和写网络 sockets
- 从磁盘文件中读取(除 EOF 外)
- 写入到磁盘文件中
#include "csapp.h"
#define BUFSIZE 64
int main(int argc, char *argv[])
char buf[BUFSIZE];
int infd = STDIN_FILENO;
if (argc == 2)
infd = Open(argv[1], O_RDONLY, 0);
while((nread = Read(infd, buf, BUFSIZE)) != 0)
Write(STDOUT_FILENO, buf, nread);
exit(0);
3. 元数据元数据是用来描述数据的数据,由内核维护,可以通过 ?
?stat?
?? 和 ??fstat?
? 函数来访问,结构是:struct stat
dev_tst_dev; // Device
ino_tst_ino; // inode
mode_tst_mode; // Protection & file type
nlink_tst_nlink; // Number of hard links
uid_tst_uid; // User ID of owner
gid_tst_gid; // Group ID of owner
dev_tst_rdev; // Device type (if inode device)
off_tst_size; // Total size, in bytes
unsigned longst_blksize; // Blocksize for filesystem I/O
unsigned longst_blocks; // Number of blocks allocated
time_tst_atime; // Time of last access
time_tst_mtime; // Time of last modification
time_tst_ctime; // Time of last change
对应的访问例子:
int main (int argc, char **argv)
struct stat stat;
char *type, *readok;
Stat(argv[1], & stat);
if (S_ISREG(stat.st_mode)) // 确定文件类型
type = "regular";
else if (S_ISDIR(stat.st_mode))
type = "directory";
else
type = "other";
if ((stat.st_mode & S_IRUSR)) // 检查读权限
readok = "yes";
else
readok = "no";
printf("type: %s, read: %s\\n", type, readok);
exit(0);
3.1 共享文件可用许多不同的方式共享Linux文件。要理解文件共享就得理解三个表示打开文件的数据结构。
- 描述符表:每个进程都有独立的描述符表,表项是进程打开的文件描述符索引,每个打开的描述符表指向文件表中的一个表项
- 文件表:打开文件的集合是由一张文件表表示,所有进程共享该表,每个文件表的表项组成包括当前文件位置、引用计数、指向v-node表中对应表项的指针。关闭描述符减少文件表项引用计数直到为0会删除文件表项
- v-node表:同文件表一样所有进程共享该v-node表,每个表包含stat结构中的大多数信息
推荐阅读
- 灵魂拷问之调度与切换十六问
- 运维小白成长记——第六周
- 系统cron计划任务小练习
- #yyds干货盘点#python面向对象之工厂函数调用__init__()
- 编写脚本实现登陆远程主机
- #yyds干货盘点#流媒体服务器
- 生成10个随机数保存于数组中,并找出其最大值和最小值
- 缓存技术和用户层缓存原理
- 采用冒泡算法对数组进行升序或降序排序