一、linux中各种文件类型
1、普通文件(- regular file)
- 文本文件。文件中的内容是由文本构成的,文本指的是ASCII码字符。文件里的内容本质上都是数字(不管什么文件内容本质上都是数字,因为计算机中本身就只有1和0),而文本文件中的数字本身应该被理解为这个数字对应的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)
二、常用文件属性获取
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,如果文件已经被打开了然后要属性那就用fsta效率会更高(stat是从磁盘去读取文件的,而fstat是从内存读取动态文件的)。
- lstat和stat/fstat的差别在于:对于符号链接文件,stat和fstat查阅的是符号链接文件指向的文件的属性,而lstat查阅的是符号链接文件本身的属性。
- int stat(const char *path, struct stat *buf);
- int fstat(int fd, struct stat *buf);
- int lstat(const char *path, struct stat *buf);
- stat:文件没有被打开时使用stat(符号链接文件指向的文件的属性)
- fstat:文件已经被打开时使用fstat(符号链接文件指向的文件的属性)
- lstat:需要查看符号链接文件本身的属性时使用lstat
2、struct stat结构体简介
- struct stat是内核定义的一个结构体,在<sys/stat.h>中声明,所以我们可以用。这个结构体中的所有元素加起来就是我们的文件属性信息。
3、写个程序来查看一些常见属性信息
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define NAME "1.txt"
int main(void)
{
int ret = -1;
struct stat buf;
memset(&buf, 0, sizeof(buf));
ret = stat(NAME, &buf);
if (ret < 0)
{
perror("stat");
exit(-1);
}
else
{
printf("inode = %d.\n", buf.st_ino);
printf("size = %d bytes.\n", buf.st_size);
printf("st_blksize = %d.\n", buf.st_blksize);
}
return 0;
}
三、stat函数的应用案例
1、用代码判断文件类型
- 文件类型就是-、d、l····
- 文件属性中的文件类型标志在struct stat结构体的mode_t st_mode元素中,这个元素其实是一个按位来定义的一个位标志(有点类似于ARM CPU的CPSR寄存器的模式位定义)。这个东西有很多个标志位共同构成,记录了很多信息,如果要查找时按位&操作就知道结果了,但是因为这些位定义不容易记住,因此linux系统给大家事先定义好了很多宏来进行相应操作。
- 譬如S_ISREG宏返回值是1表示这个文件是一个普通文件,如果文件不是普通文件则返回值是0.
- S_ISDIR宏返回值是1表示这个文件是一个文件夹,如果文件不是文件夹则返回值是0.
int result = S_ISDIR(buf.st_mode);
printf("result = %d\n", result); //result = 1 代表是普通文件
2、用代码判断文件权限设置
- st_mode中除了记录了文件类型之外,还记录了一个重要信息:文件权限。
- linux并没有给文件权限测试提供宏操作,而只是提供了位掩码,所以我们只能用位掩码来自己判断是否具有相应权限。
- S_IRUSR 宿主可读权限
- S_IWUSR 宿主可写权限
- S_IXUSR 宿主可执行权限
unsigned int result = ((buf.st_mode & S_IRUSR)? 1: 0); //文件权限测试
printf("file owner: %u.\n", result); //宿主可读
四、文件权限管理1
1、st_mode中记录的文件权限位
- st_mode本质上是一个32位的数(类型就是unsinged int),这个数里的每一个位表示一个含义。
- 文件类型和文件的权限都记录在st_mode中。我们用的时候使用专门的掩码去取出相应的位即可得知相应的信息。
2、ls -l打印出的权限列表
- 123456789一共9位,3个一组。
1、第一组三个表示文件的属主(owner、user)对该文件的可读、可写、可执行权限;
2、第2组3个位表示文件的属主所在的组(group)对该文件的权限;
3、第3组3个位表示其他用户(others)对该文件的权限。 - 属主就是这个文件属于谁,一般来说文件创建时属主就是创建这个文件的那个用户。
- 但是我们一个文件创建之后还可以用chown命令去修改一个文件的属主,还可以用chgrp命令去修改一个文件所在的组。
3、文件操作时的权限检查规则
- 一个程序a.out被执行,a.out中试图去操作一个文件1.txt,这时候如何判定a.out是否具有对1.txt的某种操作权限呢?
- 判定方法是:首先1.txt具有9个权限位,规定了3种人(user、group、others)对该文件的操作权限。
- 所以我们判定1.txt是否能被a.out来操作,关键先搞清楚a.out对1.txt到底算哪种人。
- 在执行a.out对应一个用户,而这个用户对于1.txt是哪种人。
- 注意root用户比较特殊
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define NAME "1.txt"
int main(void)
{
int ret = -1;
ret = open(NAME, O_RDONLY);
if (ret > 0)
{
printf("可读 ");
close(ret);
}
else
{
perror("read");
}
ret = open(NAME, O_WRONLY);
if (ret > 0)
{
printf("可写 ");
close(ret);
}
else
{
perror("write");
}
return 0;
}
五、文件权限管理2
1、access函数检查权限设置
- 文本权限管控其实蛮复杂,一般很难很容易的确定对一个文件是否具有某种权限。设计优秀的软件应该是:在操作某个文件之前先判断当前是否有权限做这个操作,如果有再做如果没有则提供错误信息给用户。
- access函数可以测试得到当前执行程序的那个用户在当前那个环境下对目标文件是否具有某种操作权限。
#include <stdio.h>
#include <unistd.h>
#define NAME "3.txt"
int main(void)
{
int ret = -1;
ret = access(NAME, F_OK);
if (ret < 0)
{
printf("文件不存在 \n");
return -1;
}
else
{
printf("文件存在 ");
}
ret = access(NAME, R_OK);
if (ret < 0)
{
printf("不可读 ");
}
else
{
printf("可读 ");
}
ret = access(NAME, W_OK);
if (ret < 0)
{
printf("不可写 ");
}
else
{
printf("可写 ");
}
ret = access(NAME, X_OK);
if (ret < 0)
{
printf("不可执行 \n");
}
else
{
printf("可执行 \n");
}
return 0;
}
2、chmod/fchmod与权限修改
- chmod是一个linux命令,用来修改文件的各种权限属性。chmod命令只有root用户才有权利去执行修改。
- chmod命令其实内部是用linux的一个叫chmod的API实现的。
- fchmod命令是改变已经打开的文件的权限
#include <stdio.h>
#include <sys/stat.h>
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 | S_IRGRP | S_IWOTH);
if (ret < 0)
{
perror("chmod");
return -1;
}
return 0;
}
3、chown/fchown/lchown与属主修改
- linux中有个chown命令来修改文件属主
- chown root 1.txt //将1.txt的属主改为root
- chgrp root 1.txt //修改组
- chown命令是用chown API实现的
4、umask与文件权限掩码
- 文件掩码是linux系统中维护的一个全局设置,umask的作用是用来设定我们系统中新创建的文件的默认权限的。
- umask 022 -rw-r–r--
- umask 044 -rw–w--w-
- umask 066 -rw------- //哪一位为1则没有权限
- umask 000 -rw-rw-rw- //可执行权限不能被设置为默认权限
- chmod a+x 1.txt -rwxrwxrwx //可以用chmod函数追加可执行权限
- umask命令就是用umask API实现的
六、读取目录文件
1、opendir与readdir函数
- opendir打开一个目录后得到一个DIR类型的指针给readdir使用
- readdir函数调用一次就会返回一个struct dirent类型的指针,这个指针指向一个结构体变量,这个结构体变量里面记录了一个目录项(所谓目录项就是目录中的一个子文件)。
- readdir调用一次只能读出一个目录项,要想读出目录中所有的目录项必须多次调用readdir函数。readdir函数内部户记住哪个目录项已经被读过了哪个还没读,所以多次调用后不会重复返回已经返回过的目录项。当readdir函数返回NULL时就表示目录中所有的目录项已经读完了。
2、dirent结构体
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.
3、读取目录实战演练
#include <stdio.h>
#include <sys/types.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;
}
while (1)
{
pEnt = readdir(pDir);
if(pEnt != NULL)
{
// 还有子文件,在此处理子文件
printf("name:[%s] ,", pEnt->d_name);
cnt++;
if (pEnt->d_type == DT_REG)
{
printf("是普通文件\n");
}
else
{
printf("不是普通文件\n");
}
}
else
{
break;
}
};
printf("总文件数为:%d\n", cnt);
return 0;
}
4、可重入函数介绍
- 有些函数是可重入的有些是不可重入的,具体概念可以去百度。
- readdir函数和我们前面接触的一些函数是不同的,首先readdir函数直接返回了一个结构体变量指针,因为readdir内部申请了内存并且给我们返回了地址。多次调用readdir其实readir内部并不会重复申请内存而是使用第一次调用readdir时分配的那个内存。这个设计方法是readdir不可重入的关键。
- readdir在多次调用时是有关联的,这个关联也标明readdir函数是不可重入的。
- 库函数中有一些函数当年刚开始提供时都是不可重入的,后来意识到这种方式不安全,所以重新封装了C库,提供了对应的可重复版本(一般是不可重入版本函数名==_r==)
——资料来源于朱老师物联网大讲堂