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


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

    教材内容总结

    异常控制流

    • 异常控制流:现代操作系统通过使控制流发生突变来对系统状态做出反应,这些突变称为异常控制流。
    • 异常控制流发生在计算机系统的各个层次
    硬件层:硬件检测到的事件会触发控制突然装移到异常处理程序
    操作系统层:内核通过上下文转换将控制从一个用户进程转移到另一个用户进程。
    应用层:一个进程可以发送信号到到另一个进程,而接收者将会控制突然转移到它的一个信号处理程序。
    一个程序可以通过回避通常的栈规则,并执行到其它函数中任意位置的非本地跳转来对错误做出反应。

    异常

    • 异常是异常控制流的一种形式,是控制流中的突变,用来响应处理器状态中的某些变化,由硬件和操作系统实现。

      事件:状态变化,可能和当前指令的执行有关。

    • 异常处理程序完成后
    处理程序将控制返回给事件发生时正在执行的当前指令
    处理程序将控制返回给没有发生异常将会执行的下一条指令
    处理程序终止被中断的程序
    • 异常表:当处理器检测到有事件发生时,它会通过跳转表,进行一个间接过程调用(异常),到异常处理程序。系统启动时操作系统分配和初始化一张异常表。
    • 异常号:系统中可能的某种类型的异常都分配了一个唯一的非负整数的异常号。异常号是到异常表中的索引,异常表的起始地址放在一个叫做异常表基址寄存器的特殊CPU寄存器里。
    • 异常的类别

    • 系统调用

    进程

    • 进程的经典定义:一个执行中的程序的实例
    • 系统中每个程序都是运行在某个进程上下文中的。
    • 上下文是由程序正确运行所需的状态组成的。状态包括存放在存储器中的程序代码和数据,栈、通用目的寄存器内容、程序计数器、环境变量和打开文件描述符的集合。
    • 进程提供给应用程序的关键抽象
    一个独立的逻辑控制流,提供一个假象:程序独占地使用处理器
    一个私有的地址空间,提供一个假象:程序独占地使用存储器系统
    • 逻辑控制流
      • 程序计数器(PC)值的序列叫做逻辑控制流,简称逻辑流。
    • 并发流
      • 一个逻辑流的执行在时间上与另一个流重叠(与是否在同一处理器无关),这两个流并发的运行。
    • 上下文切换
      • 内核重新启动一个被抢占的进程所需的状态。
    • 上下文切换机制
    保存当前进程的上下文
    恢复某个先前被抢占的进程被保存的上下文
    将控制传递给这个新恢复的进程。
    • 上下文切换原因
    内核代表用户执行系统调用时(进程休眠)
    中断

    系统调用错误处理

    • 包装函数被封装在一个源文件(csapp.c)中,这个文件被编译和链接到每个程序中。一个独立的头文件(csapp.h)中包含这些包装函数的函数原型。
    • 包装函数调用基本函数,检查错误,如果有任何问题就终止。

    进程控制

    • 每个进程都有一个唯一的正数进程ID(PID)。
    #include <sys/types.h>
    #include <unistd.h>
    
    pid_t getpid(void); 返回调用进程的PID
    pid_t getppid(void);    返回父进程的PID(创建调用进程的进程)
    • 进程的三种状态
    运行
    停止:被挂起且不会被调度
    终止:永远停止。
          - 收到信号,默认行为为终止进程
          - 从主程序返回
          - 调用exit函数
    • 回收子进程
      • 进程终止后还要被父进程回收,否则处于僵死状态。
      • 如果父进程没有来得及回收,内核会安排init进程来回收他们。init进程的PID为1。
    • 创建进程

      • 父进程通过调用fork函数来创建一个新的运行子进程。fork函数定义如下:

    #include <sys/types.h>
    #include <unistd.h>
    
    pid_t fork(void);
      • fork函数只被调用一次,但是会返回两次:父进程返回子进程的PID,子进程返回0.如果失败返回-1。

    • 终止进程

      • exit函数

    #include <stdlib.h>
    
    void exit(int status);
      • exit函数以status退出状态来终止进程。

    信号

    • 发送信号的两个不同步骤
    发送信号:内核通过更新目的进程上下文中的某个状态,发送(递送)一个信号给目的进程。
    接收信号:信号处理程序捕获信号的基本思想。
    • 发送信号的两个原因
    内核监测到一个系统事件,比如被零除错误或者子进程终止。
    一个进程调用了kill函数,显式地要求内核发送一个信号给目的进程。一个进程可以发送信号给它自己。
    • 待处理信号:一个只发出而没有被接收的信号.

    代码实践

    exec1.c

    程序代码

     #include <stdio.h>
     #include <unistd.h>
    
     int main()
     {
     char    *arglist[3];
    
     arglist[0] = "ls";
     arglist[1] = "-l";
     arglist[2] = 0 ;//NULL
     printf("* * * About to exec ls -l
    ");
     execvp( "ls" , arglist );
     printf("* * * ls is done. bye");
    
     return 0;
     }
    • 这个代码中用了execvp函数。execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件。如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。

    exec2.c

    程序代码

     #include <stdio.h>
     #include <unistd.h>
     int main(){
     char    *arglist[3];
     arglist[0] = "ls";
     arglist[1] = "-l";
     arglist[2] = 0 ;
     printf("* * * About to exec ls -l
    ");
     execvp( arglist[0] , arglist );
     printf("* * * ls is done. bye
    ");
     }
    • 它与exec1的区别就在于exevp函数的第一个参数,exec1传的是ls,exec2直接用的arglist[0],不过由定义可得这两个等价,所以运行结果是相同的。

    exec3.c

    程序代码

     #include <stdio.h>
     #include <unistd.h>
     int main(){
     char    *arglist[3];
     char*myenv[3];
     myenv[0] = "PATH=:/bin:";
     myenv[1] = NULL;
     arglist[0] = "ls";
     arglist[1] = "-l";
     arglist[2] = 0 ;
     printf("* * * About to exec ls -l
    ");
     execlp("ls", "ls", "-l", NULL);
     printf("* * * ls is done. bye
    ");
     }
    • 这个代码里使用了execlp函数。 execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个以后的参数当做该文件的argv[0]、argv[1]……,最后一个参数必须用空指针(NULL)作结束。 指定了环境变量,然后依然执行了ls -l指令,成功后没有返回,所以最后一句话不会输出。运行结果同exec1。

    forkdemo1.c

    程序代码

     #include    <stdio.h>
     #include<sys/types.h>
     #include<unistd.h>
     int main(){
     int ret_from_fork, mypid;
     mypid = getpid();              
     printf("Before: my pid is %d
    ", mypid);
     ret_from_fork = fork();
     sleep(1);
     printf("After: my pid is %d, fork() said %d
    ",
            getpid(), ret_from_fork);
     return 0;
     }
    • 这个代码先是打印进程pid,然后调用fork函数生成子进程,休眠一秒后再次打印进程id,这时父进程打印子进程pid,子进程返回0。

    forkdemo2.c

    程序代码

     #include <stdio.h>
     #include <unistd.h>
     int main()
     {
     printf("before:my pid is %d
    ", getpid() );
     fork();
     fork();
     printf("aftre:my pid is %d
    ", getpid() );
    
     return 0;
     }
    • 这个代码调用两次fork,一共产生四个子进程,所以会打印四个aftre输出。

    forkdemo3.c

    程序代码

     #include    <stdio.h>
     #include    <stdlib.h>
     #include    <unistd.h>
     int fork_rv;
    
     int main()
     {
    
     printf("Before: my pid is %d
    ", getpid());
    
     fork_rv = fork();       /* create new process   */
    
     if ( fork_rv == -1 )        /* check for error  */
        perror("fork");
     else if ( fork_rv == 0 ){ 
        printf("I am the parent. my child is %d
    ", getpid());
    
        exit(0);
     }
     else{
        printf("I am the parent. my child is %d
    ", fork_rv);
        exit(0);
     }
    
     return 0;
     }
    • fork产生子进程,父进程返回子进程pid,不为0,所以输出父进程的那句话,子进程返回0,所以会输出子进程那句话。

    forkdemo4.c

    程序代码

     #include    <stdio.h>
     #include    <stdlib.h>
     #include    <unistd.h>
     int main(){
     int fork_rv;
     printf("Before: my pid is %d
    ", getpid());
     fork_rv = fork();       /* create new process   */
     if ( fork_rv == -1 )        /* check for error  */
        perror("fork");
     else if ( fork_rv == 0 ){ 
        printf("I am the child.  my pid=%d
    ", getpid());
        printf("parent pid= %d, my pid=%d
    ", getppid(), getpid());
        exit(0);
     }
     else{
        printf("I am the parent. my child is %d
    ", fork_rv);
        sleep(10);
        exit(0);
     }
     return 0;
     }
    • 先打印进程pid,然后fork创建子进程,父进程返回子进程pid,所以输出parent一句,执行sleep(10)语句,休眠十秒。子进程返回0,所以输出child与之后一句。

    psh1.c

    程序代码

     #include    <stdio.h>
     #include    <stdlib.h>
     #include    <string.h>
     #include    <unistd.h> 
     #define MAXARGS     20              
     #define ARGLEN      100             
     int execute( char *arglist[] )
     {
     execvp(arglist[0], arglist);        
     perror("execvp failed");
     exit(1);
     }
    
     char * makestring( char *buf )
     {
     char    *cp;
    
     buf[strlen(buf)-1] = '';      
     cp = malloc( strlen(buf)+1 );       
     if ( cp == NULL ){          
        fprintf(stderr,"no memory
    ");
        exit(1);
     }
     strcpy(cp, buf);        
     return cp;          
     }
     int main()
     {
     char    *arglist[MAXARGS+1];        
     int     numargs;            
     char    argbuf[ARGLEN];         
    
     numargs = 0;
     while ( numargs < MAXARGS )
     {                   
        printf("Arg[%d]? ", numargs);
        if ( fgets(argbuf, ARGLEN, stdin) && *argbuf != '
    ' )
            arglist[numargs++] = makestring(argbuf);
        else
        {
            if ( numargs > 0 ){     
                arglist[numargs]=NULL;  
                execute( arglist ); 
                numargs = 0;        
            }
        }
     }
     return 0;
     }
    • 这个代码就相当于你输入要执行的指令,回车表示输入结束,然后输入的每个参数对应到函数中,再调用对应的指令。

    psh2.c

    程序代码

    #include    <stdio.h>
    #include    <stdlib.h>
    #include    <string.h>
    #include    <sys/types.h>
    #include    <sys/wait.h>
    #include    <unistd.h>
    #include    <signal.h>
    #define MAXARGS     20              
    #define ARGLEN      100             
    
    char *makestring( char *buf )
    {
        char    *cp;
    
        buf[strlen(buf)-1] = '';      
        cp = malloc( strlen(buf)+1 );       
        if ( cp == NULL ){          
            fprintf(stderr,"no memory
    ");
            exit(1);
        }
        strcpy(cp, buf);        
        return cp;          
    }
    
    void execute( char *arglist[] )
    {
        int pid,exitstatus;             
    
        pid = fork();                   
        switch( pid ){
            case -1:    
                perror("fork failed");
                exit(1);
            case 0:
                execvp(arglist[0], arglist);        
                perror("execvp failed");
                exit(1);
            default:
                while( wait(&exitstatus) != pid )
                    ;
                printf("child exited with status %d,%d
    ",
                        exitstatus>>8, exitstatus&0377);
        }
    }
    
    int main()
    {
        char    *arglist[MAXARGS+1];        
        int     numargs;            
        char    argbuf[ARGLEN];         
    
        numargs = 0;
        while ( numargs < MAXARGS )
        {                   
            printf("Arg[%d]? ", numargs);
            if ( fgets(argbuf, ARGLEN, stdin) && *argbuf != '
    ' )
                arglist[numargs++] = makestring(argbuf);
            else
            {
                if ( numargs > 0 ){     
                    arglist[numargs]=NULL;  
                    execute( arglist ); 
                    numargs = 0;        
                }
            }
        }
        return 0;
    }
    • 这个代码与psh1.c代码最大的区别就在于execute函数。 调用wait(&status)等价于调用waitpid(-1.&status,0),当option=0时,waitpid挂起调用进程的执行,直到它的等待集合中的一个子进程终止。只要有一个子进程没有结束,父进程就被挂起。所以当wait返回pid时没说明,子进程都已经结束,即用户输入的指令都已经执行完毕。因为execute函数在大的循环中调用,所以会循环执行下去,除非用户强制退出。

    testbuf1.c

    程序代码

    #include <stdio.h>
    #include <stdlib.h>
    int main()
    {
        printf("hello");
        fflush(stdout);
        while(1);
    }
    • 效果是先输出hello,然后保持在循环中不结束进程。

    testbuf2.c

    程序代码

    #include <stdio.h>
    int main()
    {
        printf("hello
    ");
        while(1);
    }
    • 运行结果和testbuf1.c的一样。从testbuf1.c和testbuf2.c的运行结果上看,可知fflush(stdout)的功能和换行符 是一样的。

    testbuf3.c

    程序代码

    #include <stdio.h>
    
    int main()
    {
        fprintf(stdout, "1234", 5);
        fprintf(stderr, "abcd", 4);
    }
    • 将内容格式化输出到标准错误、输出流中。

    waitdemo1.c

    程序代码

    #include    <stdio.h>
    #include    <stdlib.h>
    #include    <sys/types.h>
    #include    <sys/wait.h>
    #include    <unistd.h>
    
    #define DELAY   4
    
    void child_code(int delay)
    {
        printf("child %d here. will sleep for %d seconds
    ", getpid(), delay);
        sleep(delay);
        printf("child done. about to exit
    ");
        exit(17);
    }
    
    void parent_code(int childpid)
    {
        int wait_rv=0;      /* return value from wait() */
        wait_rv = wait(NULL);
        printf("done waiting for %d. Wait returned: %d
    ", 
                childpid, wait_rv);
    }
    int main()
    {
        int  newpid;
        printf("before: mypid is %d
    ", getpid());
        if ( (newpid = fork()) == -1 )
            perror("fork");
        else if ( newpid == 0 )
            child_code(DELAY);
        else
            parent_code(newpid);
    
        return 0;
    }
    • 如果有子进程,则终止子进程,成功返回子进程pid。

    waitdemo2.c

    程序代码

    #include    <stdio.h>
    #include    <stdlib.h>
    #include    <sys/types.h>
    #include    <sys/wait.h>
    #include    <unistd.h>
    
    #define DELAY   10
    
    void child_code(int delay)
    {
        printf("child %d here. will sleep for %d seconds
    ", getpid(), delay);
        sleep(delay);
        printf("child done. about to exit
    ");
        exit(27);
    }
    
    void parent_code(int childpid)
    {
        int wait_rv;    
        int child_status;
        int high_8, low_7, bit_7;
    
        wait_rv = wait(&child_status);
        printf("done waiting for %d. Wait returned: %d
    ", childpid, wait_rv);
    
        high_8 = child_status >> 8;     /* 1111 1111 0000 0000 */
        low_7  = child_status & 0x7F;   /* 0000 0000 0111 1111 */
        bit_7  = child_status & 0x80;   /* 0000 0000 1000 0000 */
        printf("status: exit=%d, sig=%d, core=%d
    ", high_8, low_7, bit_7);
    }
    
    int main()
    {
        int  newpid;
    
        printf("before: mypid is %d
    ", getpid());
    
        if ( (newpid = fork()) == -1 )
            perror("fork");
        else if ( newpid == 0 )
            child_code(DELAY);
        else
            parent_code(newpid);
    }
    • 这个比起1来就是多了一个子进程的状态区分,把状态拆分成三块,exit,sig和core。

    本周代码托管

    代码托管链接

    代码量统计

    学习进度条

     代码行数(新增/累积)博客量(新增/累积)学习时间(新增/累积)
    目标 3500行 30篇 300小时
    第一周 50/50 1/2 10/10
    第二周 120/170 1/3 20/30
    第三周 130/300 1/4 20/50
    第五周 130/430 2/6 25/75
    第六周 50/480 2/8 25/100
    第七周 53/533 1/9 20/120
    第八周 0/533 2/11 15/135
    第九周 68/601 2/13 20/155
    第十周 468/1069 1/15 20/175
    十一周 404/1473 2/17 20/195
  • 相关阅读:
    K折交叉验证
    浅谈python的第三方库——pandas(三)
    关于机器学习二分类问题的几个评估指标辨析
    浅谈python的第三方库——pandas(二)
    浅谈python的第三方库——pandas(一)
    【内核篇】Windows内核重要变量
    【逆向篇】分析一段简单的ShellCode——从TEB到函数地址获取
    【系统篇】从int 3探索Windows应用程序调试原理
    【编程篇】使用操作系统异常巧妙获取交叉链表的交点
    【灌水篇】为什么我们要拼搏北上广
  • 原文地址:https://www.cnblogs.com/5321z/p/6107575.html
Copyright © 2020-2023  润新知