Unix 系统IO
文件
在Linux里面,一切皆文件。每一个文件都有一个类型(type)来表明它在系统中的角色:
- 普通文件
(regular file)
包含任意数据 - 目录
(direction)相
关一组文件的索引 - 套接字
(socket)
和另一台机器上的进程通信的类型 - …
这篇博客主要介绍普通文件:
- 文本文件:
文本文件是只包含ASCII
或Unicode
字符的普通文件,就是一系列的文本行,每行以一个新行字符\n
结尾,其数字值是0xa
,和ASCII
码中的line feed
字符(LF)
一样。在Windows
和网络协议
之中是\r\n
(0xd 0xa)以结尾。 - 二进制文件
二进制文件则是除文本文件外的普通文件,如我们的程序、图片、视频等
打开文件
当我们想要读或写一个文件的时候,我们首先需要告诉系统内核,我们要访问到这个文件。内核(open函数
)则会返回一个小的非负整数(描述符
),我们后续的操作都得用到这个描述符。
在程序的一开始,已经默认打开了三个文件:
- 0: standard input(stdin)
- 1: standard output(stdout)
- 2: standar error(stderr)
#include<sys/types.h>
#incldue<sys/stat.h>
#include<fcntl.h>
int open(char *filename,int flags,mode_t mode);
- 1
- 2
- 3
- 4
- 5
参数介绍:
- filename
要打开或创建的目标文件的名字 - flags
打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行或
运算,构成falgs
。 - O_RDONLY: 只读打开
- O_WRONLY: 只写打开
- O_RDWR: 可读可写
上面的这三个常量,必须制定一个且只能指定一个 - O_CREAT: 若文件不存在,则创建它,需要使用mode选项。来指明新文件的访问权限
- O_APPEND: 追加写,如果文件已经有内容,这次打开文件所写的数据附加到文件的末尾而不覆盖原来的内容
- O_TRUNC: 如果文件存在,则截断它(删除已有的内容,重新写入)
- mode
mode参数描述了新文件的访问权限 - S_IRUSR: 使用者(拥有者)能够读这个文件
- S_IWUSR: 使用者(拥有者)能够写这个文件
- S_IXUSR: 使用者(拥有者)能够执行这个文件
- S_IRGRP: 拥有者所在组的成员能够读这个文件
- S_IWGRP: 拥有者所在组的成员能够写这个文件
- S_IXGRP: 拥有者所在组的成员能够执行这个文件
- S_IROTH: 其他人(任何人)能够读这个文件
- S_IWOTH: 其他人(任何人)能够写这个文件
- S_IXOTH: 其他人(任何人)能够执行这个文件
关闭文件
#include <unistd.h>
int close(int fd);
- 1
- 2
- 3
- fd:要关闭的文件的描述符。
- 返回值:若成功返回0
- 出错则返回-1
注:当一个进程终止时,内核会自动关闭它所有打开的文件
读取文件
#include <unistd.h>
ssize_t read (int fd, void *buf, size_t n);
- 1
- 2
- 3
参数:
- fd:要读取的文件的描述符。
- buf:读取到的数据要放入的缓冲区。
- count:要读取的字节数。
返回值:
- 若成功返回读到的字节数,若已到文件结尾则返回0
- 若出错则返回-1并设置变量errno的值。
注:这里的size_t是无符号整型,ssize_t是有符号整型。
写入文件
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t n);
- 1
- 2
- 3
参数:
- fd:文件描述符;
- buf:指定的缓冲区,即指针,指向一段内存单元;
- n:要写入文件指定的字节数;
返回值:
- 写入文档的字节数(成功);-1(出错)
write
函数把buf
中的n
个字节写入描述符
fd所指的文件,成功时返回写的字节数,错误时返回-1.
例子
#include "csapp.h"
int main(void)
{
char c;
int fd = open(“abc.txt”,O_WRONLY|O_CREAT|O_TRUNC);
while(read(STDIN_FILENO, &c, 1) != 0)
write(fd, &c, 1);
close(fd);
exit(0);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
这是一个比较简单的例子,把输入进终端的内容储存到文件abc.txt
里面。
下面是运行的结果:
hpj@黄培纪:/mnt/d/code/C/Computer Systems/chap10_code$ ./test
test text
^C
- 1
- 2
- 3
abc.txt
里面的内容为:
test text
注:上面还有一个换行符(\n)没显示出来
- 1
- 2
- 3
重定向
在内核里,有三个相关的数据结构用于表示所打开的文件:
- 描述符表
每个进程都有自己的描述符表(Descriptor table),表项是由文件的描述符来索引 - 文件表
打开了的文件的集合,所有的进程都共享这一张表。表项包括文件的位置、引用的计数(reference count)(当前指向该表项的描述符数量)。直到这个数字变为了0,系统内核才会将其删除,否则不会。 v-node
表
所有的进程共享这张表,且每个表项包含stat结构的大多数信息。
下面看一个代码:
#include "csapp.h"
int main(int argc, char argv[])
{
int fd1, fd2, fd3;
char c1, c2, c3;
char fname = argv[1];
fd1 = Open(fname, O_RDONLY, 0);
fd2 = Open(fname, O_RDONLY, 0);
fd3 = Open(fname, O_RDONLY, 0);
dup2(fd2, fd3);
<span class="token function">Read</span><span class="token punctuation">(</span>fd1<span class="token punctuation">,</span> <span class="token operator">&</span>c1<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">Read</span><span class="token punctuation">(</span>fd2<span class="token punctuation">,</span> <span class="token operator">&</span>c2<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">Read</span><span class="token punctuation">(</span>fd3<span class="token punctuation">,</span> <span class="token operator">&</span>c3<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"c1 = %c, c2 = %c, c3 = %c\n"</span><span class="token punctuation">,</span> c1<span class="token punctuation">,</span> c2<span class="token punctuation">,</span> c3<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">Close</span><span class="token punctuation">(</span>fd1<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">Close</span><span class="token punctuation">(</span>fd2<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">Close</span><span class="token punctuation">(</span>fd3<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
读取下面的这个文件abcde.txt
:
abcde
- 1
运行结果入下:
hpj@黄培纪:/mnt/d/code/C/Computer Systems/chap10_code$ ./ffiles1 abcde.txt
c1 = a, c2 = a, c3 = b
- 1
- 2
这是因为这里用到了一个dup2
函数,改变了fd3
描述符指向的文件,这也就是重定向的过程,如下图所示:
再来看一个例子:
#include "csapp.h"
int main(int argc, char argv[])
{
int fd1, fd2, fd3;
char fname = argv[1];
fd1 = Open(fname, O_CREAT|O_TRUNC|O_RDWR, S_IRUSR|S_IWUSR);
Write(fd1, “pqrs”, 4);
fd3 <span class="token operator">=</span> <span class="token function">Open</span><span class="token punctuation">(</span>fname<span class="token punctuation">,</span> O_APPEND<span class="token operator">|</span>O_WRONLY<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">Write</span><span class="token punctuation">(</span>fd3<span class="token punctuation">,</span> <span class="token string">"jklmn"</span><span class="token punctuation">,</span> <span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
fd2 <span class="token operator">=</span> <span class="token function">dup</span><span class="token punctuation">(</span>fd1<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/* Allocates new descriptor */</span>
<span class="token function">Write</span><span class="token punctuation">(</span>fd2<span class="token punctuation">,</span> <span class="token string">"wxyz"</span><span class="token punctuation">,</span> <span class="token number">4</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">Write</span><span class="token punctuation">(</span>fd3<span class="token punctuation">,</span> <span class="token string">"ef"</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">Close</span><span class="token punctuation">(</span>fd1<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">Close</span><span class="token punctuation">(</span>fd2<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">Close</span><span class="token punctuation">(</span>fd3<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
运行结果入下:
hpj@黄培纪:/mnt/d/code/C/Computer Systems/chap10_code$ ./ffiles3 abc.txt
- 1
abc.txt
文件的内容入下:
pqrswxyznef
- 1
大致过程入下:
首先Write(fd1, "pqrs", 4);
把pqrs
写入到abc.txt
之中;
然后Write(fd3, "jklmn", 5);
把jklmn
写入到abc.txt
之中;
然后在原本fd1的指向之下,Write(fd2, "wxyz", 4);
把wxyz
写入到pqrs
的后面,把jklmn
的jklm
覆盖了,变成了wxyzn
;
最后Write(fd3, "ef", 2);
在wxyzn
后继续写入ef
。
所以abc.txt
的内容就是pqrswxyznef
。
最后再来看一个例子:
#include "csapp.h"
int main(int argc, char argv[])
{
int fd1;
int s = getpid() & 0x1;
char c1, c2;
char fname = argv[1];
fd1 = Open(fname, O_RDONLY, 0);
Read(fd1, &c1, 1);
if (fork()) {
int status;
wait(&status);
Read(fd1, &c2, 1);
printf(“Parent: c1 = %c, c2 = %c\n”, c1, c2);
} else {
/* Child */
Read(fd1, &c2, 1);
printf(“Child: c1 = %c, c2 = %c\n”, c1, c2);
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
读取abcde.txt
的结果如下:
hpj@黄培纪:/mnt/d/code/C/Computer Systems/chap10_code$ ./ffiles2 abcde.txt
Child: c1 = a, c2 = b
Parent: c1 = a, c2 = c
- 1
- 2
- 3
子进程把父进程的描述符表也复制了一份,但指向的仍然还是同一个文件表项,所以执行Read(fd1, &c2, 1);
的结果不一样。
参考资料
</div>
<link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-b6c3c6d139.css" rel="stylesheet">
</div>