Linux C 文件IO
文件io
2021-05-31 12:46:14 星期一
文件描述符:是有限资源
文件描述符 | posix名称 | 用途 | stdio流 |
---|---|---|---|
0 |
stdin_fileno |
标准输入 | stdin |
1 |
stdout_fileno |
标准输出 | stdout |
2 |
stderr_fileno |
标准错误 | stderr |
基础io
open
#include <fcntl.h> int open(const char *path, int oflag, mode_t mode);
-
path
文件的路径和名称 -
flag
文件的打开模式,组合使用时需要使用位运算或- 基本模式
-
只读 只写 读写 o_rdonly
o_wronly
o_rdwr
00
01
02
-
- 附加模式(只列常用)
-
o_append
:总是在文件末尾添加数据 -
o_excl
:配合o_creat
标志- 表明如果文件存在则不会打开文件,并使open调用失败,否则能够创建并打开文件。这样确保了调用
open()
的进程即为创建文件的进程. - 同时不允许path是符号链接
- 表明如果文件存在则不会打开文件,并使open调用失败,否则能够创建并打开文件。这样确保了调用
-
o_creat
:没有文件存在时会创建文件,需要mode参数指明文件权限,一共9个 -
o_trunc
:如果文件存在且为普通文件且该进程对改文件有写权限,则清空文件内容
-
- 基本模式
-
mode
-
s_irusr
:文件所有者有读权限 -
s_iwusr
:文件所有者有写权限 -
s_ixusr
:文件所有者有执行权限 -
s_irgrp
:同组用户有读权限 -
s_iwgrp
:同组用户有写权限 -
s_ixgrp
:同组用户有执行权限 -
s_iroth
:其他用户有读权限 -
s_iwoth
:其他用户有写权限 -
s_ixoth
:其他用户有执行权限
-
- 返回文件描述符
fd
或者-1
错误
creat
旧版使用,新版都用open进行创建文件
read
将open
返回的fd
文件描述符中读取bytes
字节的数据到buf
中,返回实际读取的字节数,不成功返回-1
#include <unistd.h> ssize_t read(int fd, void *buf, size_t bytes);
- 当
read
普通文件:调用成功返回实际读取的字节数,遇到文件eof
时返回0
,出现错误返回-1
。 - 当
read
读取终端,遇到\n
即返回
注意:read
系统调用是逐字节读取的,所以无法遵守c语言中的字符串的规则。比如c语言中字符串以\0
(0x0
)作为结束,但是read
认为这里的字节值是0x0
并继续读下去.所以使用read
读数据时,通常的方式是:每次读取数据,再将的实际读取到的size
处设置成c语言认可的字符串结束符,即buffer[size] = '\0';
#define max_read 16 char buffer[max_read + 1]; ssize_t size; size = read(fd, buffer, max_read); if (size == -1) exit(0); buffer[size] = '\0'; close(fd);
一个例子
该文件从终端读取一行(因为read读终端时以\n作为结束)字符并打印出来,同时打印每一个字符
#include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #define max_read 16 int main(int argc, char **argv) { char buffer[max_read + 1]; memset(buffer, 0x23, sizeof(buffer)); // fill with # buffer[max_read] = '\0'; ssize_t numread, i; numread = read(stdin_fileno, buffer, max_read); if (numread == -1) perror("read"); // buffer[numread] = '\0'; printf("the input data was:%s\n", buffer); for (i = 0; buffer[i] != '\0' && i < max_read; i++) printf("%ld->%x\n", i, buffer[i]); return 0; }
$ ./4.4 abcd the input data was:abcd ############ 0->61 1->62 2->63 3->64 4->a 5->23 6->23 ... 16->0
由此可见read只是以二进制的形式照搬数据,并不对数据进行处理,因此,对数据的处理留给了程序员
write
将bytes
字节的buf
数据写到open
返回的fd
文件描述符所指的文件中,返回实际写的字节数,不成功返回-1
,写入已打开的文件。调用成功并不代表已经写入磁盘,可能先进入缓存(这样减少磁盘活动量、加快write
调用)。
#include <unistd.h> ssize_t write(int fd, void *buf, size_t bytes);
close
#include <unistd.h> int close (int fd);
close函数也有错误处理,编程时也应该错误检查。
lseek
内核打开的文件时会记录文件偏移量,第一字节的偏移量为0,文件打开时,会将偏移量设置为0
十分重要:有时候读文件读不出来,可能就是因为文件偏移量在文件末尾处,这时候需要重置
#include <unistd.h> off_t lseek(int fd, off_t offset, int whence);
-
fd
文件描述符 -
offset
表示偏移量- 正值为正向移动,向继续往下读的方向
-
wherece
表示文件读写指针从哪里开始计数-
seek_set
表示起始位置 -
seek_cur
表示当前位置 -
seek_end
表示末尾位置的后一个字节(这里直接写数据的话是恰好和文件连接)
-
- 返回新的文件偏移量或
-1
(执行失败)
文件空洞
从文件结尾后到新写入的数据间的空间,他不占用磁盘空间,直到写入了数据。这时文件的名义的大小可能比磁盘存储的总量大。具体的在14节
ls -l file 查看文件逻辑大小 du -c file 查看文件实际占用的存储块多少 od -c file 查看文件存储的内容
unlink删除
只是删除path到文件的一个链接,其文件对于的i-node
减1,为0时,改文件才从磁盘删除。
#include <unistd.h> int unlink(const char *path);
iotcl
文件和目录
目录是另一种文件,只是内容是包含的文件信息和目录信息。
链接
每一个文件对应一个inode
,文件的链接数对应inode
中的链接数,记录着这个文件的链接数值,即指向该inode
的文件数,文件和inode
是多对一的关系。本质上rm
指令调用系统调用unlink
函数,将这个文件的inode
的链接数-1
,为0
时才真正删除
- 硬链接
- 相链接的文件总是同步
- 软链接
- 理解为windows的快捷方式
$ touch a.txt $ echo "hello" > a.txt $ ls -li total 4 2104443 -rw-rw-r-- 1 dwr dwr 6 apr 10 22:20 a.txt # inode编号 文件权限 用户 组用户 不知道 创建月 日 时 文件名 $ ln a.txt a.txt.bak # 建立硬链接 $ ls -li total 8 2104443 -rw-rw-r-- 2 dwr dwr 6 apr 10 22:20 a.txt 2104443 -rw-rw-r-- 2 dwr dwr 6 apr 10 22:20 a.txt.bak $ ln -s a.txt a.txt.s # 建立软链接 $ ls -li total 8 2104443 -rw-rw-r-- 2 dwr dwr 6 apr 10 22:20 a.txt 2104443 -rw-rw-r-- 2 dwr dwr 6 apr 10 22:20 a.txt.bak 2099642 lrwxrwxrwx 1 dwr dwr 5 apr 10 22:25 a.txt.s -> a.txt
#include <unistd.h> int link(const char *__from, const char *__to); int symlink(const char *__from, const char *__to);
错误打印
perror
<errno.h>
根据设置的errno值打印对应的错误信息,打印规则是先打印s
中用户定义的错误输出,在打印系统调用错误的输出提示。一定要在系统调用之后紧跟打印,否则会被覆盖
void perror(const char *s);
strerror
将错误代码转换为字符串错误信息。
char *strerror(int errno);
原子io
fcntl
#include <fcntl.h> int fcntl(int __fd, int __cmd, ...)
文件io缓冲
这里是unix系统编程手册第13章内容,不全待完善
read()
和write()
系统调用在操作磁盘文件时不会直接发起磁盘访问,而是仅仅在用户空间缓冲区与内核缓冲区高速缓存(kernel buffer cache)之间复制数据。write()
在后续某个时刻,内核会将其缓冲区中的数据写入(刷新至)磁盘。
linux 内核对缓冲区高速缓存的大小没有固定上限。内核会分配尽可能多的缓冲区高速缓存页,而仅受限于两个因素;可用的物理内存总量,以及出于其他目的对物理内存的需求(例如,需要将正在运行进程的文本和数据页保留在物理内存中)。若可用内存不足,则内核会将
解释一下书中对io系统调用的实验:
- 总用时=cpu用时+磁盘读写用时
- cpu用时=用户cpu用时(用户模式下执行的代码)+系统cpu用时(内核模式(系统调用和数据在用户和内核模式下传输)下执行的代码)
stdio的缓冲
c语言中io
函数可以理解为系统io
调用+数据缓冲,免于编写者自己处理对数据的缓冲
int setvbuf(file *stream, char *buf, int modes, size_t n)
控制stdio库函数的缓冲形式,需要最先调用,之后的stdio操作才有效
-
stream
- 表示配置缓冲的文件流
-
buf
- 不为空则使用size大小作为缓冲区(这个buf空间应该是堆内存上,避免函数调用和返回对栈进行修改)
- 为空则自动分配(根据mode选择是否分配)size大小空间
-
modes
-
_ionbf
:not,不缓冲,每一次调用stdio函数都立即调用系统调用 -
_iolbf
:line,行缓冲,遇到换行符或缓冲区满则调用系统调用,指向终端设备的流默认使用该模式 -
_iofbf
:file,全缓冲,指向磁盘的流默认使用该模式
-
- 出错返回非0,成功返回0