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

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

网络编程

  • 套接字接口概述:

并发编程

  • 并发:逻辑控制流在时间上重叠
  • 并发程序:使用应用级并发的应用程序称为并发程序。
  • 三种基本的构造并发程序的方法:
    • 进程,用内核来调用和维护,有独立的虚拟地址空间,显式的进程间通信机制。
    • I/O多路复用,应用程序在一个进程的上下文中显式的调度控制流。逻辑流被模型化为状态机。
    • 线程,运行在一个单一进程上下文中的逻辑流。由内核进行调度,共享同一个虚拟地址空间。

基于进程的并发编程

  • 构造并发程序最简单的方法——用进程。常用函数如下:fork,exec,waitpid
  • 构造并发服务器:在父进程中接受客户端连接请求,然后创建一个新的子进程来为每个新客户端提供服务。
  • 需要注意的事情:
    • 父进程需要关闭它的已连接描述符的拷贝(子进程也需要关闭)
    • 必须要包括一个SIGCHLD处理程序来回收僵死子进程的资源
    • 父子进程之间共享文件表,但是不共享用户地址空间
  • 独立地址空间的优点是防止虚拟存储器被错误覆盖,缺点是开销高,共享状态信息才需要IPC机制

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

  • echo服务器必须响应两个相互独立的I/O时间:

    • 网络客户端发起连接请求
    • 用户在键盘上键入命令行
  • I/O多路复用技术的基本思路:使用select函数,要求内核挂起进程,只有在一个或多个I/O事件发生后,才将控制返回给应用程序。
  • 将描述符集合看成是n位位向量:b(n-1),……b1,b0,每个位bk对应于描述符k,当且仅当bk=1,描述符k才表明是描述符集合的一个元素。可以做以下三件事:
    • 分配;
    • 将一个此种类型的变量赋值给另一个变量;
    • 用FDZERO、FDSET、FDCLR和FDISSET宏指令来修改和检查它们。
  • echo函数:将来自科幻段的每一行回送回去,直到客户端关闭这个链接。
  • 状态机就是一组状态、输入事件和转移,转移就是将状态和输入时间映射到状态,自循环是同一输入和输出状态之间的转移。
  • 事件驱动器的设计优点:
    • 比基于进程的设计给了程序员更多的对程序行为的控制
    • 运行在单一进程上下文中,因此,每个逻辑流都能访问该进程的全部地址空间,使得流之间共享数据变得很容易。
    • 不需要进程上下文切换来调度新的流。
  • 缺点:
    • 编码复杂
    • 不能充分利用多核处理器
  • 粒度:每个逻辑流每个时间片执行的指令数量。并发粒度就是读一个完整的文本行所需要的指令数量。

基于线程的并发编程

  • 线程:运行子啊进程上下文中的逻辑流。
  • 线程有自己的线程上下文,包括一个唯一的整数线程ID、栈、栈指针、程序计数器、通用目的寄存器和条件码。所有运行在一个进程里的线程共享该进程的整个虚拟地址空间。

线程执行模型

  • 主线程:每个进程开始生命周期时都是单一线程。

    对等线程:某一时刻,主线程创建的对等线程

  • 线程与进程的不同:
    • 线程的上下文切换要比进程的上下文切换快得多;
    • 和一个进程相关的线程组成一个对等池,独立于其他线程创建的线程。
    • 主线程和其他线程的区别仅在于它总是进程中第一个运行的线程。
  • 对等池的影响
    • 一个线程可以杀死它的任何对等线程;
    • 等待它的任意对等线程终止;
    • 每个对等线程都能读写相同的共享资源。

Posix线程

  • 线程例程:线程的代码和本地数据被封装在一个线程例程中。每一个线程例程都以一个通用指针作为输入,并返回一个通用指针。

创建线程

  • pthread create函数创建一个新的线程,并带着一个输入变量arg,在新线程的上下文中运行线程例程f。新线程可以通过调用pthread _self函数来获得自己的线程ID。

终止线程

  • 一个线程的终止方式:

    • 当顶层的线程例程返回时,线程会隐式的终止;
    • 通过调用pthread _exit函数,线程会显示地终止。如果主线程调用pthread _exit,它会等待所有其他对等线程终止,然后再终止主线程和整个进程。

回收已终止线程的资源

  • pthread _join函数会阻塞,直到线程tid终止,回收已终止线程占用的所有存储器资源。pthread _join函数只能等待一个指定的线程终止。

分离线程

  • 在任何一个时间点上,线程是可结合的或者是分离的。一个可结合的线程能够被其他线程收回其资源和杀死;一个可分离的线程是不能被其他线程回收或杀死的。它的存储器资源在它终止时有系统自动释放。
  • 默认情况下,线程被创建成可结合的,为了避免存储器漏洞,每个可集合的线程都应该要么被其他进程显式的回收,要么通过调用pthread _detach函数被分离。

初始化线程

  • pthread _once函数允许初始化与线程例程相关的状态。
  • once _control变量是一个全局或者静态变量,总是被初始化为PTHREAD _ONCE _INIT

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

  • 对等线程的赋值语句和主线程的accept语句之间引入了竞争。

多线程程序中的变量共享

线程存储器模型

  • 每个线程和其他线程一起共享进程上下文的剩余部分。包括整个用户虚拟地址空间,是由只读文本、读/写数据、堆以及所有的共享库代码和数据区域组成的。线程也共享同样的打开文件的集合。
  • 任何线程都可以访问共享虚拟存储器的任意位置。寄存器是从不共享的,而虚拟存储器总是共享的。

将变量映射到存储器

  • 全局变量:虚拟存储器的读/写区域只会包含每个全局变量的一个实例。
  • 本地自动变量:定义在函数内部但没有static属性的变量。
  • 本地静态变量:定义在函数内部并有static属性的变量。

共享变量

  • 变量v是共享的,当且仅当它的一个实例被一个以上的线程引用。

用信号量同步线程

  • 共享变量引入了同步错误的可能性。
  • 线程i的循环代码分解为五部分:
    • Hi:在循环头部的指令块
    • Li:加载共享变量cnt到寄存器%eax的指令,%eax表示线程i中的寄存器%eax的值
    • Ui:更新(增加)%eax的指令
    • Si:将%eaxi的更新值存回到共享变量cnt的指令
    • Ti:循环尾部的指令块。

进度图

  • 进度图将指令执行模式化为从一种状态到另一种状态的转换。转换被表示为一条从一点到相邻点的有向边。合法的转换是向右或者向上。
  • 临界区:对于线程i,操作共享变量cnt内容的指令构成了一个临界区。
  • 互斥的访问:确保每个线程在执行它的临界区中的指令时,拥有对共享变量的互斥的访问。
  • 安全轨迹线:绕开不安全区的轨迹线
  • 不安全轨迹线:接触到任何不安全区的轨迹线就叫做不安全轨迹线
  • 任何安全轨迹线都能正确的更新共享计数器。

信号量

  • 当有多个线程在等待同一个信号量时,你不能预测V操作要重启哪一个线程。
  • 信号量不变性:一个正在运行的程序绝不能进入这样一种状态,也就是一个正确初始化了的信号量有一个负值。
  • 信号量定义:
  • type semaphore=record
     count: integer;
     queue: list of process
    end;
     var s:semaphore;

使用信号量来实现互斥

  • 基本思想是将每个共享变量(或者一组相关的共享变量)与一个信号量s(初始为1)联系起来,然后用P和V操作将相应的临界区包围起来。
  • 几个概念
    • 二元信号量:用这种方式来保护共享变量的信号量叫做二元信号量,取值总是0或者1.
    • 互斥锁:以提供互斥为目的的二元信号量
    • 加锁:对一个互斥锁执行P操作
    • 解锁;对一个互斥锁执行V操作
    • 计数信号量:被用作一组可用资源的计数器的信号量
    • 禁止区:由于信号量的不变性,没有实际可能的轨迹能够包含禁止区中的状态。

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

  • 信号量的作用:

    • 提供互斥
    • 调度对共享资源的访问
  • 生产者—消费者问题:
    • 生产者产生项目并把他们插入到一个有限的缓冲区中,消费者从缓冲区中取出这些项目,然后消费它们。
  • 读者—写者问题:
    • 读者优先,要求不让读者等待,除非已经把使用对象的权限赋予了一个写者。
    • 写者优先,要求一旦一个写者准备好可以写,它就会尽可能地完成它的写操作。
    • 饥饿就是一个线程无限期地阻塞,无法进展。

使用线程提高并行性

  • 写顺序程序只有一条逻辑流,写并发程序有多条并发流,并行程序是一个运行在多个处理器上的并发程序。并行程序的集合是并发程序集合的真子集。

其他并发问题

线程安全

  • 线程安全:当且仅当被多个并发线程反复地调用时,它会一直产生正确的结果。
  • 线程不安全:如果一个函数不是线程安全的,就是线程不安全的。
  • 线程不安全的类:
    • 不保护共享变量的函数
    • 保持跨越多个调用的状态的函数。
    • 返回指向静态变量的指针的函数。解决办法:重写函数和加锁拷贝。
    • 调用线程不安全函数的函数。

可重入性

  • 可重入函数:当它们被多个线程调用时,不会引用任何共享数据。可重入函数是线程安全函数的一个真子集 。
  • 关键思想是我们用一个调用者传递进来的指针取代了静态的next变量。
  • 显式可重入:没有指针,没有引用静态或全局变量

    隐式可重入:允许它们传递指针

  • 可重入性即使调用者也是被调用者的属性,并不只是被调用者单独的属性。

在线程化的程序中使用已存在的库函数

  • 使用线程不安全函数的可重入版本,名字以_r为后缀结尾。

竞争

  • 竞争发生的原因:

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

死锁

  • 死锁:一组线程被阻塞了,等待一个永远也不会为真的条件。
  • 程序员使用P和V操作顺序不当,以至于两个信号量的禁止区域重叠。
  • 重叠的禁止区域引起了一组称为死锁区域的状态。
  • 死锁是一个相当难的问题,因为它是不可预测的。
  • 互斥锁加锁顺序规则:如果对于程序中每对互斥锁(s,t),给所有的锁分配一个全序,每个线程按照这个顺序来请求锁,并且按照逆序来释放,这个程序就是无死锁的。

实践

count.c

代码

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NLOOP 5000
int counter;
void *doit( void * );
int main(int argc, char **argv)
{
    pthread_t tidA, tidB;

    pthread_create( &tidA ,NULL, &doit, NULL );
    pthread_create( &tidB ,NULL, &doit, NULL );

    pthread_join( tidA, NULL );
    pthread_join( tidB, NULL );

    return 0;
}
void * doit( void * vptr)
{
    int i, val;

    for ( i=0; i<NLOOP; i++ ) {
        val = counter++;
        printf("%x: %d \n", (unsigned int) pthread_self(), val + 1);
        counter = val + 1;
    }

}

  • 这是一个不加锁的情况,两个线程共享同一变量都实现加1操作的程序,在这个程序中虽然每个线程都给count加了5000,但由于结果的互相覆盖,最终输出值不是10000,而是5000。
  • 不过在后续的调试中,也不完全都是5000,有时少于5000有时比5000多,可能因为随机覆盖使得counter值不固定。

countwithmutex.c

代码

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define NLOOP 5000

int counter;

pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;

void *doit( void * );

int main(int argc, char **argv)
{
    pthread_t tidA, tidB;

    pthread_create( &tidA ,NULL, &doit, NULL );
    pthread_create( &tidB ,NULL, &doit, NULL );

    pthread_join( tidA, NULL );
    pthread_join( tidB, NULL );

    return 0;
}

void * doit( void * vptr)
{
    int i, val;

    for ( i=0; i<NLOOP; i++ ) {
        pthread_mutex_lock( &counter_mutex );
        val = counter++;
        printf("%x: %d \n", (unsigned int) pthread_self(), val + 1);
        counter = val + 1;
        pthread_mutex_unlock( &counter_mutex );
    }
    return NULL;
}

  • 程序首先定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁。先创建tidA线程后运行doit函数,利用互斥锁锁定资源,进行计数,执行完毕后解锁。后创建tidB,与tidA交替执行。由于定义的NLOOP值为5000,所以程序最后的输出值为10000.程序的最后还需要分别回收tidA和tidB的资源。
  • 相对于前一个实例,这个代码中加了“互斥锁”(Mutex),在其中一个线程(获得锁)执行时,另一个(未获得)只能等待,所以产生了不同于count.c的输出效果。

share.c

代码

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
char buf[BUFSIZ];

void *thr_fn1( void *arg )
{
    printf("thread 1 returning %d\n", getpid());
    printf("pwd:%s\n", getcwd(buf, BUFSIZ));
    *(int *)arg = 11;
    return (void *) 1;
}

void *thr_fn2( void *arg )
{
    printf("thread 2 returning %d\n", getpid());
    printf("pwd:%s\n", getcwd(buf, BUFSIZ));
    pthread_exit( (void *) 2 );
}

void *thr_fn3( void *arg )
{
    while( 1 ){
        printf("thread 3 writing %d\n", getpid());
        printf("pwd:%s\n", getcwd(buf, BUFSIZ));
        sleep( 1 );
    }
}
int n = 0;

int main( void )
{
    pthread_t tid;
    void *tret;

    pthread_create( &tid, NULL, thr_fn1, &n);
    pthread_join( tid, &tret );
    printf("n= %d\n",  n );
    printf("thread 1 exit code %d\n", (int) tret );

    pthread_create( &tid, NULL, thr_fn2, NULL);
    pthread_join( tid, &tret );
    printf("thread 2 exit code %d\n", (int) tret );

    pthread_create( &tid, NULL, thr_fn3, NULL);
    sleep( 3 );
    pthread_cancel(tid);
    pthread_join( tid, &tret );
    printf("thread 3 exit code %d\n", (int) tret );

}

  • 该代码主要是为了获得线程的终止状态,thr_fn 1,thr_fn 2和thr_fn 3三个函数对应终止线程的三种方法

    • 从线程函数return
    • 调用pthread_exit终止自己
    • 调用pthread_cancel终止同一进程中的另一个线程

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

  • 并发是一个之前没有见过的不同的机制,说没见过也不可能,我们使用的任何一个操作系统,哪个是只能在一个时间段上运行一个程序吗,都是可以重叠的,而经过本章节的学习,从程序级的角度了解到了并发,并进行了实践,这就是对书本理论的一个巩固。
  • 但感觉自己还是没有太弄懂,可能是快期末了,事情太多,有些时候精力就顾不上了,还是要提高效率啊,唉……
  • 我对并发的理解:并发执行只是宏观上的。在操作系统的管理下,所有正在运行的进程轮流使用CPU,每个进程允许占用CPU的时间非常短(比如10毫秒),这样用户根本感觉不出来CPU是在轮流为多个进程服务,就好象所有的进程都在不间断地运行一样。微观上一个cpu在同一时间一次还是只能执行一个进程。
  • 然后参考了一下别人的博客

参考资料

时间: 2024-10-14 05:04:55

20155236 《信息安全系统设计基础》第13周学习总结的相关文章

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

知识点总结 并发编程*序 并发:逻辑控制流在时间上重叠. 应用级并发的应用: 访问慢速I/O设备 与人交互 通过推迟工作以降低延迟 服务多个网络客户端 在多核机器上进行并行计算 进程 I/O多路复用 线程 基于进程的并发编程 最简单的构造并发程序的方法是进程. 下图是基于进程的并发echo服务器: 图 几点需要说明的内容: 因为服务器会运行很长时间,所以需要一个SIGCHLD处理程序来回收僵死子进程的资源. 父子进程必须关闭它们各自的connfd拷贝,以避免存储器泄露. 因为套接字的文件表表项的

2017-2018-1 20155228 《信息安全系统设计基础》第九周学习总结

2017-2018-1 20155228 <信息安全系统设计基础>第九周学习总结 教材学习内容总结 常见的存储技术 RAM 随机访问存储器(Random-Access Memory, RAM)分为两类:静态的和动态的.静态 RAM(SRAM)比动态RAM(DRAM)更快,但也贵得多.SRAM用来作为高速缓存存储 器,既可以在CPU芯片上,也可以在片下.DRAM用来作为主存以及图形系统的帧缓冲 区.典型地,一个桌面系统的SRAM不会超过几兆字节,但是DRAM却有几百或几千兆 字节. SRAM将每

2017-2018-1 20155331 《信息安全系统设计基础》第九周学习总结

2017-2018-1 20155331 <信息安全系统设计基础>第九周学习总结 教材学习内容总结 存储器层次结构 存储技术 随机访问存储器 随机访问存储器分为:静态的SRAM.动态的DRAM 静态RAM: SRAM的特点:存储器单元具有双稳态特性,只要有电就会永远保持它的值,干扰消除时,电路就会恢复到稳定值. 动态RAM: DRAM的特点:每一位的存储是对一个电容的充电:对干扰非常敏感. 用途:数码照相机和摄像机的传感器 DRAM存储不稳定的应对机制: 存储器系统必须周期性地通过读出,或者重

2017-2018-1 20155201 《信息安全系统设计基础》第九周学习总结

2017-2018-1 20155201 <信息安全系统设计基础>第九周学习总结 教材学习内容总结 一.存储技术 DRAM和SRAM的特性: 静态RAM(SRAM) SRAM将每个位存储在一个双稳态的存储器单元里, 动态RAM(DRAM) DRAM将每个位存储为一个电容的充电.每个DRAM芯片被连接到某个成为存储控制器的电路,电路可以一次传送w位到每个DRAM芯片或一次从每个DRAM芯片传出w位. 存储器模块 磁盘存储 磁盘是由一个或多个叠放在一起的盘片组成的,被封装在一个密封的包装里,整个装

2018-2019-1 20165225《信息安全系统设计基础》第九周学习总结

2018-2019-1 20165225<信息安全系统设计基础>第九周学习总结 教材学习内容总结 1.理解虚拟存储器的概念和作用; 2.理解地址翻译的概念; 3.理解存储器映射; 4.掌握动态存储器分配的方法; 5.理解垃圾收集的概念; 6.了解C语言中与存储器有关的错误; 虚拟存储器 物理和虚拟寻址 计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组,每字节都有一个唯一的物理地址.CPU根据物理地址访问存储器的方式是物理寻址. 虚拟存储器被组织为一个由存放在磁盘上的N个连续的字

20145216 史婧瑶《信息安全系统设计基础》第一周学习总结

20145216 <信息安全系统设计基础>第一周学习总结 教材学习内容总结 Linux基础 1.ls命令 ls或ls .显示是当前目录的内容,这里“.”就是参数,表示当前目录,是缺省的可以省略.我们可以用ls -a .显示当前目录中的所有内容,包括隐藏文件和目录.其中“-a” 就是选项,改变了显示的内容.如图所示: 2.man命令 man命令可以查看帮助文档,如 man man : 若在shell中输入 man+数字+命令/函数 即可以查到相关的命令和函数:若不加数字,那man命令默认从数字较

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

20145311 <信息安全系统设计基础>第一周学习总结 教材学习内容总结 常用的部分命令 CTRL+SHIFT+T:新建标签页,编程时有重要应用: ALT+数字N:终端中切换到第N个标签页,编程时有重要应用: Tab:终端中命令补全,当输入某个命令的开头的一部分后,按下Tab键就可以得到提示或者帮助完成: CTRL+C:中断程序运行 Ctrl+D:键盘输入结束或退出终端 Ctrl+S: 暂定当前程序,暂停后按下任意键恢复运行 Ctrl+A: 将光标移至输入行头,相当于Home键 Ctrl+E

20145216史婧瑶《信息安全系统设计基础》第九周学习总结

20145216史婧瑶<信息安全系统设计基础>第九周学习总结 教材内容总结 第十章 系统级I/O 输入/输出(I/O)是在主存和外部设备之间拷贝数据的过程. 第一节 Unix I/O 这一节涉及到操作系统的基本抽象之一--文件.也就是说,所有的I/O设备都被模型化为文件,而所有的输入输出都被当做对相应文件的读/写.相关的执行动作如下: 1.打开文件: 应用程序向内核发出请求→要求内核打开相应的文件→内核返回文件描述符 文件描述符:一个小的非负整数,用来在后续对此文件的所有操作中标识这个文件.有

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

20145311 <信息安全系统设计基础>第二周学习总结 教材学习内容总结 重新学习了一下上周的一部分命令:grep main wyx.c(grep的全文检索功能)ls > ls.txt :ls内容输出到文本find pathname -mtime -n/+nfind -size -n/+n (find的功能还是比较强大) 简单地学习了一下vim编辑器,跟着vimtutor简单地学了一些,在linux bash中使用vim能够极大地提高效率, vim的用法比较多,只学习了其中简单的一部分

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

20145339顿珠达杰<信息安全系统设计基础>第一周学习总结 ◆ Linux是一个操作系统.如果使用GUI,Linux和Windows没有什么区别.Linux学习应用的一个特点是通过命令行进行使用. 物理机系统上可以通过使用[Ctrl]+[Alt]+[F1]-[F6]进行终端和图形界面切换,在线实验环境中按下[Ctrl]+[Alt]+[F7]来完成切换.普通意义上的 Shell 就是可以接受用户输入命令的程序,Unix/Linux 操作系统下的 Shell 既是用户交互的界面,也是控制系统的