文件和I/O¶
所有(Unix)文件的通用属性¶
所有文件:
位于文件系统命名空间中(在根目录下,或
/
)。没有驱动器号!有个名字
实现读、写、打开、关闭和选择系统调用。
所有内容都可以包含在Normal或 special 文件夹
所有人都有一个概念:
拥有用户和组
拥有所有权的用户/组和其他用户/组的读/写/执行位
自定义扩展属性列表
创建日期/时间
上次访问日期/时间
除了这几件事之外,各种文件类型的语义和结构也有很大程度的变化
Unix中的文件类型¶
常规文件
符号链接
文件夹
阻止设备文件
字符设备文件
命名管道/FIFO
Unix域套接字
门(仅限Solaris)
常规文件¶
持久保存程序中的数据。驻留在文件系统中。
除了所有者/权限之外。常规文件有:
提交的大小和定义的大小(对于支持稀疏文件的文件系统,大小有所不同)
可以按顺序访问
可按随机顺序访问
设备限制也有例外,例如磁带驱动器的退出
文件夹¶
在早期的Unix实现中,文件夹是列出其他文件的文件,并设置了一个特殊的位以使其成为文件夹。
通过读取和写入文件修改了文件夹。
其中一些语义仍然存在
早期操作系统不支持文件夹:
Macintosh文件系统(大约1984年)
CP/M文件系统(MS-DOS和FAT的前身)
文件夹没有文件大小
文件夹的执行位确定:
如果可以列出文件夹的内容
如果程序可能发生更改,请将其用作其工作文件夹
符号链接¶
符号链接是一种指向另一个文件或文件夹的文件类型。
指针可以是相对路径,也可以是绝对路径。
许多现代操作系统(OS X、Unix和Windows)都提供支持
指向不存在的文件或文件夹的符号链接称为“中断”
符号链接上的文件系统操作对它们所指向的文件起作用,但Unlink系统调用除外(删除符号链接)
还存在其他系统调用来帮助确定文件/文件夹是符号链接还是实际文件/文件夹
数据块设备文件¶
数据块设备文件是操作系统公开的设备的文件抽象。
常见的设备块文件包括:
硬盘
CD/DVD/蓝光驱动器
软盘驱动器
USB介质
映射的内存设备(RAM磁盘或诊断设备)
数据块设备支持:
随机访问
缓冲读/写(通过某些特征块大小)
数据块设备文件要么由操作系统通过特殊文件系统自动公开,要么由用户通过特殊系统程序和系统调用创建。方法各不相同。
早期的Linux依赖于特殊的程序
现代的Linux使用特殊的文件系统(devf、sysfs)
字符设备文件¶
字符设备文件是操作系统公开的设备的文件抽象。
常见的字符设备包括:
航站楼
串口
调制解调器
网卡
视频/声音设备
磁带机
大多数字符设备不支持随机访问。
这样做的人通常查找操作的成本很高
命名管道/FIFO¶
命名管道是文件系统中存在的管道。
允许在具有不同生存期的程序集(如客户端服务器程序)中进行管道操作。
在讨论进程间通信时,我们将深入讨论管道的更多细节。
Unix域套接字¶
域套接字是在文件系统中具有名称的套接字。
与命名管道类似,不同之处在于它们可以在流或数据报模式下创建
与常规套接字不同,域套接字没有底层的TCP/IP或UDP/IP协议
文件系统系统调用¶
Unix操作系统中的大多数系统调用都是为了对文件进行操作
首字母缩写MS-DOS扩展为Microsoft磁盘操作系统。这个首字母缩写中的DOS部分似乎非常适用于所有操作系统。
文件系统系统调用¶
功能 |
描述 |
---|---|
|
打开/创建文件并返回文件描述符 |
|
创建新文件 |
|
关闭文件描述符(减少对文件的引用) |
|
更新文件描述符的当前文件偏移量 |
|
将数据从文件描述符读入缓冲区 |
|
将数据从缓冲区写入文件描述符 |
|
复制一个文件描述符 |
|
更新文件描述符以指向另一个文件描述符 |
|
更改文件属性(异步I/O、文件锁定) |
|
与设备文件交互、设置非典型属性等的“全部捕获”界面... |
|
返回rwx位、大小、时间戳和其他详细信息 |
|
测试文件的读、写、执行或是否存在 |
|
更新文件创建掩码 |
|
更新rwx位 |
更多文件系统系统调用¶
|
更改文件用户/组所有权 |
---|---|
|
更改文件的长度(增大或缩小) |
|
创建硬链接 |
|
删除文件系统中的名称,并可能删除它所引用的文件(没有进程打开该文件) |
|
删除空目录 |
|
将取消链接/rmdir合并为一个调用 |
|
重命名文件,可能会更改其父文件夹 |
|
创建符号链接 |
|
读取符号链接的值 |
|
更新访问和修改时间 |
|
创建文件夹 |
|
打开一个文件夹以供阅读 |
|
读取文件夹中的下一个条目 |
|
将目录条目重置为开头 |
|
关闭目录描述符 |
|
更改当前工作目录 |
|
获取当前工作目录 |
|
将文件系统的缓冲区缓存刷新到磁盘 |
使用OPEN()打开文件¶
int open(const char *pathname, int flags, mode_t mode)
int open(const char *pathname, int flags)
pathname 是文件的路径
flags 可以是以下各项的组合:
O_APPEND
:在附加模式下打开O_ASYNC
:使用信号驱动的异步I/OO_CREAT
:如果文件不存在,则创建该文件O_DIRECT
:最大限度地减少缓冲区缓存的使用O_SYNC
:为同步I/O数据块打开,直到将写入调用提交到硬件O_TRUNC
:如果文件已存在,则将其截断为长度0还有其他许多人..。
mode 用于
O_CREAT
并且通常作为八进制数传递:0XYZ
,X
是针对用户的,Y
是为了团队,Z
是给别人的每个数字是一个八进制数字,由三位组成
最重要的位是读取权限
下一个最高有效位是写入权限
最低有效位是执行权限
0700
表示用户具有rwx,组和其他用户没有访问权限0660
表示用户/组有读写权限,其他用户没有访问权限
的返回值
open()
是文件描述符,如果发生错误,则为-1
使用Close()关闭文件¶
int close(int fd)
fd 参数是由调用返回的文件描述符:Open、DUP、PIPE等...
成功时返回值为0,失败时返回值为-1(错误的文件描述符,被信号中断)
正在写入文件¶
ssize_t write(int fd, const void *buf, size_t count);
Fd是打开的文件描述符
但它是一个缓冲区
Count是该缓冲区中要在当前偏移量处写入文件的字节数
该方法的返回值将为
return == -1 如果遇到错误
return == count 在大多数成功的案例中
return < count 在某些实现中(某些情况下为网络文件系统)
典型写入算法¶
const char *data = "foobar";
int fd = open("file", O_CREAT | O_TRUNC | O_RDWR, 0666);
size_t length = strlen(data), offset = 0;
while(length > 0) {
size_t written = write(fd, data + offset, length);
offset += written;
length -- written;
}
close(fd)
从文件中读取¶
size_t read(int fd, void *buf, size_t count);
将文件描述符、目标缓冲区和要读入该缓冲区的字节数作为参数
该方法的返回值为:
return == -1
如果发生错误return == 0
如果遇到EOFreturn == count
在大多数成功案例中
典型的读取算法¶
int fd = open("file", O_RDONLY, 0666);
char buffer[5];
while((length = read(fd, &buffer[0], 5)) != 0) {
write(1, &buffer[0], length);
}
close(fd);
在文件中查找¶
并非所有文件都支持查找。
使用寻道调用是执行随机访问I/O的方式
Seek调用的使用会影响性能(稍后将详细介绍...)
off_t lseek(int fd, off_t offset, int whence)
Fd是一个文件描述符
偏移量是相对于其来源的字节数
从哪里来的是
SEEK_SET
(文件开头),SEEK_CUR
(文件描述符的当前位置),或SEEK_END
(文件末尾)这个
off_t
类型通常是64位有符号整数。可以在文件内部和外部进行查找。
在文件外部查找将导致将值0从文件末尾写入到查找位置。
支持稀疏文件的文件系统将对此进行优化,以防止不必要的写入操作。
标准文件描述符¶
- 标准
标准输入。默认为来自控制台的输入管道;默认为0
- 标准输出
标准输出。缺省为控制台的输出管道;缺省值为1
- 标准
标准误差。默认为控制台的输出管道;默认为2
默认情况下,每个程序都是在这三个文件描述符打开的情况下进行初始化的。它们的特定目标可能已被父程序重定向(稍后将详细介绍...)
复制文件描述符¶
int dup(int fd) : duplicate a file descriptor
接受文件描述符并返回具有新ID的副本
复制的文件描述符具有独立的文件偏移量和对该文件的引用
复制文件描述符的原因:
用于多线程,以避免调用lSeek()
重定向标准输入/标准输出/标准错误所需的一次调用
重定向文件描述符¶
int dup2(int oldfd, int newfd) : redirect a file descriptor
vbl.使
newfd
是…的复制品oldfd
如果
newfd
处于打开状态时,它将自动关闭此呼叫不同于
dup()
因为在这种情况下两个文件描述符共享相同的文件偏移量。所以,打电话给
lseek()
会导致另一个的偏移量发生变化。dup()
和dup2()
用于重定向 stdin , stdout ,以及 stderr 在命令行上(有时将它们组合在一起)
重定向文件描述符代码示例¶
int main(int argc, char* argv[]) {
int pipes[2];
pipe(pipes);
int input = pipes[0], output = pipes[1];
int pid = fork();
if(pid > 0) { //parent process
dup2(input, 0) //redirect stdin
close(output); //close unused half of pipe
scanf("%d\n", &value);
printf("child sent value = %d\n", value);
} else if(pid == 0) { //child process
dup2(output, 1); //redirect STDOUT
close(input); //close unused half of pipe
printf("%d\n", 5000);
}
return 0;
}
正在读取文件夹¶
int main(int argc, char* argv[]) {
const char *dir = "/";
DIR *d = opendir(dir);
struct dirent *de;
while((de = readdir(d)) != NULL) {
printf("name %s\n", de->d_name);
}
closedir(d);
return 0;
}
展望未来:I/O性能¶
性能¶
要获得良好的I/O性能,需要选择正确的缓冲策略。
使用小缓冲区的读/写将导致较低的吞吐量。
使用大缓冲区进行读/写将导致等待读/写返回的时间更长。
这段时间可以用来处理数据。
必须达到平衡。
生产者/消费者模式具有优势:
一个进程/线程读取文件(生产者)
另一个进程/线程运行计算(使用者)
这样,您可以在计算和执行I/O的同时考虑内存映射的I/O-(稍后我们讨论IPC时会有更多内容)
简单的I/O性能实验¶
dd if=/dev/zero of=tmp.dat bs=1 count=1000000 - 671 kB/s
dd if=/dev/zero of=tmp.dat bs=10 count=100000 - 5.9 MB/s
dd if=/dev/zero of=tmp.dat bs=100 count=10000 - 38.9 MB/s
dd if=/dev/zero of=tmp.dat bs=1000 count=1000 - 244 MB/s
dd if=/dev/zero of=tmp.dat bs=10000 count=100 - 537 MB/s
dd if=/dev/zero of=tmp.dat bs=100000 count=10 - 834 MB/s
dd if=/dev/zero of=tmp.dat bs=1000000 count=1 - 461 MB/s
一般而言:
增加数据块大小可提高性能。
这是一个单一的运行
dd
对于每个块大小。多次运行可能会导致更高的平均吞吐量。任何给定时间的系统负载都会影响观察到的性能数字。
阅读/写作成绩¶
另一种需要考虑的方法是向量化I/O。聚集-分散
程序通常会将读/写分离到不同的调用中。
一个例子是这样一个程序,它在两个单独的调用中写入头,然后写出内容。
其他呼叫涉及额外的上下文切换和降低的性能。
矢量化I/O允许组合多个读/写调用。
智能操作系统的实施还将允许对它们进行无序读/写。
这可以带来显著的性能提升。
当我们更深入地研究存储主题时,我们将在学习电梯算法时看到更多关于这方面的信息。
性能示例¶
char *file_data1 = "1234567890";
char *file_data2 = "abcdefghijk";
char *file_data3 = "lmnopqrstuvwxyz";
const char *file_name = "temp.dat";
int main(int argc, char* argv[]) {
int fd = open(file_name, O_CREAT|O_TRUNC|O_RDWR, 0666);
if(fd == (-1)) {
printf("open returned (-1)\n");
return (-1);
}
struct iovec buffers[3];
buffers[0].iov_base = file_data1;
buffers[0].iov_len = strlen(file_data1);
buffers[1].iov_base = file_data2;
buffers[1].iov_len = strlen(file_data2);
buffers[2].iov_base = file_data3;
buffers[2].iov_len = strlen(file_data3);
int written = writev(fd, buffers, 3);
if(written == (-1)) {
printf("writev returned (-1)\n");
return (-1);
}
printf("wrote %d bytes\n", written);
close(fd);
return 0;
}