什么是IO?
I/O即对输入输出设备的操作,在Linux中一切硬件设备都被当做文件来管理,输入/输出也不例外,这所谓Linux下一切皆文件思想,通过对输入输出设备的操作即为IO。
回顾C语言中的IO操作:
在C语言中我们一IO流的方式进行对文件读写。
- C语言默认会打开三个输入输出流:stdin(标准输入流)、stdout(标准输出流)、stderr(标准出错)。
- 这三个流的类型都为file*类型,fopen返回值类型,文件指针。
下面是C语言实际操作文件的实例:
w_hello.c(往文件hello.txt中写hello fwrite):
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
FILE* fin = fopen("hello.txt","w");
if(fin == NULL)
{
printf("fopen error!\n");
exit(-1);
}
char buf[] = "hello fwrite!\n";
int count = 5;
while(count--)
fwrite(buf,strlen(buf),1,fin);
fclose(fin);
return 0;
}
r_hello.c(读取刚才写入hello.txt中的字符串):
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
FILE* fout = fopen("hello.txt","r");
if(fout == NULL)
{
printf("fopen error!\n");
exit(-1);
}
char buf[1024];
char* msg = "hello fwrite!\n";
while(1)
{
size_t s = fread(buf,1,strlen(msg),fout);
if(s > 0)
{
buf[s] = 0;
printf("%s",buf);
}
if(feof(fout))
break;
}
fclose(fout);
return 0;
}
文件描述符:
通过回忆C语言下我们对文件的操作,那么学习系统编程,就得了解和学习一下Linux下的IO操作;在此之前我们回忆一下进程的内容,进程描述PCB有一个重要的内容:文件描述符表(即就是用来管理文件的)与IO操作有密切关系。来看一下关系图吧:
在task_struct(PCB)中有一个指向文件描述符标的指针,而它它里面有一个指针数组(file* fd_array[]),数组里面存放的是指向文件的指针,而这个数组的下标就是这片博客的重点操作对象(文件描述符)。
- 文件描述符的实质:就是一个正整数。(在此之前我们了解了重定向应该对文件描述符早有所了解)。
- 文件描述符分配规则:当我们调用系统接口open时(后面认识),会为我们所打开的文件分配一个文件描述符,系统会遍历文件描述符表找到一个空位分给当前文件。
5个底层系统调用接口:- 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);
pathname文件路径
falgs打开方式
mode权限位(用于文件建立时,存在就不需要)。
flags常用的几个打开方式:
O_RDONLY:以只读的方式打开
O_WRONLY:以只写的方式打开
O_RDWR:以读写的方式打开
O_CREAT:当文件不存在时,创建该文件
O_APPEND:已追加的方式写
成功返回文件描述符,失败返回-1:
- read&write读写文件接口:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
fd:文件描述符
buf:缓冲区
count:期望读写的文件字节数。
成功返回读取或写入的字节数,失败返回:-1
- close关闭文件接口:
int close(int fd);
成功返回:0;失败返回-1;
- lseek用来移动文件指针的函数,可以通过文件来更改文件当前读写位置。
off_t lseek(int fd, off_t offset, int whence);
offset参数:
偏移whence位置的字节数。
whence参数:
SEEK_SET:文件指针指向文件开始位置。
SEEK_CUR:文件指针指向文件当前位置。
SEEK_END:文件指针指向文件末尾。
open_read使用实例:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
int main(void)
{
//1.先打开文件
int fd = open("hello.txt",O_RDONLY);
if(fd<0)
{
perror("open error!\n");
exit(-1);
}
//2.缓冲区
char buf[1024];
//3.读取文件到缓冲区
int s = read(fd,buf,sizeof(buf)-1);
buf[s] = 0;
printf("%s\n",buf);
close(fd);
return 0;
}
open_write使用实例:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
int main(void)
{
//1.打开文件
int fd = open("hello.txt",O_WRONLY);
if(fd<0)
{
perror("open error!\n");
exit(-1);
}
char* buf = "hello open_write!\n";
//2.写入文件
int s = write(fd,buf,strlen(buf));
if(s>0)
printf("write success!\n");
close(fd);
return 0;
}
我们目前文件hello.txt中已有数据,当我们想以追加的模式写数据时有两种方式,一种open打开文件时在flags参数中加上O_APPEND参数即可。那么上面我们还提到了lseek函数不防在下面用的试一下。
lseek函数使用实例:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
int main(void)
{
//1.打开文件
int fd = open("hello.txt",O_WRONLY);
if(fd<0)
{
perror("open error!\n");
exit(-1);
}
char* buf = "hello lseek!\n";
//2.更改当前文件指针位置
lseek(fd,0,SEEK_END);
//2.写入文件
int s = write(fd,buf,strlen(buf));
if(s>0)
printf("write success!\n");
close(fd);
return 0;
}
运行结果:
可以看到我们把文件指针更改到文件末尾,这样效果就和O_APPEND追加模式一样了。
缓冲区概念:
由于内核和IO设备之间的交互是比较耗时,而且低效的,linux系统使用缓冲区来减少内核和IO之间的切换,以保证IO操作的高效。
缓冲区分为:
全缓冲:每次缓冲区写满了在回写内核,普通文件的读写就是全缓冲。
行缓冲:每次写满一行就回写到内核,当缓冲区写满了也会回写到内核,标准输入输出对应到终端就是行缓冲。如(printf)。
无缓冲:当用户每次通过系统调用写回内核时。如(write)。
fflush函数可以用来刷新缓冲
可以确保数据回写到内核,防止进程因为异常终止而丢失数据,作为一个特例,fflush(NULL)可以针对所有文件的IO操作的缓冲区进行FLUSH操作。