[4] rw_《CSAPP》2Ed 8 11-12 章 《APUE》15章·简

来源:互联网 发布:php.ini设置文件大小 编辑:程序博客网 时间:2024/06/01 08:58

2016.08.31 - 09.07
《CSAPP》2Ed.12 章。
- Bryant·O’Hallaron / 著
- 龚奕利 雷应春 / 译
个人学习笔记。

08.31

第11 章 网络编程

11.1 客户端 – 服务器编程模型

一个客户端 - 服务器事务(步骤)
这里写图片描述

11.2 网络

网络与主机。对于一个主机而言,网络只是一种I/O设备:插到I/O总线扩展槽的适配器提供了到网络的物理接口。从网络上接收到的数据从适配器经过I/O和存储器总线拷贝到存储器里(或通过DMA传送)。相应地,数据也能从存储器拷贝到网络。

[结合《计算机是怎样跑起来的》第九章]
这里写图片描述

11.3 全球IP因特网

TCP/IP是一种互联网实现。
这里写图片描述

域名 <–> IP地址。

套接字。套接字是连接的一个端点(连接是点对点)。

因特网连接/应用程序连接。
这里写图片描述

11.4 套接字接口

套接字是一组函数,用以创建网络应用。[利用互联网的实现和互联网连接的方式],套接字事务可描述如下:
这里写图片描述
客户端和服务器[使用socket函数] 创建一个套接字描述符;客户端[通过connect函数]建立和服务器的连接(通过客户端和服务器的端点);服务器(用bind)将服务器上的套接字描述符和服务器中的套接字地址联系起来(socket创建的端口是临时端口),并(用listen)将主动套接字转化为一个监听套接字。服务器(再用accept函数)等待客户端的连接请求。已建立的描述符可被用来利用Unix I/O函数与客户端通信。

监听描述符和连接描述符。
这里写图片描述

11.5 Web服务器

Web客户端和服务器之间的交互用的是一个基于文本的应用级协议,叫HTTP。[客户端与服务器的连接(套接字);互联网网络(TCP/IP + 路由器等)]

Web内容可以用HTML来编写,HTML的强大之处在于一个页面可以包含超链接,这些超链接可以指向存放在任何因特网主机上的内容。

Web内容。Web服务器以两种不同方式向客户端提供内容:[1] 取磁盘文件,并将它的内容返回给客户端(服务静态内容)。[2] 运行一个可执行文件,并将它的输出返回给客户端(服务动态内容)。[“?”字符分隔文件名和参数,参数之间用“&”隔开]
每条由Web服务器返回的内容都是和它管理的某个文件相关联的。这些文件中每一个都有一个唯一的名字,URL。

HTTP静态事务
[1] HTTP请求。一个HTTP请求行包括:一个请求行,后面跟零个或多个请求报头,再跟随一个空的文本行来终止报头列表。一个请求行的格式为

<method> <uri> <version>

如GET / HTTP /1.1 [该请求行要求服务器取出并返回HTML文件/index.html。它也告知服务器请求剩下的部分是HTTP/1.1格式的]GET方法指导服务器生成和返回URI(URL的后缀,包括文件名和可选的参数)标识的内容。

请求报头为服务器提供了额外的信息,例如浏览器的商标名。请求报头的格式为

<header name>: <header data>

如Host: www.aol.com [HTTP/1.1需要,1.0不需要]

[2] HTTP响应。一个HTTP响应的组成是这样的:一个响应行后面跟随着更多的响应报头,再跟随一个终止报头的空行,再跟随一个响应体。响应行的格式为

<version> <status code> <status message>

版本字段描述的是响应所遵循的HTTP版本。状态码是一个三位的正整数,指明对请求的处理。状态消息给出与状态码(发生错误的)等价的英文描述。响应报头提供了关于响应的附加信息(响应主题中内容的MIME类型;指示响应主体的字节大小)。响应主体中包含着被请求的内容。

HTTP动态事务
[1] 客户端传递参数给服务器。GET请求的参数在URI中传递(HTTP POST参数在请求主体中传送)
[2] 服务器传递参数给子进程。如收到如下请求
GET /cgi-bin/adder?15000&213 HTTP/1.1
服务器调用fork来创建一个子进程,并调用execve在子进程的上下文中执行 /cgi-bin/adder程序。像adder这样的程序,常常称为CGI程序,因为它们遵循CGI标准的规则。而且,因为许多CGI程序使用Perl脚本编写的,所以CGI程序也称为CGI脚本。在调用execve之前,子进程将CGI环境变量QUERY_STRING设置为“15000&123”,adder程序在运行时可以用Unix getenv函数来引用它。
[3] 服务器将其它信息传递给子进程。CGI定义了大量的其他环境变量,一个CGI程序在它运行时可以设置这些环境变量。
[4] 子进程将它的输出放在哪里。一个CGI程序将它的动态内容发送到标准输出。在子进程加载并运行CGI程序之前,它使用Unix dup2函数将标准输出重定向到和客户端相关联的已连接描述符。因此,任何CGI程序写到标准输出的东西都会直接到达客户端。

第12 章 并发编程

逻辑控制流。系统中通常有许多其他程序在运行,进程也可以向每个程序提供一种假象,好像它在独占地使用处理器。如果想用调试器单步执行程序,我们会看到一系列的程序计数器(PC)的值,这些值唯一的对应于包含在程序的可执行目标文件中的指令,或者是包含在运行时动态链接到程序的共享对象中的指令。这个PC值的序列叫作逻辑控制流。

并发。如果逻辑控制流在时间上重叠(各控制逻辑流交错运行),那么它们就是并发的。
并行。多个逻辑控制流并发地运行在不同的处理器核或者计算机上。

并发程序。使用应用级并发的应用程序。现代操作系统提供了三种基本的构造并发程序的方法:[1] 进程。每个逻辑控制流都是一个进程,由操作系统来调度和维护。进程有独立的虚拟地址空间,控制流需要使用显示的进程间通信(IPC)机制来和其他流通信。[2] I/O多路复用。应用程序在一个进程的上下文中显示地调度它们自己的逻辑流。逻辑流被模型化为状态机,数据到达文件描述符后,主程序显示地从一个状态转换到另一个状态。因为程序是一个单独的进程,所以所有的流都共享同一个地址空间。[3] 线程是运行在一个单一进程上下文中的逻辑流,由内核进行调度。

12.1 基于进程的并发编程

这里写图片描述
这里写图片描述
这里写图片描述
使用进程来并反的程序,每个控制流拥有独立的地址空间;但必须使用显示地IPC,另外进程控制的开销也比较高。

12.2 基于I/O多路复用的并发编程

I/O多路复用的基本思路就是使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。

服务器使用I/O多路复用,借助select函数检测输入事件的发生。借助select函数检测输入事件的发生。当每个已连接描述符准备好可读时,服务器就为相应的状态机执行转移。
这里写图片描述
基于I/O多路复用的事件驱动设计比进程的设计给了程序员更多的对程序行为的控制;共享数据变得容易;由于基于事件驱动的程序不需要进程上下文切换来调度新的流,所以事件驱动设计更高效。但事件驱动设计的编码要复杂。

12.3 基于线程的并发编程

线程是运行在进程上下文中的逻辑流。
线程上下文包括一个唯一的整数线程ID、栈、栈指针、程序计数器、通用目的寄存器和条件码。

线程执行模型
这里写图片描述
线程上下文比进程小,线程上下文切换比进程上下文切换快。线程不是严格按照父子层次来组织的。和一个进程相关的线程组成一个对等池(一个线程可以杀死它的任何对等线程;每个线程都能读写相同的共享数据),独立于其他线程创建的线程。

12.4 多线程程序中的共享变量

09.02
Posix线程是在C程序中处理线程的一个标准接口。线程存储器模型。一组并发线程运行在一个进程的上下文中。每个线程都有它自己独立的线程上下文,包括线程ID、栈、栈指针、程序计数器、条件码和通用目的寄存器值。每个线程和其他线程一起共享进程上下文的剩余部分。线程中的变量。对等线程可共享全局变量(或主线程中定义在线程产生之前的局部变量)、静态局部变量。[一定要注意共享变量问题,如竞争]

12.5 用信号量同步线程

一条C语句往往会对应几条汇编指令。在对等线程中引用共享变量时,对等线程若在一条C语句未执行完就切换到对等线程中,共享变量的值就有可能得不到预期的值。

使用信号量来实现互斥。信号量s是具有非负整数值的全局变量,只能由两种特殊的操作来处理。P(s):如果s是非零的,那么P将s减1,并且理解返回。如果s为零,那么就挂起这个线程,直到s变为非0,而一个V操作会重启这个线程。在重启之后,P操作将s减1,并将控制返回给调用者。V(s):V操作将s加1。如果有任何线程阻塞在P操作等待s变成非零,那么V操作会重启这些线程中的一个,然后该线程将s减1,完成它的P操作。信号量提供了一种很方便的方法来确保对共享变量的互斥访问。基本思想是将每个共享变量与一个信号量s(初始为1)联系起来,然后用P(s)和V(s)操作将相应的临界区包围起来。

12.6 使用线程提高并行性

多核处理器运行并发程序会更快,因为操作系统内核在多个核上并行地调度这些并发线程,而不是在单个核上顺序地调度。[多各线程分别在一个核上运行,那么就变成并行]

12.7 其它并发问题

线程安全函数。当该函数被多个并发线程反复地调用时,它会一直产生正确的结果。

四种线程不安全函数。[1] 不保护共享变量的函数;[2] 跨越多个状态的函数(如依赖前次调用的中间结果,而这个中间结果是一个共享变量);[3] 返回指向静态变量的指针的函数;[4] 调用线程不安全的函数(如果调用的是第一类或第三类函数,采取加锁等措施,该函数依旧可能是线程安全的)。

可重入函数。当它们被多个线程调用时,不会引用任何共享数据(可重入函数比不可重入函数效率高,因为不必做同步操作)。

有一小部分库函数是线程不安全的。对诸如返回指向静态变量指针这部分函数可以使用加锁 – 拷贝法(降低程序速度;深层拷贝)。但,加锁 – 拷贝法对跨越调用的静态状态的函数并不有效(所以,要调用该函数对应的可重入版本)

竞争
这里写图片描述
为消除竞争,可以动态地为每个整数ID分配一个独立的内存块。

死锁。程序员加锁不当所引起的死锁。

12.8 小结

[1] 并发方法;
[2] 各种方法的优缺点;
[3] 同步共享数据方法和问题;
[4] 并发引来的问题。如线程的四类不安全函数;竞争;死锁
[5] 可重入函数。

第8章 异常控制流

09.03
控制转移。从给处理器加电开始,直到断电为止,程序计数器假设一个值的序列a0,a1,,an1,其中,每个ak是某个相应指令Ik的地址。每次从akak+1的过渡称为控制转移。这样的控制转移叫做处理器的控制流

一个硬件定时器定期产生信号,这个事件必须得到处理。包到达网络适配器后,必须存放在存储器中。程序向磁盘请求数据,然后休眠,直到被通知数据就绪。当子进程终止时,创建这些子进程的父进程必须得到通知。现代系统通过使用控制流发生突变来对这些情况作出反应。一般而言,这些突变就被称为异常控制流(Exceptional Control Flow, ECF)。

8.0 异常

这里写图片描述

在任何情况下,当处理器检测到有事件发生时,它就会通过一张叫做异常表的跳转表,进行一个间接过程调用(异常),到一个专门设计用来处理这类事件的操作系统子程序(异常处理程序)。

系统中可能的每种类型的异常都分配了一个唯一的非负整数的异常号。其中一些号码是处理器设计者分配的(包括被0除、缺页、存储器访问违例、断点以及算术溢出),其他号码是由操作系统内核的设计者分配的(如系统调用和来自外部I/O设备的信号)。[结合《30天自制操作系统》理解]

异常处理表/异常处理程序
这里写图片描述
这里写图片描述

异常分类。中断、陷阱、故障和终止。[本书的讲述:]中断指外部中断;陷阱是有意的异常,如系统调用;故障由错误引起,它可能够被故障处理程序修正,此时就返回引起故障的指令处,否则就像abort一样终止引起故障的程序;终止时不可恢复的致命错误造成的结果,通常是一些硬件错误。比如DRAM或者SRAM位被损坏时发生的奇偶错误。终止处理程序从不将控制返回给应用程序。处理程序将控制返回给一个abort例程,该例程会终止这个应用程序。
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

8.1 进程

09.04
进程上下文。经常的经典定义就是一个执行中的程序实例。系统中的每个程序都是运行在某个进程的上下文中的。上下文是由程序正确运行所需的状态组成的。这个状态包括存放在存储器中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。

进程提供给应用程序的关键抽象。[1] 一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器;[2] 一个私有的地址空间,它提供一个假象,好像我们的程序独占地使用存储器系统。

[-逻辑控制流-]
并发流一个逻辑流的执行在时间上与另一个流重叠,称为并发流。更准确的说,流X和Y互相并发,当且仅当X在Y开始之后和Y结束之前开始,或者说Y在X开始之后和X结束之前开始。

多任务。一个进程和其他进程轮流运行的概念称为多任务。一个进程执行它的控制流的一部分的每一时间段叫做时间片(time slice)。

并行流。如果并发流各自运行在不同的处理器核上或者计算机上,那么我们称它们为并行流。

[-地址空间-]
进程地址空间。和进程地址空间中某个地址相关联的那个存储器字节是不能被其他进程读或者写的,从这个意义上说,这个地址空间是私有的。尽管和每个私有地址空间相关联的存储器的内容一般是不同的,但是每个这样的空间都有相同的通用结构。
这里写图片描述

用户模式和内核模式。处理器通常用某个控制器中的一个模式位来提供两种模式。当设置了内核模式,进程就运行在内核模式中(可以执行任何指令,可访问任何存储器位置);没有设置模式位,进程就运行在用户模式位(不能执行特权指令;也不允许用户模式中的进程直接引用地址空间内核区的代码和数据)

上下文切换。上下文就是内核重新启动一个被抢占的进程所需要的状态。它由一些对象的值组成,这些对象包括通用目的寄存器、浮点寄存器、程序计数器、用户栈、状态寄存器、内核栈和各种内核数据结构,比如描绘地址空间的页表、包含有关当前进程信息的进程表,以及包含进程已打开文件的信息的文件表。操作系统内核使用一种称为上下文切换的较高层形式的异常控制流来实现多任务。其包括[1] 保存当前进程的上下文;[2] 恢复某个先前被抢占的进程被保存的上下文;[3] 将控制传递给这个新恢复的进程。

调度。在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。这种决定叫做调度,是由内核中被称为调度器的代码处理的。

8.2 进程控制

进程的三种状态。[1] 运行。进程要么在CPU上执行,要么在等待被执行且最终会被内核调度。[2] 停止。进程的执行被挂起,且不会被调度。当收到SIGSTOP、SIGTSTP、SIDTTIN或者SIGTTOU信号时,进程就停止,并且保持停止直到它收到一个SIGCONT信号,在这个时刻,进程再次进入运行序列。[3] 终止。进程永远地停止了。进程会因为三种原因终止:收到终止信号;从主程序返回;调用exit函数。

09.06
fork。(由fork)创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同(但是独立的)一份拷贝,包括文本、数据和bss段、堆以及用户栈。子进程还获得与父进程任何打开文件描述符相同的拷贝,这就意味着当父进程调用fork时,子进程可以读写父进程中打开的任何文件。父进程和新建的子进程之间最大的区别在于它们有不同的PID。

[分析fork创建子进程数的一个方法]
这里写图片描述

子进程的回收。当一个进程由于某种原因终止时,内核并不是立即把它从内核中清除。该进程被保持在一种已终止的状态中,直到它的父进程回收。当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程,从此时开始,该进程就不存在了。

僵尸进程。一个终止但还未被回收的进程被称为僵尸进程。如果父进程没有回收它的僵尸进程就终止了,那么内核就会安排init进程来回收它们。init进程的PID为1,并且是在系统初始化时由内核创建的。长时间运行的程序,比如外壳或者服务器,总是应该回收它们的僵尸子进程。即使僵尸子进程没有运行,它们仍然消耗系统的存储器资源。

8.3 信号

信号就是一条小消息,它通知进程系统中发生了一个某种类型的事件。

发送信号的机制都是基于进程组的
[1] /bin/kill;
[2] 从键盘发送信号;[Uinx外壳使用作业这个抽象概念来表示对一个命令行求值而创建的进程。在任何时刻,至多只有一个前台作业和0个或多个后台作业]
这里写图片描述

输入ctrl+c会导致内核向每个前台进程组中的成员发送一个SIGINT信号。
[3] 用kill函数发送信号;进程通过调用kill函数发送信号给其它进程(包括自己)。
[4] 用alarm函数发送信号;进程可以调用alarm函数向它自己发送SIGALRM信号。

接收信号
当内核从一个异常处理程序返回,准备将控制传递给进程p时,它会检查进程p的未被阻塞的待处理信号的集合(pending&~blocked)。如果这个集合为空,那么内核将控制传递给到p的逻辑控制流的下一条指令。如果该集合未空……进程可以通过signal函数修改和信号相关联的默认行为(除了SIGSTOP和SIGKILL)

信号处理问题。待处理信号被该信号对应的处理程序阻塞;任意类型至多只有一个待处理信号;从系统调用中断处理程序中捕获到信号时,该中断处理程序可能返回给用户一个错误条件,并将errno设置为EINTR。

15 进程间通信

15.1 管道

管道局限:[1] 不应预先假定系统支持全双工管道;[2] 管道只能在具有公共祖先的两个进程间使用[如外壳调用(fork)的进程间]

每当在管道中键入一个命令序列,让shell执行时,shell都会为每条命令单独创建一个进程,然后用管道将前一条命令进程的标准输出与后一条命令的标准输入相连接。
这里写图片描述

15.2 FIFO

FIFO有时被称为命名管道。未命名管道只能在两个相关的进程之间使用,而且这两个相关的进程还要有一个共同的创建了它们的祖先进程。但是,通过FIFO,不相关的进程也能交换数据。

FIFO是一种文件类型,它的路径名存在于文件系统中。

15.2 XSI IPC

有3种称作XSI IPC的IPC:消息队列、信号量以及共享存储器。

标识符和键。每个内核中的IPC结构(消息队列、信号量或共享存储段)都用一个非负整数的标识符加以引用。无论何时创建IPC结构,都应指定一个键。这个键由内核变换成标识符。

权限结构。XSI IPC为每个IPC结构关联了一个结构,该结构规定了权限和所有者。

优点和缺点。XSI IPC的一个基本问题是:IPC结构是在系统范围内起作用的,没有引用计数(如消息队列需要被显示删除;管道在最后一个进程引用后会被完全删除;最后一个引用FIFO的进程终止时,虽然FIFO的名字仍在系统中,直至被显示地删除,但是留在FIFO中的数据已经被删除了)。XSI IPC的另一个问题是:这些IPC结构在文件系统中没有名字。

(1) 消息队列

消息队列是消息的连接表,存储在内核中,由消息队列标识符标识。
这里写图片描述

(2) 信号量

信号量与之前的IPC机构不同(管道、FIFO以及消息队列)不同。它是一个计数器,用于为多个进程提供对共享数据对象的访问。
[1] 测试控制该资源的信号量。
[2] 若此信号量的值为正,则进程可以使用该资源。在这种情况下,进程会将信号量值减1,表示它使用了一个资源单位。
[3] 否则,若此信号量的值为0,则进程进入休眠状态,直至信号量值大于0.进程被唤醒后,它返回至步骤(1)。

(3) 共享存储

共享存储允许两个或多个进程共享一个给定的存储区。(因为数据不需要在客户进程和服务进程之间复制,所以这是最快的一种IPC)。使用共享存储时要掌握的唯一窍门是,在多个进程之间同步访问一个给定的存储区。

15.3 POSIX信号量

POSIX是三种机制之一,3种IPC机制源于POSIX.1的实时扩展。(Single UNIX Specification将三种机制(消息队列、信号量和共享存储)置于可选部分)
这里写图片描述

进程间通信的多种形式:管道、命名管道(FIFO)、通常称为XSI IPC的3种形式的IPC(消息队列、信号量和共享存储),以及POSIX提供的替代信号量机制。信号量实际上同步原语而不是IPC,常用于共享资源(如共享存储段)的同步访问。[还有用于不同主机上的套接字进程通信]

[2016.09.01 - 00:26]

1 0
原创粉丝点击