Linux read返回不足值,系统级I/O
文件的基本操作
一个Linux文件就是一个m个字节的序列:
所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对应的文件的读写来执行。这种将设备优雅地映射为文件的方式,允许Linux内核引出yield简单、低级的应用接口,称为Unix I/O,这使得所有的输入输出都能以一种统一且一致的方式来执行:
打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需要记住这个描述符。Linux shell创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0,宏为STDIN_FILENO),标准输出(描述符为1,宏为STDOUT_FILENO),标准错误(描述符为2,STDERR_FILENO)。
改变当前的文件位置。对于每个打开的文件,内核保持着一个文件位置k,初始值为0。这个文件位置是从文件开始的字节偏移量。应用程序能够通过执行seek操作,显式地设置文件的当前位置k。
读写文件。一个读操作就是从文件复制n>0个字节到内存,从当前文件位置k开始,然后将k增加到k+n。给定一个大小为m字节的文件,当k≥m时执行读操作会触发一个end-of-file(EOF)条件,应用程序能检测到这个条件。在文件末尾并没有明确的“EOF符号”。类似的,写操作就是从内存复制n>0个字节到一个文件,从文件的当前位置k开始,然后更新k。
关闭文件。当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何种原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。
下面是函数的原型:
#include
#include
/* 文件名,多个掩码位,模式(一般为0),成功返回文件描述符,失败返回-1 */
int open(char *filename,int flags,mode_t mode);
/* 成功返回0,失败返回-1.关闭一个已关闭的文件描述符会出错 */
int close(int fd);
/* 成功返回读取的字节数,遇到EOF返回0,出错返回-1 */
ssize_t read(int fd,void *buf,size_t n);
/* 若成功返回写入的字节数,出错返回-1 */
ssize_t write(int fd,const void *buf,size_t n);
/* 成功返回新的文件偏移量;若出错,则返回-1,可以从文件开始SEEK_SET,当前位置SEEK_CUR,和文件末尾SEEK_END开始偏移 */
off_t lseek(int fd, off_t offset, int whence);
在某些情况下,read和write传送的字节比应用程序要求要少。这些不足值不表示有错误。出现这样情况的原因有:
读时遇到EOF。
从终端读文本行。每次read函数将一次传送一个文本行,返回的不足值得大小等于文本行的大小。
读写网络套接字或Linux管道。
下面是一个例子:
#include
#include
#include
#include
#include
#include
int main(){
/* 向屏幕输出 */
char s[] = "hello world\n";
char s2[] = "\n--io test\n";
char buf[128];
write(STDOUT_FILENO,s,strlen(s));
int fd1;
int ret;/* 用来接收返回值 */
fd1 = open("1.txt",O_CREAT|O_TRUNC|O_RDWR,0);
/* 从标准输入读数据写入文件 */
while((ret = read(STDIN_FILENO,buf,128)) >0){
write(fd1,buf,ret);
}
/* 在末尾加上其他东西 */
lseek(fd1,0,SEEK_END);
write(fd1,s2,strlen(s2));
close(fd1);
return 0;
}
readn、writen和readline函数
readn、writen和readline函数在网络编程中相当重要,一个重要原因是TCP是一个流协议,协议本身并没有维护消息的边界,可能出现粘包的情况。所以需要应用层自己维护消息的边界,最常用的有两种:发送定长的消息和使用特殊字符分割消息。特殊字符一般都是换行符,所以三个函数应用相当广泛。对于它们的实现方法也是很值得学习的。先看readn和writen的实现方法。
ssize_t readn(int fd, void *buf,size_t n){
size_t nleft = n;
ssize_t nread;
char *ptr = (char *)buf;
while(nleft > 0){
if((nread = read(fd,ptr,nleft)) < 0){
if(errno == EINTR){
continue;
}else{
return -1;
}
}else if(errno == 0){ /* EOF */
break;
}
nleft -= nread;
ptr += nread;
}
return n - nleft;
}
ssize_t writen(int fd, void *buf, size_t n){
size_t nleft = n;
ssize_t nwriten;
char *ptr = (char *)buf;
while(nleft > 0){
if((nwriten = write(fd,ptr,nleft)) <= 0){
if(errno == EINTR){
continue;
}else{
return -1;
}
}
nleft -= nwriten;
ptr += nwriten;
}
return n;
}
这里实现的技巧就是通过循环,记录读写的位置,中断后重启来实现读写固定的字节数。对于readline函数的实现,我先展示(不是我写的)一个简单的版本,然后再介绍书中的实现方法。
ssize_t recv_peek(int fd, void *buf, size_t len){
while(1){
int ret = recv(fd,buf,len,MSG_PEEK);
if(ret == -1 && errno == EINTR){
continue;
}
return ret;
}
}
ssize_t readline(int fd, void *buf, size_t maxline){
int ret;
int nleft = maxline;
int nread;
char *ptr = (char *)buf;
while(nleft > 0){
ret = recv_peek(fd,ptr,nleft);
if(ret < 0){
return -1;
}else if(ret == 0){ /* EOF */
return maxline - nleft;
}
nread = ret;
int i;
for(i=0;i
if(ptr[i] == '\n'){
ret = readn(fd,ptr,i+1);
if(ret != i+1){ /* imposible for failure if work correctly*/
exit(EXIT_FAILURE);
}
return maxline - nleft + i + 1;
}
}
ret = readn(fd,ptr,nread);
if(ret != nread){
exit(EXIT_FAILURE);
}
nleft -= nread;
ptr += nread;
}
return maxline;
}
这里的实现和readn函数比较像,值得注意的是这样一个技巧,就是需要预读数据,书上的方法的思想和这个也是一样的,因为我们不知道什么时候才能读取到换行符,所以只能一个字符一个字符的读,但是这样的读取效率太低了,所以比较好的方法是一次读取多个,然后从缓存里面读;这样可以避免频繁的使用系统调用。其实,这个方法的实现,只是比readn函数多了一个预读和检测换行符的操作。
下面给出书上的实现,书中的实现方法差不多,只不过是先把文件读到一个缓冲区中,然后在进行类似的操作;而不是看一下文件内容,再进行读取。
typedef struct{
int fd;
int cnt;
char *ptr;
char buf[BUFSIZE];
}rio_t;
void rio_init(rio_t *rp, int fd){
rp->fd = fd;
rp->cnt = 0;
rp->ptr = rp->buf;
}
ssize_t rio_read(rio_t *rp, char *buf, size_t n){
int cnt;
/* refill if buf is empty,read fd until success */
while(rp->cnt <= 0){
if((rp->cnt = read(rp->fd,rp->buf,sizeof(rp->buf))) < 0){
if(errno != EINTR){
return -1;
}else if(rp->cnt == 0){
return 0; /* EOF */
}
}else{
rp->ptr = rp->buf;
}
}
/* read from rp->buf */
cnt = n;
if(rp->cnt < cnt){
cnt = rp->cnt;
}
memcpy(buf,rp->buf,cnt);
rp->cnt -= cnt;
rp->ptr += cnt;
return cnt;
}
ssize_t readline(rio_t *rp, void *buf, size_t maxline){
int n, rc;
char c,*ptr = (char *)buf;
for(n=1;n
if((rc = rio_read(rp,&c,1)) == 1){
*ptr++ = c;
if(c == '\n'){
break;
}
}else if(rc == 0){
if(n == 1){
return 0;
}else{
break;
}
}else{
return -1;
}
}
*ptr = '\0';
return n -1;
}
文件、目录和IO重定向
下面介绍几个额外的操作I/O的方法:
/* 检查文件是否存在或是否有读、写、执行权限 */
int access(const char *pathname, int mode);
/* 读取文件的源数据到buf,成功返回0,失败返回-1 */
int stat(const char *filename,struct stat *buf);
int fstat(int fd,struct stat *buf);
/* 打开目录,成功返回指针,失败返回NULL */
DIR *opendir(const char *name);
/* 每次调用,指向下一个目录项,若果没有更多目录返回NULL */
struct dirent *readdir(DIR *dirp);
/* 关闭目录,成功返回0,失败返回-1 */
int closedir(DIR *dirp);
/* 把newfd改为oldfd */
int dup2(int oldfd,int newfd);
下面是一个例子:
#include
#include
#include
#include
#include
#include
#include
/* 遍历目录,打印文件名 */
int main(int argc,char *argv[]){
struct dirent *file;
DIR *path;
int fd1;
fd1 = open("file.txt",O_CREAT|O_TRUNC|O_RDWR,0);
/* 把标准输出流替换为文件 */
dup2(fd1,STDOUT_FILENO);
path = opendir(argv[1]);
struct stat buf;
while((file = readdir(path))!= NULL){
/* 只打印文件和目录 */
stat(file->d_name,&buf);
if(S_ISREG(buf.st_mode)){
printf("file %s\n",file->d_name);
}else if(S_ISDIR(buf.st_mode)){
printf("dir %s\n",file->d_name);
}
}
close(fd1);
closedir(path);
return 0;
}
文件共享
想要共享Linux文件,我们首先要了解内核是如何表示打开的文件。内核用三个相关的数据结构来表示打开的文件:
描述符表。每个进程都有它独立的描述符表,它的表项是由进程打开的文件描述符来索引的。每个打开的描述符表项指向文件表中的一个表项。
文件表。打开文件的集合是由一张文件表来表示的,所有的进程共享这张表。每个文件表的表项组成包括当前的文件位置,引用计数,以及一个指向v-node表中对应表项的指针。关闭一个文件描述符会减少相应的文件表项中的引用计数。内核不会删除这个表项,直到它的引用计数为0。
v-node表。同文件表一样,所有的进程共享这张v-node表。每个表项包含stat结构中大多数的信息,包括st_mode和st_size成员。
可以这样理解,描述符表相当于是文件表的句柄,句柄的意思,我的理解是,持有句柄,就可以操作句柄绑定的对象,和指针类似。文件表是在程序运行时才有的一种结构,它表明了对文件的操作状态,不同的进程可以对同一个文件进行操作。而v-node表是描述文件在磁盘上真实存在的状态,如大小、类型等信息。对于同一个文件,v-node表应该只有一张(在网上没有找到结论)。如下面的例子:
#include
#include
#include
#include
#include
#include
#include
int main(){
int fd1,fd2;
char c1,c2;
if(access("test.txt",F_OK) == -1){
printf("file not exit.\n");
return 0;
}
fd1 = open("test.txt",O_RDWR,0);
fd2 = open("test.txt",O_RDWR,0);
read(fd1,&c1,1);
read(fd2,&c2,1);
printf("c1 = %c, c2 = %c.\n",c1,c2);
ftruncate(fd1,100);
struct stat buf;
fstat(fd2,&buf);
printf("file size = %ld.\n",buf.st_size);
close(fd1);
close(fd2);
int fd = open("test.txt",O_RDONLY,0);
if(fork() == 0){
read(fd,&c1,1);
printf("c1 = %c, ",c1);
exit(0);
}
wait(NULL);
read(fd,&c2,1);
printf("c2 = %c.\n",c2);
return 0;
}
对于一个只有6个字符“123456”的文件,输出的结果如下:
c1 = 1, c2 = 1.
file size = 100.
c1 = 1, c2 = 2.
这个结果的解释是,第一个是不同的文件文件表,所以都是从头开始读的,第二次是同一个文表,所以父进程读的是第二个字符。因为v-node表只有一张,所以虽然fd1和fd2是不同的文件表,但是它们指向的v-node表是同一张表,所以通过fd2得到的文件大小已经是更新过的了。
Linux read返回不足值,系统级I/O相关推荐
- 系统级性能调优工具Perf成功移植到龙芯处理器
http://www.loongson.cn/news/company/304.html 程序优化主要包括算法优化.代码优化和系统级优化,Perf是Linux内核自带的系统级性能调优工具,2.6.31 ...
- 系统级程序设计第一课内容——Linux系统与操作 2022.5.2
系统级程序设计第一课内容--Linux系统与操作 文章目录 系统级程序设计第一课内容--Linux系统与操作 一.Linux文件系统与操作 二.文件操作 1.主要函数 2.open() 3.read( ...
- Linux系统级IO②:RIO-带缓冲区IO实现
UNXI系统级的IO函数,在某些情况下,传送的字节数比用户要求的少,会出现不足值(short count)主要原因为: ①读取时遇到EOF:文件的大小不足以填充read需要读取的字节数,那么返回不足值 ...
- linux 系统级性能分析工具 perf 的介绍与使用
目录 1. 背景知识 1.1 tracepoints 1.2 硬件特性之cache 2. 主要关注点 3. perf的使用 3.0 perf引入的overhead 3.1 perf list 3.2 ...
- 安卓 linux it之家,IT之家安卓版 7.07:紧凑排版+适配华为小米魅族OV系统级推送等...
原标题:IT之家安卓版 7.07:紧凑排版+适配华为小米魅族OV系统级推送等 IT之家 安卓版/iOS版 7.07 今日更新上架发布! 画个重点,大家往下看" 7.x 后续产品规划" ...
- linux系统下常用或有用的系统级命令
本文章记录我在linux系统下常用或有用的系统级命令,包括软硬件查看.修改命令,有CPU.内存.硬盘.网络.系统管理等命令.但本文不打算介绍生僻命令,也不介绍各个linux发行版下的特有命令,且以后会 ...
- linux 解析pdf下载工具,Linux高级系统级性能分析工具-perf.pdf
Linux高级系统级性能分析工具-perf Linux 的系统级性能剖析工具‐perf (二) 承刚 TAOBAO Kernel Team chenggang.qin@ 第三章 Perf top ...
- PHP允许输入负数,php exec在linux中返回值不能为负数
php exec在linux中返回值不能为负数有需要的朋友可参考一下. 我们先来了解一下关于exec函数的使用方法 exec() 原型: 代码如下 复制代码 string exec (string ...
- linux 脚本返回值
简单版本 1. 创建一个脚本a,内容如下: #!/bin/sh #filename: a exit 22 2. 创建一个c语言程序1.C,调用该脚本,通过system的返回值来 ...
最新文章
- keras 与tensorflow绑定在一起用的,何以见得
- 字符串格式化成时间格式_小程序wxs中的时间格式化以及格式化时间和date时间互转...
- 【敏捷案例】老板太外行,朝令夕改!要不要拿了年终奖就撤?
- 海龟交易法则15_万事俱备
- Starling中文站开发教程
- 华为某员工发飙:深圳房东又涨三百房租,感觉混不下去了
- 利用360安全防护代码加固你的网店、网站
- 一般什么时候加骨窗_高三成绩大幅度提高一般出现在什么时候?老师道出了实情...
- Android ListView优化
- python毕业论文参考文献格式范例_毕业论文参考文献规范格式及例子
- 暴风酷播云二期配置_暴风播酷云二期拆解
- 高通平台耳机类型识别
- 准确查询表空间使用情况
- JS-事件-写一个通用的事件监听函数
- html中的那些炫酷吊炸天的操作
- html+css——做一个简单的底部导航栏
- 51单片机知识点汇总
- 全球及中国M2M组件行业需求规模与投资效益预测报告2022~2027年
- 复选框 el-checkbox选不上
- Navicat 12.1.20的安装
热门文章
- 华为云VSS漏洞扫描服务之开源组件漏洞检测能力
- GaussDB(for openGauss)让数据“存得下、算得快、算得准”
- 有了这个数据强一致“利器”,DBA们轻松修复数据对加班“say no”
- LiteOS内核源码分析:任务栈信息
- 【鲲鹏来了】鲲鹏迁移过程案例分享
- 鲲鹏性能优化十板斧——鲲鹏处理器NUMA简介与性能调优五步法
- 红橙Darren视频笔记 仿酷狗侧滑效果
- crash分析中有用的管道命令
- 运用经验模态分解方法(EMD)对实测探地雷达数据进行处理
- python基础知识学习笔记(2)