Skip to content

文件IO

系统调用

操作系统提供给外界的程序编程接口(API)

img

C语言的IO函数

fopen、fclose、fseek、fgets、fputs、fread、fwrite......

​ r 只读、 r+读写

w只写并截断为0、 w+读写并截断为0

a追加只写、 a+追加读写

open

c
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_APPEND、O_CREAT、O_EXCL(文件存在的时候返回一个错误)、 O_TRUNC(打开的时候把之前的数据丢弃)、 O_NONBLOCK(非阻塞)

modo: 文件的权限, 参数2有O_CREAT的时候才会使用这一个函数, 文件权限由open的mode参数和当前进程的umask掩码共同决定, 实际的是mode & ~umask

返回值: 是一个文件描述符, 错误的时候是-1

使用头文件: unistd.h

显示错误

strerror

Linux有一个全局变量errno, 使用头文件errno.h可以使用

还可以使用strerror获取这一个错误的描述, 头文件string.h

c
#include<stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
int main()
{
	int fd;
	fd = open("test.txt", O_CREAT| O_RDWR, 0666);
	printf("fd = %d, errno = %d:%s \n", fd, errno, strerror(errno));
	close(fd);
	return 0;
}

perror

还可以使用perror(const char *);

这一个函数在打印这一个信息以后, 会根据errno的值进行显示实际的错误

read/write

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

文件, 保存的位置以及读取的大小

返回的时候返回实际读取的大小, 为0的时候已经到末尾了, On error, -1 is returned, and errno is set appropriately.

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

返回实际写入的数据的个数, 错误的话返回-1

在实际使用时候, 如果使用fget和read一次读取一个字符进行测试的话, 会发现fgetc的速度实际比read快, 原因是实际使用的时候, fgetc不是一个一个字节的进行的

可以使用命令strace 可执行文件名进行测试, 这一个命令会打印出使用的系统调用

标准库里面有一个缓冲区, 这两个函数是无用户级缓冲

文件描述符

image-20240318215501848

文件描述符是进程描述符里面的其中一项

打开一个文件以后, 实际的文件的描述符是一个struct file, 操作系统实际不会给你这一部分, 实际返回的是文件描述符

前三个文件给了标准的文件输入输出, 一个进程最大可以打开1024个文件, 实际打开的时候打开的是可以使用的最小的那一个

前三个使用的时候, 推荐使用这三个文件使用的宏定义

这一个文件描述符里面有

  • 文件的偏移
  • 文件的权限
  • 文件的打开标志
  • 文件的缓冲区
  • 文件的操作函数

可以在/usr/src/linux-headers-5.4.0-152-generic/include/linux/fs.h这一个文件里面的struct file里面获取实际的实现

阻塞和非阻塞

在实际的读取一个文件的时候是不会阻塞的, 但是在读取文件以及网络的时候有可能会发生阻塞

可以直接使用read函数对STDIO_FILENO进行读取, 不需要对文件进行打开

c
#include <unistd.h>
#include <stdlib.h>
#include<stdio.h>
int main(void){
	char buf[10];
	int n;

	n = read(STDIN_FILENO, buf, 10);
	if(n < 0){
		perror("read STDIN_FILENO");
		exit(1);
	}
	write(STDOUT_FILENO, buf, n);
	return 0;

}

这一个读取的时候会进行阻塞是文件的属性

可以再一次打开/dev/tty, 这时候打开的时候使用一个非阻塞, 如果读取的时候没有数据, 会返回一个-1, 同时会设置errno = EAGIN EWOULDBLOCK, 说明这个时候不是read失败, 而是read在读取一个设备文件的时候使用的是一个非阻塞, 并且这一个文件没有数据

fcntl改变一个文件的属性

int fcntl(int fd, int cmd, ... /* arg */ );

这里主要使用两个命令, F_GETFL, F_SETFL

image-20240319091735282

这一个命令只能改变部分的文件标志

c
flag = fcntl(fd, F_GETFL);
flag |= O_NONBLOCK;
flag = fcntl(fd, F_SETFL, flag);

lseek文件偏移

和fseek类似

off_t lseek(int fd, off_t offset, int whence);

这一个可以超过文件的末尾, 文件可以被拓展, 实际拓展的时候需要一个写入

int fseek(FILE *stream, long int offset, int whence)

这一个超出文件的末尾返回0, 超出文件的头部返回-1, 常用的参数有SEEK_SET(文件的开头),SEEK_CUR(文件当前位置), SEEK_WND(文件的结尾)

可以使用这一个函数获取这一个文件的大小(从文件的头部到尾部, 返回这一个文件的偏移(相对于文件头部))

可以使用命令od -tcx查看文件的16进制模式, od -tcd查看文件的10进制模式

truncate拓展文件大小

int truncate(const char *path, off_t length);

这一个文件必须是已经存在的文件, 这一个命令不需要写入, 成功返回0

ioctl查看物理特性

c
#include <sys/ioctl.h>
int ioctl(int fd, ind cmd,...);

ioctl函数是用来操作特殊文件的底层设备参数。特别的,许多字符文件的操作特性是通过ioctl来控制的。

参数传递

传入参数:

1. 指针为参数
1. 通常有const关键字进行修饰
1. 使用指针进行读操作
c
char *strcpy(char *dest, const char *src);

src为传入函数, dest是传出参数

传出参数

  1. 指针为参数
  2. 函数调用之前, 指向的空间可以无意义, 但是是有效的
  3. 函数内部写操作
  4. 函数结束以后作为函数的返回值

传入传出参数

  1. 指针为函数参数
  2. 指向的空间有实际的意义
  3. 函数里面先进行读, 后进行写
  4. 结束以后是返回值
c
char *strtok_r(char *str, const char *delim, char **saveptr);

这一个函数实际的作用是进行字符串的分割

返回值: 实际获取的字符串

str是传入的字符串。需要注意的是 :第一次使用strtok_r之后,要把str置为NULL

delim指向依据分割的字符串。常见的空格“ ” 逗号“,”等

saveptr保存剩下待分割的字符串。

这一个函数里面的saveptr实际是一个传入传出参数