看表情包学Linux初识文件描述符 | 虚拟文件系统 (VFS) 初探 | 系统传递标记位 | O_TRUNC | O_APPEND
Posted 柠檬叶子C
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了看表情包学Linux初识文件描述符 | 虚拟文件系统 (VFS) 初探 | 系统传递标记位 | O_TRUNC | O_APPEND相关的知识,希望对你有一定的参考价值。
爆笑教程《看表情包学Linux》👈 猛戳订阅!
💭 写在前面:通过上一章节的讲解,想必大家已对文件系统基本的接口有一个简单的了解,本章我们将继续深入讲解,继续学习系统传递标志位,介绍 O_WRONLY, O_TRUNC, O_APPEND 和 O_RDONLY。之后我们就正是打开文件描述符 fd 的大门了,之前我们所探讨讲解的系统文件操作,都是为了给文件描述符做铺垫的,可见这这一块知识点是相当的重要。话不多说,让我们正式开始本章的学习!
📜 本章目录:
0x00 引入:O_WRONLY 没有像 w 那样完全覆盖?
本篇博客全站热榜排名:未上榜
Ⅰ. 系统传递标记位
0x00 引入:O_WRONLY 没有像 w 那样完全覆盖?
语言在 模式打开文件时,文件内容是会被清空的,但是 O_WRONLY 好像并非如此?
💬 代码演示:当前我们的 log.txt 内有 5 行数据,现在我们执行下面的代码:
int main(void)
umask(0);
// 当我们只有 O_WRONLY 和 O_CREAT 时
int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
if (fd < 0)
perror("open");
return 1;
printf("fd: %d\\n", fd);
// 修改:向文件写入 2 行信息
int cnt = 0;
const char* str = "666\\n"; // 修改:内容改成666(方便辨识)
while (cnt < 2)
write(fd, str, strlen(str));
cnt++;
close(fd);
return 0;
🚩 运行结果如下:
❓ 疑点:O_WRONLY 怎么没有像 w 那样完全覆盖???
我们以前在 语言中,w 会覆盖把全部数据覆盖,每次执行代码可都是会清空文件内容的。
而我们的 O_WRONLY 似乎没有全部覆盖,曾经的数据被保留了下来,并没有清空!
其实,没有清空根本就不是读写的问题,而是取决于有没有加 O_TRUNC 选项!
因此,只有 O_WRONLY 和 O_CREAT 选项是不够的:
- 如果想要达到 w 的效果还需要增添 O_TRUNC
- 如果想到达到 a 的效果还需要 O_APPEND
下面我们就来介绍一下这两个选项!
0x01 O_TRUNC 截断清空(对标 w)
在我们打开文件时,如果带上 O_TRUNC 选项,那么它将会清空原始文件。
如果文件存在,并且打开是为了写入,O_TRUNC 会将该文件长度缩短 (truncated) 为 0。
也就是所谓的 截断清空 (Truncate Empty) ,我们默认情况下文件系统调用接口不会清空文件的,
但如果你想清空,就需要给 open() 接口 带上 O_TRUNC 选项:
💬 代码演示:让 open() 达到 fopen 中 "w" 模式的效果
int main(void)
umask(0);
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
perror("open");
return 1;
printf("fd: %d\\n", fd);
// 向文件写入 2 行信息
int cnt = 0;
const char* str = "666\\n";
while (cnt < 2)
write(fd, str, strlen(str));
cnt++;
close(fd);
return 0;
🚩 运行结果如下:
然而 语言的 fopen 函数,只需要浅浅地标上一个 "w" 就能搞定了:
fopen("log.txt", "w");
调一个 w 就以写的方式打开了,不存在会自动创建,并且会完全覆盖原始内容,是如此的简单!
它对应的底层 open 调用,调用接口所传入的选项就是 O_WRONLY, O_CREAT, O_TRUNC。
由此可见, 的 fopen 是多么的好用!open 不仅要传这么多选项,而且属性也要设置:
open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
fopen("log.txt", "w");
0x02 O_APPEND 追加(对标 a)
上一章我们复习了 a 模式, 语言中我们以 a 模式打开文件做到追加的效果。
现在我们用 open,追加是不清空原始内容的,所以我们不能加 O_TRUNC,得加 O_APPEND:
int fd = open("log.txt", O_WRONLY | O_CREATE | O_APPEND, 0666);
💬 代码演示:让 open() 达到 fopen 中 "a" 模式的效果
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(void)
umask(0);
int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
if (fd < 0)
perror("open");
return 1;
printf("fd: %d\\n", fd);
// 向文件写入 2 行信息
int cnt = 0;
const char* str = "666\\n";
while (cnt < 2)
write(fd, str, strlen(str));
cnt++;
close(fd);
return 0;
🚩 运行结果如下:
我们再来对照 语言的 fopen,想做到这样的效果只需要一个 "a" :
open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
fopen("log.txt", "a");
实际上,系统级别的接口本来就是被文件接口封装的,fopen 是系统级文件接口的底层实现。
我们的 a, w, r... 在底层上实际上就是这些 "O_" 组合而成的,使用系统接口麻烦吗?
当然麻烦!要记这么多东西,当然还是 C 语言用的更爽了,一个字母标明文件模式就行了。
0x03 O_REONLY 读取
如果我们想读取一个文件,那么这个文件肯定是存在的,我们传 O_RDONLY 选项:
int main()
umask(0);
int fd = open("log.txt", O_RDONLY);
if (fd < 0)
perror("open");
return 1;
printf("fd: %d\\n", fd);
char buffer[128];
ssize_t s = read(fd, buffer, sizeof(buffer) - 1);
if (s > 0)
buffer[s] = '\\0'; // 最后字符串序列设置为 '\\0'
printf("%s", buffer);
close(fd);
return 0;
🚩 运行结果如下:
Ⅱ. 文件描述符(fd)
0x00 引入:open 参数的返回值
int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
我们使用 open 函数举的例子中,一直是用一个叫做 fd 的变量去接收的。
fopen 中我们习惯使用 fp / pf 接收返回值,那是因为是 fopen 的返回值 FILE* 是文件指针,
file pointer 的缩写即是 fp,所以我们就习惯将这个接收 fopen 返回值的变量取名为 fp / pf。
那为什么接收 open 的返回值的变量要叫 fd 呢?
这个 fd 究竟是何方神圣?我们现在就揭开其神秘面纱,一睹芳容!它就是……
open 如果调用成功会返回一个新的 文件描述符 (file descriptor) ,如果失败会返回 -1 。
- :失败 (success)
- :成功 (failed)
💬 代码演示:我们现在多打开几个文件,观察 fd 的返回值
int main(void)
int fd_1 = open("log1.txt", O_WRONLY | O_CREAT, 0666);
int fd_2 = open("log2.txt", O_WRONLY | O_CREAT, 0666);
int fd_3 = open("log3.txt", O_WRONLY | O_CREAT, 0666);
int fd_4 = open("log4.txt", O_WRONLY | O_CREAT, 0666);
int fd_5 = open("log5.txt", O_WRONLY | O_CREAT, 0666);
printf("fd_1: %d\\n", fd_1);
printf("fd_2: %d\\n", fd_2);
printf("fd_3: %d\\n", fd_3);
printf("fd_4: %d\\n", fd_4);
printf("fd_5: %d\\n", fd_5);
close(fd_1);
close(fd_2);
close(fd_3);
close(fd_4);
close(fd_5);
return 0;
🚩 运行结果如下:
我们发现这 open 的 5 个文件的 (返回值) 分别是 ,那么问题了来了:
① 为什么从 3 开始,而不是从 0 开始?0, 1, 2 去哪了?
- 0:标准输入(键盘,stdin)
- 1:标准输出(显示器,stdout)
- 2:标准错误(显示器,stderr)
系统接口认的是外设,而 标准库函数认的是:
#include <stdio.h>
extern FILE* stdin;
extern FILE* stdout;
extern FILE* stderr;
系统调用接口!那么 stdin, stdout, stderr 和上面的 0,1,2 又有什么关系呢?
想解决这个问题,我们得先说说 :
我们知道,FILE* 是文件指针,那么 是什么呢?它是 库提供的结构体。
只要是结构体,它内部一定封装了多个成员!
虽然 用的是 FILE*,但是系统的底层文件接口只认 ,也就是说:
标准库调用的系统接口,对文件操作而言,系统接口只认文件描述符。
" 文件操作的系统接口属于是六亲不认,只认 fd "
因此, 内部必定封装了文件操作符 !
下面我们来验证一下,先验证 0,1,2 就是标准
💬 代码验证:0 是标准输入 (stdin)
int main(void)
// 验证 0,1,2 就是标准 I/O
char buffer[1024];
ssize_t s = read(0, buffer, sizeof(buffer) - 1);
if (s > 0)
buffer[s] = '\\0';
printf("echo: %s", buffer);
🚩 运行结果如下:
💬 代码验证:stdout 标准写入(1) 和 stderr 错误写入(2) :
int main(void)
const char* s = "Hello, write something\\n";
write(1, s, strlen(s)); // 1:向标准输入写入
write(2, s, strlen(s)); // 2:向标准错误写入
🚩 运行结果如下:
(1 和 2 的区别我们放到后面再讲)
至此,我们证明了 ——
每次我们打开文件虽然打开的是 3,但是可以像 3,4,5,6…… 去写,默认系统就会帮我们打开:
0 (标准输入, stdin) ,1 (标准输出, stdout),2 (错误输出, stderr)
下面我们要做的是,验证一下 0,1,2 和 stdin, stdout 和 stderr 的对应关系。
根据我们目前的分析, 本来就是一个结构体, 因为系统只认 ,
所以 语言本身调用的一定是系统结构,这就直接决定了不管怎么封装,底层必须有 !
💬 代码验证:下面我们就来证明 的存在,证明 stdin, stdout 和 stderr 的对应关系
int main(void)
printf("stdin: %d\\n", stdin->_fileno);
printf("stdout: %d\\n", stdout->_fileno);
printf("stderr: %d\\n", stderr->_fileno);
🚩 运行结果如下:
" 这……就是透过现象看本质!"
函数接口的对应:fopen / fclose / fread / fwrite : open / close / read / write
数据类型的对应:(FILE* → FILE) →
🔺 结论:我们用的 语言接口一定封装了系统调用接口!
② 这个 0, 1, 2, 3, 4, 5……,是不是有点像数组下标?
" 咳咳……不是有点像,它就是数组下标!"
刚才返回 的,用的都是系统接口,是操作系统提供的返回值。
既然操作系统能给你,那说明操作系统内部是有的。
文件描述符的值为什么是 1,2,3,4,5... ?为了理解这个问题,我们需要做大量的铺垫!
0x01 文件描述符的底层理解
💭 逻辑推导:进程:内存文件的关系 → 内存 → 被打开的文件实在内存里面的
一个进程可以打开多个文件,所以在内核中,进程与打开的文件之比为:
所以系统在运行中,有可能会存在大量的被打开的文件 → OS 要对这些被打开的文件进行管理!
OS 如何管理这些被打开的文件呢?还是我们老生常谈的那句话:
先描述,再组织!
所以对我们来说,一个文件被打开不要片面的认为只是对文件内容动动手脚!
它还要 在内核中创建被打开文件的内核数据结构 —— 先描述
struct file
// 包含了你想看到的文件的所有大部分 内容 + 属性
struct file* next;
struct file* prev;
;
* 注:上面的代码是便于理解的,可不是内核真正的代码,真的可远比这复杂得多!
如果你在内核中打开了多个的文件,那么系统会在内核中为文件创建一个 struct file 结构。
可以通过 next 和 prev 将其前后关联起来(内核的链表结构有它自己的设计,这里我们不关注)。
既然你打开了一个文件,就会创建一个 struct file,那么你打开多个文件,
系统中必然会存在大量的 struct file,并且该结构我们用链表的形式链接起来:
如此一来,对被打开的文件的管理,就转化成为了对链表的增删改查!
"这一幕怎么有些似曾相识?我们之前讲进程好像就是这么讲的!task_struct!"
进程与打开的文件之比为 ,进程能打开这么多文件,那么:
进程如何和打开的文件建立映射关系?打开的文件哪一个属于我的进程呢?
在内核中,task_struct 在自己的数据结构中包含了一个 struct files_struct *files (结构体指针):
struct files_struct *files;
而我们刚才提到的 "数组" 就在这个 file_struct 里面,该数组是在该结构体内部的一个数组。
struct file* fd_array[];
该数组类型为 struct file* 是一个 指针数组,里面存放的都是指向 struct file 的指针!
" 指向 struct file 的指针!是不是恍然大悟?这不就是文件的 stuct file 结构么?没错!"
数组元素映射到各个被打开的文件,直接指向对应的文件结构,若没有指向就设为 NULL。
此时,我们就建立起了 "进程" 和 "文件" 之间映射关系的桥梁。
🔍 看图理解:在内核中实现的映射关系
如此一来,进程想访问某一个文件,只需要知道该文件在这张映射表中的数组下标。
上面这些就是在内核中去实现的映射关系了!这个下标 0,1,2,3,4 就是对应的文件描述符 !
我们调用的 open / read / write / close 接口都需要 :
" 可以理解为买彩票,由于关系复杂就不给大家讲故事了,自行理解"
① 选号:当我们 open 打开一个新的文件时,先创建 struct file,然后在当前的文件描述表中分配一个没有被使用的下标,把 stuct file 结构体的地址填入 struct file* 中,然后通过 open 将对应的 返回给用户,比如 3,此时我们的 变量接收的 open 的返回值就是 3 了。
② 兑奖:后续用户再调用 read, write 这样的接口一定传入了对应的 ,找到了特定进程的 files,在根据对应的 索引到指针数组,通过 sturct file* 中存储的 struct file 结构体地址,找到文件对象,之后就可以对相关的操作了。
🔺 总结:其本质是因为它是一个数组下标,系统中使用指针数组的方式,建立进程和文件之间的关系。将 返回给上层用户,上层用户就可以调用后续接口 (read, write...) 来索引对应的指针数组,找到对应文件,这就是 为什么是 0,1,2... 的原因了!
0x02 理解:Linux 下一切皆文件
我们上面说的 0,1,2 → stdin, stdout, stderr → 键盘, 显示器, 显示器,这些都是硬件啊?
也用你上面讲的 struct file 来标识对应的文件吗?在解答这个问题之前,我们需要讲清楚:
" Linux 下一切皆文件 "
一切皆文件这个话题在之前的章节我们已经提过了,但是当时由于知识点尚未展开,没法讲解。
现在我们到了去讲解这个概念的时侯了,希望大家可以尝试去理解 "Linux 下一切皆文件" 。
在这之前我们先说个题外话,其实 语言也是可以模拟面向对象的!
💬 代码演示:在 中用 struct 模拟面向对象
struct file
// 对象的是属性
// 函数指针
void *(readp)(struct file* filep, int fd ...);
void *(writep)(struct file* filep, int fd...);
;
void read(struct file* filep, int fd...)
// 逻辑代码
void write(struct file* filep, int fd...)
// 代码
C++ 本身就是从 C 语言衍生出来的,并不是 "万丈高楼平地起" 的。
是大量工程实战后不断积累的产物,所以 C++ 的面向对象实际上在 C 中也能实现。
我们举个例子:我们在计算机中,有各种硬件:键盘、显示器、磁盘、网卡、其他硬件...
对我们现阶段而言,这些设备我们统一称之为 "外设",下面我们来看图。
🔍 看图理解:注意,下图的 "上层" 是刚才演示的 "映射关系图"
深灰色层:对应的设备和对应的读写方法一定是不一样的。
黑色层:看见的都是 struct file 文件(包含文件属性, 文件方法),OS 内的内存文件系统。
红色箭头:再往上就是进程,如果想指向磁盘,通过 找到对应的 struct file,根据对应的 file 结构调用读写方法,就可以对磁盘进行操作了。如果想指向对应的显示器,通过 fd 找到 struct file……最后调用读写,就可以对显示器操作了…… 以此类推。
虽然指针指向的是差异化的代码,但是在 深灰色层,我们看到的都是 struct file 文件对象!
在这一层我们 以统一的视角看待所有的设备,往上我们就看作 "一切皆文件" !
也就是说:如果想打开一个文件,打开之后把读写方法属性交给 OS,
在内核里给该硬件创建 stuct file,初始化时把对应的函数指针指向具体的设备,
在内核中存在的永远都是 struct file,然后将 struct file 互相之间用链表关联起来。
站在用户的角度看,一个进程看待所有的文件都是以统一的视角看待的,
所以当我们访问一个 file 的时候,这个 file 具体指向底层的哪个文件或设备,
这完全取决于其底层对应的读写方法指向的是什么方法!
这操作是不是感觉很熟悉!?
多态?C++ 中运行时多态用的虚表和虚函数指针,那不就是函数指针么?
"上层使用同一对象,指针指向不同的对象,最终就可以调用不同的方法"
这令人拍手叫绝的操作,你可以理解为:多态的前身
📚 补充:上面画的图,在往上走,就回到了内核的映射关系了:
这里的 struct file 指向的硬件设备是谁,就取决于底层的硬件是怎么设计的了。
通过操作系统层做了一层软件封装,达到了这样的效果。
底层叫硬件,而 具体的硬件读写方法是驱动干的,具体的硬件读写是驱动程序要做的,
OS 只管跟外设要求其提供读写方法,最终 OS 在内核中给它们抽象成 struct file,
把它们都看作文件,然后通过函数指针指向具体的文件对应的设备,就完成了 "一切皆文件" !
0x03 初识 VFS(虚拟文件系统)
上面说的这种设置一套 struct file 来表示文件的内存文件系统的操作,
我们称之为 (virtual file system) ,即 虚拟文件系统 。
虚拟文件系统(VFS)是 Linux 内核中非常有用的一个方面,因为它为文件系统提供了一个通用的接口抽象。VFS 在 SCI 和内核所支持的文件系统之间提供了一个交换层。
0x04 回头看问题:fd 的 0,1,2,3...
至此,我们梳理完了。现在我们再回过头看 fd 的 1,2,3,4... 就能有一个清楚的认识了。
现在我们再我们最开始的问题,想必大家已经做到 "知其然知其所以然" 了!
① 为什么从 3 开始,而不是从 0 开始?0, 1, 2 去哪了?
💡 stdin,stdout,stderr 和 0,1,2 是对应关系,因为 open 时默认就打开了,这也是为什么我们默认打开一个新的文件,fd 是从 3 开始的而不是 0 开始的真正原因!
"突然茅塞顿开,上一章打印出 fd 是 3 的疑惑终于解决了!"
② 0, 1, 2, 3, 4……,是不是有点像数组下标?
💡 不是有点像,它其实上就是数组下标!fd 0,1,2,3,4... 在内核中属于进程和文件的对应关系,是用数组来完成映射的,这些数字就是数组的下标。read, write, close 这些接口都必须用 0,1,2,3,4 来找到底层对应的 struct file 结构,进而访问到底层对应的读写方法 (包括相关的属性,缓冲区等) 。
📌 [ 笔者 ] 王亦优
📃 [ 更新 ] 2023.3.24
❌ [ 勘误 ] /* 暂无 */
📜 [ 声明 ] 由于作者水平有限,本文有错误和不准确之处在所难免,
本人也很想知道这些错误,恳望读者批评指正!
📜 参考资料 C++reference[EB/OL]. []. http://www.cplusplus.com/reference/. Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. . 百度百科[EB/OL]. []. https://baike.baidu.com/. |
看表情包学Linuxshell 命令及运行原理 | Linux 权限 | 文件权限的修改和转让 | 目录的权限 | Sticky bit 粘滞位
🤣 爆笑教程 👉 《看表情包学Linux》👈 猛戳订阅 🔥
💭 写在前面
本章我们将重点讲解 Linux 权限,这是 Linux 基础部分中非常重要的一部分。内容比较干,我会稍稍正经些去讲解。话不多说,我们直接切入正题。
本篇博客全站热榜排名:11
Ⅰ. shell 命令及运行原理
0x00 什么是 shell
严格意义上说的是一个操作系统,我们称之为 —— (核心),
但我们一般用户,是不能直接使用 的,而是通过 的 "外壳" 程序,
也就是所谓的 ,来与 进行 "沟通 " 。
中, 就是命令行解释器(command Interpreter),其主要包含:
① 将使用者的命令翻译给 处理。 ② 同时将 的处理结果翻译给使用者。
中, 就是 (图形化界面),
我们操作 也不是直接操作 内核的,
而是通过图形接口,点击从而完成我们的操作,所以其实也是一个外壳程序。
对于 ,有相同的作用,主要是对我们的指令进行解析,
解析指令给 内核。反馈结果在通过内核运行出结果,通过 解析给用户。
0x01 shell 的意义
❓ 思考:为什么不能直接使用 ,而是要通过 ?
① 降低操作 OS 的成本:用户不善于和内核沟通,需要通过外壳来做解释。
② 保护操作系统:外壳的存在让内核不会暴露给用户,用户所有的非法、不合理操作会直接被外壳拦住,某种意义上来说是对操作系统的一种保护。
我们通常采用的命令行解释器,命令并不是 而是 bash,
它是存在于 /usr/bin 目录下的一个可执行文件:
我们的 常用的命令行解释器(shell),叫做 bash。
他们两个之间有什么关系呢? 是所有解释器的统称,bash 是具体的一款解释器。
bash 属于 中的一种,常见的 工具:sh、bash、csh、tcsh、ash 等。
它们的关系就像是 Linux 和 Centos7.6 的关系,一个是统称的,一个是具体的。
Ⅱ. Linux 权限
0x00 Linux 具体用户的分类
Linux 的用户分为 root 和普通用户,root 为超级用户,就像 Windows 系统中的管理员。
- 超级用户:可以在 Linux 系统下做任何事情,不受限制。
- 普通用户:在 Linux下做有限的事情。
- 超级用户的命令提示符是 # ,普通用户的命令提示符是 $ 。
💭 确认当前你是哪个用户,可以输入 whoami 指令:
whoami
💭 我们当前是普通用户,如果想切换至 root 用户,我们可以输入 su -
su -
root 和 Linux 下任何一个用户,都要设置密码。
建议不要把 root 密码和普通用户的密码设置成一样的! 这不仅是出于安全角度,
设置成一样的话现阶段容易搞混,你不知道什么时候要输的是什么密码。
这里我们要切到 root,所以输入的是 root 的密码:
(输入密码时是不回显的,并不是没输进去)
💭 实操:当我们 su - 并输入正确的密码后,就可以完成用户的切换操作:
$ su -
Password:
在 root 用户下,想要切到普通用户甚至连密码都不需要输:
# su [用户名]
当然,如果你是普通用户想切换到另一个普通用户,当然还是需要输入密码的。
$ su [用户名]
Password:
root 什么身份都可以切,切到你账户删库跑路记录的还是你账户自己删的。
即便是不切,通过 root 拿你数据库的 "私♂密♂数♂据", 多危险!由此可见 root 账号非常重要!
root 密码一定不能设置的太简单!设置的太简单,就相当于是 "裸奔" !
0x01 Linux 中文件相关的权限概念
当我们输入 ls -l 显示详细信息时,你有没有注意过这些信息?
通过 ls -l 显示出来的是 文件的属性,而用过 cat 看到里面的是 文件的内容。
0x02 文件类型
我们既然要学习权限,我们重点要理解 ls -l 显示的若干的字母组合。
-rw-rw-r--
drwxrwxr-x
第一列的第一个字符,叫做 "文件类型" 。
Linux 中不是以文件名后缀区分文件类型的,而是通过 ls -l 显示的第一个字符区分文件类型的。
这有点颠覆我们的直觉,因为我们 Windows 是文件末尾 [ .类型 ] 的格式去区分文件类型:
而在 Linux 下你跟我说文件类型跟后缀没有关系?!我们验证一下看看:
在我们 Windows 下,这里的 a.txt 这就是一个文本,但是在 Linux 是完全不关心你后缀是什么的。
Linux 的文件后缀纯属摆设,我们写后缀只是是为了给自己看的。
📚 概念:Linux 是根据第一列第一个字符去区分文件的类型的。
- - 普通文件,文本、源代码、可执行文件、第三方动态库等。
- 目录文件
- 链接文件(比如快捷方式)
- 管道文件
- 块设备文件,磁盘
- 字符设备文件
📌 注意事项:
既然 Linux 文件后缀纯属摆设,那我们把 test.c 改成 test.txt,然后用 gcc 编译可以吗?
( 絕對不可以!)
虽然 Linux不以文件名后缀区分类型,但是 Linux 并不排斥以后缀做文件类型。
0x03 基本权限
❓ 思考:什么是权限?
① 约束人的(对人进行分类)
② 需要对应的事物具有特定的属性
Linux 上,对文件的权限:
这个文件谁能访问?这个文件谁不能访问?
对应的文件应该具备的某种属性:r,w,执行 x
所以权限的概念以及操作都是围绕人和概念的属性展开的。
在 Linux 系统中,人分三类:
① 文件的拥有者 owner ② 文件的所属组 grouper ③ 文件的其他用户 other
Linux 系统中文件的权限属性分为 r(读)、w(写)、x(可执行)。
root 和普通用户都可称为文件的拥有者、所属组、其他用户,他们并不冲突,反而是相互补充的。
每一组,对应的权限的为止含义是确定的。
那么文件的权限操作,应该涉及到几方面呢? ① 修改文件的属性 ② 修改人
0x04 文件权限值的表示方法
📚 字符表示方法:
Linux 表示 | 说明 | Linux 表示 | 说明 |
只读 | 仅可写 | ||
仅可执行 | 可读可写 | ||
可写可执行 | 可读可执行 | ||
可读可写可执行 | 无权限 |
只需要搞明白 r w x - 这四个字符分别代表说明意思,就能轻松理解。
📚 八进制数值表示方法:推荐使用 👍
权限符号(读写执行) | ||
4 | 100 | |
2 | 010 | |
1 | 001 | |
6 | 110 | |
5 | 101 | |
3 | 011 | |
7 | 111 | |
- - - | 0 | 000 |
(利用八进制数值进行操作,用起来会非常的方便,等下面讲完文件的修改操作后再详细讲解)
0x05 file 指令
❓ 思考:如何区分一个文件?
显然这一串信息是无法满足我们了,我们还有一个专门用来辨识文件类型的指令 —— file 指令。
file [选项] 文件或目录
🔨 功能:辨识文件类型
📜 常用选项:
- -c 详细显示指令执行过程,便于排错或分析程序执行的情形。
- -z 尝试去解读压缩文件的内容。
💭 操作演示:file
Ⅲ. 文件权限的修改
0x00 chmod 指令
chmod [参数] 权限 文件名
🔨 功能:设置文件的访问权限
📜 常用选项:
- R -> 递归修改目录文件的权限。
- 说明:只有文件的拥有者和root才可以改变文件的权限。
chmod 命令权限值的格式:
① 用户表示符+/-=权限字符
- + 向权限范围增加权限代号所表示的权限
- - 向权限范围取消权限代号所表示的权限
- = 向权限范围赋予权限代号所表示的权限
用户符号:
- u:拥有者
- g:拥有者同组用
- o:其它用户
- a:所有用户
比如:
# chmod u+w /home/abc.txt
# chmod o-x /home/abc.txt
看蒙了也没关系,我们直接看操作演示,会轻松很多:
💭 操作演示:去掉某身份的权限
如果我想去掉某身份的读权限,我们可以:
chmod [分类]-[权限] 文件名] # 去除权限
好,现在拥有者、所属组和 other 都没有读的权限了。
我们可以给让它们把读权限加回去吗?当然可以:
chmod [分类]+[权限] 文件名] # 添加权限
0x01 指令的复合使用
当然,还允许复合使用:
0x02 root 用户不受权限约束
当你什么权限都没有时,会受到种种限制:
然而这些限制对至高无上的 root 来说,并没有什么卵用:
0x03 八进制数值表示方法
讲完权限的修改了,我们再回到 Ⅱ.0x04 继续探讨 "八进制数值表示方法"。
权限符号(读写执行) | ||
4 | 100 | |
2 | 010 | |
1 | 001 | |
6 | 110 | |
5 | 101 | |
3 | 011 | |
7 | 111 | |
- - - | 0 | 000 |
任何一个八进制数(0~7)都可以被写成三个比特位,这三个比特位壹壹和每一类人对应。
我们有三类人,则需要3个八进制数与之对应,所以就有了 777、000、333 等更改权限的方案。
譬如 的 - - - 分别对应 ,有或者无(两态),111 → 八进制 → 7。
→ 5 → 101 → ,原理是将八进制看作成二进制,对应上我们的权限。
💭 用法演示:八进制数值表示方法
Ⅳ. 文件权限的转让
0x00 chown 指令
chown [用户名] [文件名] # 修改文件的拥有者
如果我是一个文件的拥有者,我想把文件给别人:
❓ 思考:当你想给别人东西的时候,要不要经过别人的同意?
当然需要!这里我们可以用 sudo 指令强制给别人:
(sudo 有一个叫信任列表的东西,你现在输入如果出现报错是因为 root 没有把你添加到信任列表里,这个我们后期讲完 vim 之后再说。在 /etc/sudoers 路径下。)
当然我们还可以直接切换成 root,通过 root 去给,别人不要也不行,就是这么🐂🍺。
(root 无视权限)
一个文件的创建者和拥有者一定是同一个人吗?
通过我们刚才修改文件的拥有者,我们就能得出 —— 不一定。
0x01 chgrp 指令
chgrp [用户名] [文件名] # 修改文件的所属组
和 chown 一样的用法,我们可以用 chgrp 指令改变文件的所属组 group。
Ⅴ. 目录的权限
0x00 目录的权限
我们来去掉 权限,看看到底能不能进入这个 dir:
这就说明进入一个目录跟 没有关系。
❓ 如果我想进入一个目录,需要说明权限?
如果目录只没有 权限,允许进入、允许在该目录下创建(写入)文件,只不过是不允许查看:
如果目录只没有 权限,允许进入,允许在该目录下查看文件列表,但是不允许创建文件:
如果同时没有 权限,允许进入,但是不允许创建和查看目录下的文件列表:
(注意:不是文件内容,文件的内容是否可以查看由文件自己的权限决定)
💡 Linux 下一切皆文件,目录也是文件。再结合 "文件 = 内容 + 属性" 去理解。
目录里面保存的是部分文件的 "属性" 。
(文件名和 id 之间是有对应的映射关系的,有可能被系统缓存了)
0x01 默认创建后的权限
为什么创建目录的默认权限是 775,而创建普通文件的默认权限是 664 ?
不同系统可能有差异吗?
创建的时候为什么不可以都是 777,权限都给我怼上呢?
📚 默认权限:① 默认目录的起始权限:777 ② 默认普通文件的权限:666
既然如此,那为什么不是 777 / 666,而是 775 / 664 呢?
💡 默认权限 != 最终权限,Linux 中的最终权限 = 默认权限 "去掉" umask 中存在的权限。
0x02 umask 权限掩码
因为系统中有一个默认的 "权限掩码" ,叫做 umask:
umask # 打印权限掩码
📚 权限掩码:凡是在权限掩码中出现的权限,都不应该在最终权限中出现!
所以,最后目录的默认权限变为了 775,文件权限变为了 664。
❓ 思考:所谓的 "去掉" umask 中的权限,是不是减去呢?
💡 这里的 "去掉" 并不是减去。首先要说的是,umask 是可以修改的:
umask [掩码] # 修改umask掩码
由此可见,我们的权限确实是受 umask 的影响的。但是实际上是这样计算的:
最终权限 = 默认权限 & (~umask)
Ⅵ. 粘滞位(Sticky bit)
0x00 共享目录
Linux 下可以存在一些目录,拥有者和所属组是 root,
其他人允许以 other 的身份在该目录下进行文件的创建、读取、删除和修改等操作:
📌 注意事项:在一个共享目录下,你不让我写,不让我读,但防不住我删除!
比如,有两个普通用户,用户A想让用户B访问它的文件,于是就 chmod 把 other 的 全撤咯,
于是用户B急了,恼羞成怒,啊你不给我用你也别想用了,得不到就毁掉!直接给你把文件删了。
如果你仍然是想在共享目录下形成临时文件,但又怕别人给你删了,
你可以给共享的 <目录> 加上粘滞位来解决。
注意,是给目录加粘滞位!即你共享哪个目录,你就在哪个目录下加上粘滞位。
我们下面来隆重介绍一下 Sticky bit ——粘滞位。
0x01 粘滞位
chmod +t [目录名] # 添置粘滞位
💭 以 root 身份给 all 目录添置粘滞位:
目录设置了粘滞位 后,作为 other 就无法随意地删除其他用户的文件了:
不过 root 还是想删就删,想干啥就干啥。
还是那句话 —— 普通用户再怎么进行文件操作,对 root 都是无效的。
📌 [ 笔者 ] 王亦优
📃 [ 更新 ] 2022.7.12
❌ [ 勘误 ] /* 暂无 */
📜 [ 声明 ] 由于作者水平有限,本文有错误和不准确之处在所难免,
本人也很想知道这些错误,恳望读者批评指正!
📜 参考资料 C++reference[EB/OL]. []. http://www.cplusplus.com/reference/. Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. . 百度百科[EB/OL]. []. https://baike.baidu.com/. 比特科技. Linux[EB/OL]. 2021[2021.8.31 xi |
以上是关于看表情包学Linux初识文件描述符 | 虚拟文件系统 (VFS) 初探 | 系统传递标记位 | O_TRUNC | O_APPEND的主要内容,如果未能解决你的问题,请参考以下文章
看表情包学Linuxshell 命令及运行原理 | Linux 权限 | 文件权限的修改和转让 | 目录的权限 | Sticky bit 粘滞位
看表情包学Linux系统下的文件操作 | 文件系统接口 | 系统调用与封装 | open,write,close 接口 | 系统传递标记位 O_RDWR,O_RDONLY,O_WRONLY...
看表情包学Linux插叙:实现简易的 Shell | 通过内建命令实现路径切换 | 再次理解环境变量
看表情包学Linux进程的概念 | 进程控制块 PCB | 父进程与子进程 | 进程 ID | task_struct
看表情包学Linux进程的概念 | 进程控制块 PCB | 父进程与子进程 | 进程 ID | task_struct
看表情包学Linux进程优先级 | 查看系统进程 | 优先级修改 | 进程的切换 | 竞争性与独立性 | 并行并发的概念 | 环境变量