linux中的文件属性
1、linux中各种文件类型
(1)普通文件(- regular file)
- 文本文件
- 文件中内容是由文本构成的,文本指的是ASCII码字符。文件里的内容本质上都是数字(不管什么文件内容本质上都是数字,因为计算机中本身就只有0和1)而文本文件中的数字本身应该被理解为这个数字对应的ASCII码,常见的.c文件、.h文件、.txt文件可被人轻松读懂、编写。
- 二进制文件
- 二进制文件中存储的本质也是数字,只不过这些数字并不是文字的编码数字,而就是真正的数字。常见的可执行程序文件(gcc编译生成的a.out,arm-linux-gcc编译链接生成的.bin)都是二进制文件。
- 文本文件与二进制文件的对比
- 从本质上看,文本文件与二进制文件并没有区别。都是一个文件里面存放了数字,区别就是理解方式不同,如果把这些数字就当作数字处理则就是二进制文件;如果把这些数字按照某种编码格式去编码成文本字符,则就是文本文件。
- 如何知道一个文件是文本文件还是二进制文件?
- 在linux系统层面是不区分这两个的,比如说open、read、write等方法操作二进制文件和文本文件时是没有区别的,所以无法从文件本身准确知道文件属于哪一种,我们只能本来就知道这个文件的类型然后用这种类型的用法去用它。有时会用一些后缀名来人为的标记文件的类型
- 两种不同类型文件的使用
- 使用文本文件时,常规用法就是用文本文件编辑器去打开它,编辑它。常见文本文件编辑器vim、gedit、notepad++、sourceInsight。文本文件编辑器去打开文件的时候,编辑器会read读出文件二进制数字内容,然后按照编码格式将其还原成文字呈现出来。如果用文本编辑器打开以各二进制文件会怎样?编辑器认为这个魏晋至文件还是文本文件试图去将其解码为文字,编码过程很多不对应的文字成了乱码
- 使用二进制阅读工具去读取文本文件得到的就是文本文字对应的二进制的编码
(2)目录文件(d directory)
- 目录就是文件夹,文件夹在linux中也是一种文件,不过是特殊的文件。用vi打开一个文件夹就能看到,文件夹其实也是一种特殊文件,里面存的内容包括这个文件的路径,还有文件夹里面的文件列表
- 但是文件夹这种文件比较特殊,本身并不适合用普通的方式来读写。linux中是使用特殊的一些API来专门读写文件夹的
(3)字符设备文件(c character)
(4)块设备文件(b block)
- 块设备文件对应的是硬件设备,也就是说这个文件虽然在文件系统中存在,但是并不是真正的存在于硬盘的一个文件,而是文件系统虚拟制造出来的(叫虚拟文件系统,如/dev /sys /proc等)
- 虚拟文件系统中的文件大多数不能或者说不用直接读写,而是用一些特殊的API产生或者使用的。
(5)管道文件(p pipe) 管道通讯
(6)套接字文件(s socket) 上网时用
(7)符号链接文件(l link) 软连接(类似于linux中的快捷方式)
2、常用文件属性获取
(1)stat、fstat、lstat函数简介
- 每个文件中都附带了这个文件的一些属性(属性信息是存在于文件本身的,但是它不像文件的内容一样可以被vi直接打开看到,属性信息只能被专用的API打开看到)
- 文件属性信息查看的API有:stat、fstat、lstat作用一样,参数不同,细节略有不同
- linux命令行下还可以使用stat命令去查看文件属性信息,实际上stat命令内部就是使用stat系统调用实现的
- stat这个API的作用:让内核将我们要查找属性的文件的属性信息结构体放入我们传递给stat函数的buf中,当stat这个API调用从内核返回的时候buf中被填充了文件的正确属性信息,然后通过查看buf这个结构体变量的元素就可以得到这个文件的各种属性
- fstat和stat的区别:
- stat是从文件名出发得到文件属性信息结构体,而fstat是从一个已经打开的文件fd出发得到一个文件的属性信息的
- 所以用的时候,若文件没有打开(并不想打开文件操作而只是希望得到文件属性)那就用stat;若文件已经被打开了,然后需要属性,那就使用fstat效率更高(stat是从磁盘静态文件去读取文件的属性,而fstat是从内存读取动态文件的属性)
- lstat和stat/fstat的差别:
- 对于符号链接文件,stat和fstat查阅的是符号链接文件指向的文件的属性,而lstat查阅的是符号链接文件本身的属性
(2)struct stat 结构体简介
-
stat是内核定义的一个结构体,在<sys/stat.h>中声明,所以我们可以用。这个结构体的所有元素加起来就是文件的属性信息
struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* inode number */ mode_t st_mode; /* protection */ 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 };
(3)写个程序来查看一些常见的属性信息
-
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <string.h> #include <unistd.h> #define NAME "1.txt" int main(void) { int ret = -1; struct stat buf; memset(&buf, 0, sizeof(buf)); // memset后buf中全是0 ret = stat(NAME, &buf); // stat后buf中就有内容了 if (ret < 0) { perror("stat"); _exit(-1); } // 成功获取了stat结构体,从中可以看到各种属性信息了 printf("inode = %d.\n", buf.st_ino); printf("size = %d bytes.\n", buf.st_size); printf("st_blksize = %d bytes.\n", buf.st_blksize); return 0; }
3、stat函数的应用案例
(1)用代码判断文件类型
-
文件类型:- d l
-
文件属性中的文件类型标志在struct stat结构体的mode_t st_mode元素中,这个元素其实是一个按位来定义的一个标志位(类似于ARM CPU的CPSR寄存器的模式位定义)。这个东西有很多的标志位共同构成,记录了很多信息,如果要查找是按位&操作就可以知道结果了,但是因为这些位的定义不容易记住,因此linux系统事先定义好了很多宏来进行操作。
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.) // 应用:S_ISREG宏返回值是1就表示这个文件是普通文件,如果文件不是普通文件则返回值是0 ret = S_ISREG(buf.stmode); printf("ret = %d\n", ret); // 判断这个文件的属性 int result0 = S_ISREG(buf.st_mode); // 测试是不是普通文件 int result1 = S_ISDIR(buf.st_mode); // 测试是不是文件夹 printf("result0 = %d.\n", result0); // 1 printf("result1 = %d.\n", result1); // 0
(2)用代码判断文件权限设置(使用位掩码)
-
st_mode不但记录了文件类型,还记录了文件权限
-
linux并没有给文件权限测试提供宏操作,而是提供了位掩码,所以我们只能用位掩码来自己判断是否具有相应的权限
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_IRUSR = 0x00400,所以要右移8位 unsigned int result = (buf.st_mode & S_IRUSR) >> 8; // 测试是否具有可读权限 printf("file owner = %d.\n", result); // 具有就为1,不具有就为0
4、文件权限管理
(1)st_mode中记录的文件权限位
-
st_mode本质是一个32位的数(类型:unsigned int)这个数里的每一个位表示一个含义
-
文件类型和文件权限都记录在st_mode中,用的时候使用专门的掩码取出相应的位即可得到相应的信息
-rwxrwxrwx 1 root root 0 3月 18 14:38 1.txt // -rwxrwxrwx 第一位表示文件类型,第2-4位表示属主的操作权限,第5-7位表示属主所在的组的操作权限,第8-10位表示其他用户的操作权限 // 1 硬链接数 // root 属主(属于那个用户) // root grop群组 // 21 size
(2)文件操作时的权限检查规则
-
一个程序a.out被执行,a.out中试图去操作一个文件1.txt,这时如何判定a.out是否具有对1.txt的某种操作权限呢?
-
判定方法:首先1.txt具有9个权限位,规定了3种人(user、group、other)对该文件的操作权限。所以判定1.txt是否可以被a.out来操作,关键是a.out对于1.txt算是那种人(自己人、其他人)。准确的说是看a.out被谁执行,也就是当前程序(进程)是哪个用户的进程
- a.out被root执行,1.txt的属主及其所在的组为root,那么执行的权限就是user
int main(void) { int ret = -1; ret = open(NAME, O_RDONLY); if (ret > 0) { printf("kedu.\n"); close(ret); } else { perror("read"); } ret = open(NAME, O_WRONLY); if (ret > 0) { printf("kexie.\n"); close(ret); } else { perror("write"); } return 0; }
(3)access函数检查权限设置
-
文本的权限管控其实是很复杂的,一般很难很容易的确定对一个文件是否具有某种权限。优秀的软件:在操作某个文件前先判断当前是否有权限做这个操作,如果有再做如果没有咋提供错误信息给用户
-
access函数可以测试得到当前执行程序的那个用户在当前那个环境下对目标文件是否具有某种操作权限
int main(void) { int ret = -1; ret = access(NAME, F_OK); if(ret < 0) { printf("文件不存在.\n"); return -1; } else { printf("文件存在.\n"); } ret = access(NAME, R_OK); if(ret < 0) { printf("文件不可读.\n"); } else { printf("文件可读.\n"); } ret = access(NAME, W_OK); if(ret < 0) { printf("文件不可写.\n"); } else { printf("文件可写.\n"); } ret = access(NAME, X_OK); if(ret < 0) { printf("文件不可执行.\n"); } else { printf("文件可执行.\n"); } return 0; }
(4)chmod/fchmod与权限修改
-
chmod是一个linux命令,用来修饰文件的各种权限属性,chmod命令只有root用户才有权利去执行修改
chmod u-x xxx
-
chmod命令其内部就是用linux的一个叫chmod的API实现的
(5)chown / fchown / lchown与属主修改
-
linux中有个chown命令修改文件属性
-
chown命令是用chown API实现的
int main(int argc, char **argv) { int ret = -1; if (argc != 2) { printf("usage: %s filename\n", argv[0]); return -1; } ret = chmod(argv[1], S_IRUSR | S_IWUSR | S_IXUSR); if (ret < 0) { perror("chmod"); return -1; } return 0; }
(6)umask与文件权限掩码
- 文件掩码是linux系统中维护的一个全局变量,umask的作用:用来设定系统中新创建的文件的默认权限
- umask 022 umask 0066
- umask命令就是用 umask API实现的
5、读取目录文件
(1)opendir与readdir函数
- opendir打开一个目录后得到一个DIR类型的指针给readdir使用
- readdir函数调用依次就会返回一个struct dirent类型的指针,这个指针指向一个结构体变量,这个结构体变量里面记录了目录项(目录中的一个子文件)
- readdir调用一次只能读出一个目录项,要想读出目录中所有的目录项必须多次调用readdir函数。readdir内部会记住那些项没读,那些项读过了,所以多次调用后不会重复返回已经返回的目录项。让readdir返回NULL时就表示目录中所有的目录项已经读完了。
(2)dirrent结构体 d_type中的值是一些宏定义
-
// dirrent结构体 struct dirent { ino_t d_ino; /* inode number */ off_t d_off; /* not an offset; see NOTES */ unsigned short d_reclen; /* length of this record */ unsigned char d_type; /* type of file; not supported by all filesystem types */ char d_name[256]; /* filename */ }; // d_type中的值的宏定义 DT_BLK This is a block device. DT_CHR This is a character device. DT_DIR This is a directory. DT_FIFO This is a named pipe (FIFO). DT_LNK This is a symbolic link. DT_REG This is a regular file. DT_SOCK This is a UNIX domain socket. DT_UNKNOWN The file type is unknown.
(3)读取目录实战演练
-
#include <stdio.h> #include <dirent.h> int main(int argc, char **argv) { DIR *pDir = NULL; struct dirent *pEnt = NULL; unsigned int cnt = 0; if (argc != 2) { printf("usage: %s dirname.\n", argv[0]); return -1; } pDir = opendir(argv[1]); if (NULL == pDir) { perror("opendir"); return -1; } else { while (1) { pEnt = readdir(pDir); if (pEnt != NULL) { // 还有子文件,在此处理子文件 printf("name: [%s] ,", pEnt->d_name); cnt++; if (pEnt->d_type == DT_REG) { printf("是普通文件\n"); } else if(pEnt->d_type == DT_DIR) { printf("是目录文件\n"); } else { printf("不是普通文件/目录文件\n"); } } else { break; } } printf("总文件数为: %d\n", cnt); } return 0; }
(4)可重入函数介绍
- readdir函数和之前接触的一些函数是不同的。readdir函数直接返回了一个结构体变量指针。因为readdir内部申请了内存并且给返回了地址。多次调用readdir其实readdir内部并不会重复申请内存,而是使用第一次调用readdir时分配的那个内存。这个设计方法是readdir不可重入的关键
- readdir在多次调用时是有关联,这个关联表明readdir函数是不可重入的。有无关联(多次调用)是判断函数是否可重入的关键
- 库函数中有一些函数当年刚开始提供时都是不可重入的(多次调用有关联),后来意识到在多进程中是不安全的,所以库函数进行了重新封装,提供了对应的可重入版本(一般是在不可重入版本函数名后加_r)
- 总结:多次调用该函数有关联,就是不可重入函数,如readdir();多次调用该函数无关联,就是可重入函数,如readdir_r()。可重入与不可重入的本质区别是:不可重入函数内部申请了内存空间,每次调用都使用的是这一份内存空间,而可重入函数内部每次调用都会重新申请一份内存空间。