《深入理解Linux内核》读书笔记—第1章:绪论

Unix内核概述

你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

进程/内核模式

当一个程序在用户态下执行时,它不能直接访问内核数据结构或内核的程序。

在这里插入图片描述

内核本身并不是一个进程而是进程的管理者。

内核进程:

它们以内核态运行在内核地址空间。

它们不与用户直接交互,因此不需要终端设备。

它们通常在系统启动时创建,一直活跃着直到系统关闭

有集中方式激活内核例程:

进程调用一个系统调用

正在执行进程的CPU发出一个异常信号(异常是一些反常情况,例如,一个无效的指令)。内核代表产生异常的进程处理异常。

外围设备向CPU发出一个中断信号以通知一个事件的发生,如一个要求注意的请求、一个状态的变化或一个I/O操作已经完成等。

内核线程被执行。因为内核线程运行在内核态,所以,尽管其相应的程序被包装成一个进程,但必须认为它是内核的一部分。

进程的执行

为了让内核管理进程,每个进程由一个进程描述符(process descriptor)表示,这个描述符包含有关进程当前状态的信息。

当内核暂停一个进程的执行时,他在进程描述符中保存几个处理器寄存器的内容。这些寄存器包括:

程序计数器(PC)和栈指针(SP)寄存器

通用寄存器

浮点寄存器

包含CPU状态信息的处理器控制寄存器(Processor Status Word, PSW)

用来跟踪进程对RAM访问的内存管理寄存器

进程

信号量

共享内存

消息队列

父进程、子进程、程序

僵死进程

进程组和登录会话

内存管理

术语:
虚拟内存(Virtual Memory)
硬件内存管理单元(MMU:Memory Manage Unit)
虚拟地址空间(Virtual Address Space)
页框(Page Frame),4K或8K
内核内存分配器(KMA:Kernel Memory Allocator)
碎片(fragmentation)

集中KMA算法:
资源图分配算法(allocator)
2的幂次方空闲列表
McKusick-Karels分配算法
伙伴(Buddy)系统
Mach的区域(Zone)分配算法
Dynix分配算法
Solaris的slab分配算法
请求调页(demand Paging)

文件系统

Linux ln命令

http://c.biancheng.net/view/740.html

访问权限和文件模式

文件潜在的用户分为三种类型:
作为这个文件所有者的用户
同组用户,不包括所有者
所有剩下的用户

三种访问权限:读、写、执行

三种附加标记:
suid(Set User ID):进程执行一个文件时通常保持进程拥有者的UID。然而,如果设置了可执行文件suid的标志位,进程就获得了该文件拥有者的UID。
sgid(Set Group ID):进程执行一个文件时保持进程组的GID。然而,如果设置了可执行文件sgid的标志位,进程就获得了该文件组的ID。
sticky(用来定义文件模式):设置了sticky标志位的可执行文件相应地对内核发出一个请求,当它执行结束以后,亦然将改程序保留在内存中。【这个标记的含义已经发生变化,1999年就已经变了,何况已经2021了,天哪我都这么老了……】

http://c.biancheng.net/view/868.html

Linux SetUID(SUID)文件特殊权限用法详解
在讲解《权限位》一节时提到过,其实除了 rwx 权限,还会用到 s 权限,例如:

[root@localhost ~]# ls -l /usr/bin/passwd
-rwsr-xr-x. 1 root root 22984 Jan  7  2007 /usr/bin/passwd

可以看到,原本表示文件所有者权限中的 x 权限位,却出现了 s 权限,此种权限通常称为 SetUID,简称 SUID 特殊权限。

SUID 特殊权限仅适用于可执行文件,所具有的功能是,只要用户对设有 SUID 的文件有执行权限,那么当用户执行此文件时,会以文件所有者的身份去执行此文件,一旦文件执行结束,身份的切换也随之消失。

举一个例子,我们都知道,Linux 系统中所有用户的密码数据都记录在 /etc/shadow 这个文件中,通过 ll /etc/shadow 命令可以看到,此文件的权限是 0(---------),也就是说,普通用户对此文件没有任何操作权限。

这就会产生一个问题,为什么普通用户可以使用 passwd 命令修改自己的密码呢?

本节开头已经显示了 passwd 命令的权限配置,可以看到,此命令拥有 SUID 特殊权限,而且其他人对此文件也有执行权限,这就意味着,任何一个用户都可以用文件所有者,也就是 root 的身份去执行 passwd 命令。

Linux 系统中,绝对多数命令的文件所有者默认都是 root。

换句话说,当普通用户使用 passwd 命令尝试更改自己的密码时,实际上是在以 root 的身份执行passwd命令,正因为 root 可以将密码写入 /etc/shadow 文件,所以普通用户也能做到。只不过,一旦命令执行完成,普通用户所具有的 root身份也随之消失。

如果我们手动将 /usr/bin/passwd 文件的 SUID 权限取消,会发生什么呢?观察如下命令的执行过程:

[root@localhost ~]# chmod u-s /usr/bin/passwd
#属主取消SetUID权限
[root@localhost ~]# ll /usr/bin/passwd
-rwxr-xr-x. 1 root root 30768 Feb 22 2012 /usr/bin/passwd
[root@localhost ~]# su - lamp
[lamp@localhost ~]$ passwd
Changing password for user lamp.
Changing password for user.
(current) UNIX password:
#看起来没有什么问题
New passwor:
Retype new password:
password:Authentication token manipulation error  <--鉴定令牌操作错误
#最后密码没有生效

显然,虽然用户有执行 passwd 命令的权限,但无修改 /etc/shadow 文件的权限,因此最终密码修改失败。

注意,实验完成后,一定要再把 /usr/bin/passwd 文件的 SetUID 权限加上。

那么,普通用户可以使用 cat 命令查看 /etc/shadow 文件吗?答案的否定的,因为 cat 不具有 SUID 权限,因此普通用户在执行 cat /etc/shadow 命令时,无法以 root 的身份,只能以普通用户的身份,因此无法成功读取。

我们可以使用下面这张图来描述上述过程:
图 1 SUID示意图
图 1 SUID示意图

由此,我们可以总结出,SUID 特殊权限具有如下特点:

只有可执行文件才能设定 SetUID 权限,对目录设定 SUID,是无效的。

用户要对该文件拥有 x(执行)权限。

用户在执行该文件时,会以文件所有者的身份执行。

SetUID 权限只在文件执行过程中有效,一旦执行完毕,身份的切换也随之消失。

http://c.biancheng.net/view/872.html

Linux Stick BIT(SBIT)文件特殊权限用法详解
Sticky BIT,简称 SBIT 特殊权限,可意为粘着位、粘滞位、防删除位等。

SBIT 权限仅对目录有效,一旦目录设定了 SBIT 权限,则用户在此目录下创建的文件或目录,就只有自己和 root 才有权利修改或删除该文件。

也就是说,当甲用户以目录所属组或其他人的身份进入 A 目录时,如果甲对该目录有 w 权限,则表示对于 A 目录中任何用户创建的文件或子目录,甲都可以进行修改甚至删除等操作。但是,如果 A 目录设定有 SBIT 权限,那就大不一样啦,甲用户只能操作自己创建的文件或目录,而无法修改甚至删除其他用户创建的文件或目录。

举个例子,Linux 系统中,存储临时文件的 /tmp 目录就设定有 SBIT 权限:

[root@localhost ~]# ll -d /tmp
drwxrwxrwt. 4 root root 4096 Apr 19 06:17 /tmp

可以看到,在其他人身份的权限设定中,原来的 x 权限位被 t 权限占用了,这就表示此目录拥有 SBIT 权限。通过下面一系列的命令操作,我们来具体看看 SBIT 权限对 /tmp 目录的作用。

[root@localhost ~]# useradd lamp
[root@localhost ~]# useradd lamp1
#建立测试用户lamp和lamp1,省略设置密码过程
[root@localhost ~]# su -lamp
#切换为lamp用户
[lamp@localhost ~]$ cd /tmp
[lamp@localhost tmp]$ touch ftest
#建立测试文件
[lamp@localhost tmp]$ll ftest
-rw-rw-r-- 1 lamp lamp Apr 19 06:36 ftest
[lamp@localhost tmp]$ su - lamp1
#省略输入lamp1用户密码的过程,切换成lamp1用户
[lamp1 @localhost ~]$ cd /tmp/
[lamp1 @localhost tmp]$ rm -rf ftest
rm:cannot remove 'ftest':Operation not permitted

可以看到,虽然 /tmp 目录的权限设定是 777,但由于其具有 SBIT 权限,因此 lamp 用户在此目录创建的文件 ftest,lamp1 用户删除失败。

有兴趣的读者也可以尝试使用 lamp1 用户修改 lamp 用户创建的 ftest 文件中的内容,你会发现根本修改不了,这就是 SBIT 权限的作用。

文件类型

正规文件(regular file)
目录(directory)
符号链(symbolic link)
块设备文件(block-oriented device file)
字符设备文件(character-oriented device file)
管道(pipe)和命名管道(named pipe)(即FIFO)
套接字(socket)

文件描述符与索引节点

文件描述符(file descriptor)
索引节点(inode)【硬链接软链接中有描述】
索引节点包含以下信息:
文件类型
与文件相关的硬链接个数
以字节为单位的文件长度
设备标识(即包含这个文件的设备的标识符)
用来在文件系统中标识文件的索引节点号
文件拥有者的UID
文件的GID
几个时间标记,说明inode状态改变的时间、最后访问时间及最后修改时间
访问权限和文件模式(mode)

以下内容引用:http://c.biancheng.net/view/3066.html

Linux文件描述符到底是什么?
Linux 中一切皆文件,比如 C++ 源文件、视频文件、Shell脚本、可执行文件等,就连键盘、显示器、鼠标等硬件设备也都是文件。

一个 Linux 进程可以打开成百上千个文件,为了表示和区分已经打开的文件,Linux 会给每个文件分配一个编号(一个 ID),这个编号就是一个整数,被称为文件描述符(File Descriptor)。

这只是一个形象的比喻,为了让读者容易理解我才这么说。如果你也仅仅理解到这个层面,那不过是浅尝辄止而已,并没有看到文件描述符的本质。

本篇文章的目的就是拨云见雾,从底层实现的角度来给大家剖析一下文件描述符,看看文件描述如到底是如何表示一个文件的。

不过,阅读本篇文章需要你有C语言编程基础,至少要理解数组、指针和结构体;如果理解内存,那就更好了,看了这篇文章你会醍醐灌顶。

好了,废话不多说,让我们马上进入正题吧。

Linux 文件描述符到底是什么?
一个 Linux 进程启动后,会在内核空间中创建一个 PCB 控制块,PCB 内部有一个文件描述符表(File descriptor table),记录着当前进程所有可用的文件描述符,也即当前进程所有打开的文件。

内核空间是虚拟地址空间的一部分,想死磕的读者请猛击《C语言内存精讲》,不想纠缠细节的读者可以这样理解:进程启动后要占用内存,其中一部分内存分配给了文件描述符表。
除了文件描述符表,系统还需要维护另外两张表:

打开文件表(Open file table)

i-node 表(i-node table)

文件描述符表每个进程都有一个,打开文件表和 i-node 表整个系统只有一个,它们三者之间的关系如下图所示。

在这里插入图片描述

从本质上讲,这三种表都是结构体数组,0、1、2、73、1976 等都是数组下标。表头只是我自己添加的注释,数组本身是没有的。实线箭头表示指针的指向,虚线箭头是我自己添加的注释。

你看,文件描述符只不过是一个数组下标吗!

通过文件描述符,可以找到文件指针,从而进入打开文件表。该表存储了以下信息:

文件偏移量,也就是文件内部指针偏移量。调用 read() 或者 write() 函数时,文件偏移量会自动更新,当然也可以使用 lseek() 直接修改。

状态标志,比如只读模式、读写模式、追加模式、覆盖模式等。

i-node 表指针。

然而,要想真正读写文件,还得通过打开文件表的 i-node 指针进入 i-node 表,该表包含了诸如以下的信息:

文件类型,例如常规文件、套接字或 FIFO。

文件大小。

时间戳,比如创建时间、更新时间。

文件锁。

对上图的进一步说明:

在进程 A 中,文件描述符 1 和 20 都指向了同一个打开文件表项,标号为 23(指向了打开文件表中下标为 23 的数组元素),这可能是通过调用 dup()、dup2()、fcntl() 或者对同一个文件多次调用了 open() 函数形成的。

进程 A 的文件描述符 2 和进程 B 的文件描述符 2 都指向了同一个文件,这可能是在调用 fork() 后出现的(即进程 A、B 是父子进程关系),或者是不同的进程独自去调用 open() 函数打开了同一个文件,此时进程内部的描述符正好分配到与其他进程打开该文件的描述符一样。

进程 A 的描述符 0 和进程 B 的描述符 3 分别指向不同的打开文件表项,但这些表项均指向 i-node 表的同一个条目(标号为 1976);换言之,它们指向了同一个文件。发生这种情况是因为每个进程各自对同一个文件发起了 open() 调用。同一个进程两次打开同一个文件,也会发生类似情况。

有了以上对文件描述符的认知,我们很容易理解以下情形:

同一个进程的不同文件描述符可以指向同一个文件;

不同进程可以拥有相同的文件描述符;

不同进程的同一个文件描述符可以指向不同的文件(一般也是这样,除了 0、1、2 这三个特殊的文件);

不同进程的不同文件描述符也可以指向同一个文件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值