【Linux/Unix系统编程手册笔记】基本概念
1.操作系统的核心——内核
管理和分配计算机资源(即CPU、RAM和设备)的核心层软件。
Linux内核可执行文件采用/boot/vmlinuz或与之类似的路径名。
内核的职责
- 进程调度
- 抢占式多任务操作系统
- 内存管理
- Linux采用虚拟内存管理机制
- 进程与进程之间,进程与内核之间彼此隔离
- 只需将进程的一部分保持在内存中
- 文件系统
- 内核在磁盘之上提供了文件系统,允许对文件执行创建、获取、更新以及删除等操作
- 创建和终止进程
- 对设备的访问
- 与外设的通信机制包括输入、输出或两者兼而有之
- 内核既为程序访问设备提供了简化版的标准接口,同时还要仲裁多个进程对一个设备的访问
- 联网
- 内核以用户进程的名义收发网络消息(数据包)
- 提供系统调用应用编程接口
- 进程可利用内核入口点(也称为系统调用)请求内核去执行各种任务
- 虚拟私有计算机
- 每个用户可以登录进入系统内,独立操作,运行程序于自有的虚拟地址空间
内核态和用户态
现代处理器架构一般允许CPU至少在两种不同状态下运行:
- 用户态
- 和心态
执行硬件指令可使CPU在两种状态间来回切换。与之对应的虚拟内存区域划分(标记)为用户空间部分或内核空间部分。在用户态下运行时,CPU只能访问被标记为用户空间的内存,试图访问内核空间的内存会引发硬件异常。当运行于核心态时,CPU既能访问用户空间内存,也能访问内核空间内存。
仅当处理器在核心态才能执行的操作:
- 执行宕机(halt)
- 访问内存管理硬件
- 设计IO操作
以进程及内核视角检视系统
内核维护的数据结构中,包含了所有正在运行的进程有关信息。随着进程的创建、状态发生变化、终结,内核会及时更新这些数据结构。
内核所维护的底层数据结构可将程序使用的文件名转换为磁盘的物理位置。
每个进程的虚拟内存与计算机物理内存及磁盘交换区(磁盘空间中的保留区域,作为计算机RAM的补充)之间的映射关系,也在内核维护的数据结构之列。
2.shell
shell是一种具有特殊用途的程序,主要用户读取哟京沪输入命令,并执行相应的程序以响应命令。有时,也称之为命令解释器。
尽管某些操作系统将命令解释器集成于内核中,而对UNIX系统而言,shell只是一个用户进程。
几种shell:
- Bourne shell(sh)
- C shell(csh)
- Korn shell(ksh)
- Bourne again shell(bash)
3.用户和组
系统会对每个用户的身份做唯一标识,用户可隶属于多个组。
用户
系统的每个用户都拥有唯一的登录名(用户名)和与之对应的整数型用户ID(UID)。
系统密码文件**/etc/passwd **中记录的信息:
- UID
- 组ID
- 主目录:用户登陆后所居于的初始目录
- 登陆shell
用户密码往往存储于单独的shadow密码文件中
组
一个用户可以同时属于组。
每个用户对应着系统组文件/etc/group中的一行记录。含有如下记录:
- 组名
- 组ID(GID)
- 用户列表
超级用户
超级用户账号ID为0,通常登陆名为root。超级用户凌驾于系统的权限检查之上。无论文件何种访问权限,超级用户都可以访问系统中的任何文件,也能发送信号干预系统运行的所有用户进程。
4.单根目录层级、目录、链接及文件
内核维护着一套单根目录结构,以放置系统的所有文件。"/" 根目录。
文件类型
文件系统中,会对文件类型进行标记,以表明其种类,含有如下种类:
- 普通文件
- 设备
- 管道
- 套接字
- 目录
- 符号链接
路径和链接
目录是一种特殊类型的文件,内容采用表格形式,数据项包括文件名以及对相应文件的引用。“文件名+引用”的组合被称为链接,每个文件都可以有多条链接,因而也可以有多个名称,在相同或不同的目录中出现。
目录可包含指向文件或其他目录的链接。
每个目录至少包含两条记录:.和..,前者指向目录自身的链接,后者指向其上级目录的链接。
符号链接
类似普通链接,符号链接给文件起了一个“别名”。
普通链接是文件名+指针的一条记录,符号链接是经过特殊标记的文件。一般会说符号链接“指向”或“引用”目标文件,在多数情况下,只要系统调用用到了路径名,内存会自动解除该路径中符号链接的引用,以符号链接所指向的文件名来替换符号链接。
- 硬链接:普通链接
- 软链接:符号链接
文件名
文件名最长达255个字符
路径名
- 绝对路径:以"/"开始
- 相对路径
当前工作目录
每个进程都有一个当前工作目录。
文件的所有权和权限
每个文件都有一个与之相关的用户ID和组ID,分别定义文件的属主和属组。
为了访问文件,系统把用户分为3类:
- 文件的属主(文件的用户)
- 文件组ID相匹配的属组成员用户
- 其他用户
文件的3中权限:
- 读
- 写
- 执行
也可针对目录进行上述权限设置,但意义稍有不同。读权限允许列出目录内容(即该目录下的文件名),写权限允许对目录内容进行更改(添加、修改、删除文件名),执行(搜索)权限允许对目录中的文件进行访问(但需受文件自身访问权限约束)
5.文件I/O模型
I/O通用性
同一套系统调用(open() read() write() close())所执行的I/O操作,可施之与所有文件类型,包括设备文件在内。
文件描述符
I/O系统调用使用文件描述符来指代打开的文件。获取文件描述符常用手法就是调用open(),在参数中指定I/O操作目标文件的路径名。
shell启动的进程会继承3个已打开的文件描述符:
- 描述符0为标准输入,指代为进程提供输入的文件
- 描述符1为标准输出,指代进程写入输出的文件
- 描述符2为标准错误,指代供进程写入错误消息或异常通告的文件。
在stdio函数库中,这几种描述符分别与文件流stdin、stdout和stderr相对应。
stdio函数库
C语言的标准I/O函数
6.程序
过滤器
从stdin读取输入,加以转换,在输出到stdout,这种行为的程序称为过滤器。
- cat
- grep
- tr
- sort
- wc
- sed
- awk
命令行参数
C语言程序可以访问命令行参数,即程序运行时在命令行中输入的内容,在程序main函数中访问:
int main(int argc, char *argv[]])
argc变量包含命令行参数的总个数,argv指针数组的成员逐一指向命令行参数字符串,首个字符串argv[0],标识程序名本身。
7.进程
进程就是正在执行的程序实例。执行程序时,内核会将程序代码载入虚拟内存,为程序变量分配空间,简历内核记账(bookkeepingn)数据结构,以记录与进程有关的各种信息(进程ID、用户ID、组ID以及终止状态)。
进程的内存布局
逻辑上将一个进程划分为以下几个部分(也称为段):
- 文本:程序的指令
- 数据:程序使用的静态变量。
- 堆:程序可以从该区域动态分配额外内存。
- 栈:随函数调用、返回而增减的一片内存,用于为局部变量和函数调用链接信息分配存储空间。
创建进程和执行程序
进程可以使用系统调用fork()来创建一个新进程。调用fork()的进程称为父进程,新创建的是子进程。内核通过对父进程的复制来创建子进程。子进程从父进程处继承数据段、栈段以及堆段的副本后,可以修改这些内容,不糊影响父进程的原版内容。在内存中被标记为只读的程序文本段则由父子进程共享。
使用execve()系统调用去加载并执行一个全新程序。
进程ID和父进程ID
每一个进程都有一个唯一的整数型进程标识符PID,一个父进程标识符PPID
进程终止和终止状态
两种方式:
- 使用_exit()系统调用
- 向进程传递信号,将其"杀死"
进程终止,都会产生一个“终止状态”,一个非负小整数,可供父进程的wait()系统调用检测。终止状态为0表示进程“正常退出”,非0则表示有错误发生,大多数shell会将前一执行程序的终止状态保存于shell变量$?中
进程的用户和组标识符(凭证)
每个进程都有一组与之相关的UID和GID。
- 真实UID和GID:用来表示进程所属的用户和组。新进程从父进程继承这些ID。
- 有效UID和GID:进程在访问受保护资源时,会使用这两个ID来确定访问权限。一般情况,进程的有效ID和相应的真实ID值相同,改变进程的有效ID实际上是一种机制,可使进程具有其他用户或组的权限。
- 补充GID:用来标识进程所属的额外组。新进程从父进程继承补充组ID。登录shell则从系统组文件中获取其补充组ID
特权进程
特权进程是指有效用户ID为0的进程。通常由内核所施加的权限限制对此类进程无效。由某一特权进程创建的进程,也可以是特权进程。另一种方法是set-user-ID机制,该机制允许某进程的有效用户ID等同于该进程所执行程序文件的用户ID
能力
Linux把传统上赋予超级用户的权限划分为一组相互独立的单元,称为“能力”。传统意义上的超级用户进程则相应开启了所有能力。
赋予某进程部分能力,使得其既能够执行某些特权级操作,又防止其执行特权级操作。
能力的命名以CAP_前缀,例如,CAP_KILL
init进程
系统引导时,内核会创建一个名为init的特殊进程,所有进程之父,该进程的响应程序文件为/sbin/init。系统的所有进程不是由init(使用fork())创建,就是由其后代创建。init的进程号总为1,且总是以超级用户权限运行。
守护进程
守护进程值具有特殊用途的进程,系统创建和处理此类进程的方式与其他进程相同,但有一下独有特征:
- “长生不老”。守护进程在系统引导时启动,直至系统关闭前,会一直健在。
- 守护进程在后台运行,且无控制终端供其读取或写入数据。
例子,syslogd(在系统日志中记录消息)和httpd(利用HTTP分发Web页面)
环境列表
每个进程都有一份环境列表,即在进程用户空间内存中维护的一组环境变量。
使用export命令创建环境变量:
$ export MYVAR = 'Hello world'
C语言可使用外部变量(char environ)来访问环境,而库函数也允许进程去获取或修改自己环境中的值。
资源限制
系统调用setrlimit()进程可以自己消耗的各类资源限制一个上限。
ulimit()可调整shell的资源限制。shell为执行命令所创建的子进程会继承上述资源设置。
8.内存映射
调用系统函数mmap()进程,会在其虚拟地址空间中创建一个新的内存映射。
9.静态库和共享库
目标库:将(通常是逻辑相关的)一组函数代码加以编译,并置于一个文件中,供其他应用程序调用。
- 静态库
- 要使用静态库中的函数,需要在创建程序的链接命令中指定相应的库。链接器在解析了引用情况后,会从库中抽取需目标模块的副本,将其赋值到最终可执行的文件中,这就是所谓的静态链接。
- 共享库
- 运行时将可执行文件载入内存,一款名为“动态链接器”的程序会确保将可执行文件所需的动态库找到,并载入内存,随后实施运行时链接,解析可执行文件中的函数调用,将其与共享库中相应的函数定义关联起来。运行时,共享代码在内存中只需保留一份。
10.进程间通信及同步
进程间通信机制(IPC):
- 信号(signal),用来表示事件的发生。
- 管道(亦即shell操作"|")和FIFO,用于在进程间传递数据。
- 套接字
- 文件锁定,为防止其他进程读取或更新文件内容,允许某进程对文件的部分区域加以锁定。
- 消息队列,用于在进程间交互消息(数据包)
- 信号量(semaphore),用来同步进程动作
- 共享内存
11.信号
进程收到信号,意味着某一事件或异常情况发生。信号以SIGxxxx形式的符号名加以定义。
内核、其他进程或进程自身均可向进程发送信号。
以下情况可向进程发送信号:
- 用户键入中断字符(Control-C)
- 进程的子进程之一已经终止
- 由进程设定的定时器(告警时钟)已经到期
- 进程尝试访问无效的内存地址
shell中kill命令,程序内部系统调用kill()可提供相应功能。
收到信号时,进程会根据信号采用如下动作之一:
- 忽略信号
- 被信号“杀死”
- 先挂起,之后再被专用信号唤醒
大多数信号类型,程序可不采用默认的信号动作,信号处理器是有程序员定义的函数,会在进程收到信号时自动调用,根据信号的产生条件执行相应的动作。
信号从产生直至送达进程期间,一直处于挂起状态。通常,系统会在接受进程下次获取调用时,将处于挂起状态的信号同时送达。程序可以将信号纳入“信号屏蔽”,以阻塞该信号,此信号一直保持挂起状态。
12.线程
每个进程都可执行多个线程。可将线程想象为共享同一虚拟内存及一干其他属性的进程。
线程共享同一数据区域和堆,但每个线程都有自己的栈。线程之间可通过共享的全局变量进行通信。
13.进程组合shell任务控制
shell执行的每个程序都会在一个新进程内发起。比如shell创建了3个进程来执行以下管道命令:
$ ls - l | sort -k5n | less
主流shell都提供了一种交互式特性,名为任务控制,该特性允许用户同时执行并操纵多条命令或管道。在支持任务控制的shell中,会将管道内的所有进程置于一个新的进程组或任务中。
内核可以对进程组中的所有成员执行各种动作,尤其是信号的传递。
14.会话、控制终端和控制进程
会话指一组进程组(任务)。会话中的所有进程都具有相同的会话标识符。会话首进程是指创建会话的进程,其进程ID会成为会话ID
由shell创建的所有进程组与shell自身隶属于同一会话,shell是此会话的会话首进程。
伪终端
伪终端是一对相互连接的虚拟设备,也称为主从设备。在这对设备之间,设有一条IPC信道,可供数据进行双向传递。
日期和时间
进程设计两种类型时间:
- 真实时间
- 进程时间:亦称CPU时间,指的是进程自启动起来,所占用的CPU时间总量
- 系统CPU时间:内核模式执行代码所花费的时间
- 用户CPU时间:用户模式
time命令会显示真实时间、系统CPU时间、执行管道中多个进程花费的CPU时间
客户端服务端架构
实时性
/proc文件系统
/proc文件系统由一组目录和文件组成,装配(mount)于/proc目录下。/proc文件系统是中虚拟文件系统,以文件系统目录和文件形式,提供一个指向内核数据结构。为查看和修改各种系统属性提供方便。还可通过/prov/PID形式的命令目录查看系统中运行各进程的相关信息。
链接:https://www.jianshu.com/p/70407f1f2a71