一、Linux文件操作概述
在Linux系统编程中,文件操作是核心技能之一。理解文件操作的本质,需要先明确内存与外存的区别:
- 内存(变量、数组、数据结构):
- 读写数据速度快
- 程序执行结束、掉电后数据丢失
- 空间小,价格昂贵
- 文件(外存/硬盘):
- 读写速度比内存慢
- 掉电后数据不丢失
- 空间大,相对内存价格便宜
文件的主要作用有两个:一是长期保存掉电不丢失的数据;二是作为Linux提供给应用层的硬件操作接口,体现了“一切皆文件”的设计哲学。
二、Linux下的文件类型
Linux系统将一切资源都抽象为文件,通过ls -l 文件名命令可以查看文件的详细信息。文件类型由输出行首的第一个字符标识:
- b:块设备文件(block),如硬盘等存储设备----->文件IO
- c:字符设备文件(character),如鼠标、键盘、显示器----->文件IO
- d:目录文件(directory),如文件夹----->目录IO
- -:普通文件(regular file),如main.c、xxx.txt、a.out----->文件IO,标准IO
- l:软连接文件(link),相当于快捷方式(符号链接)----->链接项
- s:套接字文件(socket),用于网络编程----->文件IO
- p:管道文件(pipe),用于进程间通信----->文件IO
三、Linux文件操作方法
Linux下的文件操作主要分为两类:
- 标准IO:标准C库提供的文件操作接口,属于库函数
- 文件IO:Linux内核提供的文件操作接口,属于系统调用
无论使用哪种方法,文件操作的基本思想都是相同的:
- 打开文件
- 读文件、写文件
- 关闭文件
四、标准IO操作详解
4.1 打开文件:fopen()
#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);
功能:打开一个文件,并获得一个文件流指针
参数:
pathname:要打开的文件的文件名mode:打开方式"r":以只读方式打开,文件必须要存在"r+":以读写方式打开,文件必须要存在"w":以只写方式打开,文件不存在则创建,文件存在则清空"w+":以读写方式打开,文件不存在则创建,文件存在则清空"a":以只写方式打开,文件不存在则创建,写时追加写"a+":以读写方式打开,文件不存在则创建,读从开头读,写从末尾写
返回值:
- 成功:FILE*(文件流指针)
- 失败:NULL
4.2 关闭文件:fclose()
int fclose(FILE *stream);
功能:关闭文件
参数:stream:已打开的文件的文件流指针
返回值:
- 成功:0
- 失败:EOF(-1)
注意:只打开文件而使用完不关闭文件,会造成文件描述符泄露。只有 fclose(或 fflush)之后,数据才会从“用户态缓冲区”强制输出到“内核缓冲区”(即操作系统管理的页缓存),从而让其他打开的文件流能够看到它。
4.3 标准流
操作系统默认已打开的三个标准流:
stdin:标准输入流,对应标准输入设备(键盘)stdout:标准输出流,对应标准输出设备(显示屏/终端)stderr:标准出错流,对应标准出错设备(显示屏/终端)
4.4 字符读写:fputc()和fgetc()
int fputc(int c, FILE *stream);功能:向文件中写入一个字符参数:c:要写入的字符或者字符对应的ASCII值stream:要写入的文件的文件流指针返回值:成功:写入的字符失败:EOF(-1)注意:putchar('a'); ===> fputc('a', stdout)int fgetc(FILE *stream);功能:从文件中读取一个字符参数:stream:要读取的文件的文件流指针返回值:成功:返回读到的字符失败:EOF(-1)读到文件末尾:EOF(-1)
4.5 字符串读写:fputs()和fgets()
int fputs(const char *s, FILE *stream);功能:将一个字符串写入到文件,不包括'\0'字符
参数:s:要写入的字符串的首地址stream:要写入的文件的文件流指针返回值:成功:>=0失败:EOF(-1)char *fgets(char *s, int size, FILE *stream);功能:从文件中读取一行,遇到新行结束, 会读取\n,但遇到\n就停止读取,并把\n存入缓冲区,末尾自动补\0,读到\n或缓冲区满就停。参数:s:存储读取到的数据的空间首地址size:期待读到的字节数(最多size-1,留空间给\0,每次指针从上次读取的位置继续)buff[strlen(buff)-1] = 0; //给\n的位置设为0buff[strcspn(buff, "\n")] = 0;//可用于使用fgets在终端读取字符串并去掉末位\n //strcspn函数在buff中找到\n的下标stream:要读的文件的文件流指针返回值:成功:返回s,存储数据的空间首地址失败:NULL到达文件末尾:NULL
4.6fwrite与fread
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);功能:向文件中写入大小是size的nmemb个元素
参数:
ptr:要写入的元素的首地址
size:每个元素的字节数
nmemb:要写入的元素的个数
stream:要写入的文件流指针
返回值:
成功:实际写入的元素个数
失败:< nmembsize_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);功能:从文件中读取大小是size的nmemb个元素到ptr指针指向的内存空间,读取\n,不加\0,读满指定个数或 EOF
参数:
ptr:用来存储读到数据的空间首地址
size:每个元素的字节数
nmemb:希望读取的元素个数
stream:要读的文件流指针
返回值:
成功:返回实际从文件中读到的元素个数
当文件中不足一个元素时,返回0size与nmemb不可交换,否则可能出现当读取数据不足一个元素时返回0;
4.7流定位函数
fseek()
int fseek(FILE *stream, long offset, int whence);功能:重新定位流的位置
参数:
stream:要定位的文件流指针
offset:偏移量(字节)
whence:要偏移的相对位置
SEEK_SET : 从文件开头偏移
SEEK_CUR :从当前位置偏移
SEEK_END :从文件末尾偏移
返回值:
成功:0
失败:-1抢占磁盘空间
创建空洞文件,提前抢占磁盘空间,加'\0'才可,不然只移动光标不会预分配空间
ftell()
long ftell(FILE *stream);功能:获取流所在位置到文件开头的偏移量
rewind()
void rewind(FILE *stream);功能:将流复位到开头
- 求文件大小
fseek(fp, 0, SEEK_END); len = ftell(fp); rewind(fp);
五、时间相关函数
time()
#include <time.h>
time_t time(time_t *tloc);
功能:获取1970-1-1 00:00:00到现在的秒数
参数:
tloc:保存秒数的变量的地址
返回值:
获得的秒数两种用法:
time_t now = time(NULL); // 1.直接赋值给变量 time_t now; // 2.秒数直接被写入 now 的地址中 time(&now); // 或者同时用返回值(虽然很少这么干) time_t ret = time(&now); // ret 和 now 的值相等
ctime()
char *ctime(const time_t *timep);
功能:将秒数转换成字符串时间
参数:
timep :秒数的变量的地址
返回值:
字符串时间
localtiome()
struct tm *localtime(const time_t *timep);
功能:将秒数转换成日历时间
struct tm {
int tm_sec; /* Seconds (0-60) */
int tm_min; /* Minutes (0-59) */
int tm_hour; /* Hours (0-23) */
int tm_mday; /* Day of the month (1-31) */
int tm_mon; /* Month (0-11) */
int tm_year; /* Year - 1900 */
int tm_wday; /* Day of the week (0-6, Sunday = 0) */
int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */
int tm_isdst; /* Daylight saving time */
};用法:
struct tm mtim = *localtime(&now); //用struct tm结构体接收返回的结构体值,now为由time获得的秒数
六、文件IO
文件IO是Linux内核提供的文件操作接口,属于系统调用。与标准IO相比,文件IO直接使用文件描述符进行操作。
6.1 打开文件: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);
功能:打开一个文件并获得一个文件描述符
参数:
pathname:要打开的文件的文件名flags:打开方式O_RDONLY:只读O_WRONLY:只写O_RDWR:读写O_CREAT:创建O_TRUNC:清空O_APPEND:追加
mode:用户自己、同组用户、其他用户对该文件的读写执行权限,实际权限是mode&~umask,umask本电脑为0002;(umask命令可直接查看)- 普通文件:0664
- 权限给满:0777
注意:当采用O_CREAT创建文件时需要加权限,否则即使加了内核也会忽略
返回值:
- 成功:文件描述符
- 失败:-1
6.2 关闭文件:close()
#include <unistd.h>
int close(int fd);
功能:关闭文件
参数:fd:文件描述符
返回值:
- 成功:0
- 失败:-1
注意:只打开文件,使用完不关闭文件会造成文件描述符泄露。
6.3 文件描述符
当打开一个文件时,系统会为已打开的文件分配一个文件描述符用来标记该文件。文件描述符是小的、非负的整型数据。
操作系统默认可分配出的文件描述符:0-1023,总共1024个。文件描述符的分配原则:最小未被使用。
系统默认已打开的文件:
| 标准IO | 文件IO |
|---|---|
FILE* | int |
stdin | 0(STDIN_FILENO) |
stdout | 1(STDOUT_FILENO) |
stderr | 2(STDERR_FILENO) |
6.4 读写文件:write()和read()
ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);
write()功能:向文件中写入数据,写入数据用strlen,否则会多写入无用数据,可用size接收read的返回值进行写入。
read()功能:从文件中读取数据,包括\n自动覆盖buff里的内容,若读取为空则返回0,不会覆盖也不会清空buff,只管数量,不管内容(包括\0),一直读到 count 个字节或 EOF。
read与write配合使用
while((size = read(fd,buff,sizeof(buff)))>0)
{
write(fdfifo, buff, size);
}
read与fread都是二进制函数,前者无缓冲,后者有缓冲,都可读取图片。
返回值:
- 成功:实际写入/读取的字节数
- 失败:-1
- 到达文件末尾(read):0
6.5 文件定位:lseek()
off_t lseek(int fd, off_t offset, int whence);
功能:重新定位文件的读写位置
参数:
fd:文件描述符offset:偏移量whence:要偏移的起始位置SEEK_SET:从文件开头偏移SEEK_CUR:从当前读写位置偏移SEEK_END:从文件末尾偏移
返回值:
- 成功:返回当前读写位置到文件开头的偏移量
- 失败:-1
6.6 文件拷贝示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int file_copy(const char *srcfile, const char *dstfile)
{
int fdsrc = open(srcfile, O_RDONLY);
int fddst = open(dstfile, O_WRONLY | O_CREAT | O_TRUNC, 0664);
if (-1 == fdsrc || -1 == fddst)
{
printf("open error\n");
return -1;
}
char buff[1024] = {0};
ssize_t size = 0;
while ((size = read(fdsrc, buff, sizeof(buff))) > 0)
{
write(fddst, buff, size);
}
close(fdsrc);
close(fddst);
return 0;
}
七、文件IO和标准IO的区别
文件IO和标准IO在Linux文件操作中各有特点,主要区别如下:
| 特性 | 标准IO | 文件IO |
|---|---|---|
| 操作对象 | FILE*(文件流指针) | fd(文件描述符) |
| 来源 | 标准C库函数 | Linux系统调用 |
| 可移植性 | 强(跨平台) | 弱(Linux特有) |
| 缓冲区 | 有缓冲区 | 无缓冲区 |
| 效率 | 减少用户层和内核层反复切换,提高效率 | 实时性好 |
| 适用场景 | 主要用在对普通文件操作 | 普通文件、硬件设备类相关的文件 |
优先选择“标准I/O”的场景(90%的日常代码)
-
你需要处理“文本”和“格式化”数据
比如写日志(fprintf)、读配置文件、拼接字符串(sprintf)。标准I/O 提供的fprintf、fscanf、fgets能让你一行代码搞定格式化,用文件I/O(read)你得手动解析二进制,非常痛苦。 -
你需要跨平台(Windows / Linux / macOS)
标准I/O 是C标准库的一部分,在任何操作系统上行为一致。而文件I/O(open/read)是POSIX标准,在Windows上不直接支持(需要用CreateFile/ReadFile)。 -
你操作的是“普通磁盘文件”,且数据量不大(GB级别以下)
标准I/O 自带用户态缓冲区,能有效减少系统调用次数。多数情况下,它的性能已经足够好,且代码写起来更安全。 -
你需要按“行”读取数据
比如处理 CSV、日志文件,fgets一行一行读既安全又简单。用read你需要自己处理\n的分割,容易写出bug。
必须选用“文件I/O(系统调用)”的场景
-
你需要实现“非阻塞I/O”或“异步I/O”
标准I/O(FILE*)默认是阻塞的,且无法与select、poll、epoll配合。如果你在写网络服务器或高并发程序,必须用open/read/write搭配O_NONBLOCK标志。 -
你需要精确控制“数据落盘”时机
标准I/O 的fclose或fflush只保证数据从用户态缓冲区到了内核缓冲区(页缓存),不保证写到磁盘。如果你需要确保持久化(比如数据库日志),必须用系统调用fsync或O_SYNC配合文件描述符。 -
你需要操作“大文件”或“裸设备”
标准I/O 的缓冲区策略在某些极端大文件场景下可能造成内存压力。文件I/O 允许你直接指定O_DIRECT绕过页缓存,实现用户态直接I/O(虽然限制较多)。 -
你需要操作“非普通文件”
比如管道(pipe)、Socket(套接字)、设备文件(/dev/xxx)。虽然标准I/O 也能打开它们,但往往会因为缓冲模式不匹配(如行缓冲/全缓冲)导致数据延迟或阻塞异常,系统调用在这些场景下更可控。 -
你要实现“文件锁”或“高级控制”
比如fcntl锁、ioctl控制设备,这些操作只支持文件描述符(int fd),不支持FILE*。
7.1 缓冲区类型
标准IO使用缓冲区机制,主要分为三种类型:
1. 行缓冲
- 默认大小1024字节(1K)
- 主要使用在人机交互终端上,标准输出
stdout(当输出目标是终端/屏幕时) - 刷新条件:
- 遇到
'\n'刷新 - 程序结束刷新
- 使用
fflush()强制刷新 - 缓冲区满时自动刷新
- 遇到
2. 全缓冲
- 默认大小4096字节(4K)
- 主要用于文件交互,文件流(
fopen打开的文件) - 刷新条件:
- 程序结束刷新
- 文件关闭自动刷新
- 全缓冲区满时自动刷新
- 使用
fflush()强制刷新
3. 无缓冲
- 0K
- 主要用于出错信息输出,标准出错设备
stderr
7.2 打开方式对应关系
标准IO与文件IO的打开方式对应关系:
"r"→O_RDONLY"r+"→O_RDWR"w"→O_WRONLY | O_TRUNC | O_CREAT,0664"w+"→O_RDWR | O_TRUNC | O_CREAT,0664"a"→O_WRONLY | O_CREAT | O_APPEND,0664"a+"→O_RDWR | O_CREAT | O_APPEND,0664
八、目录IO
-
opendir
DIR *opendir(const char name);功能:打开目录文件并获得目录流指针
参数:
name:目录名
返回值:
失败:NULL
成功:目录流指针
-
closedir
int closedir(DIR *dirp);功能:关闭目录文件
参数:
dirpr 目录流指针
返回值:
成功:0
失败:-1
-
readdir
struct dirent *readdir(DIR* dirp);参数:
dirp:目录流指针
返回值:
失败:NULL
成功:struct dirent 指针,代表读到的文件信息,读取后指针自动加一到下一个文件。
文件末尾:NULLstruct dirent { ino_t d_ino; //Inode number off_t d_off; //Not an offset; see below unsigned short d_reclen; // Length of this record unsigned char d_type; // Type of file; not supported by all filesystem types char d_name[256]; // Null-terminated filename }ls -i 查看ino号
ls -a 查看隐藏文件(.和..)
九、其他
1、输出流
int fprintf(FILE *stream, const char *format, ...);功能:将格式化后的字符串写入到stream文件流代表的文件中。(文件)int dprintf(int fd, const char *format, ...);功能:将格式化后的字符串写入到fd所代表的文件中。*int sprintf(char *str, const char *format, ...);功能:将格式化后的字符串写入到字符型数组中(内存)字符串的拼接,格式化可以将整形数据、浮点型数据等转换成字符串
| 函数 | 目标对象 | 数据类型 | 核心场景 |
|---|---|---|---|
printf | 标准输出(屏幕) | FILE*(stdout) | 默认终端打印 |
fprintf | 任何文件流(如 fopen 返回的指针) | FILE*(如 fp、stderr) | 写入文件、写入错误输出 |
dprinf | 文件描述符(如 open 返回的整数) | int fd(如 STDOUT_FILENO、fd) | 系统调用级别的直接写入,无缓冲区卡顿 |
sprinf | 内存缓冲区(char[] 数组) | char* | 拼字符串,做网络包,存到变量里 |
char *strtok(char *str, const char *delim);功能:字符串截取函数参数:str:要截取的原始字符串delim:要截断的分隔符返回值:成功:截取到的子字符串的首地址失败:NULL(已经到字符串末尾)注意:strtok会破坏原字符串。 它会把找到的分隔符位置替换成\0(字符串结束符),从而把原字符串切分成多个子串。
2.实战示例
1) 使用fgetc读取文件内容
#include <stdio.h>
int main(int argc, const char *argv[])
{
if (argc < 2)
{
printf("Usage : ./a.out <filename>\n");
return -1;
}
FILE *fp = fopen(argv[1], "r");
if (NULL == fp)
{
printf("fopen error\n");
return -1;
}
int ch;
while ((ch = fgetc(fp)) != EOF)
{
putchar(ch);
}
fclose(fp);
return 0;
}
2) 实现cp命令功能
#include <stdio.h>
int main(int argc, const char *argv[])
{
if (argc < 3)
{
printf("Usage: ./a.out srcfile dstfile\n");
return -1;
}
FILE *fpsrc = fopen(argv[1], "r");
FILE *fpdst = fopen(argv[2], "w");
if (NULL == fpsrc || NULL == fpdst)
{
printf("fopen error\n");
return -1;
}
int ret = 0;
while (1)
{
ret = fgetc(fpsrc);
if (EOF == ret)
{
break;
}
fputc(ret, fpdst);
}
fclose(fpsrc);
fclose(fpdst);
return 0;
}
3、主函数传参
可以在程序运行时,利用命令行参数向主函数传参:
int main(int argc, const char *argv[])
{
// argc:命令行传递的参数个数
// argv:保存多个char*类型指针的数组
// argv[0] ----> "./mycat"
// argv[1] ----> "1.txt"
// argv[2] ----> "aaa"
// ...
}
示例:执行./mycat 1.txt aaa bbb ccc时:
argc = 4argv[0] = "./mycat"argv[1] = "1.txt"argv[2] = "aaa"argv[3] = "bbb"argv[4] = "ccc"
4、man手册使用
Linux提供了完善的man手册系统,帮助开发者查阅文档:
- Linux命令:
man ls或man 1 ls - 系统调用:
man open或man 2 open - 库函数:
man fopen、man fread、man fclose
5.创建目录
int mkdir(const char *pathname, mode_t mode);
功能:创建一个目录文件(文件夹)
参数:
pathname:目录名称
mode:目录文件的操作权限
返回值:
成功:0
失败:-1
6.出错处理函数
-
strerror
char *strerror(int errnum);
功能:将错误码转换成对应的错误信息字符串
参数:
errnum:错误码:errno
返回值:
失败:NULL
成功:返回错误码对应的字符串首地址
-
perror
void perror(const char *s);
功能:打印自己的出错处理信息和系统系统给出的出错原因。
当调用 perror("open") 时,它做了三件事:
-
获取当前的
errno值。(全局变量errno,当open、read、malloc等系统调用或库函数执行失败时,Linux 内核(或 glibc 库)会自动将一个特定的数字写入errno。) -
调用
strerror(errno)拿到字符串"No such file or directory"。 -
把 传入的字符串
"open"+ 冒号空格 + 错误描述字符串 一起输出到标准错误(stderr)。
十、framebuffer
1.什么是framebuffer
帧缓存、帧缓冲技术。
是Linux为显示设备提供的一个接口,把显存抽象后的一种设备,通过该设备文件可以实现应用层向显存空间的数据写入,从而实现像素点的绘制。
2.如何操作framebuffer
通过内存映射技术,可以实现显存空间和用户空间的内存映射,通过向用户空间写入RGB颜色值,从而实现显存数据的写入。
1.打开显示设备文件/dev/fb0
2.获取显示设备相关参数:分辨率、像素点的位数(位深度)
3.建立内存映射
4.写入RGB颜色值
5.解除内存映射
6.关闭显示设备文件
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);功能:建立文件和用户空间的内存映射
参数:
addr:要映射的用户空间首地址
NULL:让内核选取空间,并将空间首地址返回
length: 要映射的空间大小
prot:对映射空间的读写执行权限PROT_READ I PROT_WRITE
flags:MAP_SHARED (共享映射区域)
fd:要映射的文件
offset:映射的偏移量
返回值:
成功:映射到的用户空间首地址
失败:MAP_FAILED((void *)-1)
1077



被折叠的 条评论
为什么被折叠?



