lLinux/UNIX系统编程手册读书笔记

基本概念

操作系统是管理计算机硬件与软件资源的计算机程序,同时也是计算机系统的内核与基石。操作系统需要处理如管理与配置内存、决定系统资源供需的优先次序、控制输入与输出设备、操作网络与管理文件系统等基本事务。操作系统也提供一个让用户与系统交互的操作界面。
操作系统内核是指管理和分配计算机资源(CPU、RAM 和设备)的核心层软件。
内核职责
进程调度:linux属于抢占式多任务操作系统
内存管理:虚拟内存管理机制
提供文件系统:内核在磁盘上提供文件系统,允许对文件执行创建、获取、更新以及删除等操作
创建和终止进程:将新程序载入内存提供运行资源,完事释放其占用资源。
对设备的访问:鼠标、键盘、磁盘等
联网:内核以用户进程的名义收发网络消息(数据包)。
提供系统调用应用编程接口(API):进程可利用系统调用请求内核执行各种任务。

现代处理器架构允许CPU至少在两种(用户态和核心态)运行。执行硬件指令可使 CPU 在两种状态来回切换。对应的,将虚拟内存区域划分为用户空间部分或内核空间部分。在用户态下运行时,CPU 只能访问被标记为用户空间的内存。当运行于核心态,CPU 可以访问两种空间内存。利用硬件设计,将操作系统置于内核管理硬件,确保用户进程不能访问内核指令和数据结构,也无法执行不利于系统运行的操作。

shell是一种具有特殊用途的程序,主要用于读取用户输入的命令,并执行相应的程序以响应命令。有时人民也称之为命令解释器。Bourne again shell(bash)提供了与C shell和Korn shell所类似的交互式特性,应用最广。
用户和组
系统会对每个用户的身份做唯一标识,用户可隶属于多个组。
系统的每个用户都拥有唯一的登录名和与之对应的整数型用户 ID(UID),还用组 ID,主目录(用户登录后初始的目录),登录 shell(执行以解释用户命令的程序名称)
将用户分为多个组,每个用户可以同时属于同各族,每个组对应系统组文件/etc/group 中的一行记录
进程
进程是具有一定独立功能的程序在某个数据集合上的执行过程,是系统进行资源分配和调度的独立单位。简而言之,进程是正在执行的程序实例。
执行程序时,内核会将程序代码载入虚拟内存,为程序变量分配空间,建立内核记账数据结构,记录与进程有关的各种信息(比如,进程 ID、用户 ID、组 ID 以及终止状态等)
在内核看来,进程是一个实体,内核必须在它们之间共享各种计算机资源。对于像内存这种受限资源来说,内核一开始为进程分配一定数量的资源,并在进程生命周期内,统筹该进程和整个系统对资源的需求,对这一分配进行调整。
进程的内存布局
逻辑上将一个进程划分为以下几个部分:
1.文本:程序的指令。
2.数据:程序使用的静态变量。
3.堆:程序可从该区域动态分配额外内存。
4.栈:随函数调用、返回而增减的一片内存,用于为局部变量和函数调用链接信息分配存储空间。
创建进程和执行程序
父进程通过系统调用 fork()函数来创建一个子进程。内核通过对父进程的复制来创建子进程,子进程获得资源后可自行修改并不会影响父进程。
子进程要么去执行与父进程共享代码段中的另一组不同函数,或者,更为常见的情况是使用系统调用 execve()去加载并执行一个全新程序。
进程ID 和父进程ID
每一个进程有一个唯一的整数型进程标志符PID, 此外,每一个进程还具有一个父进程标识符 PPID 属性,用以标识请求内核创建自己的进程。
进程终止和终止状态
可使用以下两种方式来终止进程:
进程使用exit()系统调用(或相关 exit()库函数), 请求退出;
向进程传递信号,将其“杀死”
无论哪种方式退出,进程都会生成“终止状态”, 第一种方式进程会指明自己的终止状态,第二种方式则会根据导致进程“死亡”的信号类型来设置进程的终止状态。
进程的用户和组标识符(凭证)
每个进程都有一组与之相关的用户 ID(UID)和组ID(GID), 通过这两个 ID 来确定对受保护资源的访问权限。
特权进程
特权进程是指有效用户 ID为0的进程。通常有内核所施加的权限限制对此类进程无效。由某一个进程创建的进程,也可以是特权进程。
init 进程
系统引导时,内核会创建一个名为 init 的特殊进程,即“所有进程之父”, 该进程相应程序文件为/sbin/init。系统的所有进程不是有 init(使用 fork())“亲自”创建,就是有其后代进程创建。init 进程进程号总为1,且总是以超级用户权限执行,谁都不能杀死 init 进程,只有关闭系统才能终止进程。
init 进程的主要任务使创建并监控系统运行所需的一系列进程。
守护进程
守护进程指的是具有特殊用途的进程,系统创建和处理此类进程的方式与其他进程相同,但具有以下独有特征:
~“长存”, 守护进程通常在系统引导时启动,直至系统关闭前,会一直“健在”。
~守护进程在后台运行,且无控制终端供其读取或写入数据。

所谓目标库是这样一种文件:将一组函数代码加以编译,并置于一个文件夹中,供其他应用程序调用。这一做法有利于程序的开发和维护。两类对象库:静态库和共享库。
文件类型
在文件系统内,会对文件类型进行标记,表明种类。包括:普通数据文件、设备、管道、套接字、目录以及符号链接,“文件”常用来指导任意类型的文件,不仅仅指普通文件。
进程间通信除了读写磁盘文件中的信息外还提供了丰富的IPC机制:信号、管道、套接字、文件锁定、消息队列、信息量、共享内存。
每个进程都可执行多个线程。可将线程想象为共享同一虚拟内存及其一干其他属性的进程。每个线程都会执行相同的程序代码,共享同一数据区域和堆。每个线程都拥有属于自己的栈,用来装载本地变量和函数调用链接信息。线程之间可以通过共享的全局变量进行通信。
所有的系统调用都是以原子操作方式执行的,内核保证了某系统调用中的所有步骤会独立操作而一次性加以执行,期间不会为其他进程或线程所中断,规避了竞争状态。
路径和链接
目录是一种特殊的文件,内容采用表格形式,数据项包括文件名以及对应文件的引用。“文件名+引用”的组合被称为链接。 每个文件都可以有多条链接,因而也可以有多个名称,在相同或不同的目录中出现。
目录可包括文件或其他目录的链接。路径间的链接建立如上图所示的目录层级。
文件的所有权和权限
每个文件都有一个与之相关的用户 ID 和组 ID,分别定义文件的属主和属组。系统根据文件的所有权来判定用户对文件的访问权限。
系统根据3类用户( 属主:文件的用户, 属组:与文件组相匹配的属组成员用户以及其他用户)分别设置3种权限(共计9种权限位)
~第一位表文件类型, d 表示目录, - 表示文件
~2-4位表所有者权限, 5-7表组用户权限,8-10表其他用户权限
~每三位对应 r => 读权限, w => 写权限, x => 执行权限
文件 I/O 模型
UNIX 系统I/O 模型最为显著的特性之一是其 I/O 通用性概念。也就是说,同一套系统调用所执行 I/O 操作,可施之于所有文件类型,包括设备文件,应用程序发起的 I/O 请求,内核会将其转化为相应的文件系统操作,或者设备驱动程序操作,以此来执行针对目标文件或设备的 I/O 操作。简而言之,采用这些系统调用的程序能够处理任何类型的文件。
软链接和硬链接的区别
我们知道文件都有文件名与数据,数据分两部分:用户数据 (user data) 与元数据 (metadata)。用户数据,即文件数据块 (data block),数据块是记录文件真实内容的地方;而元数据则是文件的附加属性,如文件大小、创建时间、所有者等信息。在 Linux 中,元数据中的 inode 号(inode 是文件元数据的一部分但其并不包含文件名,inode 号即索引节点号)才是文件的唯一标识而非文件名。文件名仅是为了方便人们的记忆和使用,系统或程序通过 inode 号寻找正确的文件数据块为解决文件的共享使用,Linux 系统引入了两种链接:硬链接 (hard link) 与软链接(又称符号链接,即 soft link 或 symbolic link)。链接为 Linux 系统解决了文件的共享使用,还带来了隐藏文件路径、增加权限安全及节省存储等好处。若一个 inode 号对应多个文件名,则称这些文件为硬链接。硬链接就是同一个文件使用了多个别名。
由于硬链接是有着相同 inode 号仅文件名不同的文件,因此硬链接存在以下几点特性:
文件有相同的 inode 及 data block;

  • 只能对已存在的文件进行创建;
  • 不能交叉文件系统进行硬链接的创建;
  • 不能对目录进行创建,只可对文件创建;
  • 删除一个硬链接文件并不影响其他有相同 inode 号的文件。

inode 号仅在各文件系统下是唯一的,当 Linux 挂载多个文件系统后将出现 inode 号重复的现象,因此硬链接创建时不可跨文件系统软链接与硬链接不同,若文件用户数据块中存放的内容是另一文件的路径名的指向,则该文件就是软连接。软链接就是一个普通文件,只是数据块内容有点特殊。软链接有着自己的 inode 号以及用户数据块。因此软链接的创建与使用没有类似硬链接的诸多限制:

  • 软链接有自己的文件属性及权限等;
  • 可对不存在的文件或目录创建软链接;
  • 软链接可交叉文件系统; 软链接可对文件或目录创建;
  • 创建软链接时,链接计数 i_nlink 不会增加;
  • 删除软链接并不影响被指向的文件,但若被指向的原文件被删除,则相关软连接被称为死链接(即 dangling link,若被指向路径文件被重新创建,死链接可恢复为正常的软链接);

一般情况下,文件名和inode号码是"一一对应"关系,每个inode号码对应一个文件名。但是,Unix/Linux系统允许,多个文件名指向同一个inode号码。这意味着,可以用不同的文件名访问同样的内容;对文件内容进行修改,会影响到所有文件名;但是,删除一个文件名,不影响另一个文件名的访问。这种情况就被称为"硬链接"(hard link)。

Linux VFS
Linux 有着极其丰富的文件系统,大体上可分如下几类:

  • 网络文件系统,如 nfs、cifs 等;
  • 磁盘文件系统,如 ext4、ext3 等;
  • 特殊文件系统,如 proc、sysfs、ramfs、tmpfs 等。

实现以上这些文件系统并在 Linux 下共存的基础就是 Linux VFS(Virtual File System 又称 Virtual Filesystem Switch),即虚拟文件系统。VFS 作为一个通用的文件系统,抽象了文件系统的四个基本概念:文件、目录项 (dentry)、索引节点 (inode) 及挂载点,其在内核中为用户空间层的文件系统提供了相关的接口。VFS 实现了 open()、read() 等系统调并使得 cp 等用户空间程序可跨文件系统。VFS 真正实现了上述内容中:在 Linux 中除进程之外一切皆是文件。

Linux VFS 存在四个基本对象:超级块对象 (superblock object)、索引节点对象 (inode object)、目录项对象 (dentry object) 及文件对象 (file object)。超级块对象代表一个已安装的文件系统;索引节点对象代表一个文件;目录项对象代表一个目录项,如设备文件 event5 在路径 /dev/input/event5 中,其存在四个目录项对象:/ 、dev/ 、input/ 及 event5。文件对象代表由进程打开的文件。为文件路径的快速解析,Linux VFS 设计了目录项缓存(Directory Entry Cache,即 dcache)。

文件的打开过程
open()系统调用的过程如下:
1.查看system-wide open-file table(系统打开文件表)中是否有该文件,即查看该文件是否已经被其他进程打开了
2.如果存在,那么该进程会在自己的per-process open-file table(进程打开文件表)中,建立一个项目,指向system-wide open-file table中的该文件
3.如果不存在,则需要根据file name在directory中查找该file,通常directory中的部分内容在cache中,这样可以加快搜索速度。
4.一旦文件被找到,那么FCB(file control block)文件控制块会被复制到system-wide open-file table中,该表不仅仅保存FCB,而且记录每个文件被多少个进程打开
5.接下来,在per-process open-file table(进程打开文件表)中,简直一个entry,指向进程打开文件表中该项目

当进程close()一个文件时:
1.该进程的per-process open-flle table中的对应项会被删除,系统打开表中的该文件计数器会减1
2.如果系统打开表中的计算为0,那么删除该文件项

inode的理解
操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个"块"(block)。这种由多个扇区组成的"块",是文件存取的最小单位。“块"的大小,最常见的是4KB,即连续八个 sector组成一个 block。
文件数据都储存在"块"中,那么很显然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为"索引节点”。
inode包含文件的元信息,具体来说有以下内容:
1.文件的字节数
2.文件拥有者的User ID
3.文件的Group ID* 文件的读、写、执行权限
4.文件的时间戳,共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间。
5.链接数,即有多少文件名指向这个inode
6.文件数据block的位置
7.除了文件名以外的所有文件信息,都存在inode之中
8.每个inode都有一个号码,操作系统用inode号码来识别不同的文件。

表面上,用户通过文件名,打开文件。实际上,系统内部这个过程分成三步:首先,系统找到这个文件名对应的inode号;其次,通过inode号,获取inode信息;最后,根据inode信息,找到文件数据所在的block,读出数据。目录(directory)也是一种文件,目录文件的结构非常简单,就是一系列目录项(dirent)的列表。每个目录项,由两部分组成:所包含文件的文件名,以及该文件名对应的inode号码。
数据块寻址
inode中记录了文件数据块的位置,有三种寻址方式:direct blocks直接指向数据块;single indirect指向一个block,该block中为数据块的指针;double indirect,两级blockLinux。
文件描述符
在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。POSIX标准要求每次打开文件时(含socket)必须使用当前进程中最小可用的文件描述符号码
文件描述符是系统的一个重要资源,虽然说系统内存有多少就可以打开多少的文件描述符,但是在实际实现过程中内核是会做相应的处理的,一般最大打开文件数会是系统内存的10%(以KB来计算)(称之为系统级限制)
文件描述符和打开文件之间的关系
每一个文件描述符会与一个打开文件相对应,同时,不同的文件描述符也会指向同一个文件。相同的文件可以被不同的进程打开也可以在同一个进程中被多次打开。系统为每一个进程维护了一个文件描述符表,该表的值都是从0开始的,所以在不同的进程中你会看到相同的文件描述符,这种情况下相同文件描述符有可能指向同一个文件,也有可能指向不同的文件。具体情况要具体分析,要理解具体其概况如何,需要查看由内核维护的3个数据结构。
1.进程级的文件描述符表
2.系统级的打开文件描述符表
3. 文件系统的i-node表

进程级的描述符表的每一条目记录了单个文件描述符的相关信息。
1.控制文件描述符操作的一组标志。(目前,此类标志仅定义了一个,即close-on-exec标志)
2. 对打开文件句柄的引用
内核对所有打开的文件的文件维护有一个系统级的描述符表格(open file description table)。有时,也称之为打开文件表(open file table),并将表格中各条目称为打开文件句柄(open file handle)。一个打开文件句柄存储了与一个打开文件相关的全部信息,如下所示:
1 当前文件偏移量(调用read()和write()时更新,或使用lseek()直接修改)
2 打开文件时所使用的状态标识(即,open()的flags参数)
3 文件访问模式(如调用open()时所设置的只读模式、只写模式或读写模式)
4 与信号驱动相关的设置
5 对该文件i-node对象的引用
6 文件类型(例如:常规文件、套接字或FIFO)和访问权限
7 一个指针,指向该文件所持有的锁列表
8 文件的各种属性,包括文件大小以及与不同类型操作相关的时间戳

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

进程A的文件描述符2和进程B的文件描述符2都指向了同一个打开的文件句柄(标号73)。这种情形可能是在调用fork()后出现的(即,进程A、B是父子进程关系),或者当某进程通过UNIX域套接字将一个打开的文件描述符传递给另一个进程时,也会发生。再者是不同的进程独自去调用open函数打开了同一个文件,此时进程内部的描述符正好分配到与其他进程打开该文件的描述符一样。

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

由于进程级文件描述符表的存在,不同的进程中会出现相同的文件描述符,它们可能指向同一个文件,也可能指向不同的文件2. 两个不同的文件描述符,若指向同一个打开文件句柄,将共享同一文件偏移量。因此,如果通过其中一个文件描述符来修改文件偏移量(由调用read()、write()或lseek()所致),那么从另一个描述符中也会观察到变化,无论这两个文件描述符是否属于不同进程,还是同一个进程,情况都是如此。3. 要获取和修改打开的文件标志(例如:O_APPEND、O_NONBLOCK和O_ASYNC),可执行fcntl()的F_GETFL和F_SETFL操作,其对作用域的约束与上一条颇为类似。4. 文件描述符标志(即,close-on-exec)为进程和文件描述符所私有。对这一标志的修改将不会影响同一进程或不同进程中的其他文件描述符。

展开阅读全文

没有更多推荐了,返回首页