20145212 《信息安全系统设计基础》第13周学习总结
20145212 《信息安全系统设计基础》第13周学习总结
教材学习内容总结
第11章 网络编程
客户端-服务器编程模型
-
每个网络应用都由一个服务器进程和一个或多个客户端进程组成。
-
客户端-服务器模型基本操作:事务
1.当一个客户端需要服务时,向服务器发送一个请求,发起一个事务。
2.服务器收到请求后,解释它,并以适当的方式操作它的资源。
3.服务器给客户端发送一个相应,并等待下一个请求。
4.客户端收到响应并处理它。
- 客户端和服务器都是进程
网络
- 对主机而言,网络是一种I/O设备:从网络上接收到的数据从适配器经过I/O和存储器总线拷贝到存储器,典型地是通过DMA(直接存储器存取方式)传送。
- 物理上,网络是一个按照地理远近组成的层次系统:最低层是LAN(局域网),最流行的局域网技术是以太网。
- 以太网段包括一些电缆和集线器。每根电缆都有相同的最大位带宽,集线器不加分辩地将一个端口上收到的每个位复制到其他所有的端口上,因此每台主机都能看到每个位。
- 每个以太网适配器都有一个全球唯一的48位地址,存储在适配器的非易失性存储器上。
- 一台主机可以发送一段位:帧,到这个网段内其它任何主机。每个帧包括一些固定数量的头部位(标识此帧的源和目的地址及帧长)和数据位(有效载荷)。每个主机都能看到这个帧,但是只有目的主机能读取。
- 使用电缆和网桥,多个以太网段可以连接成较大的局域网,称为桥接以太网。这些电缆的带宽可以是不同的。
- 多个不兼容的局域网可以通过叫做路由器的特殊计算机连接起来,组成一个internet互联网络。
- 互联网重要特性:由采用不同技术,互不兼容的局域网和广域网组成,并能使其相互通信。其中不同网络相互通信的解决办法是一层运行在每台主机和路由器上的协议软件,消除不同网络的差异。
- 协议提供的两种基本能力
命名机制:唯一的标示一台主机
传送机制:定义一种把数据位捆扎成不连续的片的同一方式
全球IP因特网
- TCP/IP协议族
- 因特网的客户端和服务器混合使用套接字接口函数和UnixI/O函数进行通信
- 把因特网看做一个世界范围的主机集合,满足以下特性:
主机集合被映射为一组32位的IP地址
这组IP地址被映射为一组称为因特网域名的标识符
因特网主机上的进程能够通过连接和任何其他主机上的进程
- 检索并打印一个DNS主机条目:
#include "csapp.h"
int main(int argc, char **argv)
{
char **pp;
struct in_addr addr;
struct hostent *hostp;
if (argc != 2) {
fprintf(stderr, "usage: %s <domain name or dotted-decimal>\n",
argv[0]);
exit(0);
}
if (inet_aton(argv[1], &addr) != 0)
hostp = Gethostbyaddr((const char *)&addr, sizeof(addr), AF_INET);
else
hostp = Gethostbyname(argv[1]);
printf("official hostname: %s\n", hostp->h_name);
for (pp = hostp->h_aliases; *pp != NULL; pp++)
printf("alias: %s\n", *pp);
for (pp = hostp->h_addr_list; *pp != NULL; pp++) {
addr.s_addr = ((struct in_addr *)*pp)->s_addr;
printf("address: %s\n", inet_ntoa(addr));
}
exit(0);
}
套接字接口
- 函数:
socket函数、connect函数、open_clientfd函数、bind函数、listen函数、open_listenfd函数、accept函数
- Web服务器
Web客户端和服务器之间的交互用的是一个基于文本的应用级协议,叫做 HTTP (Hypertext Transfer Protocol,超文本传输协议)。
HTTP是一个简单的协议。一个 Web 客户端(即浏览器) 打开一个到服务器的因特网连接,并且请求某些内容。服务器响应所请求的内容,然后关闭连接。浏览器读取这些内容,并把它显示在屏幕上。
Web内容可以用一种叫做 HTML(Hypertext Markup Language,超文本标记语言)的语言来编写。一个 HTML 程序(页)包含指令(标记),它们告诉浏览器如何显示这页中的各种文本和图形对象。
- Web服务器以两种不同的方式向客户端提供内容:
取一个磁盘文件,并将它的内容返回给客户端。磁盘文件称为静态内容 (static content), 而返回文件给客户端的过程称为服务静态内容 (serving static content)。
运行一个可执行文件,并将它的输出返回给客户端。运行时可执行文件产生的输出称为态内容 (dynamic content),而运行程序并返回它的输出到客户端的过程称为服务动态内容 (serving dynamic content)。
并发编程
- 并发:逻辑控制流在时间上重叠
- 并发程序:使用应用级并发的应用程序称为并发程序。
- 三种基本的构造并发程序的方法:
进程,用内核来调用和维护,有独立的虚拟地址空间,显式的进程间通信机制。
I/O多路复用,应用程序在一个进程的上下文中显式的调度控制流。逻辑流被模型化为状态机。
线程,运行在一个单一进程上下文中的逻辑流。由内核进行调度,共享同一个虚拟地址空间。
基于进程的并发编程
构造并发服务器的自然方法就是,在父进程中接受客户端连接请求,然后创建一个新的子进程来为每个新客户端提供服务。
因为父子进程中的已连接描述符都指向同一个文件表表项,所以父进程关闭它的已连接描述符的拷贝是至关重要的,而且由此引起的存储器泄露将最终消耗尽可用的存储器,使系统崩溃。
基于进程的并发echo服务器的重点内容:
需要一个SIGCHLD处理程序,来回收僵死子进程的资源。
父子进程必须关闭各自的connfd拷贝。对父进程尤为重要,以避免存储器泄露。
套接字的文件表表项中的引用计数,直到父子进程的connfd都关闭了,到客户端的连接才会终止。
进程的模型:共享文件表,但不是共享用户地址空间。
关于进程的优劣:
- 优点:一个进程不可能不小心覆盖两一个进程的虚拟存储器。
- 缺点:独立的地址空间使得进程共享状态信息变得更加困难。进程控制和IPC的开销很高。
基于I/O多路复用的并发编程
- 使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。
int select(int n,fd_set *fdset,NULL,NULL,NULL);//返回已经准备好的描述符的非0的个数,若出错则为-1。
- select函数处理类型为fd_set的集合,叫做描述符集合,看做一个大小为n位的向量:bn-1,......,b1,b0
- 对描述符集合的处理方法:
分配他们
将一个此种类型的变量赋值给另一个变量
用FD_ZERO,FD_SET,FD_CLR和FD_ISSET宏指令来修改和检查他们。
基于I/O多路复用的并发事件驱动服务器
- I/O多路复用可以用作事件并发驱动程序的基础。
- 状态机:一组状态、输入事件、输出事件和转移。
- 自循环:同一输入和输出状态之间的转移。
- I/O多路复用技术的优劣
相比基于进程的设计给了程序员更多的对进程行为的控制,运行在单一进程上下文中,每个逻辑流都能访问全部的地址空间,在流之间共享数据很容易。
编码复杂,随着并发粒度的减小,复杂性还会上升。粒度:每个逻辑流每个时间片执行的指令数量。基于I/O多路复用的并发事件驱动服务器
基于线程的并发编程
- 线程:运行在进程上下文中的逻辑流,由内核自动调度,有自己的线程上下文,包括一个唯一的整数线程ID,栈、栈指针、程序计数器、通用目的寄存器和条件码。所有运行在一个进程里的线程共享该进程的整个虚拟地址空间。
- 线程执行模型
每个进程开始生命周期时都是单一线程(主线程),在某一时刻创建一个对等线程,从此开始并发地运行,最后,因为主线程执行一个慢速系统调用,或者被中断,控制就会通过上下文切换传递到对等线程。
- Posix线程
Posix线程是C语言中处理线程的一个标准接口,允许程序创建、杀死和回收线程,与对等线程安全的共享数据。
线程的代码和本地数据被封装在一个线程例程中。
- 创建线程
线程通过调用pthread_create
来创建其他线程。
int pthread_create(pthread_t *tid,pthread_attr_t *attr,func *f,void *arg);//成功则返回0,出错则为非零
当函数返回时,参数tid包含新创建的线程的ID,新线程可以通过调用pthread_self
函数来获得自己的线程ID。
pthread_t pthread_self(void);//返回调用者的线程ID。
- 终止线程
一个线程是通过以下方式之一来终止的。
1.当顶层的线程例程返回时,线程会隐式地终止。
2.通过调用pthread_exit函数,线程会显式地终止void pthread_exit(void *thread_return); - 回收已终止的线程资源
线程通过调用pthread_join函数等待其他线程终止。
int pthread_join(pthread_t tid,void **thread_return);//成功则返回0,出错则为非零
- 分离线程
在任何一个时间点上,线程是可结合或可分离的。一个可结合的线程能够被其他线程收回其资源和杀死,在被回收之前,它的存储器资源是没有被释放的。分离的线程则相反,资源在其终止时自动释放。
int pthread_deacth(pthread_t tid);//成功则返回0,出错则为非零
- 初始化线程
pthread_once
允许初始化与线程例程相关的状态。
```pthread_once_t once_control=PTHREAD_ONCE_INIT;```
```int pthread_once(pthread_once_t *once_control,void (*init_routine)(void));//总是返回0```
- 一个基于线程的并发服务器
```对等线程的赋值语句和主线程的accept语句之间引入了竞争。```
<font size=3>**多线程程序中的变量共享**</font>
- 线程存储器模型
每个线程和其他线程一起共享进程上下文的剩余部分。包括整个用户虚拟地址空间,是由只读文本、读/写数据、堆以及所有的共享库代码和数据区域组成的。线程也共享同样的打开文件的集合。
任何线程都可以访问共享虚拟存储器的任意位置。寄存器是从不共享的,而虚拟存储器总是共享的。
- 将变量映射到存储器
全局变量:虚拟存储器的读/写区域只会包含每个全局变量的一个实例。
本地自动变量:定义在函数内部但没有static属性的变量。
本地静态变量:定义在函数内部并有static属性的变量。
- 共享变量
变量v是共享的,当且仅当它的一个实例被一个以上的线程引用。
<font size=3>**用信号量同步线程**</font>
- 共享变量引入了同步错误的可能性。
- 线程i的循环代码分解为五部分:
Hi:在循环头部的指令块
Li:加载共享变量cnt到寄存器%eax的指令,%eax表示线程i中的寄存器%eax的值
Ui:更新(增加)%eax的指令
Si:将%eaxi的更新值存回到共享变量cnt的指令
Ti:循环尾部的指令块。
- 进度图
进度图将指令执行模式化为从一种状态到另一种状态的转换。转换被表示为一条从一点到相邻点的有向边。合法的转换是向右或者向上。
临界区:对于线程i,操作共享变量cnt内容的指令构成了一个临界区。
互斥的访问:确保每个线程在执行它的临界区中的指令时,拥有对共享变量的互斥的访问。
安全轨迹线:绕开不安全区的轨迹线
不安全轨迹线:接触到任何不安全区的轨迹线就叫做不安全轨迹线
任何安全轨迹线都能正确的更新共享计数器。
- 信号量
- 当有多个线程在等待同一个信号量时,你不能预测V操作要重启哪一个线程。
- 信号量不变性:一个正在运行的程序绝不能进入这样一种状态,也就是一个正确初始化了的信号量有一个负值。
- 使用信号量来实现互斥
- 二元信号量:将每个共享变量与一个信号量s联系起来,然后用P(S)和V(s)操作将这种临界区包围起来,这种方式来保护共享变量的信号量。
- 互斥锁:以提供互斥为目的的二元信号量
- 加锁:一个互斥锁上执行P操作称为对互斥锁加锁,执行V操作称为对互斥锁解锁。对一个互斥锁加了锁但还没有解锁的线程称为占用了这个互斥锁。
- 计数信号量:一个呗用作一组可用资源的计数器的信号量
- 利用信号量来调度共享资源
1.信号量的作用:
```(1)提供互斥(2)调度对共享资源的访问```
2.生产者—消费者问题:生产者产生项目并把他们插入到一个有限的缓冲区中,消费者从缓冲区中取出这些项目,然后消费它们。
3.读者—写者问题:
读者优先,要求不让读者等待,除非已经把使用对象的权限赋予了一个写者。
写者优先,要求一旦一个写者准备好可以写,它就会尽可能地完成它的写操作。
<font size=3>**使用线程提高并行性**</font>
- 写顺序程序只有一条逻辑流,写并发程序有多条并发流,并行程序是一个运行在多个处理器上的并发程序。并行程序的集合是并发程序集合的真子集。
<font size=3>**其他并发问题**</font>
- 线程安全
线程安全:当且仅当被多个并发线程反复地调用时,它会一直产生正确的结果。
线程不安全:如果一个函数不是线程安全的,就是线程不安全的。
线程不安全的类:
不保护共享变量的函数
保持跨越多个调用的状态的函数。
返回指向静态变量的指针的函数。解决办法:重写函数和加锁拷贝。
调用线程不安全函数的函数。
- 可重入性
可重入函数:当它们被多个线程调用时,不会引用任何共享数据。可重入函数是线程安全函数的一个真子集 。
关键思想是我们用一个调用者传递进来的指针取代了静态的next变量。
显式可重入:没有指针,没有引用静态或全局变量
隐式可重入:允许它们传递指针
可重入性即使调用者也是被调用者的属性,并不只是被调用者单独的属性。
- 在线程化的程序中使用已存在的库函数
使用线程不安全函数的可重入版本,名字以_r为后缀结尾。
<font size=3>**竞争**</font>
- 竞争:当一个程序的正确性依赖于一个线程要在另一个线程到达y点之前到达它的控制流中的x点时,就会发生竞争。
- 线程化的程序必须对任何可行的轨迹线都正确工作。
- 消除方法:动态的为每个整数ID分配一个独立的块,并且传递给线程例程一个指向这个块的指针
- 死锁
- 死锁:一组线程被阻塞了,等待一个永远也不会为真的条件。
- 程序员使用P和V操作不当,以至于两个信号量的禁止区域重叠。
- 重叠的禁止区域引起了一组称为死锁区域的状态。
- 死锁是不可预测的。
- 互斥锁加锁顺序规则:如果对于程序中每对互斥锁(s,t),给所有的锁分配一个全序,每个线程按照这个顺序来请求锁,并且按照逆序来释放,这个程序就是无死锁的。
## 代码学习内容总结
<font size=4>**1.condvar.c**</font>
- 1.condvar.c代码中mutex用于保护资源,wait函数用于等待信号,signal函数用于通知信号,wait函数中有一次对mutex的释放和重新获取操作,因此生产者和消费者并不会出现死锁。
- 2.编译时加上 -lpthread
- 3.运行截图
![](http://images2015.cnblogs.com/blog/877181/201612/877181-20161211211725069-1059352317.png)
<font size=4>**2.createthread.c**</font>
- 1.createthread.c:打印进程和线程ID
- 2.运行截图
![](http://images2015.cnblogs.com/blog/877181/201612/877181-20161211212421694-213878981.png)
<font size=4>**3.share.c**</font>
- 1.代码share.c:获得线程的终止状态,thr_fn 1,thr_fn 2和thr_fn 3三个函数对应终止线程的三种方法,即从线程函数return,调用pthread_exit终止自己和调用pthread_cancel终止同一进程中的另一个线程。
- 2.运行截图
![](http://images2015.cnblogs.com/blog/877181/201612/877181-20161211212615710-1922759692.png)
<font size=4>**4.semphore.c**</font>
- 1.semaphore表示信号量,```semaphore```变量的类型为```sem_t```,```sem_init()```初始化一个semaphore变量,value参数表示可用资源 的数量,pshared参数为0表示信号量用于同一进程的线程间同步。在用完semaphore变量之后应该调用sem_destroy()释放与semaphore相关的资源。调用```sem_wait()```可以获得资源,使semaphore的值减1,如果调用```sem_wait()```时semaphore的值已 经是0,则挂起等待。如果不希望挂起等待,可以调用```sem_trywait()```。调用```sem_post()```可以释放资源,使semaphore的值加1,同时唤醒挂起等待的线程。
- 2.运行截图
![](http://images2015.cnblogs.com/blog/877181/201612/877181-20161211213058397-495452960.png)
<font size=4>**5.count.c**</font>
- 1.count.c:这是一个不加锁的创建两个线程共享同一变量都实现加一操作的程序,在这个程序中虽然每个线程都给count加了5000,但由于结果的互相覆盖,最终输出值不是10000,而是5000。
- 2.运行截图
![](http://images2015.cnblogs.com/blog/877181/201612/877181-20161211213308804-779589943.png)
<font size=4>**6.countwithmutex.c**</font>
- 1.countwithmutex.c:这次的运行结果和我们期望的就是一样的,因此对于多线程的程序,访问冲突的问题是很普遍的,解决的办法就是引入互斥锁(Mutex),获得锁的线程可以完成”读-修改-写”的操作,然后释放锁给其它线程,没有获得锁的线程只能等待而不能访问共享数据,这样”读-修改-写”三步操作组成一个原子操作,要么都执行,要么都不执行,不会执行到中间被打断,也不会在其它处理器上并行做这个操作。
- 2.运行截图
![](http://images2015.cnblogs.com/blog/877181/201612/877181-20161211213524366-932309147.png)
<font size=4>**7.cp_t.c代码**</font>
- 1.运行格式:```./cp_t [源文件名] [目的文件名] [创建线程数]```
- 2.运行截图
![](http://images2015.cnblogs.com/blog/877181/201612/877181-20161211213835319-1089977639.png)
## 本周代码托管
![](http://images2015.cnblogs.com/blog/877181/201612/877181-20161211210747632-855878569.png)
<font size=3>**- [链接](http://git.oschina.net/alovera/week13)**</font>
## 学习进度条
| | 代码行数(新增/累积)| 博客量(新增/累积)|学习时间(新增/累积)|重要成长|
| -------- | :----------------:|:----------------:|:---------------: |:-----:|
| 目标 | 5000行 | 30篇 | 400小时 | |
| 第一周 | 0/0 | 1/2 | 10/10 | 使用虚拟机安装linux系统 |
| 第二周 | 341/341 | 1/3 | 20/30 | 掌握核心的linux命令 |
| 第三周 | 177/518 | 2/5 | 16/46 | 学会了虚拟机上的VC编程 |
| 第五周 | 161/679 | 1/6 | 15/61 | |
| 第六周 | 73/752 | 1/7 | 15/76 | 安装了Y86处理器 |
| 第七周 | 134/886 | 1/8 | 12/88 | 建立了项目结构 |
| 第八周 | 0/886 | 2/10 | 12/100 | 进行了系统的复习 |
| 第九周 | 61/947 | 1/11 | 10/110 | 学习Linux操作系统的基本I/O服务 |
| 第十周 | 502/1449 | 1/12 | 10/120 | 通过实践加深了对指令的理解 |
| 第十一周 | 667/2116 | 2/16 | 15/125 | 学习了异常,通过实践了解了进程的并发 |
| 第十二周 | 69/2185 | 4/16 | 15/140 | 通过对前三周代码的复习,加深了对教材内容的认识 |
| 第十三周 | 473/2658 | 1/17 | 15/155 | 学习了网络编程和多线程的基本内容 |
## 参考资料
- [《深入理解计算机系统V2》学习指导](http://www.cnblogs.com/rocedu/p/5826467.html)
- [深入理解计算机系统](http://www.tuicool.com/articles/Zv6v6n)