[Linux] 基础IO控制 -- 文件操作接口

基础IO

  • 标准库IO接口
    • fopen
    • fseek
  • 系统IO接口
    • open
    • write
    • read
    • lseek
    • close
  • 文件描述符 fd
  • dup2()


标准库IO接口

标准库IO接口:

  1. fopen
  2. fclose
  3. fwrite
  4. fread
  5. fseek

文件流函数:(类型:FILE*

  • stdin:标准输入
  • stdout:标准输出
  • stderr :标准错误输出

fopen

  • r只读
  • r+可读可写,每次都在文件首部开始,覆盖式读写
  • w只写,文件不存在则创建,存在则清空内容。
    truncate:截断,file to zero length截断点为0,清空内容再进行写入)
  • w+读写,文件不存在则创建,存在则清空内容,覆盖式读写
  • a只写追加,文件不存在则创建,每次写入数据都是写入文件末尾
    因为是只写追加,故不能读取数据,不能使用fseek回到SEEK_SET,必须改变方式为a+即可。
  • a+可读写追加,文件不存在则创建,读数据的初始位置是在文件起始,写数据一直追加在文件末尾

其他接口
fgets:获取一行数据

printf:格式化数据,直接打印,即写入标准输出stdout
fprintf:格式化数据,写入指定的文件流指针,给定stdout就与printf等效了。
sprintf:格式化数据,放入一个容器buf。是一个用于字符串链接的函数。
snprintf:多了一个size,格式化时只向buf中写入固定个数的单位,防止溢出。

sscanf:把数据字符串按照格式拆解。

perror:是面向库函数的,库函数中会封装许多系统调用接口,所以一旦出错,不容易确定到底是哪个接口出的问题。

标准库IO接口操作句柄是:文件流指针FILE*,文件流指针这个结构体中就包含了文件描述符,当使用标准库接口进行IO,最终本质是通过文件流指针找到文件描述符,进而对文件进行操作。

fseek

函数原型:fseek(...,...,whence);

其中第3个参数whence(偏移量)的选项取值:

  1. SEEK_SET: 文件起始位置
  2. SEEK_CUR: 当前位置
  3. SEEK_END: 文件末尾位置

系统IO接口

系统调用接口:【openwritereadlseekclose

open

  • 所属头文件 < fcntl.h >
  • 函数定义:
int open(const char *pathname,int flags,mode_t mode);
  1. pathname:文件路径名

  2. flags:选项标志 (中间通过 或 “|” 间隔)

    - 必选项(必选其一) 含义 fcntl.h中宏定义
    O_RDONLY 只读 0
    O_WRONLY 只写 1
    O_RDWR 可读可写 2
    - 可选项 含义
    O_CREAT 文件不存在则创建,存在则打开
    O_EXCL 与O_CREAT同用时,若文件存在则报错
    O_TRUNC 打开文件同时截断文件长度为0处,打开时清空文件内容
    O_APPEND 追加写入
  3. mode :创建文件时给定权限。(八进制数字

  • 所属头文件:
  • 函数定义:
mode_t umask(mode_t mask);

最终创建出文件的权限情况计算方法:

mode & (~umask)

当前设定的权限仅仅针对于调用进程,不针对于操作系统。

  1. 返回值
    返回文件描述符 ,其实就是一个正整数,错误时返回-1

open函数用法演示

int fd = open("./temp.txt",O_RDWR | O_CREAT | O_TRUNC,0777);
if(fd < 0){
	perror("open error");
	return -1;
}

write

  • 所属头文件 < unistd.h >
  • 函数定义:
ssize_t write(int fd,const void *buf,size_t count);
  1. ssize_t:有符号长整型(signed size_t
  2. fd:打开文件所返回的文件描述符
  3. buf:要向文件写入的数据
  4. count:要写入的数据长度
  5. 返回值:实际的写入字节数,错误返回-1

write函数用法演示

char buf[1024] = "nihao\n";
int ret = write(f,buf,strlen(buf));
if(ret < 0){
	peror("write error");
	return -1;
}

read

  • 函数定义:
ssize_t read(int fd,void *buf,size_t count);
  1. fd: 打开文件所返回的文件描述符
  2. buf: 对读取到的数据进行储存的位置,是一个地址
  3. count: 要读取的数据长度
  4. 返回值:实际的读取字节数,错误返回-1,如果为0说明读到了文件末尾

read函数用法演示

memset(buf,0x00,1024);
ret = read(fd,buf,1023); //只读 1023 个字节是为了不出现乱码,因为上一步在最后一个字节手动置了\0
if(ret < 0){
	perror("read error");
	return -1;
}
printf("read buf:[%s]\n",bug);
close(fd);

lseek

功能:跳转读写位置

  • 函数定义:
off_t lseek(int fd,off_t offset,int whence);
  1. fd:打开文件所返回的文件描述符
  2. offset:偏移量
  3. whence:偏移位置
  4. 返回值:返回当前位置到文件起始位置的偏移量

close

  • 函数定义:int close(int fd);

如何修改调用进程的文件创建权限掩码?

mode_t umask(mode_t mask);

例如:umask(0),可以在创建进程之前设置,这样创建出的进程就和设置的参数保持一致。但还是如之前所说,当前设定的权限仅仅针对于调用进程,不针对于操作系统。

另外的一些系统IO接口:
- ftruncate :通过文件名将文件长度为指定长度
- unlink :通过文件名称删除一个文件
- fileno :通过文件流指针,获取文件描述符


文件描述符 fd

文件描述符和文件流指针的关系

  • 标准库接口使用文件流指针 FILE*
  • 系统调用接口使用文件描述符 fd

文件流指针这个结构体中包含文件描述符,文件流指针fp通过文件描述符fd来读写数据。

  • 通过调用库函数向文件中写入数据时:
    1. 库函数调用系统调用函数时,先要得到文件描述符
    2. 用文件流指针fp中找到_fileno,即fd
    3. 进行操作,完成通过系统调用来写读写数据。
  • 当使用标准库接口进行IO,则最终是通过文件流指针找到文件描述符进而对文件进行操作。

系统已定义的文件流指针

标准输入 标准输出 标准错误
stdin stdout stderr
0 1 2
头文件中的宏:STDIN_FILENO STDOUT_FILENO STDERR_FILENO

printf打印数据的时候,如果没有刷新缓冲区,数据并不会被直接写入文件而是先写入缓冲区中。每一个文件都有文件缓冲区,缓冲区的描述信息就在文件流指针中。

缓冲区在文件流指针中的描述信息:

char *_IO_read_ptr;
char *_IO_read_end;
char *_IO_read_base;
char *_IO_write_ptr;
...

缓冲区实际是文件流指针为每个文件所维护的一个缓冲区,是一个用户态的缓冲区。

内核态:进程运行在内核态:指的是当前完成功能时操作系统内核完成,操作内核空间。
用户态:进程运行在用户态:指的是当前操作,操作都是在用户空间完成。

  • 文件描述符是什么?
    是一个正整型数字,实际上是一个数组下标

【文件流指针_IO_FILE结构体中的fileno就是文件描述符】


  • 为什么可以通过这个数字操作文件?

当进程每打开一个文件:

  1. 都会先使用struct file结构体来描述这个文件。
  2. 并且将描述信息加载到struct files_struct这个结构中的file结构体数组fd_array[](类型为file*)]
  3. 并向用户返回数组下标作为文件描述符。
  4. 用户通过文件描述符对文件进行操作,但内核实际上是通过文件描述符找到文件描述信息,进而操作文件
  • 注:
    一个进程运行之后,默认打开3个文件:标准输入,标准输出,标准错误,所以要从下标为3开始分配!
    struct files_struct结构体也是PCB中的描述信息。
    描述符个数有限,不能打开了不关闭,会引起资源泄露

文件描述符分配规则:最小未使用原则

演示:

int main(){
	close(1);	//关闭标准输出
	int fd = open("./env.c",O_RDWR);
	printf("%d\n",fd);
	fflush(stdout);
	close(fd);
	return 0;
}

printf函数通过stdout,也就是下标为1的标准输出文件流指针来找文件描述符,此时原1号标准输出已经关闭,所以目前最小空闲为1号,所以env.c就占据了1号下标,内容写入了这个文件。这也就是重定向的原理,修改数据流向。

所以:结果无法显示,因为标准输出关闭了
但是env.c中写入了一个1,说明将空闲的最小下标写入了这个文件。


dup2()

  • 函数定义:
int dup2(int oldfd,int newfd);

针对文件描述符的重定向函数。

  • 功能:将newfd文件描述符重定向到oldfd所在的文件。
    newfd本身已有打开文件,重定向时则关闭已打开文件。

dup2(fd,1);1也指向了fd所指向的文件。

演示

int main(){
	int fd = open("./env.c",O_RDWR);
	dup2(fd,1);		//将1号下标描述符中内容改变成为fd下标中的描述信息,即1号也指向了fd。向fd中写入,1也会被写入
	fflush(stdout);
	close(fd);
	return 0;
}

效果

	1->stdout	3(fd)->a.txt
			↓
	1->a,txt  3->a.txt

对minishell完善重定向功能,其流程为:

  1. 接受标准输入数据
  2. 解析命令,判断是否包含重定向符号
  3. 如果包含,则认为需要输出重定向,这时获取重定向符号后的文件名(将>截断),将重定向符号替换成\0
  4. 在子进程中打开文件,将标准输出重定向到这个文件,进行程序替换

你可能感兴趣的:(Linux操作系统,linux)