2017-2018-1 20155336 《信息安全系统设计基础》第十四周学习总结
找出全书你认为学得最差的一章,深刻从新学习一下
- 总结新的收获
- 给你的结对学习搭档讲解或请教,并获取反馈
再次深刻学习并发编程
- 本周老师让咱们学习本身认为学的最差的一章,我学习的是第十二章。一方面,并发进场出如今计算机系统许多不一样的层面上,使用很是普遍;另外一方面,这个知识背景的硬件异常处理程序,Linux信号处理程序很是常见。因此为了更好的深刻理解计算机系统,必需要掌握好这一章的知识,因此借此机会从新学习这一章的内容。
教材学习内容详细总结
- 到目前为止,咱们主要将并发看作是一种操做系统内核用来运行多个应用程序的机制。可是,并发不只仅局限于内核。它也能够在应用程序中扮演重要角色。例如,咱们已经看到Unix信号处理程序如何容许应用响应异步事件,例如用户键入。或者程序访问虚拟存储器的个未定义的区域.应用级并发在其余状况下也是颇有用的,例如:
- 访问慢速I/O设备
- 与人交互
- 过推迟工做以下降延迟
- 服务多个网络客户端
- 在多核机器上进行并行计算
- 现代操做系统提供了三基本的构造并发程序的方法:进程、I/O多路复用、线程。
12.1 基于进程的并发编程
12.1.1 基于进程的并发服务器
- 一个基于进程的并发的echo服务器的代码,重要说明:
- 首先,一般服务器会运行很长时间,因此咱们必须包括一个SIGCHLD处理程序,来回收僵死子进程资源。
- 其次,父子进程必须关闭他们的connfd拷贝。
- 最后,由于套接字的文件表表项的引用计数,直到父子进程的connfd都关闭了,到客户端的链接才会终止。
12.1.2 关于进程的优劣
- 关于进程的优劣,对于在父、子进程间共享状态信息,进程有一个很是清晰的模型:共享文件表,可是不共享用户地址空间。进程有独立的地址控件爱你既是优势又是缺点。因为独立的地址空间,因此进程不会覆盖另外一个进程的虚拟存储器。可是另外一方面进程间通讯就比较麻烦,至少开销很高。
12.2 基于i/o多路复用的并发编程
- 1.好比一个服务器,它有两个I/O事件:1)网络客户端发起链接请求,2)用户在键盘上键入命令行。咱们先等待那个事件呢?没有那个选择是理想的。若是accept中等待链接,那么没法相应输入命令。若是在read中等待一个输入命令,咱们就不能响应任何链接请求(这个前提是一个进程)。
- 2.针对这种困境的一个解决办法就是I/O多路复用技术。基本思想是:使用select函数,要求内核挂起进程,只有在一个或者多个I/O事件发生后,才将控制返给应用程序。如图所示:横向的方格能够看做是一个n位的描述符向量。如今,咱们定义第0位描述是“标准输入”,第3位描述符是“监听描述符”。
12.2.1 基于i/o多路复用的并发事件驱动服务器
- I/O多路复用能够用作并发事件驱动程序的基础,在事件驱动程序中,流是由于某种事件而前进的,通常概念是将逻辑流模型化为状态机,不严格地说,一个状态机就是一组状态,输入事件和转移,其中转移就是将状态和输入事件映射到状态,每一个转移都将一个(输入状态,输入事件)对映射到一个输出状态,自循环是同一输入和输出状态之间的转移,一般把状态机画成有向图,其中节点表示状态,有向弧表示转移,而弧上的标号表示输人事件,一个状态机从某种初始状态开始执行,每一个输入事件都会引起一个从当前状态到下一状态的转移,对于每一个新的客户端k,基于I/O多路复用的并发服务器会建立一个新的状态机S,并将它和已链接描述符d联系起来。
12.2.2 i/o多路复用技术的优劣
- 1.事件驱动设计的一个优势是,它比基于进程的设计给了程序员更多的对程序行为的控制。例如咱们能够设想编写一个事件驱动的并发服务器,为某些客户提供他们须要的服务,而这对于新进程的并发服务器来讲,是很困难的
- 2.另外一个优势是,一个基于I/O多路复用的事件驱动器是运行在单一进程上下文中的,所以每一个逻辑流都能访问该进程的所有地址空间。这使得在流之间共享数据变得很容易,一个与做为单个进程运行相关的优势是,你能够利用熟悉的调试工具,例如GDB,来调试你的并发服务器,就像对顺序程序那样。最后,事件驱动设计经常比基于进利的设计要高效得多,由于它们不须要进程上下文切换来调度新的流。
- 3.事件驱动设计的一个明显的缺点就是编码复杂,咱们的事件驱动的并发服务器须要的代度是指每一个逻辑流每一个时间片执行的指令数量。基于事件的设计的另外一个重大缺点是它们不能充分利利用多核处理器。
12.3 基于线程的并发编程
- 每一个线程都有本身的线程上下文,包括一个线程ID、栈、栈指针、程序计数器、通用目的寄存器和条件码。全部的运行在一个进程里的线程共享该进程的整个虚拟地址空间。因为线程运行在单一进程中,所以共享这个进程虚拟地址空间的整个内容,包括它的代码、数据、堆、共享库和打开的文件。
12.3.1 线程执行模型
- 线程执行的模型。线程和进程的执行模型有些类似。每一个进程的声明周期都是一个线程,咱们称之为主线程。可是你们要有意识:线程是对等的,主线程跟其余线程的区别就是它先执行。
12.3.2 posix线程
- POSIX线程是在C程序中处理线程的一个标准接口。它最先出如今1995年,并且在大多数Unix系统上均可用。Pthreads定义了大约60个函数,容许程序建立、杀死和回收线程,与对等线程安全地共享数据,还能够通知对等线程系统状态的变化。
12.3.3 建立线程
12.3.4 终止线程
- 一个线程是如下列方式之一来终止的。
- 经过调用
pthread_exit
函数,线程会显它会等待全部其余对等线程终止,而后再终止式地终止。
- 某个对等线程调用
Unix
的e×it
函数,该函数终止进程以及全部与该进程相关的线程
12.3.5 回收已终止线程的资源

12.3.6 分离线程

12.3.7 初始化线程

12.4 多线程程序中的共享变量
- 全局变量和static 变量 是存储在数据段,因此,多线程共享之!
- 因为线程的栈是独立的,全部线程中的自动变量是独立的。即便多个线程运行同一段代码总的自动变量,那么他们的值也是根据线程的不一样而不一样。
- 好比C++中,类属性不是在用户栈中的。因此线程共享之!
12.4.1 线程存储器模型
- 1.一组并发线程运行在一个进程的上下文中。每一个线程都有它本身独立的线程上下文,包括线程ID、栈、栈指针、程序计数器、条件码和通用目的寄存器值。每一个线程和其余线程一块儿共享进程上下文的剩余部分。这包括整个用户虚拟地址空间,它是由只读文本代码、读/写数据、堆以及全部的共享库代码和数据区域组成的。线程也共享一样的打开文件的集合。
- 2.从实际操做的角度来讲,让一个线程去读或写另外一个线程的寄存器值是不可能的。另外一方面,任何线程均可以访问共享虚拟存储器的任意位置。若是某个线程修改了一个存储器位置,那么其余每一个线程最终都能在它读这个位置时发现这个变化。所以,寄存器是从不共享的,而虚拟存储器老是共享的。
- 3.各自独立的线程栈的存储器模型不是那么整齐清楚的。这些栈被保存在虚拟地址空间的栈区域中,而且一般是被相应的线程独立地访问的。咱们说一般而不是老是,是由于不一样的线程栈是不对其余线程设防的因此,若是个线程以某种方式获得个指向其余线程栈的指慧:那么它就能够读写这个栈的任何部分。
12.4.2 将变量映射到存储器
- 线程化的C程序中变量根据它们的存储类型被映射到虚拟存储器:
- 1.全局变量。全局变量是定义在函数以外的变量,在运行时,虚拟存储器的读/写区域域只包含每一个全局变量的一个实例,任何线程均可以引用。例如第5行声明的全局变量
ptr
在虚拟存储器的读/写区域中有个运行时实例,咱们只用变量名(在这里就是ptr
)来表示这个实例。
- 2.本地自动变量,本地自动变量就是定义在函数内部可是没有
static
属性的变量,在运行时,每一个线程的栈都包含它本身的全部本地自动变量的实例。即便当多个线程执行同一个线程例程时也是如此。例如,有个本地变量tid
的实例,它保存在主线程的栈中。咱们用tid.m
来表示这个实例
- 3.本地静态变量
12.4.3 共享变量
- 咱们说一个变量V是共享的,当且仅当它的一个实例被一个以上的线程引用。例如,示例程序中的变量cnt就是共享的,由于它只有一个运行时实例,而且这个实例被两个对等线程引用在另外一方面,myid不是共享的,由于它的两个实例中每个都只被一个线程引用。然而,认识到像msgs这样的本地自动变量也能被共享是很重要的。
12.5 用信号量同步线程
- 信号量一般称之为PV操做,虽然它的思想是将临界代码保护起来,达到互斥效果。这里面操做系统使用到了线程挂起。
将线程i的循环代码分解成五个部分:程序员

12.5.1 进度图
- 进程图将n个并发进程的执行模型化为一条n维笛卡尔空间中的轨迹线。
12.5.2 信号量
- 信号量s是具备非负整数值的全局变量,只能由两种特殊的操做来处理,这两种操做称为P和V
- P(s):若是s是非零的,那么P将s减1而且当即返回。若是s为零,那么就挂起这个线程,直到s变为非零,而一个y操做会重启这个线程。在重启以后,P操做将s减1并将控制返回给调用者
- V(s):V操做将s加1。若是有任何线程阻塞在P操做等待s变成非零,那么V操做会重启这些线程中的一个,而后该线程将s减1,完成它的P操做,P中的测试和减1操做是不可分割的,也就是说,一旦预测信号量s变为非零,就会将s减1,不能有中断。V中的加1操做也是不可分割的,也就是加载、加和存储信号量的过程当中没有中断。注意,V的定义中没有定义等待线程被从新启动的顺序。惟—的要求是V必须只能重启一个正在等待的进程。
12.5.3 使用信号量来实现互斥
- 信号量提供了一种很方便的方法来确保对共享变量的互斥访问。基本思想是将每一个共享变量(或者一组相关的共享变量)与一个信号量联系起来 。以这种方式来保护共享变量的信号量叫作二元信号量,由于它的值老是0或者1。以提供互斥为目的的二元信号量经常也称为互斥锁。在一个互斥锁上执行P操做称为对互斥锁加锁。相似地,执行V操做称为对互斥锁解锁。对一个互斥锁加了锁可是尚未解锁的线程称为占用这个互斥锁。一个被用做一组可用资源的计数器的信号量称为计数信号量。关键思想是这种P和V操做的结合建立了一组状态,叫作禁止区。由于信号量的不变性,没有实际可行的轨迹线可以包含禁止区中的状态。并且,由于禁止区彻底包括了不安全区,因此没有实际可行的轨迹线可以接触不安全区的任何部分。所以,每条实际可行的轨迹线都是安全的,并且无论运行时指令顺序是怎样的,程序都会正确地增长计数器的值。
12.5.4 利用信号量来调度共享资源
1.生产者-消费者问题。数据库

- 2.读者—写者问题:
- 读者优先,要求不让读者等待,除非已经把使用对象的权限赋予了一个写者。
- 写者优先,要求一旦一个写者准备好能够写,它就会尽量地完成它的写操做。
- 饥饿是一个线程无限期地阻塞,没法进展。
12.5.5 综合:基于预线程化的并发服务器
12.6 使用线程提升并行性
- 到目前为止,在对并发的研究中,咱们都假设并发线程是在单处许多现代机器具备多核处理器。并发程序一般在这样的机器上运理器系统上执行的。然而,在多个核上并行地调度这些并发线程,而不是在单个核顺序地调度,在像繁忙的Web服务器、数据库服务器和大型科学计算代码这样的应用中利用这种并行性是相当重要的。
12.7.1 线程安全
- 咱们编程过程当中,尽量编写线程安全函数,即一个函数当且仅当被多个并发线程反复调用时,它会一直产生正确的结果。若是作不到这个条件咱们称之为线程不安全函数。下面介绍四类线程不安全函数:
- 不保护共享变量的函数。解决办法是PV操做。
- 保持跨越多个调用的状态函数。好比使用静态变量的函数。解决方法是不要使用静态变量或者使用可读静态变量。
- 返回指向静态变量的指针的函数。解决方法是lock-and-copy(枷锁-拷贝)
- 调用线程不安全函数的函数
- 因为PV操做不当,可能形成死锁现象。这在程序中也会出现。
12.7.2 可重入性.
有一类重要的线程安全函数,叫作可重入函数。其特色在于他们具备这样一种属性:当它们被多个线程调用时,不会引用任何共享数据。尽管线程安全和可重入有时会(正确地)被用作同义词,可是它们之间仍是有清晰的技术差异的,值得留意。图展现了可重入函数、线程安全函数和线程不安全函数之间的集合关系。全部函数的集合被划分红不相交的线程安全和线程不安全函数集合。可重入函数集合是线程安全函数的一个真子集。安全

可重入函数一般要比不可重入的线程安全的函数高效一些,由于它们不须要同步操做。更进一步来讲,将第2类线程不安全函数转化为线程安全函数的惟一方法就是重写它,使之变为可重入的。服务器
12.7.3 在线程化的程序中使用已存在的库函数

12.7.4 竞争
- 当一个程序的正确性依赖于一个线程要在另外一个线程到达y点以前到达它的控制流中的X点时,就会发生竞争。一般发生竞争是由于程序员假定线程将按照某种特殊的轨迹正确工做忘记了另外一条准则规定:线程化的程序必须对任何可行的轨迹线都正确工做。
12.7.5 死锁
- 1.信号量引入了一种潜在的使人厌恶的运行时错误,叫作死锁。它指的是一组线程被阻塞了,等待一个永远也不会为真的条件。进度图对于理解死锁是一个无价的工具。
- 2.关于死锁的重要知识:
- 3.程序员使用P和V操做漏序不当,以致于两个信号量的禁止区域重叠。若是某个执行轨迹线碰巧到达了死锁状态d那么就不可能有进一步的进展了,由于重叠的禁止区域阻塞了每一个合法方向上的进展。换句话说,程序死锁是由于每一个线程在等待一个根本不可能发生的V操做
- 4.重叠的禁止区域引发了一组称为死锁区域的状态。轨迹线能够进入死锁区域,可是它们不可能离开
- 5.死锁是个至关困难的问题,由于它不老是可预测的。一些幸运的执行轨迹线将绕开死锁区域,而其余的将会陷入这个区域。
小结
- 1.一个并发程是由在时间上重叠的一组逻辑流组成的。在这一章中,咱们学习了三种不一样的应用程序程、I/O多路复用和线程。咱们以一个并发网络服务器做为贯穿全章的应用程序。
- 2.进程是由内核自动调度的,并且由于它们有各自独立的虚拟地址空间,因此要实现共享数据,必需要有显式的IPC机制。事件驱动程序建立它们本身的并发逻辑流,这些逻辑流被模型之间共享数据速度很快并且很容多路复用来显式地调度这些流。
- 3.不管哪一种并发机制,同步对共享数据的并发访问都是一个困难的问题。提出对信号量的P和V操做就是为了帮助解决这个问题。信号量操做能够用来提供对共享数据的互斥访问,也对诸如生产者-消费者程序中有限缓冲区和读者―写者系统中的共享对象这样的资源访问进行调度。
- 4.并发也引入了其余一些困难的问题。被线程调用的函数必须具备一种称为线程安全的属性。咱们定义了四类线程不安全的函数,以及一些将它们变为线程安全的建议。可重入函数是线程安全函数的一个真子集,它不访问任何共享数据。可重入函数一般比不可重入函数更为有效,由于它们不须要任何同步原语。竞争和死锁是并发程序中出现的另外一些困难的问题。当程序员错误地假设逻辑流该如何调度时,就会发生竞争。当一个流等待一个永远不会发生的事件时,时,就会产生死锁。
教材习题学习
12.1
- 父进程关闭了已链接描述符后,子进程仍可以使用该描述符和客户端通讯的缘由是?
- 解答:当父进程派生子进程是,它获得一个已链接描述符的副本,并将相关文件表中的引用计数从1增长到2.当父进程关闭他的描述符副本时,引用计数从2减小到1.由于内核不会关闭一个文件,直到文件表中他的应用计数值变为0,因此子进程这边的链接端将保持打开
12.3
- 在Linux系统里,在标准输入上键入Ctrl+D表示EOF,若阻塞发生在对select的调用上,键入Ctrl+D会发生什么?
- 解答:会致使select函数但会,准备好的集合中有描述符0
12.4
- 在服务器中,每次使用select前都初始化pool.ready_set变量的缘由?
- 解答:由于pool.ready_set即做为输入参数也做为输出参数,因此在每一次调用select前都从新初始化他。输入时,他包含读集合,在输出,它包含准备好的集合
12.16
- 编写hello.c一个版本,建立和回收n个可结合的对等线程,其中n是一个命令行参数。
代码以下:网络
#include <stdio.h>
#include "csapp.h"
void *thread(void *vargp);
#define DEFAULT 4
int main(int argc, char* argv[]) {
int N;
if (argc > 2)
unix_error("too many param");
else if (argc == 2)
N = atoi(argv[1]);
else
N = DEFAULT;
int i;
pthread_t tid;
for (i = 0; i < N; i++) {
Pthread_create(&tid, NULL, thread, NULL);
}
Pthread_exit(NULL);
}
void *thread(void *vargp) {
printf("Hello, world\n");
return NULL;
}
12.17
- 修改程序的bug,要求程序睡眠1秒钟,而后输出一个字符串
代码以下:多线程
#include "csapp.h"
void *thread(void *vargp);
int main()
{
pthread_t tid;
Pthread_create(&tid, NULL, thread, NULL);
// exit(0);
Pthread_exit(NULL);
}
/* Thread routine */
void *thread(void *vargp)
{
Sleep(1);
printf("Hello, world!\n");
return NULL;
}
上周考试错题总结
第3题
- 大多数计算机使用一样的机器指令来执行无符号和有符号加法。(A)
- 解析:书上63页,基础知识。
第10题
- Y86-64中()指令没有访存操做.(A B E)
- A rrmovl
- B irmovq
- C rmmovq
- D pushq
- E jXX
- F ret
- 解析:在Y86-64中,只有
rmmovq
,pishq
,ret
有访存操做。
第22题

- 针对以上代码:gcc -c *.c 能够获得m.o,swap.o两个模块,哪些符号会出如今swap.o模块的.symtab条目中( A C D)
- A buf
- B temp
- C swap
- D buffp0
- 解析:temp是局部变量,不出如今符号表中。
第29题
- Unix/Linux中经过调用( D )能够获取子进程PID。
- A getpid()
- B getppid()
- C getcpid()
- D fork()
- 解析:在fork的时候记录下子进程的pid
第39题
有关echo服务器代码,编译后的可执行程序为echoserv,下面说法正确的是(A C D )
并发
- A .
该echo服务器是迭代服务器
- B .
该echo服务器是并发服务器
- C .
echoserv应该先于eccho客户端启动
- D .
./echoserv 8089, 8089是服务器端的端口
- E .
./echoserv 8089, 8089是客户端的端口
解析:./echoserv 8089, 8089是服务器端的端口而不是客户端的端口!
本周代码托管连接
代码连接
结对及互评
本周结对学习状况
- 20155315
- 同伴重点学习了第九章的教材内容,我也从新回顾了第十二章的知识重点。
其余(感悟、思考等)
本周老师让咱们学习本身认为学的最差的一章,我学习的是第十二章。一方面,并发进场出如今计算机系统许多不一样的层面上,使用很是普遍;另外一方面,这个知识背景的硬件异常处理程序,Linux信号处理程序很是常见。因此为了更好的深刻理解计算机系统,必需要掌握好这一章的知识,因此借此机会从新学习这一章的内容。
学习进度条
目标 |
5000行 |
30篇 |
400小时 |
|
第一周 |
200/200 |
2/2 |
20/20 |
|
第二周 |
300/500 |
2/4 |
18/38 |
|
第三周 |
500/1000 |
3/7 |
22/60 |
|
第四周 |
600/1300 |
4/9 |
30/90 |
|
第五周 |
650/1300 |
5/9 |
40/90 |
|
第六周 |
700/1300 |
6/9 |
50/90 |
|
第七周 |
800/1300 |
7/9 |
60/90 |
|
第八周 |
1200/1700 |
8/10 |
80/110 |
|
第九周 |
1800/2000 |
9/11 |
100/120 |
|
第十一周 |
2400/3000 |
10/15 |
120/140 |
|
第十三周 |
3000/3500 |
11/15 |
140/160 |
|
第十四周 |
3400/3800 |
12/15 |
155/170 |
|
尝试一下记录「计划学习时间」和「实际学习时间」,到期末看看能不能改进本身的计划能力。这个工做学习中很重要,也颇有用。
耗时估计的公式
:Y=X+X/N ,Y=X-X/N,训练次数多了,X、Y就接近了。
参考:软件工程软件的估计为何这么难,软件工程 估计方法
(有空多看看现代软件工程 课件
软件工程师能力自我评价表)
参考资料