四川做网站设计的公司,深圳广告牌制作公司,建立网站线上营销,如何成立一个房产网站背景
作为linux 开发者#xff0c;我们不可避免会接触到文件编程。比如通过文件记录程序配置参数#xff0c;通过字符设备与外设进行通信。因此作为合格的linux开发者#xff0c;一定要熟练掌握文件编程。在文件编程中#xff0c;我们一般会有两类接口函数#xff1a;标准…
背景
作为linux 开发者我们不可避免会接触到文件编程。比如通过文件记录程序配置参数通过字符设备与外设进行通信。因此作为合格的linux开发者一定要熟练掌握文件编程。在文件编程中我们一般会有两类接口函数标准I/O带缓冲和POXIS.1 I/O(不带缓冲)。本章节主要介绍不带缓冲的相关API及注意事项。
open 接口
open函数的作用是打开或创建一个文件。其声明如下
int open(const char* path, int oflag, .../*mode_t mode*/);参数解析
path 是打开或需要创建的文件名称oflag 设置打开文件的权限,该参数取值范围较广。并且需要区分。大致可以分为两类
权限类型标识。需要关注的有O_RDONLY只读权限、O_WRONLY只写权限、O_RDWR可读写权限。这三个标识位必须指定一个且只能指定一个。O_ECEC(只执行)和O_SEARCH只搜索已被移除。特性类标识。这些标识可多选常见的有如下
标识常量含义O_APPEND每次写都追加到文件的尾端。即使你显式的调用lseek改变文件当前偏移量但是在write时依然会追加到文件末尾O_CREAT若文件不存在则创建它。O_EXCL如果同时指定了O_CREAT而文件已经存在则出错。经常用于判断文件是否存在与access()函数功能类似。O_NOBLOCK如果path引用的是一个FIFO、块特殊文件、字符特殊文件。那么本文件描述符后续的I/O操作都设置为非阻塞方式。O_SYNC每次write 都会等待物理I/O操作完成包括文件属性更新。 在linux ext4 系统中该标识可能不生效O_TRUNC如果文件存在且以只写或读写权限打开。则将长度截断为0。常见的业务场景就是更新配置文件。
mode 可选参数。只有当oflag参数中具备O_CREAT属性时才需要指定新创建的文件权限。 知识点 open 函数返回的文件描述符一定时最小的未用描述符数值。 基于上述知识点经常会被用来重定向程序的标准输入、标准输出或标准错误输出。
场景如下有一个封装库内部是通过采用的是printf进行日志打印无法体现在我们日志系统中。我们如何观察其日志输出呢常见做法如下
#includestdlib.h
#includestdio.h
int main()
{ printf(hello world\n);return 0;
}默认情况下日志输出到终端
xieyihuaxieyihua:~/test$ gcc 1.c -o 1
xieyihuaxieyihua:~/test$ ./1
hello world
xieyihuaxieyihua:~/test$可以做以下修改
#includestdlib.h
#includestdio.h
#includefcntl.h
#includeunistd.h
int main()
{close(1); /* 关闭 文件描述符1*/open(./log,O_WRONLY|O_CREAT,0755);/* 此时文件描述符1 与 ./log文件绑定*/printf(hello world\n);return 0;
}输出如下
xieyihuaxieyihua:~/test$ gcc 1.c -o 1
xieyihuaxieyihua:~/test$ ./1
xieyihuaxieyihua:~/test$ cat log
hello world
xieyihuaxieyihua:~/test$creat 接口
creat函数主要用于创建一个新文件。函数原型声明如下
#include fcntl.h
int creat(const char* path, mode_t mode);其等效于open(path,O_WRONLY|O_CREAT|O_TRUNC,mode);但是由于creat只能以只写方式打开文件使用场景便较少渐渐被‘冷落’了。
close 接口
close函数关闭一个打开文件。其函数原型声明如下
#includeunistd.h
int close(int fd);知识点 当一个进程终止时内核会自动关闭它所有的打开文件。很多程序都利用了这一功能而不显示调用close。 lseek 接口
每个打开文件都有一个与其相关的“当前文件偏移量”。它通常是一个非负整数/dev/kmem/支持负的偏移量用于度量从文件开始处计算的字节数。通常读、写操作都是从当前文件偏移处开始的并使偏移量增加所读写的字节数。
lseek接口就可以显式的为一个打开的文件设置偏移量。其函数原型声明如下
#include unistd.h
off_t lseek(int fd,off_t offset,int whence);fd, 文件描述符offset其含义与whence 的值相关。
若 whence 是 SEEK_SET则将该文件的偏移量设置为距文件开始处的offset个字节。若 whence 是 SEEK_CUR则将该文件的偏移量设置为当前值加上offset个字节offset可为正或负。若 whence 是 SEEK_END则将该文件的偏移量设置为文件长度加上offset个字节offset可为正或负。 知识点 系统默认情况下当打开一个文件时除非指定O_APPEND选项否则该偏移量被设置为0。 lseek 仅是将当前的文件偏移量记录在内核中并不引起任何I/O操作其目的是用于接下来的读写操作。
空洞文件
文件偏移量可以被设置为大于文件当前长度在这种情况下对该文件的下一次写将加长该文件并在文件中构成一个空洞。位于文件中没有写过的字节都被读为0。并且文件中的空洞并不要求在磁盘上占用存储区。示例代码如下
#includefcntl.h
#includeunistd.h
#includestdlib.h
#includestdio.hint main()
{int fd;char* buf1 123456789a;char* buf2 abcdefghij;if((fd creat(file.hole,0755)) 0){printf(creat error\n);return -1;}printf(fd %d\n,fd);if(write(fd,buf1,10) ! 10){printf(write buf1 error\n);}if(lseek(fd,16384,SEEK_SET) -1){printf(lseek error\n);}if(write(fd,buf2,10) ! 10){printf(write buf1 error\n);}return 0;
}结果如下
/*文件长度有16394 Byte*/
xieyihuaxieyihua:~/test$ ls -la file.hole
-rwxr-xr-x 1 xieyihua xieyihua 16394 Jul 4 01:58 file.hole/*文本的实际内容也只有两个字符串*/
xieyihuaxieyihua:~/test$ od -c file.hole
0000000 1 2 3 4 5 6 7 8 9 a \0 \0 \0 \0 \0 \0
0000020 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0010000 a b c d e f g h i j
0010012
xieyihuaxieyihua:~/test$ cat file.hole
123456789aabcdefghijxieyihuaxieyihua:~/test$/* 没有空洞的文件其占用了16个磁盘块*/
xieyihuaxieyihua:~/test$ ls -ls file.*8 -rwxr-xr-x 1 xieyihua xieyihua 16394 Jul 4 01:58 file.hole
16 -rwxr-xr-x 1 xieyihua xieyihua 16384 Jul 4 01:58 file.nohole文件空洞的特性分配了文件偏移量范围但是实际却没有分配磁盘空间。我们一般可在两个方向应用
多线程下载。当创建一个巨大的文件时单个线程逐步构建文件会耗费大量时间。一种优化思路是将文件划分为多个段利用多线程同时操作每个线程负责写入其中一段数据。这类似于现实生活中修路的场景如修建高速公路时单个施工队的进度可能较慢但通过安排多个施工队每个队负责修建一段最终将它们连接起来大大提高了效率。共享内存。当不同进程需要共享内存时并不清楚实际需要多大的文件可以先开辟一个大文件。比如在创建虚拟机时如果一开始就分配了100GB的磁盘空间而实际上系统安装完成后可能只使用了3、4GB的空间这就是空洞文件的应用。通过空洞文件可以避免一开始就分配过多的资源节约了存储空间的浪费。
read 接口
read接口用于向打开文件中读数据。其原型声明如下
#includeunistd.h
ssize_t read(int fd, void* bff, size_t nbyte);若read成功则返回读到的字节数如果已经达到文件的尾端则返回0。 知识点 大多数文件系统为改善性能都会采用某种预读技术即即使你每次仅读取100Byte内容但是实际上会从磁盘中读取一页数据保存在内存中。从而减少磁盘I/O操作提高系统性能。但是也会增加内存使用压力。 write 接口
write接口用于向打开文件写数据。其接口声明如下
#include unistd.h
ssize_t write(int fd, const void* buf,size_t nbytes);其返回值通常与参数nbytes的值相同否则标识出错。其出错原因
磁盘已写满超过文件限定长度
linux 内核标识打开文件的方式
linux 内核通过三个数据结构表示打开的文件。记录项、文件表项、V节点。其三者关系大致如下: 进程表项中记录中文件描述符与文件表项的关系文件表项中记录文件状态标志位、当前文件偏移量、V节点指针V节点中记录文件类型、各种操作函数指针、指向i节点。而i节点包含文件的详细信息比如文件的所有者、文件长度、指向文件实际数据块再磁盘上所在位置的指针等。
注意每一个文件只有一个唯一的V节点表多个文件表项可以指向同一个V节点表每调用一次open,则创建一个新的文件表项不同fd可以指向同一个文件表项即可能存在以下场景 正是这样的机制原理linux 让我们可以多任务同时访问同一文件。但是在写文件时我们需要关注写入时序以及数据错乱问题。
在完成每一个read或write操作后文件表项中的当前文件偏移量增加所读写的字节数。如果使用O_APPEND标志打开一个文件则响应标志也被设置到文件表项的文件状态标志中。每次进行写操作时文件表项中的当前文件偏移量首先会被设置为表项中的文件长度。这就确保每次写入的数据追加到当前尾端处。lseek 函数只是修改文件表项中的当前文件偏移量不进行任何I/O操作。每一个进程都有它自己的文件表项和进程表项。
dup和dup2
这两个接口用于复制一个现有的文件描述符。其接口声明如下
#includeunistd.hint dup(int fd);
int dup2(int fd, int fd2);
/*两函数的返回值若成功返回新的文件描述符;若出错返回-1*/dup返回新的文件描述符一定是当前可用文件描述符中的最小数值。其效果就是多个fd指向同一个文件表项。其关系与上图中多线程访问文件一致。
sync、fsync、fdatasync接口
传统的linux 系统中设备缓冲区高速缓存或页高速缓存大多数磁盘I/O都通过缓冲区进行。当我们向文件写入数据时内核通常先将数据复制到缓冲区中然后排入队列晚些时候在写入磁盘。这种方式称为“延迟写”。
“延迟写”虽然提高了write的响应速度不需要等待数据经过IO写入磁盘。但是也带来了风险当应用层认为已经将数据写入文件了但实际数据还并没有落入磁盘。若此时系统出现异常则会将这部分数据丢失。为了避免这种情况linux 系统提供了sync 、fsync 、fdatasync接口应用层主动要求内核将缓冲区中的数据进行落盘。原型声明如下
#includeunistd.h
int fsync(int fd);
int fdatasync(int fd);void sync(void);sync 只是将所有修改过的块缓冲区排入写队列然后就返回。它并不等待实际写磁盘操作结束。fsync 函数只对文件描述符fd指定的文件起作用并等待写磁盘操作结束才返回。fdatasync 函数类似于 fsync但只影响文件的数据部分。
注 open 接口中有一个标识 O_SYNC含义标识同步写但经过验证似乎并不起作用与预期不一致。建议为了保险起见还是调用fsync接口。
总结
文件编程是Linux开发者必须掌握的技能。本文介绍了Linux文件编程中常用的API及其注意事项包括open、creat、close、lseek、read、write、dup和dup2等。还介绍了sync、fsync和fdatasync等接口用于确保数据安全。此外文章还解释了Linux内核如何标识打开的文件以及文件表项、V节点和进程表项之间的关系。希望能给您带来帮助。
若我的内容对您有所帮助还请关注我的公众号。不定期分享干活,剖析案例也可以一起讨论分享。 我的宗旨 踩完您工作中的所有坑并分享给您让你的工作无bug人生尽是坦途 参考文章https://applink.feishu.cn/client/message/link/open?tokenAmX27V1AQAADZjdT9KRAgAQ%3D