IO详解二 page cashe 与 系统级 IO Shepard-Wang

二 pagecache

1、了解 pagecahe

io2.png

io6.png

pcstat 命令

这是一个 go 开发的查看文件有多少页被缓存进内存的程序。

安装参考:

磁盘上的程序在执行时(如果程序文件很大)不会一次性全部拷贝到物理内存中去,也就是说对应进程的代码段(.txt)不会一次性包含磁盘上程序的全部代码。

使用 pcstat 命令查看 bash 程序有多少页拷贝进内存:

free 命令

dirty page 相关参数

查看参数:

❯ sysctl -a | grep dirty
vm.dirty_background_bytes = 0
vm.dirty_background_ratio = 10
vm.dirty_bytes = 0
vm.dirty_expire_centisecs = 3000
vm.dirty_ratio = 20
vm.dirty_writeback_centisecs = 500
vm.dirtytime_expire_seconds = 43200

参数说明:

  • vm.dirty_background_ratio = x :当 page cashe 的大小占用到可用内存的 $x\%$ 时,开启一个线程淘汰一些页(lru,可能存在在脏页写回磁盘)。
  • vm.dirty_ratio = x :当进程使用的 page cashe 的大小占用到可用内存的 $x\%$ 时,阻塞当前进程,等待操作系统回收可用内存。

  • vm.dirty_expire_centisecs = 3000vm.dirty_writeback_centisecs = 500 centisecs 为 百分之一秒。

修改参数:

❯ vim /etc/sysctl.conf

pmap

pmap $(pidof pm)

-x pid

❯ pmap -x $(pidof main)
105035:   ./main
Address           Kbytes     RSS   Dirty Mode  Mapping
0000557c16c31000       4       4       4 r---- main
0000557c16c32000       4       4       4 r-x-- main
0000557c16c33000       4       4       4 r---- main
0000557c16c34000       4       4       4 r---- main
0000557c16c35000       4       4       4 rw--- main
0000557c16d5c000     132       4       4 rw---   [ anon ]
00007facf1c39000     148     148       0 r---- libc-2.31.so
00007facf1c5e000    1504     768       0 r-x-- libc-2.31.so
00007facf1dd6000     296      64       0 r---- libc-2.31.so
00007facf1e20000       4       0       0 ----- libc-2.31.so
00007facf1e21000      12      12      12 r---- libc-2.31.so
00007facf1e24000      12      12      12 rw--- libc-2.31.so
00007facf1e27000      24      24      24 rw---   [ anon ]
00007facf1e42000       4       4       0 r---- ld-2.31.so
00007facf1e43000     140     140       0 r-x-- ld-2.31.so
00007facf1e66000      32      32       0 r---- ld-2.31.so
00007facf1e6f000       4       4       4 r---- ld-2.31.so
00007facf1e70000       4       4       4 rw--- ld-2.31.so
00007facf1e71000       4       4       4 rw---   [ anon ]
00007ffc648bb000     132      12      12 rw---   [ stack ]
00007ffc649c4000      12       0       0 r----   [ anon ]
00007ffc649c7000       4       4       0 r-x--   [ anon ]
ffffffffff600000       4       0       0 --x--   [ anon ]
---------------- ------- ------- ------- 
total kB            2492    1256      96

这个命令可以看到脏页的大小

-XX pid show me all

更多关于 Page Cache

  • [Page Cache, the Affair Between Memory and FilesMany But Finite](https://manybutfinite.com/post/page-cache-the-affair-between-memory-and-files/)
  • Page Cache and Page Cange Writeback

##

2、IO 方式对比

io7.png

1)直接使用 read/write

io11.jpg

2)使用 mmap 直接使用内核 page cache

可以减少系统调用的次数,这是 java 的 MappedByteBuffer 的的实现。C 貌似没有相应的接口,但是这种思路我觉得很好

io11.png

int * arr = new int[len]; //len is larger than the largest int from the data
fill_n(arr, len, -1);  //fill with -1
long loadFromIndex = 0;
struct stat sizeResults;
long size;
if (stat(fileSrc, &sizeResults) == 0) {
    size = sizeResults.st_size; //here size would be ~551950000 for 552M test file
}
mmapFile = (char *)mmap(NULL, size, PROT_READ, MAP_SHARED, fd, pageNum*pageSize);
long offset = loadFromIndex % pageSize;
while (offset < size) {
    int i = htonl(*((int *)(mmapFile + offset)));
    offset += sizeof(int);
    int j = htonl(*((int *)(mmapFile + offset)));
    offset += sizeof(int);
    swapElem(i, j, arr);
}
return arr;

3)Direct IO

由于是直接读写文件,没有使用内核 page cache,其他进程无法共享文件的缓存。

Direct I/O is a feature of the file system whereby file reads and writes go directly from the applications to the storage device, bypassing the operating system read and write caches. Direct I/O is used only by applications (such as databases) that manage their own caches.

  1. 在 open 文件时加上O_DIRECT 标志,这样以通告内核我们想对该文件进行 直接 io 操作。

  2. 在源文件的最顶端加上 _GNU_SOURCE 宏定义,或在编译时加在命令行上也可以。

  3. 存放文件数据的缓存区起始位置以及每一次读写数据长度必须是磁盘逻辑块大小的整数倍,一般也就是512字节(也有可能是一内存页大小,4096),否则将导致read/write失败,perror将提示:read failed: Invalid argument或write failed: Invalid argument。 1和2很容易做到,而第3点,要满足缓存区起始位置与512对齐,这可以在进行缓存区空间申请时使用posix_memalign这样的函数指定512对齐:

    int ret = posix_memalign((void **)&buf, 512, BUF_SIZE);
    

    或者进行手动调节对齐:

    real_buf = malloc(BUF_SIZE + 512);
    aligned_buf = ((((unsigned int)real_buf + 512 - 1) / 512) * 512);
    
/**
 * gcc direct_io_read_file.c -o direct_io_read_file -D_GNU_SOURCE
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h> 
#include <string.h>
#define BUF_SIZE 1024
 
int main()
{
    int fd;
    int ret;
    unsigned char *buf;
    ret = posix_memalign((void **)&buf, 512, BUF_SIZE);
    if (ret) {
        perror("posix_memalign failed");
        exit(1);
    }
 
    fd = open("./direct_io.data", O_RDONLY | O_DIRECT, 0755);
    if (fd < 0){
        perror("open ./direct_io.data failed");
        exit(1);
    }
 
    do {
        ret = read(fd, buf, BUF_SIZE);
        if (ret < 0) {
            perror("write ./direct_io.data failed");
        }
    } while (ret > 0);
     
    free(buf);
    close(fd);
}
  • [4.9. Direct I/O Red Hat Enterprise Linux 5Red Hat Customer Portal](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/5/html/global_file_system/s1-manage-direct-io)
  • Linux direct io

4)java 的几种 IO 方式

5)使用带缓存的 fopen

见下一节 C 的 IO

三 linux 系统 IO

0、进程对文件描述符的管理

io10.png

父子进程会共享 file ,文件描述符表项

int main()
{	
	int fd = open("new_file", O_RDONLY);
	
	char buf[1024];
	if( fork() == 0)
	{
		read(fd, buf, 10);
		write(1, buf, 10);
	}
	else 
	{
		read(fd, buf, 10);
		write(1, buf, 10);
	}
	
	return 0;
}
cat new_file
write1
line 2
line 3
line 4
line 5
❯ gcc main.c -o main
❯ ./main
write1
line 2
line 3#     

1、cwd 的概念

current work directory 当前工作目录

进程的 cwd 并不是该进程对应的可执行程序所在的路径,而是进程创建时所在的路径。

mian.c 程序如下

#define PRINT_ERR(x) { perror(x), exit(EXIT_FAILURE); }

int main()
{
	int fd = open("new_file", O_RDWR|O_CREAT);
	if(fd == -1) PRINT_ERR("open");
	getchar();
	close(fd);

	return 0;
}
❯ pwd
/root

❯ ./main &
[1] 177120
[1]  + 177120 suspended (tty input)  ./main        

❯ ll /proc/$(pidof main)
total 0
...
lrwxrwxrwx  1 root root 0 May 20 10:03 cwd -> /root
lrwxrwxrwx  1 root root 0 May 20 10:03 exe -> /root/main
...

可执行程序 main 所在的目录为 /root ,当我们在 /root 目录下运行程序时,进程的 cwd 就是 /root

❯ ls
dou  hard_main.c  main  main.c  new_file  soft_main.c  source-git  test  tool  work

new_file 被创建在当前目录下

❯ cd /

❯ ./root/main&
[1] 177437
[1]  + 177437 suspended (tty input)  ./root/main                                                                                                      
❯ ll /proc/$(pidof main)
total 0
...
lrwxrwxrwx  1 root root 0 May 20 10:21 cwd -> /
lrwxrwxrwx  1 root root 0 May 20 10:21 exe -> /root/main
...

❯ ls
bin   dev  GOPATH  lib    lib64   lost+found    new_file  ...

进入 / 目录,再次运行 /root 目录下的 main,发现此时 main 进程的 cwd 变为 /,同时文件生成在了 / 目录下。

2、open

open 是原子操作。

#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>

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

Parameter:

flag 定义打开方式:

  • O_RDONLY 只读

  • O_WRONLY 只写

  • O_RDWR 读写
  • O_CREAT 若文件不存在将创建 一个新文件
  • O_TRUNC 如文件已经存在 , 且是一个普通文件 ,打开模式又是可写就把 文件长度设置为零 , 丢弃其中的现有内容
  • O_EXCL 通过 O_CREAT, 生成 文件 , 若文件已经存在 , 则 open 出错 , 调用失败
  • O_APPEND 文件以追加模式打开

Return value

失败返回 -1;成功,返回整数,代表被打开的文件,是文件描述符(句柄 handler)

文件描述符个数:

$ ulimit -a 

输入上面的命令,我们可以看到一行:

open files                      (-n) 1024

其实我们最多能打开的文件只有 1021 个(1024 - 3),stdin,stdout,stderr 三个文件默认打开。

ulimit 命令查看的是当前进程的信息

如何查看系统最多能打开的文件数呢?

cat /proc/sys/fs/file-max

系统能打开的文件总数受物理内存大小的影响。

文件描述符分配规则

从 3 开始按顺序分配,如果关闭了之前打开的文件描述符,下次分配从最小的未分配的文件描述符开始分配。比如将 0 号文件描述符关闭,下次分配的文件描述符就为 0

3、 read

#include <unistd.h>

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

Return value

  • -1 出错
  • 0 文件读完

程序:输出文件内容

#include<unistd.h>
#include<stdio.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>

#define BUFSIZE 10

int main(void){

  int fd = open("test_open.c", O_RDONLY);
  if(fd == -1) perror("open"),exit(EXIT_FAILURE);

  char buf[BUFSIZE + 1];

  while(1){
    memset(buf, 0x00, sizeof(buf));
    // 一定要把 buf 中的全部元素初始化为 \0 
    // 原因有二:
    // 1.后面输出时使用 %s 输出的,如果没有 \0 会发生越界访问
    // 2.最用一次从文件中读入时,可能没有填满 buf ,这样调用 %s 时就是未定义的 
    
    int r = read(fd, buf, 10);
    if(r == -1) perror("read"),exit(EXIT_FAILURE);
    
    if(r == 0){ 
      printf("\n读取完成\n");
      break;
    }

    printf("%s", buf);

  }

  if( close(fd) == -1 ) perror("close"),exit(EXIT_FAILURE);
  
  return 0;
}

你是否有办法可以一个单词一个单词的打印出来

// 这个程序功能并不完善,仅供参考

#include<unistd.h>
#include<stdio.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h> 

#define BUFSIZE 100
#define IN 1
#define OUT 0

void read_word(char buf[], int bufsize){

  int i, flg = OUT;

  for(i = 0; i < bufsize;){
    // 单词入口
    if(flg == OUT && (isalnum( buf[i]) || buf[i] == '_')){
      flg = IN;
      putchar(buf[i]);
      i++;
    }
    // 读入单词的字母到缓冲区
    else if(flg == IN && (isalnum(buf[i]) || buf[i] == '_')){
      putchar(buf[i]);
      i++;
    }
    // 单词出口
    else if(flg == IN && (!isalnum(buf[i]) && buf[i] != '_')){
      flg = OUT;
      fflush(stdout);
      usleep(500000);
    }
    // 其他字符一个一个打印
    else{
      putchar(buf[i]);
      fflush(stdout);
      usleep(500000);
      i++;
    }
  }
}


int main(void){

  int fd = open("test_open.c", O_RDONLY);
  if(fd == -1) perror("open"),exit(EXIT_FAILURE);

  char buf[BUFSIZE];
  memset(buf, 0x00, sizeof(buf));

  while(1){
    int r = read(fd, buf, BUFSIZE);
    if(r == -1) perror("read"),exit(EXIT_FAILURE);
    
    if(r == 0){ 
      printf("\n读取完成\n");
      break;
    }

    read_word(buf, BUFSIZE);
  }

  if( close(fd) == -1 ) perror("close"),exit(EXIT_FAILURE);
  
  return 0;
}

4、write

#include <unistd.h>

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

Description

write 向文件描述符 fd 所引用的文件中写入从 buf 开始的缓冲区中 count 字节的数据

Return value

成功时返回所写入的字节数(若为零则表示没有写入数据). 错误时返回-1,并置errno为相应值. 若count为零,对于普通文件无任何影响,但对特殊文件将产生不可预料的后果.

程序:实现 cp 的复制文件功能

一般我们使用 cp 命令复制文件时,会这样使用:

cp src dst

.

#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>

int main(int argc, char* argv[]){

  if(argc != 3){
    fprintf(stderr, "%s usage: src dst\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  int src_fd = open(argv[1], O_RDONLY);
  // 打开文件失败
  if(src_fd == -1){
    fprintf(stderr, "open %s failed: %s \n", argv[1], strerror(errno));
    exit(EXIT_FAILURE);
  }

  int dst_fd = open(argv[2], O_WRONLY|O_CREAT, 0644);
  // O_CREAT 表示如果文件不存在,则创建一个新文件
  if(dst_fd == -1){
    close(src_fd);// 关闭打开的 src 文件
    fprintf(stderr, "open %s failed: %s \n", argv[2], strerror(errno));
    exit(EXIT_FAILURE);
  }

  char buf[1024];

  while(1){
    memset(buf, 0x00, 1024);
    
    int num_read = read(src_fd, buf, 1024);
    //读失败
    if(num_read == -1){
      fprintf(stderr, "read failed: %s\n", strerror(errno));
      exit(EXIT_FAILURE);
    }
    if(num_read == 0) 
      break; //读完文件
    
    int num_write = write(dst_fd, buf, num_read);
    //写失败
    if(num_write == -1){
      fprintf(stderr, "write failed: %s\n", strerror(errno));
      exit(EXIT_FAILURE);
    }
  
  }

  close(src_fd);
  close(dst_fd);
  return 0;
}

如果目标文件 dst 已经存在且有内容,src 并不会 覆盖 dst 的全部内容,只会覆盖 dst 文件中前 src 文件字节数的字节。

如果想要覆盖 dst 文件,在打开 dst 文件时需要加上 O_TRUNC选项

int dst_fd = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0644);

5、lseek

#include <sys/types.h> #include <unistd.h>

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

whence 选项

SEEK_SET 文件首 The offset is set to offset bytes.

SEEK_CUR 现在的位置 The offset is set to its current location plus offset bytes.

SEEK_END 文件尾 The offset is set to the size of the file plus offset bytes.

offset

正数向后,负数向前

Return value

Upon successful completion, lseek() returns the resulting offset location as measured in bytes from the beginning of the file. On error, the value (off_t) -1 is returned and errno is set to indicate the error.

如何以追加的方式向文件写?

lseek(fd, 0, SEEK_END); // 将文件指针定位在文件尾

程序:文件随机读取

#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>

typedef struct Student{
  int id;
  char name[20];
}Stu; 

void input(int fd, int n){

  Stu student;
  int i = 0;

  while(n-- > 0){
    student.id = i++;
    scanf("%s", student.name);
    if( write(fd, &student,sizeof(Stu)) == -1) perror("write failed."), exit(EXIT_FAILURE); // 写入 Stu 大小的内容 
  }
}

void output(int fd, int n){

  int no;
  
  do{
  printf("查看第几名学生的信息:");
  scanf("%d", &no);
  }while(!(no <= n && no > 0)); // 输入的学生数应该比 0 大 比 学生总人数小
  
  Stu buf;

  lseek(fd, sizeof(Stu) * (no - 1), SEEK_SET);
  if( read(fd, &buf, sizeof(Stu)) == -1 ) perror("read failed."), exit(EXIT_FAILURE);// 向 read 传入 buf的地址,不要忘了 &
  printf("ID: %d name: %s\n", buf.id, buf.name);
}


int main(void){
  
  int fd = open("Student", O_RDWR|O_CREAT|O_APPEND, 0644);
  if(fd == -1) perror("open failed."), exit(EXIT_FAILURE);

  int n;

  printf("请输入学生数:");
  scanf("%d", &n);

  input(fd, n);
  output(fd, n);

  close(fd);
  if(fd == -1) perror("close failed."), exit(EXIT_FAILURE);

  return 0;
}

6、dup & dup2

除了 fork 子进程外,dup 函数也可以使引用计数增加

int dup(int oldfd);

#include <unistd.h>

These system calls create a copy of the file descriptor oldfd.

有时候我们会把程序的输出不显示到屏幕上(stdout),而是重定向到一个文件中;或者使用echo "123" >file。这是如何实现的呢?

我们知道标准输出的 fd 为 1 。而 dup 创建的新的 fd 为最小的(从 0 开始)那个没有使用的数:

int fd = open("output.txt", O_RWONLY);
close(1); // 关闭标准输出
dup(fd); // 1 号 fd 指向 output.txt

之后该程序的输出就会输出到这个 1 号 fd 指示的文件中了。

7、fcntl

#include <unistd.h> #include <fcntl.h>

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

fcntl - manipulate file descriptor

File descriptor flags

F_GETFD (void) Read the file descriptor flags; arg is ignored.

F_SETFD (int) Set the file descriptor flags to the value specified by arg.

File status flags

F_GETFL (void) Get the file access mode and the file status flags; arg is ignored.

F_SETFL (int) Set the file status flags to the value specified by arg. File access mode (O_RDONLY, O_WRONLY, O_RDWR) and file creation flags(i.e., O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC) in arg are ignored.

On Linux this command can change only the O_APPEND, O_ASYNC,O_DIRECT, O_NOATIME, and O_NONBLOCK flags.

Advisory locking

F_GETLK, F_SETLK and F_SETLKW are used to acquire, release, and test for the existence of record locks

struct flock { … short l_type; /Type of lock: F_RDLCK,F_WRLCK, F_UNLCK / short l_whence; /How to interpret l_start:SEEK_SET, SEEK_CUR, SEEK_END / off_t l_start; /Starting offset for lock / off_t l_len; /Number of bytes to lock / pid_t l_pid; /PID of process blocking our lock (F_GETLK only) / … };

short l_type:

F_RDLCK : 读锁,共享锁。多个进程可以同时读取;但是在一个进程写的时候,其他进程不能读

F_WRLCK : 写锁, 排他锁。只要有一个进程上锁,其他进程无法打开文件

F_UNLCK : 解锁

1. 删除文件描述符

FD_CLOEXEC

使用 exec 系列函数替换进程时,默认新的进程可以使用被替换的进程打开的文件描述符。为了避免这种情况,可以在调用 exec 前调用 fntl 函数。下面的例子演示了如何在替换进程前关闭 fd 为 1 的文件描述符。

fcntl(1, F_SETFD, FD_CLOEXEC);
execlp("./ttt", "./ttt", NULL);

ttt 是由 ttt.c 编译来的可执行程序。 ttt.c 中有一条 printf 语句。运行上面的程序,没有任何输出(标准输出 1 被关闭)。

比如原进程 打开了一个文件,文件描述符是 4,然后调用 fcntl 关闭该文件,再调用 execlp。如果你在进程被替换后使用 lsof -p 命令会发现 4 号文件没有了。

注意fcntl 不会在当前进程中关闭该文件。你可以在 fcntl 后加一条 printf 语句,看看会不会写出到标准输出。

2. 设置文件非阻塞

O_NONBLOCK : 设置为非阻塞(有内容直接读,没有则跳过)

#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>

int main(void){

  char buf[1024] = {};
  int flg = fcntl(0, F_GETFL);

  fcntl(0, F_SETFL, flg|O_NONBLOCK); //O_NONBLOCK 设置为非阻塞
  
  int r = read(0, buf, sizeof(buf));
  if(r == -1) perror("read"), exit(EXIT_FAILURE);

  printf("buf = [%s]\n", buf);

  return 0;
}

输出:

read: Resource temporarily unavailable

为了读入内容,而键盘输入又过于慢,我们可以使用输入重定向:

$ ./a.out <file

8、 stat

stat 函数可以显示文件部分元数据(metadata) ,比如 ls -l 显示的信息。文件的元数据存放在结构体 inode 中。

` #include <sys/types.h> #include <sys/stat.h> #include `

` int stat(const char *pathname, struct stat *buf);`

  The stat structure
       All of these system calls return a stat structure, which contains the following fields:
struct stat {
    dev_t     st_dev;         /* ID of device containing file */ 设备编号
    ino_t     st_ino;         /* inode number */
    mode_t    st_mode;        /* file type and mode */ 权限
    nlink_t   st_nlink;       /* number of hard links */
    uid_t     st_uid;         /* user ID of owner */
    gid_t     st_gid;         /* group ID of owner */
    dev_t     st_rdev;        /* device ID (if special file) */
    off_t     st_size;        /* total size, in bytes */ 文件大小
    blksize_t st_blksize;     /* blocksize for filesystem I/O */
    blkcnt_t  st_blocks;      /* number of 512B blocks allocated */

    /* Since Linux 2.6, the kernel supports nanosecond
    precision for the following timestamp fields.
    For the details before Linux 2.6, see NOTES. */

    struct timespec st_atim;  /* time of last access */  最后一次被访问时间
    struct timespec st_mtim;  /* time of last modification */ 最后一次修改时间
    struct timespec st_ctim;  /* time of last status change */ 文件元数据被改时间

    #define st_atime st_atim.tv_sec      /* Backward compatibility */
    #define st_mtime st_mtim.tv_sec
    #define st_ctime st_ctim.tv_sec
};
Because tests of the above form are common, additional macros are defined by POSIX  to allow the test of the file type in st_mode to be written more concisely:

S_ISREG(m)  is it a regular file?

S_ISDIR(m)  directory?

S_ISCHR(m)  character device?

S_ISBLK(m)  block device?

S_ISFIFO(m) FIFO (named pipe)?

S_ISLNK(m)  symbolic link?  (Not in POSIX.1-1996.)

S_ISSOCK(m) socket?  (Not in POSIX.1-1996.)

The preceding code snippet could thus be rewritten as:

stat(pathname, &sb);
if (S_ISREG(sb.st_mode)) {
    /* Handle regular file */
}

0. struct stat 和 struct inode 的区别

下面是 inode 结构体:

struct inode {
	/**
	 * 通过此字段将对象放入哈希表。
	 */
	struct hlist_node	i_hash;
	/**
	 * 通过此字段将队列链入不同状态的链表中。
	 */
	struct list_head	i_list;
	/**
	 * 通过此字段将其链入到超级块的inode链表中。
	 */
	struct list_head	i_sb_list;
	/**
	 * 引用索引节点的目录项对象链表头。
	 */
	struct list_head	i_dentry;
	/**
	 * 索引节点编号。
	 */
	unsigned long		i_ino;
	/**
	 * 引用计数器。
	 */
	atomic_t		i_count;
	/**
	 * 文件类型与访问权限。
	 */
	umode_t			i_mode;
	/**
	 * 硬链接数目。
	 */
	unsigned int		i_nlink;
	/**
	 * 所有者ID
	 */
	uid_t			i_uid;
	/**
	 * 所有者组标识符。
	 */
	gid_t			i_gid;
    /**
	 * 对表示设备文件的inode结构,该字段包含了真正的设备编号。
	 */
	dev_t			i_rdev;
	/**
	 * 文件的字节数。
	 */
	loff_t			i_size;
	/**
	 * 上次访问文件的时间。
	 */
	struct timespec		i_atime;
	/**
	 * 上次与文件的时间。
	 */
	struct timespec		i_mtime;
	/**
	 * 上次修改索引节点的时间。
	 */
	struct timespec		i_ctime;
	/**
	 * 块的位数。
	 */
	unsigned int		i_blkbits;
	/**
	 * 块的字节数。
	 */
	unsigned long		i_blksize;
	/**
	 * 版本号,每次使用后递增。
	 */
	unsigned long		i_version;
	/**
	 * 文件的块数。
	 */
	unsigned long		i_blocks;
	/**
	 * 文件最后一个块的字节数。
	 */
	unsigned short          i_bytes;
	/**
	 * 非0表示文件是一个套接字。
	 */
	unsigned char		i_sock;
	/**
	 * 保护索引节点某些字段的自旋锁。
	 */
	spinlock_t		i_lock;	/* i_blocks, i_bytes, maybe i_size */
	/**
	 * 保护索引节点的信号量。
	 */
	struct semaphore	i_sem;
	/**
	 * 在直接IO文件操作中避免出现竞争条件的读写信号量。
	 */
	struct rw_semaphore	i_alloc_sem;
	/**
	 * 索引节点的操作。
	 */
	struct inode_operations	*i_op;
	/**
	 * 缺省文件操作。
	 */
	struct file_operations	*i_fop;	/* former ->i_op->default_file_ops */
	/**
	 * inode所在的超级块。
	 */
	struct super_block	*i_sb;
	/**
	 * 文件锁链表,通过此字段将文件上的所有锁链接成一个单链表。
	 */
	struct file_lock	*i_flock;
	/**
	 * 指向address_space对象的指针。
	 */
	struct address_space	*i_mapping;
	/**
	 * 文件的address_space对象。
	 */
	struct address_space	i_data;
#ifdef CONFIG_QUOTA
	/**
	 * 索引节点的磁盘限额。
	 */
	struct dquot		*i_dquot[MAXQUOTAS];
#endif
	/* These three should probably be a union */
	/**
	 * 用于具体的字符或块设备的索引节点链表指针。
	 */
	struct list_head	i_devices;
	/**
	 * 如果内核是一个管道则非0
	 */
	struct pipe_inode_info	*i_pipe;
	/**
	 * 指向块设备驱动程序的指针。
	 */
	struct block_device	*i_bdev;
	/**
	 * 表示字符设备的内部数据结构。当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针。
	 */
	struct cdev		*i_cdev;
	/**
	 * 次设备号索引。
	 */
	int			i_cindex;

	/**
	 * 索引节点版本号。由某些文件系统使用。
	 */
	__u32			i_generation;

#ifdef CONFIG_DNOTIFY
	/**
	 * 目录通知事件掩码。
	 */
	unsigned long		i_dnotify_mask; /* Directory notify events */
	/**
	 * 用于目录通知。
	 */
	struct dnotify_struct	*i_dnotify; /* for directory notifications */
#endif

	/**
	 * 索引节点状态标志。
	 */
	unsigned long		i_state;
	/**
	 * 索引节点弄脏的时间,以jiffies为单位。
	 */
	unsigned long		dirtied_when;	/* jiffies of first dirtying */

	/**
	 * 文件系统的安装标志。
	 */
	unsigned int		i_flags;

	/**
	 * 用于写进程的引用计数。
	 */
	atomic_t		i_writecount;
	/**
	 * 索引节点安全结构。
	 */
	void			*i_security;
	/**
	 * 文件系统私有数据指针。
	 */
	union {
		void		*generic_ip;
	} u;
#ifdef __NEED_I_SIZE_ORDERED
	/**
	 * SMP系统为i_size字段获取一致性时使用的顺序计数器。
	 */
	seqcount_t		i_size_seqcount;
#endif
};

1. 设备编号

st_dev 高 8 位存储的是主设备号,低八位存储的是次设备号

程序演示:

#include<unistd.h>
#include<sys/stat.h>
#include<stdio.h>
#include<stdlib.h>

int main(void){

  struct stat state;
  stat("test_st_dev.c", &state);

  printf("主设备号:%hhu\n", (state.st_dev >> 8) & 0xFF);// 右移八位获取高八位,按位与 0xFF(256)
  printf("次设备号:%hhu\n", state.st_dev & 0xFF);
  
  return 0;
}

运行结果:

[dev@localhost 04]$ ./a.out 
主设备号:253
次设备号:0

我们来验证一下该文件在系统中的设备号和我们打印的是否相同:

22.png

2. 文件类型和权限

类型:
The following mask values are defined for the file type of the st_mode field:

S_IFMT     0170000   bit mask for the file type bit field

S_IFSOCK   0140000   socket
S_IFLNK    0120000   symbolic link
S_IFREG    0100000   regular file
S_IFBLK    0060000   block device
S_IFDIR    0040000   directory
S_IFCHR    0020000   character device
S_IFIFO    0010000   FIFO

Thus, to test for a regular file (for example), one could write:

stat(pathname, &sb);
if ((sb.st_mode & S_IFMT) == S_IFREG) {
/* Handle regular file */
}

权限:           
The following mask values are defined for the file mode component of the st_mode field:

S_ISUID     04000   set-user-ID bit
S_ISGID     02000   set-group-ID bit (see below)
S_ISVTX     01000   sticky bit (see below)

S_IRWXU     00700   owner has read, write, and execute permission
S_IRUSR     00400   owner has read permission
S_IWUSR     00200   owner has write permission
S_IXUSR     00100   owner has execute permission

S_IRWXG     00070   group has read, write, and execute permission
S_IRGRP     00040   group has read permission
S_IWGRP     00020   group has write permission
S_IXGRP     00010   group has execute permission

S_IRWXO     00007   others  (not  in group) have read, write, and
execute permission
S_IROTH     00004   others have read permission
S_IWOTH     00002   others have write permission
S_IXOTH     00001   others have execute permission

其他

进程 ID 查询

  1. ps -ef | grep "可执行程序名称"
  2. pidof 可执行程序名称