文件描述符

对于内核而言,所有打开的文件都是通过文件描述符来引用。文件描述符是一个非负的整数,当打开一个现有文件或是创建一个新的文件时,内核返回一个文件描述符。

  • 0—标准输入
  • 1—标准输出
  • 2—标准错误

    open和openat

    ```cpp

    include

    include

    include

int open(const char pathname, int flags); int open(const char pathname, int flags, mode_t mode);

int creat(const char *pathname, mode_t mode);

int openat(int dirfd, const char pathname, int flags); int openat(int dirfd, const char pathname, int flags, mode);

  1. The open() system call opens the file specified by pathname. If the specified file does not exist, it may optionally (if `O_CREAT` is specified in flags) be created by open().<br />In addition, zero or more file creation flags and file status flags can be bitwise-or'd in flags. The file creation flags are ` O_CLOEXEC, O_CREAT, O_DIRECTORY, O_EXCL, O_NOCTTY, O_NOFOLLOW, O_TMPFILE, and O_TRUNC.`<br />`openat`和`open`的功能基本相似,区别在于path是否使用了绝对路径
  2. - 如果`path`使用的绝对路径,那么参数`fd`被忽略。`openat`相当于`open`
  3. - 否则fd参数指出了相对路径名在文件系统中的开始地址。
  4. <a name="H21so"></a>
  5. # create
  6. `creat()`<br /> A call to creat() is equivalent to calling open() with flags equal to `O_CREAT|O_WRONLY|O_TRUNC.`
  7. <a name="PeavS"></a>
  8. # close
  9. int close(int fd);<br />关闭一个文件描述符时还会自动释放加在该文件上的所有记录锁<br />进程终止时,自动关闭所有打开的文件
  10. <a name="Cw1fm"></a>
  11. # lseek
  12. ```cpp
  13. #include <sys/types.h>
  14. #include <unistd.h>
  15. off_t lseek(int fd, off_t offset, int whence);

每个打开的文件都有一个与之相关的当前文件偏移量,用来度量从文件开始处计算的字节数。通常读写操作都是从当前文件偏移量处开始,并且使偏移量增加所读写的字节数。
lseek() repositions the file offset of the open file description associated with the file descriptor fd to the argument offset according to the directive whence as follows:
参数offset取决于whence:

  • SEEK_SET:The file offset is set to offset bytes. 偏移量设置为offset
  • SEEK_CUR:The file offset is set to its current location plus offset bytes. 偏移量设置为当前值+offset
  • SEEK_END:The file offset is set to the size of the file plus offset bytes. 当前文件长度+offset

    read

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

    read() attempts to read up to **count** bytes from file descriptor fd into the buffer starting at buf.

  • 如果read成功,则返回读到的字节数,如果已经达到末尾,则返回0

  • 读到的字节数可能小于要求的字节数

    write

    1. #include<unistd.h>
    2. ssize_t wirte(int fd, void *buf, size_t count);
  • 若成功返回字节数,否则返回-1

  • 通常返回值和count相同
  • read和write都在内核执行,称之为不带缓冲的IO函数 :::info read和write都是调用了系统调用,会发生栈的切换(系统调用int中断,用户栈切换到内核栈,对应状态从用户态切换到内核态)这一过程会有开销,因此之后会有带缓冲的IO函数,当缓冲存了一定数量时,调用read和write。 :::

    文件共享

    内核用三种数据结构描述打开文件
  1. 每个进程在进程表里都有一个记录向,记录项包含一张打开的文件描述符表。该表可以看作是一个向量表,每个描述符占用一条:
    1. 文件描述符
    2. 指向一个文件表项的指针
  2. 内核为所有打开文件维持一张文件表,包含
    1. 文件状态标准
    2. 文件当前的偏移量
    3. 指向该文件v节点的指针
  3. 每个打开文件都有一个v节点
    1. 文件类型以及各种操作函数的指针
    2. i节点(i-node)

image.png
如果两个独立的进程打开了同一个文件,那么:
image.png
所以只有v节点是独一无二的
每个打开该文件的进程都会获得一个各自的文件表项
如果多个进程同时访问一个文件,会出问题?

原子操作

  1. #include <unistd.h>
  2. ssize_t pread(int fd, void *buf, size_t count, off_t offset);
  3. ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);

调用pread、pwirte相当于调用lseek后调用read,但是是原子性操作

dup和dup2

  1. #include <unistd.h>
  2. int dup(int oldfd);
  3. int dup2(int oldfd, int newfd);

dup和dup2的作用都是复制一个文件描述符
不同的是dup2相当于先调用close(newfd),在调用fcntl(oldfd,F_DUPFD,newfd)
dup2是个原子操作,而且包含上述两个调用

image.png

fcntl

fcntl可以改变已经打开的文件的属性

  1. #include <unistd.h>
  2. #include <fcntl.h>
  3. int fcntl(int fd, int cmd, ... /* arg */ );

fcntl() performs one of the operations described below on the open file descriptor fd. The operation is determined by cmd.

  • F_GETFD (void) Return (as the function result) the file descriptor flags; arg is ignored.
  • F_SETFD (int) Set the file descriptor flags to the value specified by arg.

修改文件描述符或者文件状态标志时需要谨慎,要先获得现在的标志值,然后按照期望修改,不能直接指向F_SETFD\F_SETFL,这样会改变之前设置的标志位

  1. void setfl(int fd,int flags){
  2. int val;
  3. if(val=fcntl(fd,F_GETFL,0)<0)
  4. err_sys("error");
  5. val|=flags;
  6. if(fcntl(fd,F_SETFL,val)<0){
  7. err_sys("error");
  8. }
  9. }