欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux的系统调用open,write,read,close,及相关总结

程序员文章站 2022-07-14 17:49:42
...

在进行C语言学习的时候我们了解到了C语言相关的一些IO操作,如fopen,fwrite,fread,fprintf,fclose等相关函数,他们都是由C库函数提供的一些函数,是将操作系统的系统调用加以封装,虽说Linux是由C语言实现的,但为了使我们更加的了解Linux,就需要了解更接近与底层的一些IO操作,因此就需要来了解下基本的系统调用—open,write,read,close

首先我们来了解下open,write,read,close的系统调用

open

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags, mode_t mode);

open有三个参数
pathname:要打开或创建的目标文件名
flags:对文件进行多种操作也就有有多个参数,这多个参数可以进行或运算,即就是flags
参数:

  1. O_RDONLY:只读打开
  2. O_WRONLY:只写打开
  3. O_RDWR:读,写打开
  4. O_CREAT:若文件不存在,创建文件
  5. O_APPEND:追加写

参数1,2,3,必须制定一个且只能制定一个,使用参数4,必须使用open的第三个参数mode:新文件的访问权限

返回值:成功:新打开文件的文件描述符(fd)
失败:-1

write

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

fd:文件描述符
buf:写入的缓冲区
count:写的字符长度,也就是看你需要写多少

read

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

read的参数和write的参数很相像,只有第二个参数的含义有些不一样,它的buf是需要读的缓冲区

close

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

close的参数就相对简单了,这一个操作是不能遗漏的,只要了使用fd就必须close它

在这几个函数中都涉及到了关键的参数fd,因此要了解这几个函数,就必须先了解下文件描述符(fd)。

什么是文件描述符,这是一个相对抽象的概念,我们先来看看下面这张图

Linux的系统调用open,write,read,close,及相关总结

在PCB结构体中存在一个files指针,它指向一个file_struct结构体,而在file_struct结构体中存在一个file* fd数组,这个数组里面存放的是file指针,用来指向不同的file文件,而fd就可以理解为这个指针数组的下标,因此要打开一个文件,我们就可以拿到该文件的fd就可以了。

fd的分配原则:
在files_struct数组当中,使用没有被使用的最小下标,作为新的文件描述符。
操作系统默认使用了该数组的前三个元素,0号下标指向标准输入(stdin),1号下标指向标准输出(stdout),2号下标指向标准错误(stderr)。
因此正常情况下,新的fd都是从3开始的,但如果我们关闭了默认的fd,新的文件的fd就从关闭的fd处开始。

说到了fd,我们就不得不来区分下FILE和fd

FILE是C库当中提供的一个结构体,而fd是系统调用,更加接近于底层,因此FILE中必定封装了fd。

我们可以来看看FILE的结构体:
typedef struct _IO_FILE FILE;在/usr/include/stdio.h

它的结构体中有这么一段:

struct _IO_FILE {
  int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

//缓冲区相关
  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;   /* Current read pointer */
  char* _IO_read_end;   /* End of get area. */
  char* _IO_read_base;  /* Start of putback+get area. */
  char* _IO_write_base; /* Start of put area. */
  char* _IO_write_ptr;  /* Current put pointer. */
  char* _IO_write_end;  /* End of put area. */
  char* _IO_buf_base;   /* Start of reserve area. */
  char* _IO_buf_end;    /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */ 
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;//fd的封装

可以看到int_fileno就是对fd的封装,在这一部分的开头有一大段跟缓冲区相关的内容,为什么要诺列出它呢,首先可以来看一个很诡异的例子:

  #include <stdio.h>                                                            
  #include <string.h>
  #include <unistd.h>
  #include <sys/stat.h>
  #include <sys/types.h>
  #include <fcntl.h>

  int main(){
      const char *msg1 = "hello printf\n";
      const char *msg2 = "hello fwrite\n";
      const char *msg3 = "hello write\n";

      printf(msg1);
      fwrite(msg2, 1, strlen(msg2), stdout);
      write(1, msg3, strlen(msg3));
      fork();
      return 0;
  }

运行结果:
[aaa@qq.com test]$ ./hello
hello printf
hello fwrite
hello write

但当我们对进程实现输出重定向,你就会发现诡异的事情:
[aaa@qq.com test]$ ./hello > file

[aaa@qq.com test]$ cat file
hello write
hello printf
hello fwrite
hello printf
hello fwrite

这是为什么呢,这是跟C库的缓冲数据有关,C库缓冲数据分为三种(1)、无缓冲(2)、行缓冲(3)、全缓冲。
行缓冲就是往显示器上写,全缓冲就是往文件里写。
在上面的现象中,write不受影响是因为它属于系统调用,没有缓冲区,而printf和fwrite会自带缓冲区,当发生重定向到普通文件的时候,它就会从行缓冲转变为全缓冲,也就是会往文件里面写写,但是我们缓冲区里的数据,即使fork也不会立即被刷新,当进程退出后会统一刷新,写入文件,但是fork的时候会发生写时拷贝,也就是当父进程准备刷新的时候,子进程就已经有了一份相同的数据,所以就会产生上面的现象。

了解下重定向。
重定向分为三种:

  1. 输出重定向(>) 也就是关闭fd为1下标所指向的内容
  2. 输入重定向(<) 同理就是关闭fd为0下标所指向的内容
  3. 追加重定向(>>) 后面多一个追加选项

本文纯属作者总结,若有什么错误,请留言