20145229《信息安全系统设计基础》第13周学习总结

20145229《信息安全系统设计基础》第13周学习总结

教材学习内容总结

并发编程

  • 逻辑控制流在时间上重叠称为并发
  • 使用应用级并发的应用程序称为并发程序,三种基本构造并发程序的方法:(1)进程 (2)I/O多路复用 (3)线程

12.1 基于进程的并发编程

  • 构造并发程序最简单的方法就是用进程,用熟悉的函数来构造
  • 在工作时父进程把连接描述符关掉是非常重要的,否则永远不会释放已连接描述符的文件表条目,最后使系统崩溃

12.1.1 基于进程的并发服务器

  • 我们必须有一个SIGCHLD处理程序来回收僵死子进程的资源,因为SIGCHLD信号是阻塞的,Unix信号是不用排队的
  • 父子进程必须关闭各自的connfd(已连接的资源描述符)拷贝,以防存储器泄露
  • 套接字文件表表项中的引用计数直到父子进程的connfd都关闭,客户端的连接才会终止

12.1.2 关于进程的优劣

  • 父子进程间共享信息有一个模型:共享文件表、不共享地址空间、
  • 优点为进程不会轻易覆盖另一个进程的虚拟存储器;缺点为不共享地址空间使得共享信息更难,为了共享他们必须使用显式的IPC机制,还有一个缺点是它们很慢,因为进程控制和IPC花费很高

12.2 基于I/O多路复用的并发编程

  • 为了对用户从标准输入键入的交互命令做出响应,响应两个相互独立的I/O事件:(1)网络客户端发起连接请求 (2)用户在键盘上键入命令行,解决这个情况的方法就是I/O多路复用。基本思想即是使用select函数,要求内核挂起进程直到有一个或多个I/O事件发生
  • select函数处理类型为fd_set的集合,也称为描述符集合,当且仅当bk=1,描述符k才是描述符集合中的元素,可以对描述符集合做三种事:(1)分配他们 (2)将一个此种类型的变量赋值给另一个变量 (3)用FD_ZERO FD_SET FD_CLR和FD_ISSET宏指令来修改和检查他们
  • select有两个输入:(1)称为读集合的描述符集合和该读集合的基数(实际上是任何描述符集合的最大基数)
  • select函数会一直阻塞直到读集合中至少有一个描述符准备好了
  • 该函数有一个副作用:select修改了参数fdset指向的fd_set,我们必须在每次调用select函数时进行读更新
  • 服务器循环中,我们调用select函数,函数会一直堵塞知道监听到描述符或标准输出准备好了
  • 一旦select返回,可使用FD_ISSET判断哪个描述符准备好了
    (1)标准输入准备好了,调用command函数,函数在返回前会读、解析和响应命令
    (2)监听符准备好了,调用accept来得到一个已连接的描述符

12.2.1 基于I/O多路复用的并发事件驱动服务器

  • I/O多路复用可以用作并发事件驱动程序的基础
  • 一个状态机中有一组状态、输入事件和转移,转移就是将状态和输入映射到状态,自循环就是同一输入和输出状态之间的转移
  • 服务器使用I/O多路复用,借助select函数检测输入事件的发生,当已连接描述符准备好后,服务器为相应的状态机执行转移
  • init_pool函数初始化客户端池。clientfd数组表示已连接描述符的集合,-1表示有一个可用的槽位。初始时,已连接描述符集合为空,监听描述符是select读集合中唯一的描述符
  • add_client函数添加一个新的客户端到客户端池;maxfd变量记录select的最大描述符,maxi变量记录clientfd数组的最大索引,以便于check_client不用搜索整个数组
  • 如果客户端关闭连接的那一端,检测到EOF,我们将关闭这边的连接端,并从池中删掉描述符
  • select函数检测输入事件,add_client函数创建新的状态机,check_client函数通过回送输入行来执行状态转移,完成后还得删除状态机

12.2.2 I/O多路复用技术的优劣

  • 事件驱动设计的优点:
    (1)比基于进程设计给了程序员更多的对程序行为的控制
    (2)因为是运行在单一进程上下文中的,流之间的共享数据变得更容易
    (3)你可以用你熟悉的调试工具来调试你的并发服务器
    (4)由于不需要程序上下文切换,比基于进程设计更加高效
  • 事件驱动设计的缺点:
    (1)编码复杂 (2)并发力度减小,复杂性上升 (3)不能充分利用多核处理器
  • 基于进程设计的优点:处理部分文件行非常优异且是自动处理

12.3 基于线程的并发编程

  • 基于线程的创建并发逻辑流是之前两种方法的混合
  • 线程是运行在进程上下文中的逻辑流。线程由内核自动调度,每个线程有自己的线程上下文,包括唯一的整数线程ID、栈、栈指针、程序计数器、通用目的寄存器和条件码
  • 所有运行在一个进程里的线程共享该进程的整个地址虚拟空间
  • 基于线程的逻辑流结合了基于进程和基于I/O多路复用的流的特性

12.3.1 线程执行模型

  • 每个进程开始生命周期都是单一线程,这个线程称为主线程
  • 当主线程创建一个对等线程时,这两个线程开始并发的运行,最后会因为主线程执行一个慢速系统调用或者被系统的间隔计时器中断,通过切换上下文互相传递
  • 线程执行与进程执行的区别:(1)由于一个线程的上下文比进程的上下文小很多,所以执行起来也快很多 (2)线程不是按照严格的父子层组织的,而是组成一个线程池,独立于其他线程创造的线程。
  • 主线程与其他线程的区别为主线程总是第一个运行的线程
  • 对等线程池概念的影响:一个线程可以杀死他任意对等线程或等待对等线程终止;每个对等线程都读写相同的共享数据

12.3.2 Posix线程

  • Posix线程是在C程序中处理线程的一个标准接口。定义了大约60个函数,允许程序创建、杀死和回收线程,并与对等线程安全的共享数据,可以通知对等线程系统状态的变化
  • 线程的代码和本地数据被封装在一个线程例程中,每个线程例程都以一个通用指针为输入,并返回一个通用指针。
  • 本地变量tid可以用来存放对等线程的线程ID
  • 主线程通过调用pthread_creat函数创建一个新的对等线程,当对pthread_creat调用返回时,主线程和新创建的对等线程同时运行,且tid包含新线程的ID

12.3.3 创建线程

  • pthread_creat函数能创建一个新的进程,带着一个输入变量arg,在上下文运行例程f
  • attr可以改变新创建线程的默认属性,新线程可以调用pthread_self函数来获得它自己的线程ID

12.3.4 终止线程

  • 终止线程的方法:
    (1)当顶层的线程例程返回时,线程会隐式的终止
    (2)通过调用pthread_exit函数,线程会显式的终止。如果是主线程调用,它会等待所有对等线程终止后再终止主线程和整个进程,返回值为pthread_exit
    (3)某个对等线程调用Unix的exit函数,该函数终止进程以及所有与进程相关的线程
    (4)对等线程通过以当前线程的ID为参数调用pthread_cancle来终止当前线程

12.3.5 回收已终止线程的资源

  • 线程通过调用pthread_join来等待线程终止
  • pthread_join会阻塞等到线程tid终止,将线程例程返回的指针赋值给thread_return指向的方向,回收已终止线程所占的存储器资源
  • pthread_join只能等待指定的线程终止,不能等待任意

12.3.6 分离线程

  • 任何时候,线程是可结合的或者分离的
  • 一个可结合的线程可以被其他线程回收其资源或杀死,被回收前资源是没有释放的;一个分离的线程是不能被其他线程回收或杀死。它的资源在它终止时由系统自动释放
  • 默认情况下,线程被创造为可结合的,每个可结合的线程要么被回收,要么通过调用pthread_detach函数被分离
  • pthread_detach函数分离可结合函数tid,线程能通过以pthread_self()为参数的pthread_detach来分离他们自己

12.3.7 初始化线程

  • pthread_once函数允许你初始化与线程例程相关的状态
  • once_control变量是一个全局或静态变量,总是被初始化为PTHREAD_ONCE_INIT;第一次用的时候,调用init_routine,没有输入参数也没有任何返回的函数。之后用的时候once_control为参数,不做任何事情
  • 当你需要初始化多个线程共享的全局变量,pthread_once可以用

12.3.8 一个基于线程的并发服务器

  • 将已连接描述符传递给对等线程最有效的方法就是传递一个指向这个描述符的指针,再让对等线程引用这个指针,并将它赋值给一个局部变量
  • 为了避免两个线程间潜在的竞争,必须将每个accept返回的已连接描述符分配给它自己的动态分配的存储块
  • 为了在线程例程中避免存储器泄露,我们不显式的收回线程,我们必须分离每个线程,使得它终止时它的资源能被收回,而且必须小心释放主线程分配的存储器块

12.4 多线程程序中的共享变量

  • 线程最具吸引力的一个原因就是多个线程很容易共享相同的程序变量

12.4.1 线程存储器模型

  • 每个线程和其他线程一起共享进程上下文的剩余部分,包括整个用户虚拟地址空间。线程也共享同样的打开文件的集合
  • 寄存器不共享,而虚拟存储器是共享的
  • 一个线程以某种方式得到一个指向其他线程栈的指针,它可以随意读写任意。

12.4.2 将变量映射到存储器

  • 根据存储类型映射:
    (1)全局变量,定义在函数之外,运行时任何线程都可以引用
    (2)本地自动变量,定义在函数内部
    (3)本地静态变量,定义在函数内部,和全局一样

12.4.3 共享变量

  • 变量v共享当且仅当它的一个实例被一个以上的线程引用
  • 像msgs这样的本地自动变量也能被共享

12.5 用信号量同步线程

  • 虽然共享变量什么方便,但是也引入了同步错误的可能性
  • 一般而言,你没有办法预测操作系统是否将为你的线程选择一个正确的顺序
  • 我们可以借助进程图来阐明正确的和不正确的指令顺序的概念

12.5.1 进程图

  • 进程图将n个并发线程的执行模型化为一条笛卡尔空间中的轨迹线
  • 原点对应没有任何线程完成一条指令的初始状态
  • 合法的转换是向右或者向上,两条指令不能在同一时刻完成,程序绝不会反向运行
  • 确保每个线程在执行它临界区的指令时,拥有对共享变量的互斥访问
  • 两个临界区的交集形成的空间区域称为不安全区,不安全区与他交界的状态相毗邻,但并不包括这些状态
  • 任何安全轨迹线都将正确的更新共享计数器
  • 必须以某种方法同步线程,使他们总是有一条安全的轨迹线。
  • 一个经典的方法是基于信号量的思想

12.5.2 信号量

  • 经典的解决同步不同执行线程问题的方法是基于信号量的特殊类型变量,信号量s是非负整数的全局变量,两种特殊操作来执行:
    (1)P(s)
    (2)V(s)
  • P中的测试和减1是不可分割的,V中的加1操作是不可分割的,过程是不会中断的
  • V中的等待线程重启的顺序是不可预测的
  • P和V的定义确保正在运行的程序不会进入一个初始化的信号量有负值,这个属性称为信号量不变性,为控制安全轨迹线提供了工具
  • sem_init函数将信号量sem初始化为value,每个信号量在使用前必须初始化
  • 程序分别调用sem_wait和sem_post函数来执行P和V操作

12.5.3 使用信号量来实现互斥

  • 信号量用来确保共享变量的互斥访问基本思想是将每个共享变量与一个信号量s联系起来,再用P和V操作将临界区包围起来
  • 以这种方式保护共享响亮的信号量称为二元信号量,它的值总是0或1
  • 以互斥为目的的二元信号量也称为互斥锁,执行P操作称为对互斥锁枷锁,执行V操作称为对互斥锁解锁,对互斥锁加了锁还没有解锁的线程称为占用这个互斥锁
  • 一个被用作一组可用资源的信号量称为计数信号量
  • 因为禁止区完全包含了不安全区,所以每条可行的轨迹都是安全的,不管运行时指令是什么,程序都会正确的增加计数器的值
  • 信号量操作确保了对临界区互斥的访问
  • 首先声明信号量mutex,再将mutex初始化为1,再对共享向量cnt更新进程PV操作

12.5.4 利用信号量来调度共享资源

  • 信号量的另一个作用是调度对共享资源的访问
    1.生产者和消费者问题
  • 生产者生成项目插入缓冲区,消费者从缓冲区中取出消费
  • 必须保证对缓冲区的访问是互斥的,还需要调度缓冲区的访问
  • (1)多媒体中的编码视频桢 (2)图形用户接口设计
  • SBUF操作类型为sbuf_t的有限缓冲区。front和rear索引值记录数组的第一项和最后一项;三个信号量同步对缓冲区的访问。mutex信号量提供互斥的缓冲区访问。slots和ITEMS信号量记录空槽位和可用项目数量
  • sbuf_init为缓冲区分配堆存储器,设置front和rear一个空的缓冲区,并给三个信号量赋值
  • sbuf_deinit是使用完缓冲区后释放储存的
  • sbuf_insert等待槽位对互斥锁加锁解锁
  • subf_remove等待缓冲区空闲加锁解锁
    2.读者写着问题
  • 读者可以与无限个其他读者共享,写者必须拥有对对象的独占访问
  • 第一类读者-写者,读者优先,第二类读者-写者,写者优先
  • 这两种可能导致饥饿,如果有读者不断到达,写者可能无限期等待

12.5.5 综合

  • echo_cnt记录了所有从客户端接收的字节数,这种情况需要初始化计数器和信号量

12.6 使用线程提高并行性

  • 写顺序程序只有一条逻辑流,写并发程序有多条并发流,并行程序的集合是并发程序集合的真子集

12.7 其他并发问题

12.7.1 线程安全

  • 一个线程是安全的,当且仅当被多个并发线程反复的调用时,它会一直产生正确的结果。
  • 四个线程不安全函数类:
    (1)不保护共享变量的函数 (2)保持跨越多个调用的状态的函数 (3)返回指向静态变量的指针的函数 (4)调用线程不安全函数的函数

12.7.2 可重入性

  • 可重入函数的特点是他们被多个线程调用时,不会引用任何共享数据
    (1)显式可重入 (2)隐式可重入
    -显式可重入的:所有函数参数都是传值传递,没有指针,并且所有的数据引用都是本地的自动栈变量,没有引用静态或全剧变量。
  • 隐式可重入的:调用线程小心的传递指向非共享数据的指针。
  • 线程不安全函数的可重入版本,名字以_r为后缀结尾。

12.7.4 竞争

  • 发生的原因:一个程序的正确性依赖于一个线程要在另一个线程到达y点之前到达它的控制流中的x点。也就是说,程序员假定线程会按照某种特殊的轨迹穿过执行状态空间,忘了一条准则规定:线程化的程序必须对任何可行的轨迹线都正确工作。
  • 消除方法:动态的为每个整数ID分配一个独立的块,并且传递给线程例程一个指向这个块的指针

12.7.5 死锁

  • 一组线程被阻塞了,等待一个永远也不会为真的条件。死锁是不可预测的。

网络编程

11.1 客户端-服务器编程模型

  • 一个应用是由一个服务器进程和一个或多个客户端进程组成
  • 客户端-服务器模型中最基本的操作是事务,由四步组成:
  • 当一个客户端需要服务时,向服务器发送一个请求,发起一个事务。
  • 服务器收到请求后,解释它,并以适当的方式操作它的资源。
  • 服务器给客户端发送一个相应,并等待下一个请求。
  • 客户端收到响应并处理它。
  • 客户端和服务器都是进程

11.2 网络

  • 物理上,网络是一个按照地理远近组成的层次系统:最低层是LAN(局域网),最流行的局域网技术是以太网
  • 每个以太网都有一个全球唯一的48位地址
  • 一台主机可以发送一段位,称为帧,到这个网段内其它任何主机。每个帧包括一些固定数量的头部位(标识此帧的源和目的地址及帧长)和数据位(有效载荷)。每个主机都能看到这个帧,但是只有目的主机能读取
  • 使用电缆和网桥,多个以太网段可以连接成较大的局域网,称为桥接以太网。这些电缆的带宽可以是不同的。
  • 互联网重要特性:由采用不同技术,互不兼容的局域网和广域网组成,并能使其相互通信
  • 不同网络相互通信的解决办法:一层运行在每台主机和路由器上的协议软件,消除不同网络的差异
  • 协议提供的两种基本能力:
    (1)命名机制:唯一的标示一台主机
    (2)传送机制:定义一种把数据位捆扎成不连续的片的同一方式

11.3 全球因特网

  • 每台因特网主机都运行实现TCP/IP协议,因特网的客户端和服务器混合使用套接字接口函数和Unix I/O函数进行通信
    把因特网看做一个世界范围的主机集合,满足以下特性:
    (1)主机集合被映射为一组32位的IP地址
    (2)这组IP地址被映射为一组称为因特网域名的标识符
    (3)因特网主机上的进程能够通过连接和任何其他主机上的进程
  • IP地址是一个32位无符号整数,存放在IP地址结构中
  • 在IP地址结构中存放的地址总是以(大端法)网络字节顺序存放的,主机字节顺序是小端法
  • htonl函数将主机字节顺序转换为网络字节顺序,ntohl反之;htons和ntons为16位的整数进行转换
  • IP地址通常是以一种称为点分十进制表示法来表示的,每个字节由他的十进制表示,并且用句点和其他字节间分开
  • TCP/IP是一个协议族,每个都提供不同的功能:IP UDP TCP

11.4 套接字接口

  • 套接字接口是一组用来结合unit I/O函数创建网络应用的函数
  • 一个套接字就是通信的一个端点,套接字就是一个有相应描述符的打开文件
  • in后缀是互联网络的缩写i额,不是输入的缩写
  • 需要将sockaddr_in强制转换为sockaddr结构使用P623的类型
  • 函数:
    (1)socket函数:创建套接字描述符,如何完成打开套接字的工作取决于我们是客户端还是服务器
    (2)connect函数:建立和服务器的连接,一旦连接成功sockfd描述符可以准备好读写了,x和y唯一的确定了客户端主机上的客户端进程
    (3)open_clientfd函数:客户端用该函数和服务器建立连接
    (4)bind函数:被服务器用来和客户端建立连接
    (5)listen函数:被服务器用来和客户端建立连接,告诉内核描述符是被服务器而不是客户端使用的
    (6)open_listenfd函数:服务器用来创建一个监听描述符
    (7)accept函数:被服务器用来和客户端建立连接,服务器等待来自客户端的连接请求;监听描述符被创建一次,存在于服务器的整个生命周期;已连接描述符每次接收请求都会创建一个
  • 一次只能处理一个客户端的服务器一次一个的在客户端间迭代,称为迭代服务器

11.5 WEB服务器

  • Web客户端和服务器之间的交互用的是一个基于文本的应用级协议,叫做 HTTP
  • HTTP 是一个简单的协议。一个 Web 客户端(即浏览器) 打开一个到服务器的因特网连接,并且请求某些内容。服务器响应所请求的内容,然后关闭连接。浏览器读取这些内容,并把它显示在屏幕上
  • Web服务器和常规文件检索服务的区别:Web内容可以用一种叫做 HTML的语言来编写。一个 HTML 程序(页)包含指令(标记),它们告诉浏览器如何显示这页中的各种文本和图形对象
  • 对于Web服务器和客户端,内容是与一个MIME相关的字节序列
  • Web 服务器以两种不同的方式向客户端提供内容:
  • 取一个磁盘文件,并将它的内容返回给客户端。磁盘文件称为静态内容, 而返回文件给客户端的过程称为服务静态内容
  • 运行一个可执行文件,并将它的输出返回给客户端。运行时可执行文件产生的输出称为态内容,而运行程序并返回它的输出到客户端的过程称为服务动态内容
  • 每条由web服务器返回的内容都与某个文件相关联,文件名字为URL
  • HTTP请求的组成:一个请求行,后面跟随零个或更多个请求报头,再跟随一个空的文本行来终止报头列表
  • 唯一需要关注的报头是HOST报头,1.1中需要,1.0中不需要
  • HTTP响应的组成:一个响应行,后面跟随零个或更多个响应报头,再跟随一个终止报头的空行,再跟随一个响应主体

本周代码托管截图

其他(感悟、思考等,可选)

本次的内容很多,有两章内容,我先选择了老师让我们重点看的12章,学会了很多并发有关的知识,对11章只是简单的过了一遍,还是感觉时间很不够用,临近期末,需要复习的内容增加了所以这周没有留足够的时间学习,下次还是得每天分配任务,保证学习的质量与数量。说实话现在博客已经成为我日常的一件事了,像老师说的一样,记录下自己的心得体会也是好的,既可以为以后的工作提供素材,也可以记录下生活的琐事,不失为一件妙事。

学习进度条

代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积) 重要成长
目标 5000行 30篇 400小时
第一周 130/130 1/1 17/17
第二周 90/270 1/1 16/16
第三周 120/390 2/2 16/16
第四周 89/479 1/1 17/17
第五周 120/599 1/1 16/16
第六周 110/709 1/1 18/18
第七周 128/837 1/1 18/18
第八周 5/842 1/1 16/16
第九周 65/907 1/1 13/13
第十周 160/1067 1/1 14/14
第十一周 380/1447 1/1 15/15
第十二周 0/1447 4/4 24/24
第十三周 132/1579 1/1 13/13

参考资料

posted on 2016-12-11 20:31  sssssqyk  阅读(148)  评论(1编辑  收藏  举报

导航