zoukankan      html  css  js  c++  java
  • 2017-2018-1 20155225 《信息安全系统设计基础》第十四周学习总结

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

    我认为学得最差的一章是第8章异常控制流。因为这一章是老师上课讲的,课下我没有再去深入理解和实践,导致我在学习第十二章的过程中感觉很困难。“困难的事越做越容易,容易的事越做越困难”,那我现在重新好好学习一下这一章,希望能在今后的学习过程越来越轻松。

    知识要点

    Linux/86-64系统调用

    C程序用syscall函数可以直接调用任何系统调用。

    所有linux系统调用的参数都是通过通用寄存器传递的。

    %rax包含系统调用号,%rdi,%rsi,%rdx……包含其他参数。

    系统调用write版本的helloworld:

    int main()
    {
     write(1,"hello,world
    ",13);
     _exit(0);
    }
    

    image

    通过反汇编,可以看到与书上不同的是:

    • 对write函数的调用并不是用syscall,而是直接用call。
    • 但确实是通过寄存器传递的参数,len(13)放在%edx,string(.LC0)放在%esi,write第一个参数1放在%edi。
    • 在%eax里放的是0,不是write的系统调用号1,和_exit的系统调用号60也没有放入%eax,就直接用call调用了。

    系统调用错误处理

    当Unix系统级函数遇到错误时,通常会返回-1,并设置全局整数变量errno来表示出了什么错。所以我们应该注意检查错误,而不能偷懒。

    可以使用错误处理包装函数。对于一个给定的函数foo,定义一个具有相同参数的包装函数Foo,包装函数再调用基本函数,检查错误。

    例如Fork包装函数如下:

    pid_t Fork(void)
    {
        pid_t pid;
        if((pid = fork())<0)
          unix_error("Fork error");
        return pid;
    }
    

    那么,对fork的调用就可以直接写成:

    pid = Fork();
    

    进程控制

    1. getpid返回调用进程的PID。
    2. getppid返回父进程的PID。
    3. exit终止进程。
    4. fork创建子进程。
    5. waitpid等待它的子进程终止或停止。
    6. sleep函数将一个进程挂起一段指定的时间。
    7. pause函数让调用函数休眠,直到该进程收到一个信号。
    8. execve函数在当前进程的上下文中加载并运行一个新程序。

    使用fork创建新进程:

    #include "csapp.h"
    
    int main() 
    {
        pid_t pid;
        int x = 1;
    
        pid = Fork();
        if (pid == 0) {  /* child */
    	printf("child : x=%d
    ", ++x);
    	exit(0);
        }
    
        /* parent */
        printf("parent: x=%d
    ", --x);
        exit(0);
    }
    

    从结果来看,与书上的结果一致。

    image

    练习题8.2

    #include "csapp.h"
    
    int main() 
    {
        int x = 1;
    
        if (Fork() == 0)
    	   printf("printf1: x=%d
    ", ++x);
        printf("printf2: x=%d
    ", --x);
        exit(0);
    }
    

    首先对结果预测,p1是子进程x等于2,p2是父进程x等于0。

    image

    子进程执行p1、p2,父进程只执行p2。

    练习题8.3

    分析下面程序可能的输出序列。

    #include "csapp.h"
    
    int main() 
    {
        if (Fork() == 0){
    	printf("a");fflush(stdout);
        }
        else{
        printf("b");fflush(stdout);
        waitpid(-1,NULL,0);
        }
        printf("c");fflush(stdout);
        exit(0);
    }
    

    通过画进程图可知,有acbc、abcc、bacc三种可能。最终运行结果是bacc。

    image

    使用waitpid函数不按照特定顺序回收僵死子进程:

    image

    使用waitpid函数按照创建子进程的顺序来回收僵死子进程:

    image

    execve函数调用一次从不返回。

    在Unix shell和Web服务器这样的程序,大量使用fork和execve函数。

    image

    用户层的异常——信号

    信号提供一种机制,通知用户进程发生了这些异常。

    传送一个信号到目的进程是由两个不同步骤组成的:

    1. 发送信号
    2. 接受信号

    注意:发出而没有接收的信号叫待处理信号,任何时刻,一种类型至多只会有一个待处理信号,一个待处理信号最多只能被接收一次

    发送信号的机制

    1. /bin/kill程序可以向另外的进程发送任意的信号。
    2. 从键盘发送信号。比如:在键盘上输入Ctrl+C会导致内核发送一个SIGINT信号,终止前台作业。Ctrl+Z会发送一个SIGTSTP信号,挂起前台作业。
    3. 调用kill函数发送信号给其他进程(包括它们自己)

    父进程用kill函数发送一个SIGKILL信号给它的子进程。

    #include "csapp.h"
    
    int main() 
    {
        pid_t pid;
    
        if ((pid = Fork()) == 0) {   
    	Pause();  
    	printf("control should never reach here!
    ");
    	exit(0);
        }
    
        Kill(pid, SIGKILL);
        exit(0);
    }
    

    实验结果如下:

    子进程被终止,没有打印control should never reach here!这句话。

    image

    1. 调用alarm函数向它自己发送SIGALRM信号。

    接收信号的机制

    进程可以通过signal函数修改和信号相关联的默认行为。除SIGSTOP和SIGKILL的默认行为不能修改。

    在书上的示例程序中,捕获了通过键盘发送的SIGINT信号,默认行为是立即终止该进程,这里修改为输出一条消息,终止该进程。

    #include "csapp.h"
    
    void handler(int sig) 
    {
        printf("Caught SIGINT
    ");
        exit(0);
    }
    
    int main() 
    {
        if (signal(SIGINT, handler) == SIG_ERR) 
    	unix_error("signal error");
        
        pause(); 
        
        exit(0);
    }
    

    image

    linux提供阻塞信号的隐式和显式的机制:

    • 隐式阻塞机制
    • 显式阻塞机制

    编写信号处理程序的规则:

    1. 安全的信号处理
    • 处理程序要尽可能简单
    • 在处理程序中只调用异步信号安全的函数
      输出唯一安全的方法是使用write函数,printf或sprintf是不安全的。
    • 保存和恢复errno
    • 阻塞所有的信号
    • 用volatile声明全局变量
    • 用sig_atomic_t声明标志
    1. 正确的信号处理

    书上通过一个例子:父进程通过SIGCHLD处理程序来回收子进程,而不是显示地等待子进程终止。

    但实际运行中,发现出现了僵死进程,如图所示进程15031并没有被回收。

    image

    由此得到的教训是,不可用信号对其他进程中发生的事件计数。

    修改程序,使得每次SIGCHLD处理程序被调用时,回收尽可能多的僵死子进程。

    image

    1. 可移植的信号处理

    sigaction函数,允许用户在设置信号处理时,明确指定他们想要的信号处理语义。

    更简洁的包装函数signal。

    消除竞争的方法:

    1. 用sigprocmask来同步进程。

    一个进程的信号屏蔽字规定了当前阻塞而给该进程的信号集。调用函数sigprocmask可以检测或更改其信号屏蔽字,或者在一个步骤中同时执行这两个操作。

    #include <signal.h>
    int sigprocmask( int how, const sigset_t *restrict set, sigset_t *restrict oset );
    返回值:若成功则返回0,若出错则返回-1
    
    1. 用循环来等待信号。
    2. 用sigsuspend来等待信号。

    sigsuspend函数暂时用mask替换当前的阻塞集合,然后挂起该进程,直到收到一个信号,其行为要么是运行一个处理程序,要么是终止该进程。如果它的行为是终止,那么该进程不从sigsuspend返回就直接终止。如果它的行为是运行一个处理程序,那么sigsuspend从处理程序返回,恢复调用sigsuspend是原有的阻塞集合。

    #include <signal.h>
    
    int sigsuspend(const sigset_t *mask);
    

    C语言提供了一种用户级异常控制流形式——非本地跳转。通过两个函数提供:setjmp和longjmp。

    通过下面这个示例程序,我们知道了这两个函数是如何工作的。

    #include "csapp.h"
    
    jmp_buf buf;
    
    int error1 = 0; 
    int error2 = 1;
    
    void foo(void), bar(void);
    
    int main() 
    {
        int rc;
    
        rc = setjmp(buf);
        if (rc == 0)
    	foo();
        else if (rc == 1) 
    	printf("Detected an error1 condition in foo
    ");
        else if (rc == 2) 
    	printf("Detected an error2 condition in foo
    ");
        else 
    	printf("Unknown error condition in foo
    ");
        exit(0);
    }
    
    void foo(void) 
    {
        if (error1)
    	longjmp(buf, 1); 
        bar();
    }
    
    void bar(void) 
    {
        if (error2)
    	longjmp(buf, 2); 
    }
    

    可见,首先是调用了setjmp保存当前环境,然后调用foo函数,foo再调用bar,如果遇到错误,就通过longjmp调用从setjmp返回。

    image

  • 相关阅读:
    CF 633 E. Binary Table
    BZOJ 4589 Hard Nim
    不走弯路,微信小程序的快速入门?
    如果通过cookies和localStorage取值?
    Airbub 弃用React Native
    如何在登陆注册的时候,实现密码框的小眼睛的显示与与隐藏?
    js 实用封装 点击按钮复制到剪贴板
    css渐变写法 从左到右渐变三种颜色示例;
    vue-router 使用二级路由去实现子组件的显示和隐藏
    vue 路由传参中刷新页面参数丢失 及传参的几种方式?
  • 原文地址:https://www.cnblogs.com/clever-universe/p/8099372.html
Copyright © 2011-2022 走看看