20155223 《信息安全系统设计基础》第13周学习总结
练习题回答
12.1
父进程派生出子进程时,子进程自动获得一个已连接描述符的副本,并将相关文件表中的引用计数从1加到2。.父进程关闭其本身的描述符副本时,引用数减1,但是不为0,。只有引用数为0时,系统才会关闭文件,所以子进程依然保持连接端打开。
12.2
当一个进程终止时,内核将自动关闭所有打开的描述父。所以,当子进程退出时,其已连接文件描述符的副本也随之被删除。
12.3
假设EOF在一个描述符上为真,那么读操作将立即返回一个零返回码,以示EOF。所以,Ctrl+D会导致函数select返回,准备好的集合中有描述符0。
12.4
因为变量pool.read_set既可当输入参数又可以当输出参数,所以必须要在每一次调用select前重新初始化变量pool.read_set。
12.5
因为线程都运行在同一个进程中,他们共享同一个描述符表。因此无论有多少个线程用这个已连接描述符,文件表的引用数都等于1。所以只用一个close操作就可以释放与这个文件表相关的所有内存资源了。
12.6
- A.表格填写
变量实例 | 被主线程引用? | 被对等线程0引用? | 被对等线程1引用? | |
---|---|---|---|---|
ptr | 是 | 是 | 是 | |
cnt | 否 | 是 | 是 | |
i.m | 是 | 否 | 否 | |
msgs.m | 是 | 是 | 是 | |
myid.p0 | 否 | 是 | 否 | |
myid.p1 | 否 | 否 | 是 |
- 说明:
ptr是全局变量
cnt是静态变量,被两个对等线程读写
i.m是本地自动变量,对等线程可以获得其值,但是不会在栈中引用它
msgs.m是本地自动变量,被两个对等线程通过ptr间接引用
myid.p0和myid.p1是本地自动变量,分别驻留在线程0和线程1当中。 - B.变量ptr、cnt、msgs.m被主线程和两个对等线程引用,所以这三个线程是共享的。
12.7
表格填写:
%rdx1:—;0;—;—;—;—;1;1;1;—;
%rdx2:—;—;—;0;1;1;—;—;—;1;
cnt:0;0;0;0;0;1;1;1;1;1;
变量cnt最终只有一个不正确的值1。
12.8
通过画图可以看出:A、C的轨迹线避开了临界区,而B的轨迹线插入了临界区。所以A、C是安全的,B是不安全的。
12.9
解答:
A.p=1,c=1,n>1:是,必须使用互斥锁,因为生产者和消费者的访问是并发的。
B.p=1,c=1,n=1:不是,在这种情况下就不需要互斥锁信号量。一个非空缓冲区等价于一个满载缓冲区。当缓冲区包含一个项目时,生产者被阻塞;反之,消费者被阻塞。所以在任意时刻,只有一个线程可以访问缓冲区,因此不需要互斥锁。
C.p>1,c>1,n=1:不是,原因同B。
教材内容学习总结:第12章
三种并发方式
使用应用级并发的应用程序成为并发程序。现在操作系统提供了三种基本的构造并发程序的方法:
- 进程:每个逻辑控制流都是一个进程,由内核来调度和维护。进程间通信依靠进程间通信机制。
- I/O多路复用:应用程序在一个进程的上下文中显式地调度它们自己的逻辑控制六,所有流应为程序是个单独的进程而共享同一个地址空间。
- 线程:运行在一个单一进程上下文中的逻辑流,由内核进行调度。可以将线程看成前两种的混合体。
进程并发
进程是构造并发程序的最简单方法。使用熟悉的函数如fork、exec、waitpid等。例如,一个构造并发服务器的自然方法就是,在父进程中接受客户端的连接请求,然后创建一个新的子进程来为每个新客户端提供服务。
进程并发可以让父、子进程之间共享文件表,但是各个子进程之间不共享用户地址空间。一个子进程不可能会将另一个子进程的虚拟内存覆盖掉,但是各进程之间的通信仰赖进程间通信机制,使进程并发式程序比其他两种更慢。
I/O多路复用并发
I/O多路复用使用select函数,要求内核挂起进程,只有存在一个或多个I/O时间发生后,才将控制返回给应用程序。
I/O多路复用方式使得程序员可以更多地控制程序行为。一个基于I/O多路复用的并发服务器是运行在单一进程的上下文中的,因此每个逻辑流都可以访问该进程的全部地址空间。但是此类并发程序编码量特大,难以进行快速修改。而当单位时间内访问程序的次数增加,I/O多路复用服务器的代码量也必须增加,不然极易被过多的访问量冲击而损坏。
线程并发
线程是I/O多路复用和进程的混合体。线程不存在进程那样的父子结构,每个对等线程都可以读写相同的共享数据。因为线程上下文比进程上下文小得多,因此线程上下文的切换比进程的要快很多。
线程相关函数
- 创建线程:pthread_create。新创建的线程通过函数pthread_self来获取自身的线程ID。
- 终止线程:pthread_exit。线程会显式地终止,如果是主线程调用此函数,那么这个线程必须等待其他所有对等线程终止,然后再终止主线程和整个进程。终止当前进程用函数pthread_cancel。
- 回收线程资源:pthread_join。此函数会阻塞,直到目标线程终止。
- 分离线程:pthread_detach。
- 线程初始化:pthread_once。
用信号量同步线程
共享变量是线程之间共享信息的一个重要手段,但是共享变量也引入了同步错误的可能性。同步错误是一种同步不同执行线程问题。解决这种问题的方案是信号量这一特殊类型变量。
信号量是一种全局非负整数型变量,信号量只能通过两种操作:P和V来处理。
P操作:当信号量非零时,信号量减一。当信号量为0时,系统挂起这个线程,直到信号量重新变为非0。
V操作:与P操作相反。
P操作和V操作保证信号量不为负。
使用信号量来实现互斥的基本思想是将每个共享变量(或者一组相关的共享变量)与一个初始值为1的信号量联系起来,然后用P操作和V操作将相应地临界区包围起来。以这种方式来保护变量的信号量叫二元信号量,因为它的值总是0和1。以提供互斥为目的的二元信号量常常也成为互斥锁。在互斥锁上执行P操作叫加锁,执行V操作叫解锁。
利用信号量来调度共享资源
-
生产者——消费者问题
-
读者——写者问题
一组并发的线程要访问一个共享对象。有些线程只读对象,而其他线程只修改对象。读对象的是读者,修改对象的叫写者。写者必须独占对象,读者可以和其他读者一同共享对象。一般来说,有无限多个并发的读者和写者。
教材学习中的问题和解决过程
- 问题1:读者——写者问题有两大类:读者优先、写者优先。那么怎么用信号量来解决这类问题?
- 问题1的解决方案:
这是我上网找的一份代码。
使用AW表示正在写的线程,WW表示正在等待写的线程。
AR表示正在读的线程,WR表示正在读的线程。
使用锁lock对AW WW AR WR进行互斥访问。
okToread okTowrite为条件变量。
AR = 0;
AW = 0;
WR = 0;
WW = 0;
Condition okToRead;
Condition okToWrite;
Lock lock;
void reader()
{
while(true)
{
startread(); //等,直到没有在等待或正在写的写者
read;
doneread(); //如果有等待的写者,唤醒写者
}
}
void startread()
{
lock.acquire();
while((WW+AW)>0) //如果有在等待或正在写的写者,读者阻塞
{
WR++;
okToread.wait(&lock);
WR--;
}
AR++;
lock.release();
}
void doneread()
{
lock.require();
AR--;
if(AR==0 && WW>0) //如果正在读的读者为零且有在等待的写者,唤醒写者
okToWrite.signal();
lock.release();
}
void writer()
{
while(true)
{
startwrite(); //等,直到没有正在写的读者或写者
write;
donewrite(); //如果有正在等待的写者,唤醒,否则如果有正在等待的读者,唤醒
}
}
void startwrite()
{
lock.require();
while((AR + AW) >0 ) //如果有正在写的读者或写者,写者等待
{
WW++;
okTowrite.wait(&lock);
WW--;
}
AW++;
lock.release();
}
void donewrite()
{
lock.require();
AW--;
if(WW>0) //如果有写者等待,唤醒写者
okTowrite.signal();
else if(WR>0) //如果有读者等待,唤醒读者
okToread.broadcast();
lock.release();
}
能帮助我理解怎么用信号量来解决读者写者问题。
第12章关键字
进程并发、I/O多路复用并发、线程并发、信号量解决互斥和共享资源。
代码调试中的问题和解决过程
- 问题1:从网上寻找的部分代码不能在虚拟机中实现,尽管已经排除掉所有语法错误、编译错误、存储错误,仍然是不能出结果。
- 问题1的解决方案:后确认是系统版本问题,我使用的乌班图系统版本很低,有些输入输出格式不兼容。
代码托管
结对及互评
本周结对学习情况
20155207
结对照片
结对学习内容
- 第12章线程部分
- 实验五
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 31/31 | 1/1 | 20/20 | |
第三周 | 24/55 | 2/3 | 24/44 | 知道浮点数怎么储存的 |
第四周 | 177/328 | 2/5 | 17/61 | 现在我的C语言程序也会在Linux命令行下使用了:*) |
第五周 | 54/382 | 2/7 | 18/79 | 复习一遍汇编语言 |
第七周 | 2360/2722 | 1/8 | 12/91 | |
第八周 | 624/3344 | 2/10 | 19/110 | 了解多线程和多进程 |
第九周 | 1112/4456 | 3/13 | 15/125 | 学习怎么实现pwd命令 |
第十一周 | 157/4613 | 2/15 | 10/135 | 在紧急情况下恢复不可使用的虚拟机 |
第十三周 | 999/5471 | 2/17 | 17/152 | 回顾了线程部分的内容 |