unix文件和i/o流

时间:2020-12-12 10:02:08



1. 关于unix文件结构

在unix/linux文件系统中,一切皆是文件,目录是文件,设备是文件,文件是文件......文件需要有文件的各项属性,在unix中,可以使用stat函数族来获取文件属性。使用stat函数获取的节点文件存储在struct stat *buf指向的内存中。

 
 
  1.     #include <sys/stat.h>

  2.    

  3.     int

  4.     fstat(int fildes, struct stat *buf);

  5.     int

  6.     lstat(const char *restrict path, struct stat *restrict buf);

  7.     int

  8.     stat(const char *restrict path, struct stat *restrict buf);

  9.     int

  10.     fstatat(int fd, const char *path, struct stat *buf, int flag);


 
 
  1.     struct stat { /* when _DARWIN_FEATURE_64_BIT_INODE is NOT defined */

  2.         dev_t    st_dev;    /* device inode resides on */

  3.         ino_t    st_ino;    /* inode's number */

  4.         mode_t   st_mode;   /* inode protection mode */

  5.         nlink_t  st_nlink;  /* number of hard links to the file */

  6.         uid_t    st_uid;    /* user-id of owner */

  7.         gid_t    st_gid;    /* group-id of owner */

  8.         dev_t    st_rdev;   /* device type, for special file inode */

  9.         struct timespec st_atimespec;  /* time of last access */

  10.         struct timespec st_mtimespec;  /* time of last data modification */

  11.         struct timespec st_ctimespec;  /* time of last file status change */

  12.         off_t    st_size;   /* file size, in bytes */

  13.         quad_t   st_blocks; /* blocks allocated for file */

  14.         u_long   st_blksize;/* optimal file sys I/O ops blocksize */

  15.         u_long   st_flags;  /* user defined flags for file */

  16.         u_long   st_gen;    /* file generation number */

  17.     };


这里需要简单介绍以下几个struct stat元素和相关函数,实际使用时可以使用man查看具体使用方法,为了节约时间和保持思维连贯性,在此不做赘述。


a. st_mode

表示文件类型和访问权限

umask 设置创建文件时默认的permission

chmod

fchmod

chown

fchown

lchown


b. st_ino

st_ino表示一个inode节点号,在unix/linux系统中,我们为每一个文件分配一个inode指针,这个inode指针指向文件存储的一组内存块blocks,如果一个block是4k,而我们文件中存了7k的文件,那么操作系统就会给这个文件分配2个block和一个inode,inode就是这两个block的索引。


c. st_nlink

关于链接计数需要具体分文件和目录来讨论。对于文件来说,st_nlink表示指向该文件inode的目录项数,也可以称之为硬链接数(hard link),这意味着什么呢?假设我在/usr/bin/目录下有一个文件f_nlink,f_nlink的inode号是1553,这个时候/usr/bin目录项中必然有一条目录项是(1553,f_nlink),此时f_nlink的st_nlink就是1;如果使用ln命令给f_nlink创建一个硬链接到/usr/目录下,则在/usr/目录中也必然有一条目录项是(1553,f_link),/usr/和/usr/bin/都有目录想指向1553这个inode,此时f_nlink的st_nlink是2(硬链接数是2);此时如果在/usr/bin/目录下删除f_nlink(使用unlink或者rm),则f_nlink的链接数减少1,但是inode仍然存在,且从/usr/目录下也可以访问f_nlink文件,只有当链接数减为0时才会删除f_nlink的inode,彻底删除文件释放内存。这里又需要扩展开来聊聊软链接了,对于软连接来说,如果本体被删除,则副本就不能访问本体。


对于目录来说,一个目录项下有几个文件或者子目录,则链接数就是几(包括.和..)。


硬链接有两个限制:(1)不允许给目录创建硬链接,防止产生环路,遍历目录时陷入死循环;(2)只有在同一个文件系统中的文件之间才能创建硬链接,这也很好理解,只有在同一个文件系统中才能访问inode



2. 关于unix文件i/o流

在unix系统调用层面我们使用read和write来读写文件。

  
  
  1. #include <unistd.h>
  2. ssize_t read(int fd, void *buf, size_t nbytes);
  3. 参数:
  4. fd: 将要读取的数据的文件描述符
  5. buf: 所要读到数据的内存缓冲的缓冲区指针
  6. nbytes: 读取的数据大小
  7. 返回值:
  8. 返回读取的数据大小,如果读到文件末尾则返回0;读取失败返回-1
  9. ssize_t write(int fd, const void *buf, size_t nbytes);
  10. 返回值:
  11. 写入文件成功则返回写的字节数;失败返回-1

当频繁的使用read和write系统调用读写文件,进程就需要不断的在内核态和用户态之间转换,系统开销太大;因此流(stream)就出现了,流的结构FILE了几个部分:文件描述符fd,指向该流缓冲区指针,缓冲区大小,当前缓冲区中的字符数,出错标志等。我们通常使用文件指针FILE*来表示流,个人认为FILE*的使用是对文件系统调用函数的又一层封装。总结几个有关流的unix函数如下:

a. 打开流

  
  
  1. #include <stdio.h>
  2. FILE *
  3. fdopen(int fildes, const char *mode);
  4. FILE *
  5. fopen(const char *restrict filename, const char *restrict mode);
  6. FILE *
  7. freopen(const char *restrict filename, const char *restrict mode,
  8. FILE *restrict stream);

b. 关闭流

  
  
  1. #include <stdio.h>
  2. int
  3. fclose(FILE *stream);
  4. void
  5. fcloseall(void);

c. 读写流

读写流也分几种方法:

无缓冲,即每次只读或者写一个字符的i/o流。

  
  
  1. #include <stdio.h>
  2. int
  3. fgetc(FILE *stream);
  4. int
  5. getc(FILE *stream);
  6. int
  7. getc_unlocked(FILE *stream);
  8. int
  9. getchar(void);
  10. int
  11. getchar_unlocked(void);
  12. int
  13. getw(FILE *stream);

  
  
  1. #include <stdio.h>
  2. int
  3. fputc(int c, FILE *stream);
  4. int
  5. putc(int c, FILE *stream);
  6. int
  7. putc_unlocked(int c, FILE *stream);
  8. int
  9. putchar(int c);
  10. int
  11. putchar_unlocked(int c);
  12. int
  13. putw(int w, FILE *stream);

行缓冲,即每次读或者写一行字符的i/o流,每行以一个换行符\r终止。

  
  
  1. #include <stdio.h>
  2. char *
  3. fgets(char * restrict str, int size, FILE * restrict stream);
  4. char *
  5. gets(char *str);

  
  
  1. #include <stdio.h>
  2. int
  3. fputs(const char *restrict s, FILE *restrict stream);
  4. int
  5. puts(const char *s);

全缓冲,也称直接i/o,每次i/o操作读或者写某个数量的对象。

  
  
  1. #include <stdio.h>
  2. size_t
  3. fread(void *restrict ptr, size_t size, size_t nitems,
  4. FILE *restrict stream);
  5. size_t
  6. fwrite(const void *restrict ptr, size_t size, size_t nitems,
  7. FILE *restrict stream);

与文件描述符0,1,2分别指标准输入,标准输出,标准错误文件相对,流也有stdin,stdout,stderr


3. 进程与文件

在一个进程中打开文件,则进程表项中有一个分配一个对应的文件描述符fd和其指针映射;指针指向一个文件表,每当打开一个文件就会分配一个文件表,文件表中存储了文件状态标志(只读,只写,可读可写),当前文件偏移量,v节点指针;v节点指针指向v节点信息,v节点又包含了inode。

a. 使用dup/dup2可以复制fd或者改变fd,这样可以创造进程表中两个fd指向同一个打开文件的情况;

b. 可以打开一个文件两次,则有两个进程表项中分别有fd指向文件表,每个文件表都有其自己的文件偏移量,涉及到同步问题;

c. 一个进程表中,两个fd分别指向不同的文件。