0%

操作系统-Linux

Linux简介

关于POSIX的介绍在这里。Linux系统的层次结构如下:

Linux层次结构

内核坐落在硬件之上,负责实现与I/O设备和存储管理单元的交互,并控制CPU对前述设备的访问。如图所示,在最底层,内核包含中断处理程序,它们是与设备交互的主要方式,以及底层的分派机制。这种分派在中断时发生。底层的代码中止正在运行的进程,将其状态存储在内核进程结构中,然后启动相应的驱动程序。进程分派也在内核完成某些操作,并且需要再次启动一个用户进程时发生。进程分派的代码是汇编代码,并且和进程调度代码有很大不同。

Linux内核结构

Linux系统进程

Linux系统内存管理

Linux I/O

Linux文件系统

Linux系统启动

当计算机启动时,BIOS加电自检,并对硬件进行检测和初始化,这是因为操作系统的启动过程可能会依赖于磁盘访问、屏幕、键盘等。接下来,启动磁盘的第一个扇区,即主引导记录(MBR),被读入到一个固定的内存区域并且执行。这个分区中含有一个很小的程序(只有512字节),这个程序从启动设备中,比如SATA磁盘或SCSI磁盘,调入一个名为boot的独立程序。boot程序将自身复制到髙地址的内存当中从而为操作系统释放低地址的内存。

复制完成后,boot程序读取启动设备的根目录。为了达到这个目的,boot程序必须能够理解文件系统和目录格式,这个工作通常由引导程序,如GRUB(多系统启动管理器)来完成。

然后,boot程序读入操作系统内核,并把控制交给内核。从这里开始,boot程序完成了它的任务,系统内核开始运行。

内核的启动代码是用汇编语言写成的,具有较髙的机器依赖性。主要的工作包括创建内核堆栈、识别CPU类型、计算可用内存、禁用中断、启用内存管理单元,最后调用C语言写成的main函数开始执行操作系统的主要部分。

C语言代码也有相当多的初始化工作要做,但是这些工作更逻辑化(而不是物理化)。

接下来,内核数据结构得到分配。大部分内核数据结构的大小是固定的,但是一少部分,如页面缓存和特殊的页表结构,依赖于可用内存的大小。

从这里开始,系统进行自动配置。使用描述何种设备可能存在配置文件,系统开始探测哪些设备是确实存在的。如果一个被探测的设备给出了响应,这个设备就会被加入到已连接设备表中。如果它没有响应,就假设它未连接或直接忽略掉它。不同于传统的UNIX版本,Linux系统的设备驱动程序不需要被静态链接至内核中,它们可以被动态加载。

一旦所有的硬件都配置好了,接下来要做的事情就是细心地手动运行进程0,建立它的堆栈,运行它。进程0继续进行初始化,做如下的工作:配置实时时钟,挂载根文件系统,创建init进程(进程1)和页面守护进程(进程2)。

init进程检测它的标志以确定它应该为单用户还是多用户服务。

  • 单用户:调用fork函数创建一个shell进程,并且等待这个进程结束。
  • 多用户:调用fork函数创建一个运行系统初始化shell脚本(即/etc/rc)的进程,这个进程可以进行文件系统一致性检测、挂载附加文件系统、开启守护进程等。然后这个进程从/etc/ttys中读取数据,其中/etc/ttys列出了所有的终端和它们的属性。

在多用户服务中,对于每一个启用的终端,/etc/rc进程调用fork函数创建一个自身的副本,进行内部处理并运行一个名为getty的程序。getty程序为每条连线设置传输速率和其他属性(比如,有一些可能是调制解调器),然后在终端的屏幕上输出:

1
login:

等待用户从键盘键入用户名。当有人坐在终端前,提供了一个用户名后,getty程序就结束了,登录程序/bin/login开始运行。login程序要求输入密码,给密码加密,并与保存在密码文件/etc/passwd中的加密密码进行对比。如果是正确的,login程序以用户shell程序替换自身,等待第一个命令。如果是不正确的,login程序要求输入另一个用户名。

Linux的安全性

基本概念一个Linux系统的用户群体由一定数量的注册用户组成,其中毎个用户拥有一个唯一的UID(用户ID)。UID是介干0到65535之间的一个整数。文件(进程及其他资源)都标记了它的所有者的UID。尽管可以改变文件所有权,但是默认情况下,文件的所有者是创建该文件的用户。

用户可以被分组,其中每组同样由一个16位的整数标记,叫作GID(组ID)。给用户分组通过在系统数据库中添加一条记录指明哪个用户属于哪个组的方法手工(由系统管理员)完成。一个用户可以同时属于多个组。

Linux中的基本安全机制很简单。每个进程记录它的所有者的UID和GID。当一个文件被创建时,它的UID和GID被标记为创建它的进程的UID和GID。该文件同时获得由该进程决定的一些权限。这些权限指定所有者、所有者所在组的其他用户及其他用户对文件具有什么样的访问权限。对干这三类用户而言,潜在的访问权限为读、写和执行,分别由r、w和x标记。当然,执行文件的权限仅当文件是可执行二进制程序时才有意义。试图执行一个拥有执行权限的非可执行文件(即,并非由一个合法的文件头开始的文件)会导致错误。因为有三类用户,每类用户的权限由3个比特位标记,那么9个比特位就足够标记访问权限。下图给出了一些9位数字及其含义的例子:

二进制 标记 准许的文件访问权限
111000000 rwx—— 所有者可以读、写和执行
111111000 rwxrwx— 所有者和组可以读、写和执行
110100100 rw-r—– 所有者可以读和写;组可以读
110100100 rw-r–r– 所有者可以读和写;其他人可以读
111101101 rwxr-xr-x 所有者拥有所有权限,其他人可以读和执行
000000000 ——— 所有人都不拥有任何权限
000000111 ——rwx 只有组以外的其他用户拥有所有权限(奇怪但是合法)

UID为0的用户是一个特殊用户,称为超级用户(或者根用户)。超级用户能够读和写系统中的任何文件,不论这个文件为谁所有,也不论这个文件的保护模式如何。UID为0的进程拥有调用一小部分受保护的系统调用的权限,而普通用户是不能调用这些系统调用的。

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

与I/O相关的特殊文件拥有与普通文件一样的保护位。这种机制可以用来限制对I/O设备的访问权限。例如,假设打印机是特殊文件——/dev/lp,可以被根用户或者一个叫守护进程的特殊用户拥有,具有保护模式rw--------,从而阻止其他所有人对打印机的访问权限。当然,这意味着其他任何人都不可以使用打印机,这种做法限制了很多合法的打印要求。

事实上,允许对I/O设备及其他系统资源进行受控访问的做法具有一个更普遍的问题。这个问题通过增加一个保护位SETUID到之前的9个比特位来解决。当一个进程的SETUID位打开,它的有效UID将变成相应可执行文件的所有者的UID,而不是当前使用该进程的用户的UID。当一个进程试图打开一个文件时,系统检査的将是它的有效UID,而不是真正的UID。将访问打印机的程序设置为被守护进程所有,同时打开SETUID位,这样任何用户都可以执行该程序,并拥有守护进程的权限(例如访问/dep/lp),但是这仅限于运行该程序(例如给打印任务排序)。

许多敏感的Linux程序通过打开SETUID位被根用户所有。例如,允许用户改变密码的程序需要写password文件。允许password文件公开可写显然不是个好主意。解决的方法是,提供一个被根用户所有同时SETUID位打开的程序。虽然该程序拥有对password文件的全部权限,但是它仅仅改变调用该程序的用户的密码,而不允许其他任何的访问权限。

除了SETUID位,还有一个SETGID位,工作原理同SETUID类似。它暂时性地给用户该程序的有效GID。然而在实践中,这个位很少用到。

当用户登录时,登录程序login(为根用户所有且SETUID打开)要求输入登录名和密码。它首先计算密码的散列值,然后在/etc/passwd文件中査找,看是否有相匹配的项(网络系统工作得稍有不同)。使用散列的原因是防止密码在系统中以非加密的方式存在。如果密码正确,登录程序在/etc/passwd中读取该用户选择的shell程序的名称,例如可能是bash,但是也有可能是其他的shell。然后登录程序使用setuid和setgid来使自己的UID和GID变成用户的UID和GID(注意,它一开始的时候是根用户所有且SETUID打开)。然后它打开键盘作为标准输入(文件描述符0),屏幕为标准输出(文件描述符1),屏幕为标准错误输出(文件描述符2)。最后,执行用户选择的shell程序,因此终止自己。

到这里,用户选择的shell已经在运行,并且被设置了正确的UID和GID,标准输人、标准输出和标准错误输出都被设置成了默认值。它创建任何子进程(也就是用户输入的命令)都将自动继承shell的UID和GID,所以它们将拥有正确的UID和GID,这些进程创建的任何文件也具有这些值。

当任何进程想要打开一个文件,系统首先将文件的i节点所记录的保护位与用户的有效UID和有效GID对比,来检査访问是否被允许。如果允许访问,就打开文件并且返回文件描述符:否则不打开文件,返回-1。在接下来的read和write中不再检査权限。因此,当一个文件的保护模式在它被打开后修改,新模式将无法影响已经打开该文件的进程。

Linux安全模型及其实现在本质上跟其他大多数传统的UNIX系统相同。