初级文件IO——IO过程、open、close、write、read、lseek、dup、dup2、errno、perror

时间:2022-12-03 19:08:02

先要回答的问题

文件IO指的是什么?

本文主要讲述如何调用Linux OS所提供的相关的OS API,实现文件的读写。

如何理解文件IO?

IO就是input output的意思,文件io就是文件输入输出,也就是文件读写。

文件读写,读写的是什么?

是数据。

文件IO(Input Output),也就是输入输出是对什么而言的?参考点是什么?

是CPU

初级文件IO——IO过程、open、close、write、read、lseek、dup、dup2、errno、perror

能不能越过OS,直接操作文件呢?

当有OS的时候,应用程序基于OS运行时,必须通过OS API假借OS之手,才能操作底层硬件,无法回避。

文件IO涉及到的OS API

①open函数:打开文件

②close函数:关闭文件

③read函数:从打开的文件读数据

④write函数:向打开的文件写数据

⑤lseek函数:移动在文件中要读写的位置

⑥dup函数:文件读写位置重定位函数,本来是写到这个文件,重定位后可以写到另一个文件里面

⑦fcntl函数:文件描述符设置函数

⑧ioctl函数:一个特殊的函数

文件读写的简单例子

文件操作三步曲

①打开文件  open函数

②读、写等操作文件  read、write函数

③关闭文件  close函数

代码演示

初级文件IO——IO过程、open、close、write、read、lseek、dup、dup2、errno、perror初级文件IO——IO过程、open、close、write、read、lseek、dup、dup2、errno、perror
 1 #include <stdio.h>
 2 #include <sys/types.h>
 3 #include <sys/stat.h>
 4 #include <fcntl.h>
 5 #include <unistd.h>
 6 
 7 int main(void)
 8 {
 9     int fd = 0;
10 
11     fd = open("./file.txt", O_RDWR);
12     if(-1 == fd)
13     {
14         printf("open fail\n");
15         return 0;
16     }
17     else
18     {
19         printf("open ok\n");
20     }
21 
22     char buf1[] = "hello world";
23     write(fd, (void *)buf1, 11);
24 
25     lseek(fd, 0, SEEK_SET);
26 
27     char buf2[30] = {0};
28     read(fd, buf2, sizeof(buf2));
29     
30     printf("buf2 = %s\n", buf2);    
31     
32     close(fd);
33     
34     return 0;
35 }
View Code

API

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); 

功能

第一种:只能打开存在的文件,如果文件不存在就返回-1报错。

第二种:如果文件存在就直接打开,如果文件不存在,就按照mode指定的文件权限,创建一个该名字的新文件。也就是说三个参数时,不仅包含打开已存在文件的功能,还多了一个创建文件的功能。

参数

pathname:表示路径名,很简单

flags

flags的作用?

flags用于指定文件的打开方式,这些宏还可以使用|组合,比如O_RDONLY | O_APPEND,同时指定多个宏。

这些宏对应的就是一些整形数,#define O_RDONLY 2。

这些宏被定义在了那里?

定义在了open所需要的头文件中,使用open函数时,必须要包含对应的头文件,否者,这些宏你就用不了。

宏的含义

(1)O_RDONLY:只读方式打开文件,只能对文件进行读    

(2)O_WRONLY:只写方式打开文件,只能对文件记性写

(3)O_RDWR:可读可写方式打开文件,既能读文件,也能写文件

以上这三个在指定时,只能唯一指定,不可以组合,比如O_RDONLY|O_WRONLY。

(4)O_TRUNC:打开时将文件内容全部清零空

(5)O_APPEND:打开文件后,写数据时,原有数据保留,新写的数据追加到文件末尾,此选项很重要。如果不指定这个选项的话,新写入的数据会从文件头上开始写,覆盖原有的数据。

(6)O_CREAT

open两个参数时的缺点?

只能用于打开已经存在的文件,如果文件不存在就返回-1报错。

O_CREAT的作用

可以解决两个参数的缺点,指定O_CREAT时,如果:

文件存在:直接打开

文件不存在:创建该“名字”的文件。

不过指定O_CREAT,需要给open指定第三个参数mode,用于指定新创建文件的原始权限。

(7)O_EXCL

当O_EXCL与O_CREAT同时被指定,打开文件时,如果文件之前就存在的话,就报错。

意义:保证每次open的是一个新的文件,如果文件以前就存在,提醒你open的不是一个新文件。

mode:创建文件时,用于指定文件的原始权限,其实就是rwxrwxr--。

返回值

如果打开成功,返回一个非负整数的文件描述符。

如果打开失败,返回-1,并且设置错误号给系统定义的全局变量errno,用于标记函数到底出了什么错误。

close

原型 

#include <unistd.h>
int close(int fd); 

功能

关闭打开的文件。

参数

fd:文件描述符

返回值

成功返回零,失败返回-1并设置errno

备注

就算不主动的调用close函数关闭打开的文件,进程结束时,也会自动关闭进程所打开的所有的文件。但是如果因为某种需求,你需要在进程结束之前关闭文件的话,就主动的调用close函数来实现。Linux c库的标准io函数fclose,向下调用时,调用就是close系统函数。

write

原型 

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count); 

功能

向fd所指向的文件写入数据。

参数

fd:指向打开的文件  

buf:保存数据的缓存空间的起始地址

count:从起地址开始算起,把缓存中count个字符,写入fd指向的文件

返回值

调用成功:返回所写的字符个数

调用失败:返回-1,并给errno自动设置错误号

数据中转过程

应用缓存(buf)————>open打开文件时开辟的内核缓存——————>驱动程序的缓存——————>块设备上的文件

read

原型 

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count); 

功能

从fd指向的文件中,将数据读到应用缓存buf中

参数

fd:指向打开的文件

buf:读取到数据后,用于存放数据的应用缓存的起始地址

count:缓存大小(字节数)

返回值

调用成功:返回所写的字符个数

调用失败:返回-1,并给errno自动设置错误号

数据中转的过程

应用缓存(buf)<————open打开文件时开辟的内核缓存<——————驱动程序的缓存<——————块设备上的文件

lseek

原型 

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence); 

功能

调整读写的位置,就像在纸上写字时,挪动笔尖所指位置是一样的。

C库的标准io函数里面有一个fseek函数,也是用于调整读写位置的,fseek就是对lseek系统函数封装后实现的

参数

fd:文件描述符,指向打开的文件

whence

粗定位,选项有:

SEEK_SET:调到文件起始位置
SEEK_CUR:调到文件当前读写的位置
SEEK_END:调到文件末尾位置

offset:  

精定位:微调位置,从whence指定的位置,向前或者向后移动指定字节数。

为负数:向前移动指定字节数
为正数:向后移动指定字节数

不过当whence被指定为SEEK_SET时,如果offset被指定为负数的话,是没有意义,为什么?

因为已经到文件头上了,在向前移动就越界了,不再当前文件的范围内了,如果非要向前调整,lseek函数会报错。

返回值

返回当前读写位置相对于文件开始位置的偏移量(字节)。

可以使用lseek函数获取文件的大小,怎么获取?  

答:将文件读写的位置移动到最末尾,然后获取返回值,这个返回值就是文件头与文件尾之间的字节数,也就是文件大小。

调用失败,返回-1,并给errno设置错误号。

dup

原型

#include <unistd.h>
int dup(int oldfd);

功能

复制某个已经打开的文件描述符,得到一个新的描述符,这个新的描述符,也指向被复制描述符所指向的文件。

比如:4指向了某个文件,从4复制出5,让5也指向4指向的文件。

初级文件IO——IO过程、open、close、write、read、lseek、dup、dup2、errno、perror

复制某个已经打开的文件描述符,得到一个新的描述符,这个新的描述符,也指向被复制描述符所指向的文件。

参数

oldfd:会被复制的、已经存在的文件描述符。

返回值

成功:返回复制后的新文件描述符

失败:返回-1,并且errno被设置。

dup2

原型 

#include <unistd.h>
int dup2(int oldfd, int newfd); 

功能

功能同dup,只不过在dup2里面,我们可以自己指定新文件描述符。如果这个新文件描述符已经被打开了,dup2会把它给关闭后,再使用。

比如:dup(2, 3);  

从2复制出3,让3也指向2所指向的文件,如果3之前被打开过了,dup2会关闭它,然后在使用。

dup2和dup的不同之处在于:

dup:自己到文件描述符池中找新文件描述符

dup2:我们可以自己指定新文件描述符

参数

oldfd:会被复制的、已经存在的文件描述符。

newfd:新的文件描述符

返回值

成功:返回复制后的新文件描述符

失败:返回-1,并且errno被设置。

Linux错误号——errno

在前面的代码中,如果open失败了,只是笼统的打印出“打开文件失败了”,但是并没有提示具体出错的原因,没有详细的出错原因提示,遇到比较难排查的错误原因时,很难排查出具体的函数错误。open失败,如何具体打印出详细的出错信息呢?这就不得不提errno的作用了。

什么是ernno?

函数调用出错时,Linux系统使用错误编号(整形数)来标记具体出错的原因,每个函数有很多错误号,每个错误号代表了一种错误,产生这个错误时,会自动的将错误号赋值给errno这个全局变量。errno是Linux系统定义的全局变量,可以直接使用。

错误号和errno全局变量被定义在了哪里?

都被定义在了errno.h头文件,使用errno时需要包含这个头文件。

打印出具体的出错原因

perror

原型 

#include <stdio.h>
void perror(const char *s); 

功能

perror(s) 用来将上一个函数发生错误的原因输出到标准设备(stderr)。参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。

参数

  s:在错误原因前面打印的一串字符

返回值

  无

工作原理

perror函数可以自动将“错误号”换成对应的文字信息,并打印出来,方便我们理解。perror是一个C库函数,不是一个系统函数。调用perror函数时,它会自动去一张对照表,将errno中保存的错误号,换成具体的文字信息并打印出来,我们就调用perror函数时,它会自动去一张对照表,将errno中保存的错误号,换成具体的文字信息并打印出来,我们就能知道函数的具体错误原因了。

代码演示

初级文件IO——IO过程、open、close、write、read、lseek、dup、dup2、errno、perror初级文件IO——IO过程、open、close、write、read、lseek、dup、dup2、errno、perror
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int main(void)
{
    int fd = 0;


    fd = open("./file.txt", O_RDWR);
    //fd = open("./file.txt", O_RDWR|O_CREAT|O_EXCL, 0664);
    if(-1 == fd)
    {
        printf("open fail: %d\n", errno);
        perror("open fail");
        return 0;
    }
    else
    {
        printf("open ok\n");
        printf("fd = %d\n", fd);
    }

    
    char buf1[] = "hello world";
    write(fd, (void *)buf1, 11);

    lseek(fd, SEEK_SET, 0);

    char buf2[30] = {0};
    read(fd, buf2, sizeof(buf2));
    

    printf("buf2 = %s\n", buf2);    
    close(fd);

    return 0;
}
View Code

输出结果

初级文件IO——IO过程、open、close、write、read、lseek、dup、dup2、errno、perror初级文件IO——IO过程、open、close、write、read、lseek、dup、dup2、errno、perror
open fail: 2
open fail: No such file or directory
View Code

perror会在入参字符串后加:,打印错误原因,在换行