Linux系统 - 基本概念
介绍一些Linux系统的基本概念
1 操作系统的核心—内核
“操作系统”通常包含两种不同含义。
1.指完整的软件包,这包括用来管理计算机资源的核心层软件,以及附带的所有标准软件工具,诸如命令行解释器、图形用户界面、文件操作工具和文本编辑器等。
2.在更狭义的范围内,是指管理和分配计算机资源(即CPU、RAM 和设备)的核心层软件。
“内核”通常是第二种含义,虽然在没有内核的情况下,计算机也能运行程序,但有了内核会极大简化其他程序的编写和使用。
1.1 内核的职责
内核所能执行的主要任务如下所示。
- 进程调度:计算机内均配备有一个或多个 CPU,以执行程序指令。Linux 属于抢占式多任务操作系统。“多任务”意指多个进程可同时驻留于内存,且每个进程都能获得对 CPU 的使用权。“抢占”则是指一组规则。这组规则控制着哪些进程获得对 CPU 的使用,以及每个进程能使用多长时间,这两者都由内核进程调度程序(而非进程本身)决定。
- 内存管理:物理内存(RAM)属于有限资源,内核必须以公平、高效地方式在进程间共享这一资源。与大多数现代操作系统一样,Linux也采用了虚拟内存管理机制,这项技术主要具有以下两方面的优势。
- 进程与进程之间、进程与内核之间彼此隔离,因此一个进程无法读取或修改内核或其他进程的内存内容。
- 只需将进程的一部分保持在内存中,这不但降低了每个进程对内存的需求量,而且还能在 RAM 中同时加载更多的进程。
- 提供文件系统:内核在磁盘之上提供有文件系统,允许对文件执行创建、获取、更新以及删除等操作。
- 创建和终止进程:内核可将新程序载入内存,为其提供运行所需的资源(比如,CPU、内存以及对文件的访问等)。这样一个运行中的程序我们称之为“进程”。一旦进程执行完毕,内核还要确保释放其占用资源,以供后续程序重新使用。
- 对设备的访问:计算机外接设备(鼠标、键盘、磁盘和磁带驱动器等)可实现计算机与外部世界的通信,这一通信机制包括输入、输出或是两者兼而有之。内核既为程序访问设备提供了简化版的标准接口,同时还要仲裁多个进程对每一个设备的访问。
- 联网:内核以用户进程的名义收发网络消息(数据包)。
- 提供系统调用应用编程接口(API):进程可利用内核入口点(也称为系统调用)请求内核去执行各种任务。
1.2 内核态和用户态
现代处理器架构一般允许 CPU 至少在两种不同状态下运行,即:用户态和核心态(有时也称之为监管态 supervisor mode)。执行硬件指令可使 CPU 在两种状态间来回切换。与之对应,可将虚拟内存区域划分(标记)为用户空间部分或内核空间部分。
- 在用户态下运行时,CPU 只能访问被标记为用户空间的内存,试图访问属于内核空间的内存会引发硬件异常。
- 当运行于核心态时,CPU 既能访问用户空间内存,也能访问内核空间内存。
- 仅当处理器在核心态运行时,才能执行某些特定操作(执行指令去关闭系统,访问内存管理硬件,以及设备 I/O 操作的初始化等)。
实现者们利用这一硬件设计,将操作系统置于内核空间。这确保了用户进程既不能访问内核指令和数据结构,也无法执行不利于系统运行的操作。
1.3 以进程及内核视角检视系统
在完成诸多日常编程任务时,程序员们习惯于以面向进程(process-oriented)的思维方式来考虑编程问题。有时有必要转换视角,站在内核的角度上来看问题。
一个运行系统通常会有多个进程并行其中。对进程来说,许多事件的发生都无法预期。执行中的进程不清楚自己对 CPU 的占用何时“到期”,系统随之又会调度哪个进程来使用CPU(以及以何种顺序来调度),也不知道自己何时会再次获得对 CPU 的使用。信号的传递和进程间通信事件的触发由内核统一协调,对进程而言,随时可能发生。诸如此类,进程都一无所知。进程不清楚自己在 RAM 中的位置。或者换种更通用的说法,进程内存空间的某块特定部分如今到底是驻留在内存中还是被保存在交换空间(磁盘空间中的保留区域,作为计算机 RAM 的补充)里,进程本身并不知晓。与之类似,进程也闹不清自己所访问的文件“居于”磁盘驱动器的何处,只是通过名称来引用文件而已。进程的运作方式堪称“与世隔绝”——进程间彼此不能直接通信。进程本身无法创建出新进程,哪怕“自行了断”都不行。最后还有一点,进程也不能与计算机外接的输入输出设备直接通信。
相形之下,内核则是运行系统的中枢所在,对于系统的一切无所不知、无所不能,为系统上所有进程的运行提供便利。由哪个进程来接掌对 CPU 的使用,何时“接任”,“任期”多久,都由内核说了算。在内核维护的数据结构中,包含了与所有正在运行的进程有关的信息。随着进程的创建、状态发生变化或者终结,内核会及时更新这些数据结构。内核所维护的底层数据结构可将程序使用的文件名转换为磁盘的物理位置。此外,每个进程的虚拟内存与计算机物理内存及磁盘交换区之间的映射关系,也在内核维护的数据结构之列。进程间的所有通信都要通过内核提供的通信机制来完成。响应进程发出的请求,内核会创建新的进程,终结现有进程。最后,由内核(特别是设备驱动程序)来执行与输入/输出设备之间的所有直接通信,按需与用户进程交互信息。
2 shell
shell 是一种具有特殊用途的程序,主要用于读取用户输入的命令,并执行相应的程序以响应命令。有时,人们也称之为命令解释器。
尽管某些操作系统将命令解释器集成于内核中,而对 UNIX 系统而言,shell 只是一个用户进程。shell 的种类繁多,登入同一台计算机的不同用户同时可使用不同的 shell(就单个用户来说,情况也一样)。纵观 UNIX 历史,出现过以下几种重要的 shell。
- Bourne shell(sh):这款由 Steve Bourne 编写的 shell 历史最为悠久,且应用广泛,曾是第七版 UNIX 的标配 shell。Bourne shell 包含了在其他 shell 中常见的许多特性,I/O 重定向、管道、文件名生成(通配符)、变量、环境变量处理、命令替换、后台命令执行以及函数。对于所有问世于第七版 UNIX 之后的实现而言,除了可能提供有其他 shell之外,都附带了 Bourne shell。
- C shell(csh):由 Bill Joy 于加州大学伯克利分校编写而成。其命名则源于该脚本语言的流控制语法与 C 语言有着许多相似之处。C shell 当时提供了若干极为实用的交互式特性,并不为 Bourne shell 所支持,这其中包括命令的历史记录、命令行编辑功能、任务控制和别名等。C shell 与 Bourne shell 并不兼容。尽管 C shell 曾是 BSD 系统标配的交互式 shell,但一般情况下,人们还是喜欢针对 Bourne shell 编写 shell 脚本(稍后介绍),以便其能够在所有 UNIX 实现上移植。
- Korn shell(ksh):AT&T 贝尔实验室的 David Korn 编写了这款 shell,作为 Bourne shell的“继任者”。在保持与 Bourne shell 兼容的同时,Korn shell 还吸收了那些与 C shell相类似的交互式特性。
- Bourne again shell(bash):这款 shell 是 GNU 项目对 Bourne shell 的重新实现。Bash提供了与 C shell 和 Korn shel 所类似的交互式特性。Brian Fox 和 Chet Ramey 是 bash的主要作者。bash 或许是 Linux 上应用最为广泛的 shell 了。在 Linux 上,Bourne shell(sh)其实正是由 bash 仿真提供的。
3 用户和组
系统会对每个用户的身份做唯一标识,用户可隶属于多个组。
3.1 用户
系统的每个用户都拥有唯一的登录名(用户名)和与之相对应的整数型用户ID(UID)。系统密码文件/etc/passwd 为每个用户都定义有一行记录,除了上述两项信息外,该记录还包含如下信息。
- 组 ID:用户所属第一个组的整数型组 ID。
- 主目录:用户登录后所居于的初始目录。
- 登录 shell:执行以解释用户命令的程序名称。
该记录还能以加密形式保存用户密码。然而,出于安全考虑,用户密码往往存储于单独的 shadow 密码文件中,仅供特权用户阅读。
3.2 组
出于管理目的,尤其是为了控制对文件和其他资源的访问,将多个用户分组是非常实用的做法。例如,某项目的开发团队人员需要共享同一组文件,就可以将他们编为同一组的成员。一个用户可以隶属于多个组。每个用户组都对应着系统组文件/etc/group 中的一行记录,该记录包含如下信息。
- 组名:(唯一的)组名称。
- 组 ID(GID):与组相关的整数型 ID。
- 用户列表:隶属于该组的用户登录名列表(通过密码文件记录的 group ID 字段未能标识出的该组其他成员,也在此列),以逗号分隔。
3.3 超级用户
超级用户在系统中享有特权。超级用户账号的用户 ID 为 0,通常登录名为 root。在一般的 UNIX 系统上,超级用户凌驾于系统的权限检查之上。因此,无论对文件施以何种访问权限限制,超级用户都可以访问系统中的任何文件,也能发送信号干预系统运行的所有用户进程。
4 单根目录层级、目录、链接及文件
内核维护着一套单根目录结构,以放置系统的所有文件。这一目录层级的根基就是名为“/”的根目录。所有的文件和目录都是根目录的“子孙”。图 1-2 所示为这种文件层级结构的示例。
4.1 文件类型
在文件系统内,会对文件类型进行标记,以表明其种类。其中一种用来表示普通数据文件,人们常称之为“普通文件”或“纯文本文件”,以示与其他种类的文件有所区别。其他文件类型包括设备、管道、套接字、目录以及符号链接。
4.2 路径和链接
目录是一种特殊类型的文件,内容采用表格形式,数据项包括文件名以及对相应文件的引用。这一“文件名+引用”的组合被称为链接。每个文件都可以有多条链接,因而也可以有多个名称,在相同或不同的目录中出现。
目录可包含指向文件或其他目录的链接。路径间的链接建立起如上图所示的目录层级。
每个目录至少包含两条记录:.和…,前者是指向目录自身的链接,后者是指向其上级目录—父目录的链接。除根目录外,每个目录都有父目录。对于根目录而言,…是指向根目录自身的链接。
4.3 符号链接
类似于普通链接,符号链接给文件起了一个“别号(alternative name)”。在目录列表中,普通链接是内容为“文件名+指针”的一条记录,而符号链接则是经过特殊标记的文件,内容包含了另一文件的名称。所谓“另一文件”通常被称为符号链接的目标,人们一般会说符号链接“指向”或“引用”目标文件。在多数情况下,只要系统调用用到了路径名,内核会自动解除该路径名中符号链接的引用,以符号链接所指向的文件名来替换符号链接。若符号链接的目标文件自身也是一个符号链接,那么上述过程会以递归方式重复下去。(为了应对可能出现的循环引用,内核对解除引用的次数作了限制。)如果符号链接指向的文件并不存在,那么可将该链接视为空链接(dangling link)。
通常,人们会分别使用硬链接(hard link)或软链接(soft link)这样的术语来指代正常链接和符号链接。
4.4 文件名
在大多数 Linux 文件系统上,文件名最长可达 255 个字符。文件名可以包含除“/”和空字符(\0)外的所有字符。但是,只建议使用字母、数字、点(“.”)、下划线(“_”)以及连字符(“−”)。
4.5 路径名
路径名是由一系列文件名组成的字符串,彼此以“/”分隔,首字符可以为“/”(非强制)。除却最后一个文件名外,该系列文件名均为目录名称(或为指向目录的符号链接)。路径名的尾部可标识任意类型的文件,包括目录在内。有时将该字符串中最后一个“/”字符之前的部分称为路径名的目录部分,将其之后的部分称为路径名的文件部分。
路径名描述了单根目录层级下的文件位置,又可分为绝对路径名和相对路径名:
- 绝对路径名以“/”开始,指明文件相对于根目录的位置。
- 相对路径名定义了相对于进程当前工作目录的文件位置,与绝对路径名相比,相对路径名缺少了起始的“/”。
4.6 当前工作目录
每个进程都有一个当前工作目录(有时简称为进程工作目录或当前目录)。这就是单根目录层级下进程的“当前位置”,也是进程解释相对路径名的参照点。
进程的当前工作目录继承自其父进程。对登录 shell 来说,其初始当前工作目录,是依据密码文件中该用户记录的主目录字段来设置。
4.7 文件的所有权和权限
每个文件都有一个与之相关的用户 ID 和组 ID,分别定义文件的属主和属组。系统根据文件的所有权来判定用户对文件的访问权限。
为了访问文件,系统把用户分为 3 类:文件的属主、与文件组(group)ID 相匹配的属组成员用户以及其他用户。可为以上 3 类用户分别设置 3 种权限(共计 9 种权限位):只允许查看文件内容的读权限;允许修改文件内容的写权限;允许执行文件的执行权限。这里的文件要么指程序,要么是交由某种解释程序(通常指 shell 的一种,但也有例外)处理的脚本。
也可针对目录进行上述权限设置,但意义稍有不同。读权限允许列出目录内容(即该目录下的文件名),写权限允许对目录内容进行更改(比如,添加、修改或删除文件名),执行(有时也称为搜索)权限允许对目录中的文件进行访问(但需受文件自身访问权限的约束)。
5 文件 I/O 模型
UNIX 系统 I/O 模型最为显著的特性之一是其 I/O 通用性概念。也就是说,同一套系统调用(open()、read()、write()、close()等)所执行的 I/O 操作,可施之于所有文件类型,包括设备文件在内。(应用程序发起的 I/O 请求,内核会将其转化为相应的文件系统操作,或者设备驱动程序操作,以此来执行针对目标文件或设备的 I/O 操作。)因此,采用这些系统调用的程序能够处理任何类型的文件。
就本质而言,内核只提供一种文件类型:字节流序列,在处理磁盘文件、磁盘或磁带设备时,可通过 lseek()系统调用来随机访问。
许多应用程序和函数库都将换行符(十进制 ASCII 码为 10)视为文本中一行的结束和另一行的开始。UNIX 系统没有文件结束符的概念,读取文件时如无数据返回,便会认定抵达文件末尾。
5.1 文件描述符
I/O 系统调用使用文件描述符—(往往是数值很小的)非负整数—来指代打开的文件。获取文件描述符的常用手法是调用 open(),在参数中指定 I/O 操作目标文件的路径名。
通常,由 shell 启动的进程会继承 3 个已打开的文件描述符:
- 描述符 0 为标准输入,指代为进程提供输入的文件;
- 描述符 1 为标准输出,指代供进程写入输出的文件;
- 描述符 2 为标准错误,指代供进程写入错误消息或异常通告的文件。
在交互式 shell 或程序中,上述三者一般都指向终端。在 stdio 函数库中,这几种描述符分别与文件流 stdin、stdout 和 stderr 相对应。
5.2 stdio 函数库
C 编程语言在执行文件 I/O 操作时,往往会调用 C 语言标准库的 I/O 函数。也将这样一组I/O 函数称为 stdio 函数库,其中包括 fopen()、fclose()、scanf()、printf()、fgets()、fputs()等。stdio 函数位于 I/O 系统调用层(open()、close()、read()、write()等)之上。
6 程序
程序通常以两种面目示人。其一为源码形式,由使用编程语言写成的一系列语句组成,是人类可以阅读的文本文件。要想执行程序,则需将源码转换为第二种形式—计算机可以理解的二进制机器语言指令。一般认为,术语“程序”的上述两种含义几近相同,因为经过编译和链接处理,会将源码转换为语义相同的二进制机器码。
6.1 过滤器
从 stdin 读取输入,加以转换,再将转换后的数据输出到 stdout,常常将拥有上述行为的程序称为过滤器,cat、grep、tr、sort、wc、sed、awk 均在其列。
6.2 命令行参数
C 语言程序可以访问命令行参数,即程序运行时在命令行中输入的内容。要访问命令行参数,程序的 main()函数需做如下声明:
int main(int argc, char *argv[])
argc 变量包含命令行参数的总个数,argv 指针数组的成员指针则逐一指向每个命令行参数字符串。首个字符串 argv[0],标识程序名本身。
7 进程
进程是正在执行的程序实例。执行程序时,内核会将程序代码载入虚拟内存,为程序变量分配空间,建立内核记账(bookkeeping)数据结构,以记录与进程有关的各种信息(比如,进程 ID、用户 ID、组 ID 以及终止状态等)。
在内核看来,进程是一个个实体,内核必须在它们之间共享各种计算机资源。对于像内存这样的受限资源来说,内核一开始会为进程分配一定数量的资源,并在进程的生命周期内,统筹该进程和整个系统对资源的需求,对这一分配进行调整。程序终止时,内核会释放所有此类资源,供其他进程重新使用。其他资源(如 CPU、网络带宽等)都属于可再生资源,但必须在所有进程间平等共享。
7.1 进程的内存布局
逻辑上将一个进程划分为以下几部分(也称为段)。
- 文本:程序的指令。
- 数据:程序使用的静态变量。
- 堆:程序可从该区域动态分配额外内存。
- 栈:随函数调用、返回而增减的一片内存,用于为局部变量和函数调用链接信息分配存储空间。
7.2 创建进程和执行程序
进程可使用系统调用 fork()来创建一个新进程。调用 fork()的进程被称为父进程,新创建的进程则被称为子进程。内核通过对父进程的复制来创建子进程。子进程从父进程处继承数据段、栈段以及堆段的副本后,可以修改这些内容,不会影响父进程的“原版”内容。(在内存中被标记为只读的程序文本段则由父、子进程共享。)
然后,子进程:
- 去执行与父进程共享代码段中的另一组不同函数。
- 更为常见的情况是使用系统调用 execve()去加载并执行一个全新程序。execve()会销毁现有的文本段、数据段、栈段及堆段,并根据新程序的代码,创建新段来替换它们。
以 execve()为基础,C 语言库还提供了几个相关函数,接口虽然略有不同,但功能全都相同。
7.3 进程 ID 和父进程 ID
每一进程都有一个唯一的整数型进程标识符(PID)。此外,每一进程还具有一个父进程标识符(PPID)属性,用以标识请求内核创建自己的进程。
7.4 进程终止和终止状态
可使用以下两种方式之一来终止一个进程:
- 进程可使用_exit()系统调用(或相关的exit()库函数),请求退出;
- 向进程传递信号,将其“杀死”。
无论以何种方式退出,进程都会生成“终止状态”,一个非负小整数,可供父进程的 wait()系统调用检测。在调用_exit()的情况下,进程会指明自己的终止状态。若由信号来“杀死”进程,则会根据导致进程“死亡”的信号类型来设置进程的终止状态。(有时会将传递进_exit()的参数称为进程的“退出状态”,以示与终止状态有所不同,后者要么指传递给_exit()的参数值,要么表示“杀死”进程的信号。)
根据惯例,终止状态为 0 表示进程“功成身退”,非 0 则表示有错误发生。大多数 shell 会将前一执行程序的终止状态保存于 shell 变量$?中。
7.5 进程的用户和组标识符(凭证)
每个进程都有一组与之相关的用户 ID (UID)和组 ID (GID),如下所示。
- 真实用户 ID 和组 ID:用来标识进程所属的用户和组。新进程从其父进程处继承这些 ID。登录 shell 则会从系统密码文件的相应字段中获取其真实用户 ID 和组 ID。
- 有效用户 ID 和组 ID:进程在访问受保护资源(比如,文件和进程间通信对象)时,会使用这两个 ID(并结合下述的补充组 ID)来确定访问权限。一般情况下,进程的有效 ID 与相应的真实 ID 值相同。正如即将讨论的那样,改变进程的有效ID 实为一种机制,可使进程具有其他用户或组的权限。
- 补充组 ID:用来标识进程所属的额外组。新进程从其父进程处继承补充组 ID。登录shell 则从系统组文件中获取其补充组 ID。
7.6 特权进程
在 UNIX 系统上,就传统意义而言,特权进程是指有效用户 ID 为 0(超级用户)的进程。通常由内核所施加的权限限制对此类进程无效。与之相反,术语“无特权”(或非特权)进程是指由其他用户运行的进程。此类进程的有效用户 ID 为非 0 值,且必须遵守由内核所强加的权限规则。
由某一特权进程创建的进程,也可以是特权进程。例如,一个由 root(超级用户)发起的登录 shell。成为特权进程的另一方法是利用 set-user-ID 机制,该机制允许某进程的有效用户ID 等同于该进程所执行程序文件的用户 ID。
7.7 能力(Capabilities)
始于内核 2.2,Linux 把传统上赋予超级用户的权限划分为一组相互独立的单元(称之为“能力”)。每次特权操作都与特定的能力相关,仅当进程具有特定能力时,才能执行相应操作。传统意义上的超级用户进程(有效用户 ID 为 0)则相应开启了所有能力。
赋予某进程部分能力,使得其既能够执行某些特权级操作,又防止其执行其他特权级操作。
7.8 init 进程
系统引导时,内核会创建一个名为 init 的特殊进程,即“所有进程之父”,该进程的相应程序文件为/sbin/init。系统的所有进程不是由 init(使用 frok())“亲自”创建,就是由其后代进程创建。init 进程的进程号总为 1,且总是以超级用户权限运行。谁(哪怕是超级用户)都不能“杀死”init 进程,只有关闭系统才能终止该进程。init 的主要任务是创建并监控系统运行所需的一系列进程。
7.9 守护进程
守护进程指的是具有特殊用途的进程,系统创建和处理此类进程的方式与其他进程相同,但以下特征是其所独有的:
- “长生不老”。守护进程通常在系统引导时启动,直至系统关闭前,会一直“健在”。-
- 守护进程在后台运行,且无控制终端供其读取或写入数据。
守护进程中的例子有 syslogd(在系统日志中记录消息)和 httpd(利用 HTTP 分发 Web 页面)。
7.10 环境列表
每个进程都有一份环境列表,即在进程用户空间内存中维护的一组环境变量。这份列表的每一元素都由一个名称及其相关值组成。由 fork()创建的新进程,会继承父进程的环境副本。这也为父子进程间通信提供了一种机制。当进程调用 exec()替换当前正在运行的程序时,新程序要么继承老程序的环境,要么在 exec()调用的参数中指定新环境并加以接收。
在绝大多数shell 中,可使用export 命令来创建环境变量(C shell 使用setenv 命令),如下所示:
$ export MYVAR="hello"
C 语言程序可使用外部变量(char **environ)来访问环境,而库函数也允许进程去获取或修改自己环境中的值。
环境变量的用途多种多样。例如,shell 定义并使用了一系列变量,供 shell 执行的脚本和程序访问。其中包括:变量 HOME(明确定义了用户登录目录的路径名)、变量 PATH(指明了用户输入命令后,shell 查找与之相应程序时所搜索的目录列表)。
7.11 资源限制
每个进程都会消耗诸如打开文件、内存以及 CPU 时间之类的资源。使用系统调用 setrlimit(),进程可为自己消耗的各类资源设定一个上限。此类资源限制的每一项均有两个相关值:
- 软限制(soft limit)限制了进程可以消耗的资源总量。
- 硬限制(hard limit)软限制的调整上限。
非特权进程在针对特定资源调整软限制值时,可将其设置为 0 到相应硬限制值之间的任意值,但硬限制值则只能调低,不能调高。由 fork()创建的新进程,会继承其父进程对资源限制的设置。
使用 ulimit 命令(在 C shell 中为 limit)可调整 shell 的资源限制。shell 为执行命令所创建的子进程会继承上述资源设置。
8 内存映射
调用系统函数 mmap()的进程,会在其虚拟地址空间中创建一个新的内存映射。映射分为两类。
- 文件映射:将文件的部分区域映射入调用进程的虚拟内存。映射一旦完成,对文件映射内容的访问则转化为对相应内存区域的字节操作。映射页面会按需自动从文件中加载。
- 匿名映射,其映射页面的内容会被初始化为 0。
由某一进程所映射的内存可以与其他进程的映射共享。达成共享的方式有二:
- 两个进程都针对某一文件的相同部分加以映射。
- 由 fork()创建的子进程自父进程处继承映射。
当两个或多个进程共享的页面相同时,进程之一对页面内容的改动是否为其他进程所见呢?这取决于创建映射时所传入的标志参数。若传入标志为私有,则某进程对映射内容的修改对于其他进程是不可见的,而且这些改动也不会真地落实到文件上;若传入标志为共享,对映射内容的修改就会为其他进程所见,并且这些修改也会造成对文件的改动。
内存映射用途很多,其中包括:以可执行文件的相应段来初始化进程的文本段、内存(内容填充为 0)分配、文件 I/O(即映射内存 I/O)以及进程间通信(通过共享映射)。
9 静态库和共享库
所谓目标库是这样一种文件:将(通常是逻辑相关的)一组函数代码加以编译,并置于一个文件中,供其他应用程序调用。这一做法有利于程序的开发和维护。现代 UNIX 系统提供两种类型的对象库:静态库和共享库。
9.1 静态库
本质上说来,静态库是对已编译目标模块的一种结构化整合。要使用静态库中的函数,需要在创建程序的链接命令中指定相应的库。主程序会对静态库中隶属于各目标模块的不同函数加以引用。链接器在解析了引用情况后,会从库中抽取所需目标模块的副本,将其复制到最终的可执行文件中,这就是所谓静态链接。对于所需库内的各目标模块,采用静态链接方式生成的程序都存有一份副本。这会引起诸多不便。
- 在不同的可执行文件中,可能都存有相同目标代码的副本,这是对磁盘空间的浪费。同理,调用同一库函数的程序,若均以静态链接方式生成,且又于同时加以执行,这会造成内存浪费,因为每个程序所调用的函数都各有一份副本驻留在内存中,
- 如果对库函数进行了修改,需要重新加以编译、生成新的静态库,而所有需要调用该函数“更新版”的应用,都必须与新生成的静态库重新链接。
9.2 共享库
设计共享库的目的是为了解决静态库所存在的问题。
如果将程序链接到共享库,那么链接器就不会把库中的目标模块复制到可执行文件中,而是在可执行文件中写入一条记录,以表明可执行文件在运行时需要使用该共享库。一旦在运行时将可执行文件载入内存,一款名为“动态链接器”的程序会确保将可执行文件所需的动态库找到,并载入内存,随后实施运行时链接,解析可执行文件中的函数调用,将其与共享库中相应的函数定义关联起来。在运行时,共享库代码在内存中只需保留一份,且可供所有运行中的程序使用。
经过编译处理的函数仅在共享库内保存一份,从而节约了磁盘空间。另外,这一设计还能确保各类程序及时使用到函数的最新版本,只需将带有函数新定义体的共享库重新加以编译即可,程序会在下次执行时自动使用新函数。
10 进程间通信及同步
Linux 系统上运行有多个进程,其中许多都是独立运行。然而,有些进程必须相互合作以达成预期目的,因此彼此间需要通信和同步机制。
读写磁盘文件中的信息是进程间通信的方法之一。可是,对许多程序来说,这种方法既慢又缺乏灵活性。因此,Linux 提供了丰富的进程间通信(IPC)机制,如下所示。
- 信号(signal),用来表示事件的发生。
- 管道(亦即 shell 用户所熟悉的“|”操作符)和 FIFO,用于在进程间传递数据。
- 套接字,供同一台主机或是联网的不同主机上所运行的进程之间传递数据。
- 文件锁,为防止其他进程读取或更新文件内容,允许某进程对文件的部分区域加以锁定。
- 消息队列,用于在进程间交换消息(数据包)。y 信号量(semaphore),用来同步进程动作。
- 共享内存,允许两个及两个以上进程共享一块内存。当某进程改变了共享内存的内容时,其他所有进程会立即了解到这一变化。
UNIX 系统的 IPC 机制种类如此繁多,有些功能还互有重叠,部分原因是由于各种 IPC 机制是在不同的 UNIX 实现上演变而来的,需要遵循的标准也各不相同。例如,就本质而言,FIFO和 UNIX 套接字功能相同,允许同一系统上并无关联的进程彼此交换数据。二者之所以并存于现代 UNIX 系统之中,是由于 FIFO 来自 System V,而套接字则源于 BSD。
11 信号
尽管上一节将信号视为 IPC 的方法之一,但其在其他方面的广泛应用则更为普遍,因此值得深入讨论。
人们往往将信号称为“软件中断”。进程收到信号,就意味着某一事件或异常情况的发生。信号的类型很多,每一种分别标识不同的事件或情况。采用不同的整数来标识各种信号类型,并以 SIGxxxx 形式的符号名加以定义。
内核、其他进程(只要具有相应的权限)或进程自身均可向进程发送信号。例如,发生下列情况之一时,内核可向进程发送信号。
- 用户键入中断字符(通常为 Control-C)。
- 进程的子进程之一已经终止。
- 由进程设定的定时器(告警时钟)已经到期。
- 进程尝试访问无效的内存地址。
在 shell 中,可使用 kill 命令向进程发送信号。在程序内部,系统调用 kill()可提供相同的功能。收到信号时,进程会根据信号采取如下动作之一。
- 忽略信号。
- 被信号“杀死”。
- 先挂起,之后再被专用信号唤醒。
就大多数信号类型而言,程序可选择不采取默认的信号动作,而是忽略信号(当信号的默认处理行为并非忽略此信号时,会派上用场)或者建立自己的信号处理器。信号处理器是由程序员定义的函数,会在进程收到信号时自动调用,根据信号的产生条件执行相应动作。
信号从产生直至送达进程期间,一直处于挂起状态。通常,系统会在接收进程下次获得调度时,将处于挂起状态的信号同时送达。如果接收进程正在运行,则会立即将信号送达。然而,程序可以将信号纳入所谓“信号屏蔽”1 以求阻塞该信号。如果产生的信号处于“信号屏蔽”之列,那么此信号将一直保持挂起状态,直至解除对该信号的阻塞。
12 线程
在现代 UNIX 实现中,每个进程都可执行多个线程。可将线程想象为共享同一虚拟内存及一干其他属性的进程。每个线程都会执行相同的程序代码,共享同一数据区域和堆。可是,每个线程都拥有属于自己的栈,用来装载本地变量和函数调用链接信息。
线程之间可通过共享的全局变量进行通信。借助于线程 API 所提供的条件变量和互斥机制,进程所属的线程之间得以相互通信并同步行为——尤其是在对共享变量的使用方面。
线程的主要优点在于协同线程之间的数据共享(通过全局变量)更为容易,而且就某些算法而论,以多线程来实现比之以多进程实现要更加自然。再者,显而易见,多线程应用能从多处理器硬件的并行处理中获益匪浅。
13 进程组和 shell 任务控制
shell 执行的每个程序都会在一个新进程内发起。比如,shell 创建了 3 个进程来执行以下管道命令(在当前的工作目录下,根据文件大小对文件进行排序并显示):
$ ls -l | sort -k5n | less
主流 shell 都提供了一种交互式特性,名为任务控制。该特性允许用户同时执行并操纵多条命令或管道。在支持任务控制的 shell 中,会将管道内的所有进程置于一个新进程组或任务中。(如果情况很简单,shell 命令行只包含一条命令,那么就会创建一个只包含单个进程的新进程组。)进程组中的每个进程都具有相同的进程组标识符(以整数形式),其实就是进程组中某个进程(也称为进程组组长 process group leader)的进程 ID。
内核可对进程组中的所有成员执行各种动作,尤其是信号的传递。
14 会话、控制终端和控制进程
会话指的是一组进程组(任务)。会话中的所有进程都具有相同的会话标识符。会话首进程(session leader)是指创建会话的进程,其进程 ID 会成为会话 ID。
使用会话最多的是支持任务控制的 shell,由 shell 创建的所有进程组与 shell 自身隶属于同一会话,shell 是此会话的会话首进程。
通常,会话都会与某个控制终端相关。控制终端建立于会话首进程初次打开终端设备之时。对于由交互式 shell 所创建的会话,这恰恰是用户的登录终端。一个终端至多只能成为一个会话的控制终端。
打开控制终端会致使会话首进程成为终端的控制进程。一旦断开了与终端的连接(比如,关闭了终端窗口),控制进程将会收到 SIGHUP 信号。
在任一时点,会话中总有一个前台进程组(前台任务),可以从终端中读取输入,向终端发送输出。如果用户在控制终端中输入了“中断”(通常是 Control-C)或“挂起”字符(通常是 Control-Z),那么终端驱动程序会发送信号以终止或挂起(亦即停止)前台进程组。一个会话可以拥有任意数量的后台进程组(后台任务),由以“&”字符结尾的行命令来创建。
15 伪终端
伪终端是一对相互连接的虚拟设备,也称为主从设备。在这对设备之间,设有一条 IPC信道,可供数据进行双向传递。
从设备(slave device)所提供的接口,其行为方式与终端相类似,基于这一特点,可以将某个伪终端编写的程序与从设备连接起来,然后,再利用连接到主设备的另一程序来驱动这一“面向终端”的程序,这是伪终端的一个关键用途。
伪终端广泛应用于各种应用领域,最知名的要数 telnet 和 ssh 之类提供网络登录服务的应用。
16 日期和时间
进程涉及两种类型的时间。
- 真实时间:指的是在进程的生命期内(所经历的时间或时钟时间),以某个标准时间点(日历时间)或固定时间点(通常是进程的启动时间)为起点测量得出的时间。在UNIX 系统上,日历时间是以国际协调时间(简称 UTC)1970 年 1 月 1 日凌晨为起始点,按秒测量得出的时间,再进行时区调整(定义时区的基准点为穿过英格兰格林威治的经线)。这一日期与 UNIX 系统的生日很接近,也被称为纪元(Epoch)。
- 进程时间:亦称为 CPU 时间,指的是进程自启动起来,所占用的 CPU 时间总量。可进一步将 CPU 时间划分为系统 CPU 时间和用户 CPU 时间。前者是指在内核模式中,执行代码所花费的时间(比如,执行系统调用,或代表进程执行其他的内核服务)。后者是指在用户模式中,执行代码所花费的时间(比如,执行常规的程序代码)。
17 客户端/服务器架构
本书有多处论及客户端/服务器应用程序的设计和实现。客户端/服务器应用由两个组件进程组成。
- 客户端:向服务器发送请求消息,请求服务器执行某些服务。
- 服务器:分析客户端的请求,执行相应的动作,然后,向客户端回发响应消息。
有时,服务器与客户端之间可能需要就一次服务而进行多次交互。
客户端应用通常与用户打交道,而服务器应用则提供对某些共享资源的访问。一般说来,都是众多客户端进程与为数不多的一个或几个服务器端进程进行通信。
服务器可以提供各种服务,如下所示。
- 提供对数据库或其他共享信息资源的访问。y 提供对远程文件的跨网访问。
- 对某些商业逻辑进行封装。
- 提供对共享硬件资源的访问(比如,打印机)。
- 提供 WWW 服务。
将某项服务封装于单独的服务器应用中,这一做法原因很多,举例如下。
- 效率:较之于在本地的每台计算上提供相同资源,在服务器应用管理之下提供资源的一份实例,则要节约许多。
- 控制、协调和安全:由于资源(尤其是信息资源)的统一存放,服务器既可以协调对资源的访问(例如,两个客户端不能同时更新同一信息),还可以保护资源安全,令其只对特定客户端开放。
- 在异构环境中运行:在网络中,客户端和服务器应用所运行的硬件平台和操作系统可以不同。
18 实时性
实时性应用程序是指那些需要对输入做出及时响应的程序。此类输入往往来自于外接的传感器或某些专门的输入设备,而输出则会去控制外接硬件。具有实时性需求的应用程序示例包括自动化装配流水线、银行 ATM 机,以及飞机导航系统等。
虽然许多实时性应用程序都要求对输入做出快速响应,但决定性因素却在于要在事件触发后的一定时限内,保证响应的交付。
要提供实时响应,特别是在短时间内加以响应,就需要底层操作系统的支持。由于实时响应的需求与多用户分时操作系统的需求存在冲突,大多数操作系统“天生”并不提供这样的支持。虽然已经设计出不少实时性的 UNIX 变体,但传统的 UNIX 实现都不是实时操作系统。Linux 的实时性变体也早已诞生,而近期的 Linux 内核正转向对实时性应用原生而全面的支持。
为支持实时性应用,POSIX.1b 定义了多个 POSIX.1 扩展,其中包括异步 I/O、共享内存、内存映射文件、内存锁定、实时性时钟和定时器、备选调度策略、实时性信号、消息队列,以及信号量等。
19 /proc 文件系统
Linux 也提供了/proc 文件系统,由一组目录和文件组成,装配(mount)于/proc 目录下。
/proc 文件系统是一种虚拟文件系统,以文件系统目录和文件形式,提供一个指向内核数据结构的接口。这为查看和改变各种系统属性开启了方便之门。此外,还能通过一组以/ proc/PID 形式命名的目录(PID 即进程 ID)查看系统中运行各进程的相关信息。
通常,/proc 目录下的文件内容都采取人类可读的文本形式,shell 脚本也能对其进行解析。程序可以打开、读取和写入/proc 目录下的既定文件。大多数情况下,只有特权级进程才能修改/proc 目录下的文件内容。
相关文章:

Linux系统 - 基本概念
介绍一些Linux系统的基本概念 1 操作系统的核心—内核 “操作系统”通常包含两种不同含义。 1.指完整的软件包,这包括用来管理计算机资源的核心层软件,以及附带的所有标准软件工具,诸如命令行解释器、图形用户界面、文件操作工具…...
kerberos在无痕浏览器 获取用户信息失败 如何判断是否无痕浏览器
kerberos在无痕浏览器 获取用户信息失败 如何判断是否无痕浏览器 js 代码 其他地方用直接导入js getCurrentUserId 这是自己后端获取 域账号地址 我是成功返回200 //true普通浏览器 fasle 无痕浏览器 export const checkBrowserMode async () > {try {const response a…...
在h5端实现录音发送功能(兼容内嵌微信小程序) recorder-core
本文将通过一个实际的 Vue3 组件示例,带你一步步实现“按住录音,松开发送,上滑取消”的语音录制功能。 我们将使用强大且小巧的开源库 recorder-core,支持 MP3、WAV、AAC 等编码格式,兼容性较好。 🔧 项目…...

PDF电子发票数据提取至Excel
声明:本软件是吾爱大佬th4c3y原创,本人只是搬运工! 发票识别更新记录 【2025-3-14】更新 v2.0 在字段设置中新增自定义字段(仅在 PDF 正则式接口下生效),支持自定义正则表达式或固定字符。 自定义字段会…...

【身份证识别表格】把大量手机拍摄的身份证信息转换成EXCEL表格的数据,拍的身份证照片转成excel表格保存,基于WPF和腾讯OCR的实现方案
基于WPF和腾讯OCR的身份证照片转Excel方案 应用场景 企业人事管理:新员工入职时批量录入数百份身份证信息,传统手动录入易出错且耗时。通过OCR自动提取姓名、身份证号等字段,生成结构化Excel表格,效率提升10倍以上。 …...

FPGA高速接口 mipi lvds cameralink hdml 千兆网 sdi
mipi: https://blog.csdn.net/SDJ_success/article/details/146541776 cameralink CameraLink协议 CameraLink协议是一种专门针对机器视觉应用领域的串行通信协议,它使用低压差分信号(LVDS)进行数据的传输和通信。CameraLink标准是在ChannelLink标准的基础上多加了…...

Linux路径解析指南:逻辑路径 vs 实际路径详解
在 Linux 系统中,逻辑路径(Logical Path)和 实际路径(Physical Path)是两个不同的概念,主要区别在于它们如何解析文件或目录的位置。以下是详细解释: 目录 1. 逻辑路径(Logical Path…...

Azure 公有云基础架构与核心服务:从基础到实践指南
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 Azure 的基础架构由多个核心组件构成,理解这些概念是掌握其技术框架的第一步: 地理区域(Geographic R…...

【运维_日常报错解决方案_docker系列】一、docker系统不起来
今天忽然想起来哎,还有一台”尘封“着的服务器,好久没用了,就随便打开登了登,然后想看一下服务器上面还有正在跑着的容器服务吗,然后使用docker ps 发现报错了。 然后重启也是下面这个状态。 查看docker状态…...

C# 数组与字符串:全面解析与应用实践
在C#编程语言中,数组和字符串是两种最基础也是最重要的数据类型。无论是简单的控制台应用程序,还是复杂的企业级系统,数组和字符串都扮演着不可或缺的角色。本文将全面深入地探讨C#中数组和字符串的特性、使用方法、性能考量以及实际应用场景…...
前端vue中使用signalr
一、引入SignalR库 使用NPM引入SignalR库 npm install @microsoft/signalrJs文件中引入 import * as signalR from @microsoft/signalr;二、初始化连接 这一步需要指定SignalR Hub的URL。 const connection = new signalR.HubConnectionBuilder().withUrl("https://y…...
Stable Diffusion底模对应的VAE推荐
以下是主流Stable Diffusion底模对应的VAE推荐表格: 底模版本推荐VAE类型说明SD1.5SD1.5专用VAE通常使用vae-ft-mse-840000-ema-pruned.safetensorsSD2.0SD1.5兼容VAE或SD2专用VAE部分SD2模型需配套512-ema-only.vae.ptSD3内置VAESD3系列模型通常自带集成VAE无需额…...
centos7.5安装kubernetes1.25.0
centos7.5安装kubernetes centos7.5kubernetes1)准备阶段准备2台虚拟机配置静态IP修改主机名桥接设置配置阿里云的repo源配置k8s切国际源配置时间同步安装基础软件包 2)安装containerd服务安装配置开启启动 3)安装k8s4)安装kubersphere下载helm安装包解压将helm配置…...

AT2659S射频前端芯片技术解析:L1频段低噪声高增益GNSS信号放大
以下是关于AT2659S L1频段卫星导航射频前端芯片的客观描述,严格基于用户提供的原始信息,采用分享式表述,保持参数和核心内容不变: AT2659S芯片概述 AT2659S是一款基于SiGe工艺的射频前端芯片,专为L1频段&#…...

ROS2学习(15)------ROS2 TF2 机器人坐标系管理器
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 ROS版本:2 在 ROS 2 中,TF2(Transform Library, v2) 是一个非常核心的工具库,用于管理多个坐标系之间的 变换关系(tran…...
每日c/c++题 备战蓝桥杯(洛谷P3382 三分法求极值详解)
洛谷P3382 三分法求极值详解 题目描述 P3382 三分法 要求在给定区间内寻找一个多项式函数的最大值点。题目保证函数在区间内先严格递增后严格递减(单峰函数),适合使用三分法求解。 算法原理 三分法核心思想 对于单峰函数,在区…...

Vue+css实现扫描动画效果(使用@keyframes scan)
实现效果 扫描效果 参考链接 MDN Web Docs: CSS Animations 关键代码 示例代码 <div class"scanner-container"><div class"scanner-line"></div><div class"scanner-icon">📷</div><p>Scan m…...
Windows 配置 ssh 秘钥登录 Ubuntu
在 Windows 上推送 SSH 公钥到远程服务器(类似于 Linux 上的 ssh-copy-id)可以通过以下几种方法实现: ** 手动复制公钥内容** 查看本地公钥内容:type $env:USERPROFILE\.ssh\id_rsa.pub登录远程服务器,将公钥内容粘贴…...
Conda:环境移植及更新1--使用conda-pack
更多内容:XiaoJ的知识星球 目录 一、使用conda-pack1.安装 conda-pack2.移植整个 Anaconda 环境3.移植单个虚拟环境4.验证是否生效 在相同Linux设备上移植Miniconda3(Anaconda3同理)常用方法有。 使用conda-pack:使用conda-pack工…...
github好玩的工具
以下是 GitHub 上一些有趣且实用的开源工具推荐,涵盖 AI 应用、效率提升、趣味开发等方向,结合最新趋势和项目热度整理: 一、AI 与深度伪造工具 Deep-Live-Cam 仅需一张图片即可在视频直播中实时替换人脸,适用于内容创作和虚拟角色开发,支持多平台硬件运行(如 NVIDIA CUD…...

PHP学习笔记(九)
箭头函数 箭头函数是 PHP 7.4的新语法。是一种更简洁的匿名函数的写法,它们都是closure类的实现。 箭头函数的基本语法为fn(argument_list) > expr 箭头函数支持与匿名函数相同的功能,只是其父作用域的变量总是自动的。 当表…...
共现矩阵的SVD降维与低维词向量计算详解
共现矩阵的SVD降维与低维词向量计算详解 1. 原始共现矩阵构建 根据用户提供的共现对: 句子1: (I, like), (like, apples)句子2: (I, like), (like, bananas) 词汇表:[I, like, apples, bananas] 窗口大小2(假设共现对直接作为矩阵的非零元…...

信创 CDC 实战 | OGG、Attunity……之后,信创数据库实时同步链路如何构建?(以 GaussDB 数据入仓为例)
国产数据库加速进入核心系统,传统同步工具却频频“掉链子”。本系列文章聚焦 OceanBase、GaussDB、TDSQL、达梦等主流信创数据库,逐一拆解其日志机制与同步难点,结合 TapData 的实践经验,系统讲解从 CDC 捕获到实时入仓࿰…...
PyQt学习系列08-插件系统与模块化开发
PyQt学习系列笔记(Python Qt框架) 第八课:插件系统与模块化开发 (原课程规划中的第12课,按用户要求调整为第9课) 课程目标 掌握Qt插件系统的原理与开发方法实现可扩展的模块化应用程序理解QPluginLoader动…...
Redis核心数据结构操作指南:字符串、哈希、列表详解
注:此为苍穹外卖学习笔记 Redis作为高性能的键值数据库,其核心价值来自于丰富的数据结构支持。本文将深入解析字符串(String)、哈希(Hash)、**列表(List)**三大基础结构的操作命令&…...

微服务(SpringCloud)的简单介绍
一.什么是微服务? 微服务是一种软件架构风格,核心思想是用职责单一的小型项目,组合出复杂的大型项目。 二.举例 1.单体架构(SpringBoot) 无论项目中有多少功能,都是放在一个项目中。 如下图所示࿱…...

Python 爬虫开发
文章目录 1. 常用库安装2. 基础爬虫开发2.1. 使用 requests 获取网页内容2.2. 使用 BeautifulSoup 解析 HTML2.3. 处理登录与会话 3. 进阶爬虫开发3.1. 处理动态加载内容(Selenium)3.2. 使用Scrapy框架3.3. 分布式爬虫(Scrapy-Redisÿ…...

第十一周作业
一、实现bluecms旁注,并解释为什么旁站攻击可以拿下主站?跨库的意思是什么? 1、为什么旁站攻击可以拿下主站 因为主站业务和旁站业务共处于同一个服务器上面,当我们无法攻破主站业务时,可以通过攻破旁站业务…...

猿大师办公助手网页编辑Office/wps支持服务器文件多线程下载吗?
浏览器兼容性割裂、信创替代迫切的2025年,传统WebOffice控件因依赖NPAPI/PPAPI插件已无法适配Chrome 107等高版本浏览器。猿大师办公助手通过系统级窗口嵌入技术,直接调用本地Office/WPS内核,实现: 真内嵌非弹窗:将Of…...

英码科技携带 “无感知AI数字课堂”解决方案,亮相第22届广东教育装备展
5月23日至25日,第22届广东教育装备展览会在广州国际采购中心盛大举行。作为华为生态重要合作伙伴,英码科技携“无感知AI数字课堂解决方案”重磅登场,聚焦教学提质增效,为教育数字化转型注入新动能。 聚焦课堂真实场景,…...