POSIX标准下的非缓冲类型的I/O流(Unix或Linux)
首先在这里先介绍一下什么是全缓存,行缓存以及无缓存的这几种类型;便于区分
标准C的io缓存类型:
全缓存:
要求填满整个缓存区后才进行I/O系统调用操作,对于磁盘文件通常使用全缓存访问
行缓存:
涉及一个终端时,使用行缓存
行缓存满自动输出
碰到换行符自动输出
无缓存:
标准错误流stderr通常是不带缓存去的,这使得错误信息能够尽快的显示出来
在前面的博客中其实已经介绍了,关于标准C下的I/O流相关的操作,
其实在标准C中的IO流都是带有缓存的IO流形式,是对原来内核提供的无缓存的IO又进行了一次加缓存的封装,遵循的是ISO标准,而内核所提供的IO流都是遵循的是POSIX标准
标准C的IO函数最终还是去调用内核提供的读写的函数
标准库函数:
遵循ISO标准,基于流的I/O,对文件指针(FILE结构体)进行操作
系统调用
兼容POSIX标准,基于文件描述符的IO,对文件描述符进行操作
在标准C中,我们最主要用到的就是FILE*,文件指针类型,但是在系统调用中,可能就并没有这样的一个文件指针类型,而是用另外一种文件描述符fd来进行代替
下面就先来说说系统调用中的文件描述符的概念:
文件描述符:
一个非负的整数,当打开或创建一个新文件时,内核向进程返回一个文件描述符,当读写一个文件时
用open,creat返回的文件描述该文件,将其作为参数传递给read或write函数
在posix应用程序中,证书0,1,2被替换成符号常数
STDIN_FILENO
STDOUT_FILENO
STDERR_FILENO
这些都在unistd.h头文件中
目前Linux下文件描述符的范围是1024;
文件描述符与文件指针之间的相互转换关系如下所示:
文件描述符与文件指针:
fdopen()
FILE *fdopen(int fd,const char *mode);
将文件描述符转换成文件指针
fileno()
int fileno(FILE *fp);
将文件指针转换成文件描述符fd
后面会依次的介绍相关系统调用的下列相关相关函数
open(); 打开文件
create(); 创建文件
close(); 关闭文件
read(); 读文件
write(); 写文件
lseek(); 文件定位
1:open()函数
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 flag,mode_t mode);
参数:
pathname:要打开或者创建文件的路径
flags: //如果同时去使用 ,用按位或|来进行参数组合
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写
O_APPEND 追加,在网络文件系统中进行操作没有保证
O_CREAT 如果指定文件不存在,则按照mode参数制定的文件权限来创建文件
O_EXCL 如果同时指定了O_CREAT,并且文件已存在,则出错返回。
O_DIRECTORY 如果path不是一个目录,读写出错
O_TRUNC 如果文件已存在,并且以只写或可读可写方式打开,则将其长度截断(Truncate)为0字节(清空)
O_NONBLOCK (非阻塞形式)如果pathname值的是一个FIFO一个块特殊文件或一个字符特殊文件,
则此选项为此文件的本次 打开操作和后续的IO操作设置非阻塞方式
mode:
新建文件的访问权限,对于open函数而言,仅当创建新文件时才使用第三个参数(创建新文件的时候用到第三个参数),655,777,755,
返回:
文件描述符
2:creat()函数:
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int creat(const char *pathname,mode_t mode);
返回:若成功为只写打开的文件的描述符,若出错为-1
功能创建一个新文件
此函数等效于:
open(pathname,O_WRONLY | O_CREAT |O_TRUNC,mode);
creat的一个不足之处就是它以只写方式打开所创建的文件,创建成功的文件只能写
3:close()函数:
#include<unistd.h>
int close(int fd);
返回:若成功为0,若出错为-1;
功能:关闭一个打开的文件
参数:fd 已打开的文件描述符,
当一个进程终止时,它所有的打开文件都由内核自动关闭
4:read()函数
read函数:
#include<unistd.h>
ssize_t read(int fd, void* buf,size_t count);
返回:读到的子节数,若已到文件尾为0,若出错为-1
功能;从打开文件中读数据
参数:
fd 读取文件的文件描述符
buf 存放读取数据的缓存
count 要求读取一次数据的子节数
有多种情况可使实际读到的子节数少于要求读子节数:
读普通文件的时候,在读到要求子节数之前已到达文件尾部
从终端设备读时,通常一次最多读一行
当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的子节数
某些面向记录的设备
进程由于信号造成中断
读操作从文件的当前位移量处开始,在成功返回之前,该位移量增加实际读得的字节数
5:write函数:
#include<unistd.h>
ssize_t write(int fd,const void* buf,size_t count);
返回:若成为已写的字节数,若出错为-1
功能:
向打开的文件中写数据
参数:
fd 写入文件的文件描述符
buf 存放待写数据的缓存
count 要求写入一次数据的子节数
其返回值通常与参数count的值不同,否则表示出错
write出错的一个常见原因是:
磁盘已写满,或者超过了对一个给定进程的文件长度限制
对于普通文件,写操作从文件的当前位移量处开始,如果在打开该文件时,指定了O_APPEND选项
则在每次写操作之前,将文件为以来设置在文件的当前结尾处,在一次成功写之后,该文件位移量增加实际写的字节数。
6:lseek函数:
#include <sys/types.h>
#include<unistd.h>
offset lseek(int fd,off_t offset, int whence);
返回:若成功则返回心的文件位移量,若出错为-1
功能:定位一个已打开的文件
参数:
fd:已打开文件的文件的文件描述符号
offset :位移量,是long类型的,所以返回的时候要使用%ld
whence:定位的位置
SEEK_SET:将该文件的位移量设置为距文件开始处offset个字节
SEEK_CUR:将文件的位移量设置为其当前值加offset,offset正负都可以
SEEK_END:将该文件的位移量设置为文件长度加offset,offset可为正负
lseek也可用来确定所设计的文件是否可以设置位移量。如果文件描述符引用的是一个管道或者FIFO,则lseek返回-1,并且将errno设置为EPIPE。
每个打开文件都有一个与其相互关联的“当前文件偏移量”,
它是一个非负整数,用以度量从文件开始处计算的字节数。
通常读写操作都是从当前文件偏移量出开始,并使偏移量增加所读或写的字节数,
按系统默认,当打开一个文件时,除非制定O——APPEND选项,否则该位移量设置为0;
使用lseek可以计算出文件的长度:
lseek(fdin,0L,SEEK_END);
下面提供一个设置缓存相关的命令。缓存设置的好的话,可以提高文件的读写的效率
od -c 文件名字 //查看文件的ascii编码
如:od -c file.txt
df -k
sudo tune2fs -l /dev //通过网络查找磁盘块的大小,便于我们去设置我们的缓存
查看相关block的参数。如果block为4096的话,那么说明缓存最合适就是4096,如果是1024 则最合适的缓存就是1024
接下来就是上述函数的代码段部分了,很简单的文件拷贝函数代码是可以直接进行run的
1:io.h
#ifndef __IO_H__
#define __IO_H__
/**
*拷贝函数
*/
extern void copy(int fdin,int fdout);
/**
*使用lseek函数来创建空洞文件
*/
extern void writeNullFile(int fdout);
#endif
copy.c
#include<unistd.h>
#include"io.h"
#include<malloc.h>
#include<stdio.h>
#include<errno.h>
#include<sys/types.h>
#define BUFFER_SIZE 1024
char * str = "7777799900876555";
#define OFFSET 10
/**
*关于文件的拷贝函数
*
*/
extern void copy(int fdin,int fdout){
ssize_t size_in;
ssize_t size_out;
char* buffer = (char*)malloc(BUFFER_SIZE*sizeof(char));
while((size_in = read(fdin,buffer,BUFFER_SIZE)) != 0){
if(size_in > 0){
fprintf(stdout,"read success\n");
size_out = write(fdout,buffer,size_in);
if(size_out > 0){
fprintf(stdout,"write success\n");
}else{
fprintf(stderr,"write error:%s\n",strerror(errno));
}
}else{
fprintf(stderr,"read error:%s\n",strerror(errno));
}
}
free(buffer);
}
/**
*在原来拷贝过后的文件后面创建空洞文件,隔去一些空白的字节后,然后再去写
*SEEK_CUR 从当前位置开始定位
*SEEK_END 从结束开始定位
SEEK_SET 从开始的位置
*/
extern void writeNullFile(int fdout){
lseek(fdout,OFFSET,SEEK_END);
ssize_t size_out;
size_out = write(fdout,str,sizeof(str)/sizeof(char));
if(size_out >0){
fprintf(stdout,"write success\n");
}else{
fprintf(stdout,"write error\n",strerror(errno));
}
}
copy_test.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include "io.h"
#include<fcntl.h>
int main(int argc,char* argv[]){
if(argc != 3){
fprintf(stderr,"缺少参数数量\n");
exit(EXIT_FAILURE);
}
int fdin;
int fdout;
fdin = open(argv[1],O_RDONLY);
if(fdin < 0){
fprintf(stderr,"open read file error\n");
exit(EXIT_FAILURE);
}else{
fprintf(stdout,"open read file success\n");
}
fdout = open(argv[2],O_WRONLY | O_CREAT | O_TRUNC,0777);
if(fdout < 0 ){
fprintf(stderr,"open or creat write file failed\n");
exit(EXIT_FAILURE);
}else{
fprintf(stdout,"open or creat write file successed\n");
}
printf("fdin:%d,fdout:%d\n",fdin,fdout);
copy(fdin,fdout);
writeNullFile(fdout);
close(fdin);
close(fdout);
return 0;
}
代码拷贝下来是可以直接run的
在最后谢谢 大家的观看,写的不好的,或者错误的,请各位看官不腻赐教,不懂的地方,也可以相互交流,这也是在记录自己的成长过程的一种方式,谢谢,欢迎持续访问博客:会有更多精彩的