对不起,学会这些 Linux 知识后,我有点飘 linux 基础之IO

deanyuancn
发布于 2020-11-16 18:55
浏览
0收藏

本期为最后一期,想看其他几期可以先去我都主页看,话不多说,让我们开始。

 

 

Linux IO

 

我们之前了解过了 Linux 的进程和线程、Linux 内存管理,那么下面我们就来认识一下 Linux 中的 I/O 管理。

 

Linux 系统和其他 UNIX 系统一样,IO 管理比较直接和简洁。所有 IO 设备都被当作文件,通过在系统内部使用相同的 read 和 write 一样进行读写。

 

Linux IO 基本概念

 

Linux 中也有磁盘、打印机、网络等 I/O 设备,Linux 把这些设备当作一种 特殊文件 整合到文件系统中,一般通常位于 /dev 目录下。可以使用与普通文件相同的方式来对待这些特殊文件。

 

特殊文件一般分为两种:

 

块特殊文件是一个能存储固定大小块信息的设备,它支持以固定大小的块,扇区或群集读取和(可选)写入数据。每个块都有自己的物理地址。通常块的大小在 512 - 65536 之间。所有传输的信息都会以连续的块为单位。块设备的基本特征是每个块都较为对立,能够独立的进行读写。常见的块设备有 硬盘、蓝光光盘、USB 盘与字符设备相比,块设备通常需要较少的引脚。对不起,学会这些 Linux 知识后,我有点飘 linux 基础之IO-鸿蒙开发者社区

块特殊文件的缺点基于给定固态存储器的块设备比基于相同类型的存储器的字节寻址要慢一些,因为必须在块的开头开始读取或写入。所以,要读取该块的任何部分,必须寻找到该块的开始,读取整个块,如果不使用该块,则将其丢弃。要写入块的一部分,必须寻找到块的开始,将整个块读入内存,修改数据,再次寻找到块的开头处,然后将整个块写回设备。

 

另一类 I/O 设备是字符特殊文件。字符设备以字符为单位发送或接收一个字符流,而不考虑任何块结构。字符设备是不可寻址的,也没有任何寻道操作。常见的字符设备有 打印机、网络设备、鼠标、以及大多数与磁盘不同的设备。

 

每个设备特殊文件都会和 设备驱动 相关联。每个驱动程序都通过一个 主设备号 来标识。如果一个驱动支持多个设备的话,此时会在主设备的后面新加一个 次设备号 来标识。主设备号和次设备号共同确定了唯一的驱动设备。

 

我们知道,在计算机系统中,CPU 并不直接和设备打交道,它们中间有一个叫作 设备控制器(Device Control Unit)的组件,例如硬盘有磁盘控制器、USB 有 USB 控制器、显示器有视频控制器等。这些控制器就像代理商一样,它们知道如何应对硬盘、鼠标、键盘、显示器的行为。

 

绝大多数字符特殊文件都不能随机访问,因为他们需要使用和块特殊文件不同的方式来控制。比如,你在键盘上输入了一些字符,但是你发现输错了一个,这时有一些人喜欢使用 backspace 来删除,有人喜欢用 del 来删除。为了中断正在运行的设备,一些系统使用 ctrl-u 来结束,但是现在一般使用 ctrl-c 来结束。

 

网络

 

I/O 的另外一个概念是网络, 也是由 UNIX 引入,网络中一个很关键的概念就是 套接字(socket)。套接字允许用户连接到网络,正如邮筒允许用户连接到邮政系统,套接字的示意图如下

对不起,学会这些 Linux 知识后,我有点飘 linux 基础之IO-鸿蒙开发者社区

套接字的位置如上图所示,套接字可以动态创建和销毁。成功创建一个套接字后,系统会返回一个文件描述符(file descriptor),在后面的创建链接、读数据、写数据、解除连接时都需要使用到这个文件描述符。每个套接字都支持一种特定类型的网络类型,在创建时指定。一般最常用的几种

 

  • 可靠的面向连接的字节流
  • 可靠的面向连接的数据包
  • 不可靠的数据包传输

 

可靠的面向连接的字节流会使用管道 在两台机器之间建立连接。能够保证字节从一台机器按照顺序到达另一台机器,系统能够保证所有字节都能到达。

 

除了数据包之间的分界之外,第二种类型和第一种类型是类似的。如果发送了 3 次写操作,那么使用第一种方式的接受者会直接接收到所有字节;第二种方式的接受者会分 3 次接受所有字节。除此之外,用户还可以使用第三种即不可靠的数据包来传输,使用这种传输方式的优点在于高性能,有的时候它比可靠性更加重要,比如在流媒体中,性能就尤其重要。

 

以上涉及两种形式的传输协议,即 TCP 和 UDP,TCP 是 传输控制协议,它能够传输可靠的字节流。UDP 是 用户数据报协议,它只能够传输不可靠的字节流。它们都属于 TCP/IP 协议簇中的协议,下面是网络协议分层

对不起,学会这些 Linux 知识后,我有点飘 linux 基础之IO-鸿蒙开发者社区

可以看到,TCP 、UDP 都位于网络层上,可见它们都把 IP 协议 即 互联网协议 作为基础。

 

一旦套接字在源计算机和目的计算机建立成功,那么两个计算机之间就可以建立一个链接。通信一方在本地套接字上使用 listen 系统调用,它就会创建一个缓冲区,然后阻塞直到数据到来。另一方使用 connect 系统调用,如果另一方接受 connect 系统调用后,则系统会在两个套接字之间建立连接。

 

socket 连接建立成功后就像是一个管道,一个进程可以使用本地套接字的文件描述符从中读写数据,当连接不再需要的时候使用 close 系统调用来关闭。

 

Linux I/O 系统调用

 

Linux 系统中的每个 I/O 设备都有一个特殊文件(special file)与之关联,什么是特殊文件呢?

 

在操作系统中,特殊文件是一种在文件系统中与硬件设备相关联的文件。特殊文件也被称为 设备文件(device file)。特殊文件的目的是将设备作为文件系统中的文件进行公开。特殊文件为硬件设备提供了借口,用于文件 I/O 的工具可以进行访问。因为设备有两种类型,同样特殊文件也有两种,即字符特殊文件和块特殊文件

对于大部分 I/O 操作来说,只用合适的文件就可以完成,并不需要特殊的系统调用。然后,有时需要一些设备专用的处理。在 POSIX 之前,大多数 UNIX 系统会有一个叫做 ioctl 的系统调用,它用于执行大量的系统调用。随着时间的发展,POSIX 对其进行了整理,把 ioctl 的功能划分为面向终端设备的独立功能调用,现在已经变成独立的系统调用了。

 

下面是几个管理终端的系统调用对不起,学会这些 Linux 知识后,我有点飘 linux 基础之IO-鸿蒙开发者社区

Linux IO 实现

 

Linux 中的 IO 是通过一系列设备驱动实现的,每个设备类型对应一个设备驱动。设备驱动为操作系统和硬件分别预留接口,通过设备驱动来屏蔽操作系统和硬件的差异。

 

当用户访问一个特殊的文件时,由文件系统提供此特殊文件的主设备号和次设备号,并判断它是一个块特殊文件还是字符特殊文件。主设备号用于标识字符设备还是块设备,次设备号用于参数传递。

 

每个驱动程序 都有两部分:这两部分都是属于 Linux 内核,也都运行在内核态下。上半部分运行在调用者上下文并且与 Linux 其他部分交互。下半部分运行在内核上下文并且与设备进行交互。驱动程序可以调用内存分配、定时器管理、DMA 控制等内核过程。可被调用的内核功能都位于 驱动程序 - 内核接口 的文档中。

 

I/O 实现指的就是对字符设备和块设备的实现

 

块设备实现

 

系统中处理块特殊文件 I/O 部分的目标是为了使传输次数尽可能的小。为了实现这个目标,Linux 系统在磁盘驱动程序和文件系统之间设置了一个 高速缓存(cache) ,如下图所示对不起,学会这些 Linux 知识后,我有点飘 linux 基础之IO-鸿蒙开发者社区

在 Linux 内核 2.2 之前,Linux 系统维护着两个缓存:页面缓存(page cache) 和 缓冲区缓存(buffer cache),因此,存储在一个磁盘块中的文件可能会在两个缓存中。2.2 版本以后 Linux 内核只有一个统一的缓存一个 通用数据块层(generic block layer) 把这些融合在一起,实现了磁盘、数据块、缓冲区和数据页之间必要的转换。那么什么是通用数据块层?

 

通用数据块层是一个内核的组成部分,用于处理对系统中所有块设备的请求。通用数据块主要有以下几个功能

 

将数据缓冲区放在内存高位处,当 CPU 访问数据时,页面才会映射到内核线性地址中,并且此后取消映射

 

实现 零拷贝机制,磁盘数据可以直接放入用户模式的地址空间,而无需先复制到内核内存中

 

管理磁盘卷,会把不同块设备上的多个磁盘分区视为一个分区。

 

利用最新的磁盘控制器的高级功能,例如 DMA 等。

cache 是提升性能的利器,不管以什么样的目的需要一个数据块,都会先从 cache 中查找,如果找到直接返回,避免一次磁盘访问,能够极大的提升系统性能。

 

如果页面 cache 中没有这个块,操作系统就会把页面从磁盘中调入内存,然后读入 cache 进行缓存。

 

cache 除了支持读操作外,也支持写操作,一个程序要写回一个块,首先把它写到 cache 中,而不是直接写入到磁盘中,等到磁盘中缓存达到一定数量值时再被写入到 cache 中。

 

Linux 系统中使用 IO 调度器 来保证减少磁头的反复移动从而减少损失。I/O 调度器的作用是对块设备的读写操作进行排序,对读写请求进行合并。Linux 有许多调度器的变体,从而满足不同的工作需要。最基本的 Linux 调度器是基于传统的 Linux 电梯调度器(Linux elevator scheduler)。Linux 电梯调度器的主要工作流程就是按照磁盘扇区的地址排序并存储在一个双向链表 中。新的请求将会以链表的形式插入。这种方法可以有效的防止磁头重复移动。因为电梯调度器会容易产生饥饿现象。因此,Linux 在原基础上进行了修改,维护了两个链表,在 最后日期(deadline) 内维护了排序后的读写操作。默认的读操作耗时 0.5s,默认写操作耗时 5s。如果在最后期限内等待时间最长的链表没有获得服务,那么它将优先获得服务。

 

字符设备实现

 

和字符设备的交互是比较简单的。由于字符设备会产生并使用字符流、字节数据,因此对随机访问的支持意义不大。一个例外是使用 行规则(line disciplines)。一个行规可以和终端设备相关联,使用 tty_struct 结构来表示,它表示与终端设备交换数据的解释器,当然这也属于内核的一部分。例如:行规可以对行进行编辑,映射回车为换行等一系列其他操作。

 

什么是行规则?

 

行规是某些类 UNIX 系统中的一层,终端子系统通常由三层组成:上层提供字符设备接口,下层硬件驱动程序与硬件或伪终端进行交互,中层规则用于实现终端设备共有的行为。

对不起,学会这些 Linux 知识后,我有点飘 linux 基础之IO-鸿蒙开发者社区

网络设备实现

 

网络设备的交互是不一样的,虽然 网络设备(network devices) 也会产生字符流,因为它们的异步(asynchronous) 特性是他们不易与其他字符设备在同一接口下集成。网络设备驱动程序会产生很多数据包,经由网络协议到达用户应用程序中。

 

Linux 中的模块

 

UNIX 设备驱动程序是被静态加载到内核中的。因此,只要系统启动后,设备驱动程序都会被加载到内存中。随着个人电脑 Linux 的出现,这种静态链接完成后会使用一段时间的模式被打破。相对于小型机上的 I/O 设备,PC 上可用的 I/O 设备有了数量级的增长。绝大多数用户没有能力去添加一个新的应用程序、更新设备驱动、重新连接内核,然后进行安装。

 

Linux 为了解决这个问题,引入了 可加载(loadable module) 机制。可加载是在系统运行时添加到内核中的代码块。

 

当一个模块被加载到内核时,会发生下面几件事情:第一,在加载的过程中,模块会被动态的重新部署。第二,系统会检查程序程序所需的资源是否可用。如果可用,则把这些资源标记为正在使用。第三步,设置所需的中断向量。第四,更新驱动转换表使其能够处理新的主设备类型。最后再来运行设备驱动程序。

 

在完成上述工作后,驱动程序就会安装完成,其他现代 UNIX 系统也支持可加载机制。

 

Linux 安全

 

Linux 作为 MINIX 和 UNIX 的衍生操作系统,从一开始就是一个多用户系统。这意味着 Linux 从早期开始就建立了安全和信息访问控制机制。下面我们主要探讨的就是 Linux 安全性的一些内容

 

Linux 安全基本概念

 

一个 Linux 系统的用户群里由一系列注册用户组成,他们每一个都有一个唯一的 UID (User ID)。一个 UID 是一个位于 0 到 65535 之间的整数。文件(进程或者是其他资源)都标记了它的所有者的 UID。默认情况下,文件的所有者是创建文件的人,文件的所有者是创建文件的用户。

 

用户可以被分成许多组,每个组都会由一个 16 位的整数标记,这个组叫做 GID(组 ID)。给用户分组是手动完成的,它由系统管理员执行,分组就是在数据库中添加一条记录指明哪个用户属于哪个组。一个用户可以属于不同组。

 

Linux 中的基本安全机制比较容易理解,每个进程都会记录它所有者的 UID 和 GID。当文件创建后,它会获取创建进程的 UID 和 GID。当一个文件被创建时,它的 UID 和 GID 就会被标记为进程的 UID 和 GID。这个文件同时会获取由该进程决定的一些权限。这些权限会指定所有者、所有者所在组的其他用户及其他用户对文件具有什么样的访问权限。对于这三类用户而言,潜在的访问权限是 读、写和执行,分别由 r、w 和 x 标记。当然,执行文件的权限仅当文件时可逆二进制程序时才有意义。试图执行一个拥有执行权限的非可执行文件,系统会报错。

 

Linux 用户分为三种

 

root(超级管理员),它的 UID 为 0,这个用户有极大的权限,可以直接无视很多的限制 ,包括读写执行的权限。
系统用户,UID 为 1~499。


普通用户,UID 范围一般是 500~65534。这类用户的权限会受到基本权限的限制,也会受到来自管理员的限制。不过要注意 nobody 这个特殊的帐号,UID 为 65534,这个用户的权限会进一步的受到限制,一般用于实现来宾帐号。

 

Linux 中的每类用户由 3 个比特为来标记,所以 9 个比特位就能够表示所有的权限。

 

下面来看一下一些基本的用户和权限例子对不起,学会这些 Linux 知识后,我有点飘 linux 基础之IO-鸿蒙开发者社区

 

我们上面提到,UID 为 0 的是一个特殊用户,称为 超级用户(或者根用户)。超级用户能够读和写系统中的任何文件,不管这个文件由谁所有,也不管这个文件的保护模式如何。 UID 为 0 的进程还具有少数调用受保护系统调用的权限,而普通用户是不可能有这些功能的。通常情况下,只有系统管理员知道超级用户的密码。

 

在 Linux 系统下,目录也是一种文件,并且具有和普通文件一样的保护模式。不同的是,目录的 x 比特位表示查找权限而不是执行权限。因此,如果一个目录的保护模式是 rwxr-xr-x,那么它允许所有者读、写和查找目录,而其他人只可以读和查找,而不允许从中添加或者删除目录中的文件。

 

与 I/O 有关的特殊文件拥有和普通文件一样的保护位。这种机制可以用来限制对 I/O 设备的访问权限。举个例子,打印机是特殊文件,它的目录是 /dev/lp,它可以被根用户或者一个叫守护进程的特殊用户拥有,具有保护模式 rw-------,从而阻止其他所有人对打印机的访问。毕竟每个人都使用打印机的话会发生混乱。

 

当然,如果 /dev/lp 的保护模式是 rw-------,那就意味着其他任何人都不能使用打印机。

 

这个问题通过增加一个保护位 SETUID 到之前的 9 个比特位来解决。当一个进程的 SETUID 位打开,它的 有效 UID 将变成相应可执行文件的所有者 UID,而不是当前使用该进程的用户的 UID。将访问打印机的程序设置为守护进程所有,同时打开 SETUID 位,这样任何用户都可以执行此程序,而且拥有守护进程的权限。

 

除了 SETUID 之外,还有一个 SETGID 位,SETGID 的工作原理和 SETUID 类似。但是这个位一般很不常用。

 

Linux 安全相关的系统调用

 

Linux 中关于安全的系统调用不是很多,只有几个,如下列表所示

对不起,学会这些 Linux 知识后,我有点飘 linux 基础之IO-鸿蒙开发者社区

我们在日常开发中用到最多的就是 chmod了,没想到我们日常开发过程中也能用到系统调用啊,chmod 之前我们一直认为是改变权限,现在专业一点是改变文件的保护模式。它的具体函数如下

s = chmod("路径名","值");

例如

s = chmod("/usr/local/cxuan",777);

 

 

他就是会把 /usr/local/cxuan 这个路径的保护模式改为 rwxrwxrwx,任何组和人都可以操作这个路径。只有该文件的所有者和超级用户才有权利更改保护模式。

 

access 系统调用用来检验实际的 UID 和 GID 对某文件是否拥有特定的权限。下面就是四个 getxxx 的系统调用,这些用来获取 uid 和 gid 的。

 

注意:其中的 chown、setuid 和 setgid 是超级用户才能使用,用来改变所有者进程的 UID 和 GID。

 

Linux 安全实现

 

当用户登录时,登录程序,也被称为 login,会要求输入用户名和密码。它会对密码进行哈希处理,然后在 /etc/passwd 中进行查找,看看是否有匹配的项。使用哈希的原因是防止密码在系统中以非加密的方式存在。如果密码正确,登录程序会在 /etc/passwd 中读取用户选择的 shell 程序的名称,有可能是 bash,有可能是 shell 或者其他的 csh 或 ksh。然后登录程序使用 setuid 和 setgid 这两个系统调用来把自己的 UID 和 GID 变为用户的 UID 和 GID,然后它打开键盘作为标准输入、标准输入的文件描述符是 0 ,屏幕作为标准输出,文件描述符是 1 ,屏幕也作为标准错误输出,文件描述符为 2。最后,执行用户选择的 shell 程序,终止。

 

当任何进程想要打开一个文件,系统首先将文件的 i - node 所记录的保护位与用户有效 UID 和 有效 GID 进行对比,来检查访问是否允许。如果访问允许,就打开文件并返回文件描述符;否则不打开文件,返回 - 1。

 

Linux 安全模型和实现在本质上与大多数传统的 UNIX 系统相同。

 

至此,本篇彻底结束,我也终于能从天上下来休息会儿了,感谢各位浏览。

 

分类
已于2020-11-16 18:55:37修改
收藏
回复
举报
回复
    相关推荐