2017-2018-1 20155314 《信息安全系统设计基础》第14周学习总结
2017-2018-1 20155314 《信息安全系统设计基础》第14周学习总结
学习目标
找出全书你认为学得最差的一章,深入重新学习一下,要求(期末占5分):
总结新的收获
给你的结对学习搭档讲解或请教,并获取反馈
第12章 并发编程
学习要点:
- 掌握三种并发的方式:进程、线程、I/O多路复用
- 掌握线程控制及相关系统调用
- 掌握线程同步互斥及相关系统调用
总结新的收获
第12章 并发编程
- 并发:逻辑控制流在时间上重叠
- 应用级并发的作用:
- 访问慢速I/O设备
- 与人交互
- 通过推迟工作以降低延迟
- 服务多个网络客户端
- 在多核机器上进行并行计算
- 并发程序:使用应用级并发的应用程序
- 构造并发程序的方法:
- 进程(程序级并发)
- 线程(函数级并发)
- I/O多路复用
到目前为止,我们主要将并发看做是一种操作系统内核用来运行多个应用程序的机制。但是,并发不仅仅局限于内核。它也可以在应用程序中扮演重要角色。例如,我们已经看到Linux信号处理程序如何允许应用响应异步事件,例如用户键入Ctrl+C
,或者程序访问虚拟内存的一个未定义的区域。
12.1基于进程的并发编程
在接受连接请求之后,服务器派生一个子进程,这个子进程获得服务器描述符表的完整拷贝。子进程关闭它的拷贝中的监听描述符3,而父进程关闭它的已连接描述符4的拷贝,因为不再需要这些描述符了。这就得到了图中的状态,其中子进程正忙于为客户端提供服务。
因为父子进程中的已连接描述符都指向同一个文件表表项,所以父进程关闭它的已连接描述符的拷贝是至关重要的。否则,将永远不会释放已连接描述符4的文件表条目,而且由此引起的存储器泄漏将最终消耗尽可用的存储器,使系统崩溃。
现在假设在父进程为客户端1创建了子进程之后,它接受一个新的客户端2的连接请求,并返回一个新的已连接描述符(比如描述符5)如图所示。然后,父进程又派生另一个子进程,这个子进程用已连接描述符5为它的客户端提供服务,如图所示。此时,父进程正在等待下一个连接请求,而两个子进程正在形地为它们各自的客户端提供服务。
12.2基于I/O多路复用的并发编程
一个服务器,它有两个I/O事件:
- 网络客户端发起连接请求
- 用户在键盘上键入命令行
我们先等待那个事件呢?没有那个选择是理想的。如果accept中等待连接,那么无法相应输入命令。如果在read中等待一个输入命令,我们就不能响应任何连接请求(这个前提是一个进程)。 针对这种困境的一个解决办法就是I/O多路复用技术。基本思想是:使用select函数,要求内核挂起进程,只有在一个或者多个I/O事件发生后,才将控制返给应用程序。如图所示:横向的方格可以看作是一个n位的描述符向量。现在,我们定义第0位描述是“标准输入”,第3位描述符是“监听描述符”。
- 并发事件驱动echo服务器中逻辑流的状态机:
12.3基于线程的并发编程
-
线程:运行在进程上下文中的逻辑流
-
每个线程都有它自己的线程上下文,包括:
- 线程ID
- 栈
- 栈指针
- 程序计数器
- 通用目的寄存器
- 条件码
-
所有的运行在一个进程里的线程共享该进程的整个虚拟地址空间
-
由于多个线程运行在单一进程中,因此共享这个进程虚拟地址空间的整个内容,包括它的代码、数据、堆、共享库和打开的文件
-
线程执行模型:
每个进程开始生命周期时都是主线程。在某一时刻,主线程创建一个对等线程,从这个时间点开始,两个线程就并发地运行。最后,因为主线程执行一个慢速系统调用(如read或sleep),或者因为被系统的间隔计时器中断,控制就会通过上下文切换传递到对等线程。对等线程会执行一段时间,然后控制传递回主线程,依次类推。
-
创建线程:调用
pthread_create()
#include <pthread.h> typedef void *(func)(void *); int pthread_create(pthread_t *tid, pthread_attr_t *attr, func *f, void *arg);
(成功则返回0,出错则为非零)
-
获得线程ID:调用
pthread_self()
#include <pthread.h> pthread_t pthread_self(void);
(返回调用者的线程ID)
-
终止线程:
-
顶层的线程例程返回
-
调用
pthread_exit()
:主线程会等待所有其他对等线程终止,然后再终止主线程和整个进程,返回值为thread_return#include <pthread.h>
-
void pthread_exit(void *thread_return);
```
(从不返回)
- 调用pthread_cancel()
:另一个对等线程通过以当前线程ID作为参数调用该函数来终止当前线程
```
#include <pthread.h>
int pthread_cancel(pthread_t tid);
```
(成功则返回0,出错则为非零)
- 某个对等线程调用Linux的`exit()`,终止进程以及所有与该进程相关的线程
-
回收已终止线程的资源:调用
pthread_join()
等待其他线程终止:#include <pthread.h> int pthread_join(pthread_t tid, void **thread_return);
-
分离线程(避免内存泄漏):调用
pthread_detach()
#include <pthread.h> int pthread_detach(pthread_t tid);
(成功则返回0,出错则为非零)
-
初始化线程:调用
pthread_once()
#include <pthread.h> pthread_once_t once_control = PTHREAD_ONCE_INIT; int pthread_once(pthread_once_t *once_control,
void (*init_routine)(void));
```
(总是返回0)
-
基于线程的并发服务器:
#include "csapp.h" void echo(int connfd); void *thread(void *vargp); int main(int argc, char **argv) { int listenfd, *connfdp, port; socklen_t clientlen=sizeof(struct sockaddr_in); struct sockaddr_in clientaddr; pthread_t tid; if (argc != 2) { fprintf(stderr, "usage: %s <port>\n", argv[0]); exit(0); } port = atoi(argv[1]); listenfd = Open_listenfd(port); while (1) { connfdp = Malloc(sizeof(int)); *connfdp = Accept(listenfd, (SA *) &clientaddr, &clientlen); Pthread_create(&tid, NULL, thread, connfdp); } } /* Thread routine */ void *thread(void *vargp) { int connfd = *((int *)vargp); Pthread_detach(pthread_self()); Free(vargp); echo(connfd); Close(connfd); return NULL; }
12.4多线程程序中的共享变量
全局变量和static变量是存储在数据段,所以多线程共享之!由于线程的栈是独立的,所有线程中的自动变量是独立的。即使多个线程运行同一段代码总的自动变量,那么他们的值也是根据线程的不同而不同。
- 线程内存模型
- 将变量映射到内存
- 全局变量
- 本地自动变量
- 本地静态变量
- 共享变量
12.5用信号量同步线程
信号量通常称之为PV操作,虽然它的思想是将临界代码保护起来,达到互斥效果。这里面操作系统使用到了线程挂起。 将线程i的循环代码分解成五个部分:
-
进度图
-
信号量
- PV原语
- P(s)
- V(s)
- 信号量不变性
- PV原语
-
使用信号量来实现互斥
-
利用信号量来调度共享资源
-
生产者-消费者问题
生产者产生项目并把它们插入到一个有限的缓冲区中,消费者从缓冲区中取出这些项目,然后消费它们。
-
读者-写者问题
-
-
基于预线程化的并发服务器
预线程化的并发服务器的组织结构:一组现有的线程不断地取出和处理来自有限缓冲区的已连接描述符。
12.6使用线程提高并行性
到目前为止,在对并发的研究中,我们都假设并发线程是在单处许多现代机器具有多核处理器。并发程序通常在这样的机器上运理器系统上执行的。然而,在多个核上并行地调度这些并发线程,而不是在单个核顺序地调度,在像繁忙的Web服务器、数据库服务器和大型科学计算代码这样的应用中利用这种并行性是至关重要的。
- 顺序、并发和并行程序集合之间的关系:
12.7其他并发问题
- 线程安全
- 可重入性
- 可重入函数、线程安全函数和线程不安全函数之间的集合关系:
- 在线程化的程序中使用已存在的库函数
- 竞争
- 死锁
- 进程图
- 会死锁的程序:
- 无死锁程序:
- 互斥锁加锁顺序规则:给定所有互斥操作的一个全序,如果每个线程都是以一种顺序获得互斥锁并以相反的顺序释放,那么这个程序就是无死锁的
- 进程图
本章考试错题总结(CH12)
-
下面代码对于并发程序来说逻辑上是()条指令?
for(i=0;i<1000; i++) cnt++;
A. 2
B. 3
C. 4
D. 5
E. 6
F. 1
【错选】F
【答案】D
【解析】参考课本p699
-
有关“生产者-消费者”和“读者-写者”模型,下面说法正确的是()
A. 二者除处理的都是互斥问题
B. 二者除处理的都是同步问题
C. 二者都要保证对缓冲区的访问是互斥的
D. “生产者-消费者”模型要保证对缓冲区的访问是互斥的
E. “读者-写者”模型要保证读者对缓冲区的访问是互斥的
【错选】A D
【答案】B D
【解析】参考课本p704
-
有关下面的代码hello.c,编译后的可执行程序为phello,下面说法正确的是()
A. 编译命令是:gcc hello.c -o phelloB. 编译命令是:gcc hello.c -lpthread -o phello
C. 编译命令是:gcc hello.c -pthread -o phello
D. phello运行时有一个线程
E. phello运行时有两个线程
F. phello运行时主线程先执行完
G. phello运行时对等线程先执行完
H. phello运行时对等线程和主线程执行顺序不确定
【错选】C E G
【答案】B C E G
【解析】多线程编译需要-lpthread或-pthread参数
pthread_join使得主线程等待对等线程先执行完
-
有关线程控制,下面说法正确的是()
A. 与进程一样,线程也有父子关系
B. 与进程控制中fork() 等价的是pthread_create()
C. 与进程控制中exit() 等价的是pthread_exit()
D. 与进程控制中waitpid()等价的是pthread_join()
E. 与进程控制中kill() 等价的是pthread_cancel()
F. 与进程控制中getpid() 等价的是pthread_self()
【错选】A B D F
【答案】B C D E F
【解析】线程只有主线程和对等线程,其他与进程的对比要理解。
-
有关下面代码,编译后的可执行程序是echoserv,下面说法正确的是()
A. 第19行中的STDIN_FILENO的值可以用grep -nr STDIN_FILENO /usr/include
查到为1B. 第24行select()会导到致程序阻塞,可以替代accept()
C. 程序运行时,输入CTRL+D,可以让select返回
D. 以上代码中加入csapp.h就能编译成功
【错选】A B C
【答案】C
【解析】
grep -nr STDIN_FILENO /usr/include STDIN_FILENO
为0,参考课本p686
上周考试错题总结
第十四周测试
-
Y86-64中()指令没有访存操作.
A. rrmovl
B. irmovq
C. rmmovq
D. pushq
E. jXX
F. ret
【错选】A B
【答案】A B E
【解析】参考课本4.3节
-
The following table gives the parameters for a number of different caches. For each cache, determine the number of cache sets (S), tag bits (t), set index bits (s), and block o set bits (b)
A. 第三行S为1
B. 第一行t为24
C. 第二行b为5
D. 第三行s的值为0
【错选】A D
【答案】A C D
【解析】参考课本p427
-
有关磁盘操作,说法正确的是()
A. 对磁盘扇区的访问时间包括三个部分中,传送时间最小。
B. 磁盘以字节为单位读写数据
C. 磁盘以扇区为单位读写数据
D. 读写头总处于同一柱面
【错选】A C
【答案】A C D
【解析】参考课本p409
结对学习中的问题和解决过程
学习搭档(20155323)无法打开Y86-64模拟器界面
解决方法
这是Y86-64模拟器的Makefile中配置GUI相关路径错误导致的,解决方法是将两个GUI路径分别设置为TKLIBS=-L/usr/lib -ltk8.5 -ltcl8.5
和TKINC=-I/usr/include/tcl8.5
即可,如下图:
成功打开Y86-64模拟器:
代码托管
本周结对学习情况
- 20155323
- 结对学习内容
- 教材第6章 存储器层次结构
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 200/200 | 2/2 | 20/20 |
尝试一下记录「计划学习时间」和「实际学习时间」,到期末看看能不能改进自己的计划能力。这个工作学习中很重要,也很有用。
耗时估计的公式
:Y=X+X/N ,Y=X-X/N,训练次数多了,X、Y就接近了。
-
计划学习时间:5小时
-
实际学习时间:2小时