apue学习第六天——文件I/O(第三章)

今天这些都是Unbuffered I/O啦,神马open,read,write,lseek,colse这些函数。

来,我们先看看这个unbuffered的意思,英文版里这句话是这样说的:

“The term unbuffered means that each read or write invokes a system call in the kernel.”(我又要吐槽中文版了,你们读读你们对unbuffered这个术语的翻译到底是这个意思不?)我还是来解释吧:

unbuffered是每一次的read或write都要进入一次内核(也就是进行一次系统调用)!看下面的图:

库函数与系统调用的层次关系

注意了,对于UNIX系统层面上来说,buffer是在内核之上的,上面提到的read, write, open, close, lseek那五个函数都是系统调用!看上图,write是system call,对unbuffered I/O来说,每次都要进内核,显然比直接在用户空间的标准库中调函数来得慢的多,所以像printf这些buffered I/O函数在用户空间开辟缓冲区还是很有必要的。

接下来还有个细节值得注意,为什么C标准I/O库函数在<stdio.h>中声明,而read, write等函数在<unistd.h>中声明?原因嘛,当然是一旦C库移植到其它非UNIX平台上时候照样用啊,<unistd.h>中定义的函数只用于UNIX系统,而C库中定义的函数有很好的可移植性,这点也恰恰印证了在用户空间定义缓冲区的重要性。你想想,要在内核里设缓冲区,万一换了个非UNIX平台,你去哪里缓冲啊?具体的看这个吧:C标准I/O库函数与unbuffered I/O函数(http://akaedu.github.io/book/ch28s02)。

还有一点很重要的要提一下,这个buffer/unbuffer和内核以下用不用缓存机制没有丝毫关系,如果明白了上面的东西这句话的意思肯定就会明白了。OK,关于unbuffered I/O的解释到此结束,下面来看file descriptors。


前几章提到过,进程开始的时候打开三个file descriptors:STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO,它们的值分别是0,1,2,这三个常量也就是被标准化的magic numbers啦!你不用管file descriptor的变化范围啦,你可以用sysconf来查,是0~OPEN_MAX-1,不过现在应该已经没限制了(存疑)。那么具体来看文件描述符~

首先,学过操作系统理论知识的话,PCB灰常熟悉吧?Process Control Block,进程控制块嘛!而在Linux里面,它也叫Process descriptor,进程描述符。这个pd(就这样简称吧),通过一个task_struct结构提来维护进程信息,task_struct里面有个指针指向files_strcut结构体,这个就是与file descriptors息息相关的文件描述符表啦!看下图:

文件描述符表

旁边的索引0,1,2,3就是文件描述符啦,我们创建或打开一个文件的时候就分配一个嘛!指针指向哪里?当然指向文件信息啦,但具体的指向有不同的方案细节,这点暂时不用管它。所以,文件描述符这个东西封装性还是很好的,很强大!

上次说file descriptor的时候还提到了一个内容,就是STDIN_FILENO和stdin的区别,当时我们只局限讨论了int和FILE *的区别,现在看一看,这个FILE*指针,指向了相应的FILE 结构体中,FILE结构体中存放着对应的文件描述符。由于stdin是C库中的概念,STDIN_FILENO是UNIX内核中定义在<unistd.h>的概念,从上面看,我们又一次见到了很棒的层次!在这里顺便说一句,UNIX有个传统:everything is a file,自己体会去吧。


说了那么多了,终于该说那几个system calls了,直接看程序3-2:

#include "apue.h"
#include <fcntl.h>

char buf1[] = "asdfghjklp";
char buf2[] = "ASDFGHJKLP";


int main(void)
{
	int fd;

	if((fd = creat("file.hole", FILE_MODE)) < 0)<span style="white-space:pre">	</span>//成功return fd;不成功return -1;
		err_sys("creat error");

	if(write(fd, buf1, 10) != 10)<span style="white-space:pre">	</span>//向fd中写10个字节;
		err_sys("buf1 write error");
	/*offset now = 10*/

	if(lseek(fd, 16384, SEEK_SET) == -1)
		err_sys("lseek error");
	/*offset now = 16384*/

	if(write(fd, buf2, 10) != 10)
		err_sys("buf2 write error");

	exit(0);
}
把代码直接copy上来有点不地道了,直接看吧!

fd不用说,是file descriptor的简称啦,一个int型的常量;首先是关于creat和open这两个函数,看定义:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);
摆出来一看就知道了,这俩货是一个东西,还有人说creat只是个macro,这点我没具体查明,细看:pathname是文件名,mode_t主管的是创建或打开文件的权限,3-2中的FILE_MODE是个macro,具体定义是

#define FILE_MODE (S_IRUSR | S_IWUSR | S_IROTH)

分别代表user read permission,user write permission, others read permission,具体的man creat就出来啦!

下面接着看read和write,这两个函数很有意思,定义如下:

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);<span style="white-space:pre">	</span>//看这个void*,是个通用指针,以前是char*,改版啦
ssize_t write(int fd, const void *buf, size_t count);
很像吧!看参数:fd就不用说了,file descriptor嘛。buf也不用说,看最后size_t,注意啦,和返回的ssize_t不一样哦!

哪里不一样呢?ssize_t中第一个s是sighed的意思,就是有符号数,那么size_t就是无符号数咯,它指定的是你希望读写多少,而返回的ssize_t就是实际读写多少咯~

对于read,意外情况比较多,比如读不到那么多啊,从终端从网络从管道读啊,各有个的不同,返回值也不同,这个用到再查;而write返回值通常与count相同,常见错误就是写满了的情况才会出错。当然出错的话都会返回负值。

最后说一说lseek,这个第一个字符l是历史遗留下来的,意思是长整型,先注意一下前面open函数中有个flag,它会影响到lseek偏移的位置,如果显式的flag=O_APPEND追加,那么lseek默认偏移在结尾,其它都默认为0.好了,看它的定义:

#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);

这个函数的意思呢是查看偏移,所以它返回的off_t也代表一个偏移值喽。offset参数的解释和whence有关,whence是SEEK_SET时,将偏移设置到距文件开始处offset的地方,SEEK_CUR是当前位置,SEEK_END是文件结尾。看3-2程序中,本来fd写完10个字节的buf1之后偏移指向10(文件开始是0),现在通过lssek把偏移定到16384,再写buf2,就是这样子。

看一看保存的文件长短:16394,也正好等于16384+10,就是文件结尾指向16394啦!但那个hole的定义是什么呢?这个文件偏移16384之前有很大一块地方没用,它们都被读为0,虽然文件长度依旧是16394,但是中间的0们都不占磁盘,看一下结果:

liuzch@liuzch:~/workspace$ od -c file.hole
0000000   a   s   d   f   g   h   j   k   l   p  \0  \0  \0  \0  \0  \0
0000020  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
*
0040000   A   S   D   F   G   H   J   K   L   P
0040012
中间的*都是\0,就是hole(空洞),这个文件占了8个磁盘块,但如果16394都写满的话要占20个磁盘块,说明很有效啊!

好啦,以上就是几个unbuffered I/O system calls~







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值