首页 > 代码库 > 20145239 《信息安全系统设计基础》第11周学习总结

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

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

教材学习内容总结

8.1 异常

  • 从处理器运行开始到结束,程序计数器假设一个序列的值a0a1......an-1,这个控制转义序列叫做处理器的控制流。
  • 异常,就是控制流中的突变,用来响应处理器状态中的某些变化。
  • 状态的变化称为事件,在任何情况下,当处理器检测到有事件发生时,会通过一张叫做异常表的跳转表,进行一个间接过程调用到专门处理程序——异常处理程序。当异常处理程序完成之后,根据引起引起异常的事件类型,会发生以下三种情况之一:
  • 处理程序将控制返回给当前指令,即事件发生之时正在执行的指令。
  • 处理程序将控制返回给如果没有异常将会执行的下一条指令。
  • 处理程序终止被中断的程序。

异常处理

  • 系统中可能的每种异常都被分配了唯一一个非负整数的异常号,异常表中的条目k中包含异常k的处理程序地址。异常表的起始地址存放在一个叫做异常表基址寄存器的特殊寄存器中。
  • 异常类和过程调用的不同之处:
  • 返回地址是当前地址或者下一条指令
  • 处理器也会把额外的处理器状态压回栈中,在处理程序返回时,重新开始被中断的程序会需要这些状态。
  • 如果控制从一个用户程序转移到内核,那么所有项目都会被压到内核栈中而不是用户栈。
  • 异常处理程序运行在内核模式下,意味着他们对所有的系统资源拥有完全的访问权限。

异常的类别

技术分享

1.中断

  • 中断是异步发生的,是来自处理器外部的I/O设备的信号的结果。因此是异步的。硬件中断的异常处理程序通常称为中断处理程序。
  • 其余异常类型都是同步发生的,是执行当前指令的结果。这一类指令称为故障指令。

2.陷阱

  • 陷阱是有意的异常,最重要的用途是在用户程序和内核之间提供一个向过程一样的接口,叫做系统调用。
  • 为了允许内核服务的受控访问,使用“syscall n”指令,跳转到一个异常处理程序的陷阱,处理程序对参数解码并调用适当的内核程序。

3.故障

  • 故障由错误情况引起,可能能够被故障处理程序修正。故障发生时,处理器将控制转移给故障处理程序,若能修正,则将控制返回到引起故障的指令,重新执行;若不能修正,处理程序返回abort例程,终止引起故障的应用程序。

4.终止

  • 终止是不可恢复的致命错误造成的结果,通常是硬件错误。终止处理程序将控制直接返回给abort例程,直接终止该应用程序。

Linux/A32系统中的异常

  • Linux/A32故障和终止
  • 除法错误(异常0):应用试图除以0,或者除法指令的结果对于目标操作数过大。
  • 一般故障保护(异常13):通常因为一个程序引用一个未定义的虚拟存储区域,或者试图写一个只读文本段。
  • 缺页(异常14):处理程序将磁盘上虚拟存储器相应页面映射到物理存储器的一个页面,然后重新开始执行这条指令。
  • 机器检查(异常18):在导致故障的指令执行中检测到致命的硬件错误。
  • Linux/A32系统调用
  • 每个系统调用都对应着唯一的整数号,对应于一个到内核中跳转表的偏移量。
  • IA32系统调用是通过一条称为 int n 的陷阱指令提供的。
  • C程序通过syscall函数可以直接调用任何系统调用。
  • 所有Linux系统调用都是通过通用寄存器而不是栈传递的,%eax包含系统调用号,%ebx、%ecx、%edx、esi%、%edi和%ebp包含最多6个参数。栈指针%esp不能使用,因为当进入内核模式时,内核会覆盖它。

进程

  • 进程,就是一个执行中的程序的实例,系统中的每个程序都是定义在运行在某个进程的上下文中的。异常是允许操作系统提供进程的概念所需要的基的本构造块。
  • 逻辑控制流
  • PC的值唯一地对应于包含在程序可执行目标文件中的指令,或者是包含在运行时动态链接到程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流。
  • 进程轮流使用处理器,每个进程执行流的一部分,然后被抢占(暂时挂起)。
  • 并发流
  • 一个逻辑流的执行在时间上与另一个流重叠,称为并发流。
  • 一个进程和其他进程轮流运行的概念称为多任务,一个进程执行它的控制流的一部分时间称为时间片。
  • 私有地址空间
  • 一个进程为每个程序提供他自己的私有地址空间,一般而言,和这个空间中某个地址相关联的存储器字节不能被其他程序读或写。
  • 地址空间底部是保留给用户程序的,顶部保留给保留给内核,用来存放内核在代表进程执行时的指令。
  • 用户模式和内核模式
  • 处理器通过某个控制寄存器中的一个模式位来提供这种功能。
  • 内核模式:设置模式位,进程可以执行指令集中的任何指令,并且访问系统中的任何存储器位置。
  • 用户模式:不设置模式位,进程不允许执行特权指令,也不允许直接引用地址空间中内核区的代码和数据。
  • 初始模式是用户模式,进入内核模式的唯一方法是通过诸如中断、故障或者陷入系统调用这样的异常。
  • /proc文件系统,允许用户模式进程访问内核数据结构的内容。
  • 上下文切换
  • 上下文:内核重新启动一个被抢占的进程所需的状态。包括描绘地址空间的页表、包含有关当前进程信息的进程表,以及半酣进程已打开文件的信息的文件表。
  • 调度:进程执行过程中,内核决定抢占当前进程并开始一个先前被抢占的进程,由内核中的调度器完成。
  • 内核为每个进程维持一个上下文。并通过一种称为上下文切换的较高层形式的异常控制流来实现多任务上下文切换:
    1.保存当前进程的上下文;
    2.恢复某个先前被抢占的进程被保存的上下文;
    3.将控制传递给新恢复的进程。

8.3系统调用错误处理

  • Unix系统级函数遇到错误时,会典型地返回-1,并设置全局变量errno来表示出错内容。
  • 通过使用错误处理包装函数,可以进一步简化代码。

8.4进程控制

获取进程ID

  • 每个进程有一个唯一的非零正数进程ID(PID)。

    pid_t getpid(void);     /*返回调用进程的PID*/
    pid_t getppid(void);             /*返回它的父进程的PID*/

创建和终止进程

进程总是处于以下三种状态之一:

  • 运行。在CPU上执行,或者等待被执行且最终会被内核调度。
  • 停止。进程的执行被挂起,且不会被调度。(与信号有关)
  • 终止。进程永远停止。进程终止的原因:1)收到一个信号,默认行为是终止程序2)从主程序返回3)
    调用exit函数。

  • exit函数以status退出状态来终止进程。void exit(int ststus)
  • 父进程通过调用fork函数创建一个新的运行子进程。
    pid_t fork(void); /*子进程返回0,父进程返回子进程的pid,出错则返回-1。*/
  • fork函数:
  • 调用一次,返回两次。一次返回到父进程,一次返回到新创建的子进程。
  • 并发执行,父进程和子进程是并发运行的独立程序。内核能够以任意方式交替执行他们的逻辑控制流中的指令。
  • 相同但是独立的地址空间,父进程和子进程地址空间都相同,但对于变量所做的改变都是独立的。
  • 共享文件,子进程继承了父进程所有打开的文件。

回收子进程

  • 当一个进程由于某种原因终止,内核不会将他马上清除,而是将进程保持在已终止的状态中,直到被他的父进程回收。
  • 系统通过调用waitpid函数来等待它的子进程终止或停止。
    pid_t waitpid(pid_t pid,int *status,int options);
  • 判定等待集合的成员:pid>0,则等待集合为单独的进程;pid=-1,等待集合是由父进程所有的子进程组成的。
  • 修改默认行为:可以通过将options设置为常量,修改默认行为。

  • 检查已回收子进程的退出状态:如果status参数是非空的,则status参数会放上关于导致返回的子进程的状态信息。

  • 错误条件:如果调用进程没有子进程,那么waitpid返回-1,并设置errno为ECHILD,如果函数被一个信号中断,那么返回-1,并设置errno为EINTR。
  • wait函数pid_t wait(int *status);
  • 调用wait(&status)等价于调用waitpid(-1,&status,0)。

让进程休眠

  • sleep函数将一个进程挂起一段指定的时间。
    unsigned int sleep(unsigned int secs); /*返回还要休眠的秒数*/
  • pause函数让调用函数休眠,直到进程收到一个信号。
    int pause(void); /*总是返回-1*/

加载并运行程序

  • execve函数在当前进程的上下文中加载并运行一个新程序。

    int execve(const char*filename,const char *argv[],const char *envp);
    /*成功则不返回,错误返回-1*/

利用fork和execve运行程序

  • 外壳:一个交互型的应用级程序,代表用户运行其他程序,执行一系列的读/求值步骤,然后终止。读步骤读取来自于命令行,求值步骤解析命令行,并代表用户运行程序。

8.5信号

  • 信号:一条小消息,通知进程系统中发生了一个某种类型的事件。

信号术语

  • 发送信号:内核通过更新目的进程上下文中的某个状态,发送一个信号给目的进程。
  • 接收信号:当目的进程被内核强迫以某种方式对信号的发送做出反应时,目的进程就接收了信号。通过信号处理程序捕获信号。

发送信号

  • 每个进程都只属于一个进程组,由一个正整数进程组ID来标识。
  • getpgrp函数返回当前进程的进程组ID。
    pid_t getpgrp(void);
  • setpgrp函数改变自己或者其他进程的进程组。
    int setpgrp(pid_t pid,pid_t pgid);
  • 用/bin/kill程序发送信号
  • /bin/kill 程序可以向另外的进程发送任意的信号。一个为负的PID会导致信号被发送到进程组PID中的每个进程。
  • 从键盘发送信号
  • Unix外壳使用作业这个抽象概念来表示为对一个命令行求值而创建的进程。
    int kill(pid_t pid,int sig);
  • 用kill函数发送信号
  • 进程通过调用kill函数发送信号给其他进程(包括自己)。
  • 如果pid大于0,则发送信号sig给进程pid;若小于0,则发送信号给sig给进程组abs(pid)中的每个进程。
  • 用alarm函数发送信号
  • 进程通过调用alarm函数向他自己发送SIGALRM信号
    unsigned int alarm(unsigned int secs); /*返回前一次闹钟剩余的秒数,若没有则返回0*/

接收信号

  • 内核从一个异常处理的程序返回,准备将控制传递给p时,会检查p未被阻塞的待处理信号的集合,若集合为空,则转到p逻辑控制流中的下一条指令;若集合不为空,则内核选择集合中的某个信号k,并且强制p接受信号k,触发进程的某种行为,完成后转到p逻辑控制流中的下一条指令。
  • k的默认行为有:
  • 进程终止。
  • 进程终止并转储存储器。
  • 进程停止直到被SIGCONT信号重启。
  • 进程忽略该信号。

信号处理问题

  • 一个程序捕获多个信号时产生的问题:
  • 待处理信号被阻塞。
  • 待处理信号不会排队等待。
  • 系统调用可以被中断。

不可以用信号来对其他进程中发生的事件计数。

可移植的信号处理

  • 为了处理不同系统之间信号处理语义的差异,通过sigaction函数使不同系统上兼容的用户明确指定想要的信号处理语义。
    int sigaction(int signum,struct sigaction *act,struct sigaction *oldact);

显示地阻塞和取消阻塞信号

int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);

  • sigprocmask函数改变当前已阻塞信号的集合,具体行为依赖how的值。
  • SIG_BLOCK:添加set中的信号到blocked中。
  • SIG_UNBLOCK:从blocked中删除set中的信号。
  • SIG_SETMASK:blocked=set。

同步流以避免并发错误

  • 以某种方式同步并发流,从而得到最大的可行的交错的集合,每个可行的交错都能得到正确的结果。

8.6非本地跳转

  • 用户级异常控制流形式,称为非本地跳转,它将控制直接从一个函数转移到另一个当前正在执行的函数,而不需要经过正常的调用-返回序列。
  • setjmp函数在env缓冲区中保存当前调用环境,供后面longjmp使用,并返回0。调用环境包括程序计数器、栈指针和通用目的寄存器。

    int setjmp(jmp_buf env);
    int sigsetjmp(setjmp_buf env,int savesigs);
  • longjmp函数从env缓冲区中恢复调用环境,然后触发一个从最近一次初始化env的setjmp调用的返回。然后setjmp返回,并带有非零的返回值retval。

    void longjmp(jmp_buf env,int retval);
    void siglongjmp(sigjmp_buf env,int retval);

8.7操作进程的工具

  • STRACE:打印一个正在运行的程序和它的子进程调用的每个系统调用的轨迹。
  • PS:列出当前系统中的进程(包括僵死的进程)。
  • TOP:打印出关于当前进程资源使用的信息。
  • PMAP:显示进程的存储器映射。
  • /proc:一个虚拟文件系统,以ASCII格式输出大量内核数据结构内容。

关于exec.1

  • exec1.c代码运行如下
    技术分享

  • exec1.c中execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件
  • 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中
  • exevp函数调用成功没有返回,所以没有打印出“* * * ls is done. bye”这句话

 

关于env

  • getenv函数是获得环境变量值的函数,参数是环境变量名name,例如”HOME”或者”PATH”。如果环境变量存在,那么getenv函数会返回环境变量值,即value的首地址;如果环境变量不存在,那么getenv函数返回NULL
  • setenv函数是修改或添加环境变量的函数

    1.如果name在环境中不存在,那么很好办,在环境中添加这个新的变量就OK。 setenv函数必须在environment list中增加一个新的entry,然后动态申请存储空间来存储name=value,并且使entry指向该空间。 2.如果在环境中name已经存在,那么 (a)若overwrite非0,那么更新name的value(实质是更新环境表,指向新的value) (b)若overwrite为0,则环境变量name不变,并且也不出错 setenv函数不必在environment list中增加一个新的entry。当overwrite为0, 则不必改动entry的指向;当overwrite非0, 则直接使该entry指向name=value,当然该name=value也是存储在动态申请的内存里。

  • environvar.c代码简单打印环境变量表,运行结果如下
    技术分享

  • 每个程序都有一个环境表,它是一个字符指针数组,其中每个指针包含一个以NULL结尾的C字符串的地址。全局变量environ则包含了该指针数组的地址

 

 

关于fifo

  • FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中
  • FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作
  • FIFO往往都是多个写进程,一个读进程
  • consumer.c代码中:

    memset(void *s,int ch,size_t n);将s中前n个字节用ch替换并返回s
    open(const char *pathname,int flags);第一个参数是欲打开的文件路径字符串,第二个参数是打开方式
  • fifo是一种文件类型,可以通过查看文件stat结构中的stmode成员的值来判断文件是否是FIFO文件。fifo是用来在进程中使用文件来传输数据的,也具有管道特性,可以在数据读出的时候清除数据
  • consumer.c代码运行如下
    技术分享

  • testmf.c代码中调用了mkfifo函数

    mkfifo(FIFO_NAME, 0777);//依据FIFO_NAME创建fifo文件,0777依次是相应权限
  • mkfifo()建立的FIFO文件其他进程都可以用读写一般文件的方式存取

关于forkdemo4.c

  • 先打印进程pid,然后fork创建子进程,父进程返回子进程pid,所以输出parent一句,休眠十秒;子进程返回0,所以输出child与之后一句
  • 运行如下
    技术分享

关于forkgdb.c

  • 父进程打印是先打印两句,然后休眠一秒,然后打印一句,子进程先打印一句,然后休眠一秒,然后打印两句。并且这两个线程是并发的,所以可以看到在一个线程休眠的那一秒,另一个线程在执行,并且线程之间相互独立互不干扰

关于waitdemo2.c

  • waitdemo2.c比起1来就是多了一个子进程的状态区分,把状态拆分成三块,exit,sig和core
  • 运行如下
    技术分享

 

 

关于pipe

  • pipe用来创建管道并将其两端连接到两个文件描述符,array[0]为读数据端的文件描述符,而array[1]则为写数据端的文件描述符,内部则隐藏在内核中,进程只能看到两个文件描述符
  • listargs.c 代码运行结果如下,证明了shell并不将重定向标记和文件名传递给程序
    技术分享

  • pipedemo.c展示了如何创建管道并使用管道来向自己发送数据
    技术分享

  • pipedemo2.c说明了如何将pipe和fork结合起来,创建一对通过管道来通信的进程。在程序中显示了从键盘到进程,从进程到管道,再从管道到进程以及从进程回到终端的数据传输流
    技术分享

 

 

关于psh1.c

  • psh1.c代码的效果是输入要执行的指令,回车表示输入结束,然后输入的每个参数对应到函数中,再调用对应的指令
  • 运行结果如下
    技术分享

 

关于signal

 

    • sigactdemo.c中sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum可以指定SIGKILL和SIGSTOP以外的所有信号

      int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);
    • sigactdemo.c执行如下
      技术分享

      SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL
      SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
      SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号

      本周代码托管截图

      技术分享
代码链接:https://git.oschina.net/929210354/Linux

学习进度条

 代码行数(新增/累积)博客量(新增/累积)学习时间(新增/累积)重要成长
目标 5000行 30篇 400小时  
第一周 100/100 1/2 20/20  
第二周 92/192 1/3 18/38  
第三周 195/387 1/4 22/60  
第四周 180/567 0/4 30/90  
第五周 120/687 1/5 20/20  
第六周 130/817 1/6 18/38  
第七周 550/1367 1/7 22/60  
第八周 0/1367 2/9 30/90  
第九周 60/1427 2/11 20/20  
第十周 514/1941 2/13 18/38  
第十一周 1856/3797 2/15 22/60  

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