2017-2018-1 20155220 《信息安全系统设计基础》第十三周学习总结
学习目标
- 本周我主要学习第八章,目标如下:
- 完成这一章所有习题
- 详细总结本章要点
- 给我的结对学习搭档讲解你的总结并获取反馈 参考上面的学习总结模板,把学习过程通过博客(随笔)发表,博客标题“学号 《信息安全系统设计基础》第十三周学习总结”,博客(随笔)要通过作业提交,截至时间本周日 23:59。
教材学习内容总结
网络编程
- 套接字接口概述:
并发编程
- 并发:逻辑控制流在时间上重叠
- 并发程序:使用应用级并发的应用程序称为并发程序。
- 三种基本的构造并发程序的方法:
- 进程,用内核来调用和维护,有独立的虚拟地址空间,显式的进程间通信机制。
- I/O多路复用,应用程序在一个进程的上下文中显式的调度控制流。逻辑流被模型化为状态机。
- 线程,运行在一个单一进程上下文中的逻辑流。由内核进行调度,共享同一个虚拟地址空间。
基于进程的并发编程
- 构造并发程序最简单的方法——用进程。常用函数如下:fork,exec,waitpid
- 构造并发服务器:在父进程中接受客户端连接请求,然后创建一个新的子进程来为每个新客户端提供服务。
- 需要注意的事情:
- 父进程需要关闭它的已连接描述符的拷贝(子进程也需要关闭)
- 必须要包括一个SIGCHLD处理程序来回收僵死子进程的资源
- 父子进程之间共享文件表,但是不共享用户地址空间
- 独立地址空间的优点是防止虚拟存储器被错误覆盖,缺点是开销高,共享状态信息才需要IPC机制
基于I/O多路复用的并发编程
- echo服务器必须响应两个相互独立的I/O时间:
- 网络客户端发起连接请求
- 用户在键盘上键入命令行
- I/O多路复用技术的基本思路:使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。
- 将描述符集合看成是n位位向量:
b(n-1),……b1,b0
,每个位bk对应于描述符k,当且仅当bk=1,描述符k才表明是描述符集合的一个元素。可以做以下三件事:- 分配;
- 将一个此种类型的变量赋值给另一个变量;
- 用FDZERO、FDSET、FDCLR和FDISSET宏指令来修改和检查它们。
- echo函数:将来自科幻段的每一行回送回去,直到客户端关闭这个链接。
- 状态机就是一组状态、输入事件和转移,转移就是将状态和输入时间映射到状态,自循环是同一输入和输出状态之间的转移。
- 事件驱动器的设计优点:
- 比基于进程的设计给了程序员更多的对程序行为的控制
- 运行在单一进程上下文中,因此,每个逻辑流都能访问该进程的全部地址空间,使得流之间共享数据变得很容易。
- 不需要进程上下文切换来调度新的流。
基于线程的并发编程
- 线程:运行子啊进程上下文中的逻辑流。
- 线程有自己的线程上下文,包括一个唯一的整数线程ID、栈、栈指针、程序计数器、通用目的寄存器和条件码。所有运行在一个进程里的线程共享该进程的整个虚拟地址空间。
线程执行模型
- 主线程:每个进程开始生命周期时都是单一线程。 对等线程:某一时刻,主线程创建的对等线程
- 线程与进程的不同:
- 线程的上下文切换要比进程的上下文切换快得多;
- 和一个进程相关的线程组成一个对等池,独立于其他线程创建的线程。
- 主线程和其他线程的区别仅在于它总是进程中第一个运行的线程。
- 对等池的影响
- 一个线程可以杀死它的任何对等线程;
- 等待它的任意对等线程终止;
- 每个对等线程都能读写相同的共享资源。
Posix线程
- 线程例程:线程的代码和本地数据被封装在一个线程例程中。每一个线程例程都以一个通用指针作为输入,并返回一个通用指针。
创建线程
pthread create
函数创建一个新的线程,并带着一个输入变量arg
,在新线程的上下文中运行线程例程f。新线程可以通过调用pthread _self
函数来获得自己的线程ID。
终止线程
- 一个线程的终止方式:
- 当顶层的线程例程返回时,线程会隐式的终止;
- 通过调用
pthread _exit
函数,线程会显示地终止。如果主线程调用pthread _exit,它会等待所有其他对等线程终止,然后再终止主线程和整个进程。
回收已终止线程的资源
pthread _join
函数会阻塞,直到线程tid
终止,回收已终止线程占用的所有存储器资源。pthread _join
函数只能等待一个指定的线程终止。
分离线程
- 在任何一个时间点上,线程是可结合的或者是分离的。一个可结合的线程能够被其他线程收回其资源和杀死;一个可分离的线程是不能被其他线程回收或杀死的。它的存储器资源在它终止时有系统自动释放。
- ,线程被创建成可结合的,为了避免存储器漏洞,每个可集合的线程都应该要么被其他进程显式的回收,要么通过调用
pthread _detach
函数被分离。
初始化线程
pthread _once
函数允许初始化与线程例程相关的状态。once _control
变量是一个全局或者静态变量,总是被初始化为PTHREAD _ONCE _INIT
一个基于线程的并发服务器
- 对等线程的赋值语句和主线程的accept语句之间引入了竞争。
信号量
- 当有多个线程在等待同一个信号量时,你不能预测V操作要重启哪一个线程。
- 信号量不变性:一个正在运行的程序绝不能进入这样一种状态,也就是一个正确初始化了的信号量有一个负值。
- 信号量定义:
type semaphore=record count: integer; queue: list of processend; var s:semaphore;
使用信号量来实现互斥
- 基本思想是将每个共享变量(或者一组相关的共享变量)与一个信号量s(初始为1)联系起来,然后用P和V操作将相应的临界区包围起来。
- 几个概念
- 二元信号量:用这种方式来保护共享变量的信号量叫做二元信号量,取值总是0或者1.
- 互斥锁:以提供互斥为目的的二元信号量
- 加锁:对一个互斥锁执行P操作
- 解锁;对一个互斥锁执行V操作
- 计数信号量:被用作一组可用资源的计数器的信号量
- 禁止区:由于信号量的不变性,没有实际可能的轨迹能够包含禁止区中的状态。
利用信号量来调度共享资源
- 信号量的作用:
- 提供互斥
- 调度对共享资源的访问
- 生产者—消费者问题:
- 生产者产生项目并把他们插入到一个有限的缓冲区中,消费者从缓冲区中取出这些项目,然后消费它们。
- 读者—写者问题:
- 读者优先,要求不让读者等待,除非已经把使用对象的权限赋予了一个写者。
- 写者优先,要求一旦一个写者准备好可以写,它就会尽可能地完成它的写操作。
- 饥饿就是一个线程无限期地阻塞,无法进展。
使用线程提高并行性
- 写顺序程序只有一条逻辑流,写并发程序有多条并发流,并行程序是一个运行在多个处理器上的并发程序。并行程序的集合是并发程序集合的真子集。
线程安全
- 线程安全:当且仅当被多个并发线程反复地调用时,它会一直产生正确的结果。
- 线程不安全:如果一个函数不是线程安全的,就是线程不安全的。
- 线程不安全的类:
- 不保护共享变量的函数
- 保持跨越多个调用的状态的函数。
- 返回指向静态变量的指针的函数。解决办法:重写函数和加锁拷贝。
- 调用线程不安全函数的函数。
可重入性
- 可重入函数:当它们被多个线程调用时,不会引用任何共享数据。可重入函数是线程安全函数的一个真子集 。
- 关键思想是我们用一个调用者传递进来的指针取代了静态的next变量。
- 显式可重入:没有指针,没有引用静态或全局变量 隐式可重入:允许它们传递指针
- 可重入性即使调用者也是被调用者的属性,并不只是被调用者单独的属性。
死锁
- 死锁:一组线程被阻塞了,等待一个永远也不会为真的条件。
- 程序员使用P和V操作顺序不当,以至于两个信号量的禁止区域重叠。
- 重叠的禁止区域引起了一组称为死锁区域的状态。
- 死锁是一个相当难的问题,因为它是不可预测的。
- 互斥锁加锁顺序规则:如果对于程序中每对互斥锁(s,t),给所有的锁分配一个全序,每个线程按照这个顺序来请求锁,并且按照逆序来释放,这个程序就是无死锁的。
课后作业及实现
解答:
进程A和B是互相并发的,就像B和C一样,因为它们各自的执行是重叠的,也就是一个进程在另一个进程结束前开始。进程A和C不是并发的,因为它们的执行没有重叠;A在C开始之前就结束了。
解答:
A.这里的关键点是子进程执行了两个prin七f语句。在fork返回之后,它执行了第8行的prin七fe然后它从if语句中出来,执行了第9行的printf语句。下面是子进程产生的输出:printfl:x=2 printf2: x=1
printf:printf2: x=0
解答:
父进程打印b,然后是c。子进程打印a,然后是c。意识到你不能对父进程和子进程是如何交错执行的做任何假设是非常重要的。因此,任何满足b}c和a -- c的拓扑排序都是可能的输出序列。有四个这样的序列:acbc, bcac, abcc和bacco
解答:
A.每次我们运行这个程序,就会产生6个输出行。 B.输出行的顺序根据系统不同而不同,取决于内核如何交替执行父子进程的指令。一般而言,满足下图的任意拓扑排序都是合法的顺序:比如unix>Hello01Bye2Bye当我们在系统上运行这个程序时,会得到下面的输出:
unix> ./waitprob1Hello01Bye2Bye
在这种情况下,父进程首先运行,在第6行打印''Hello'',在第8行打印“0”。对wait的调用会阻塞,因为子进程还没有终止,所以内核执行一个上下文切换,并将控制传递给子进程,子进程在第8行打印''1'',在第15行打印''Bye'',然后在第16行终止,退出状态为2。在子进程终止后,父进程继续,在第12行打印子进程的退出状态,在第15行打印''Bye''。
解答:
解答: 解答: 只要休眠进程收到一个未被忽略的信号,sleep函数就会提前返回。但是,因为收到一个SIGINT信号的默认行为就是终止进程(见图8-25 ),我们必须设置一个SIGINT处理程序来允许sleep函数返回。处理程序简单地捕获SIGNAL,并将控制返回给sleep函数,该函数会立即返回。解答:
这个程序打印字符串“213",这是卡内基梅隆大学CS:APP课程的缩写名。父进程开始时打印''2'',然后创建子进程,子进程会陷入一个无限循环。然后父进程向子进程发送一个信号,并等待它终止。子进程捕获这个信号(中断这个无限循环),对计数器值(从初始值2)减一,打印''1",然后终止。在父进程回收子进程之后,它对计数器值(从初始值2)加一,打印,''3'',并且终止。