这里回顾第6章,本章介绍了机制:受限直接执行。

书籍介绍:

学习资料:

参考资料:

参考资料:

第6 章 机制:受限直接执行

内容回顾

关键概念

  • 时分共享(time sharing):运行一个进程一段时间,然后运行另一个进程,如此轮换。
    • 挑战:
      • 性能
      • 控制权
  • 受限制的操作
    • 用户模式和内核模式
    • 如果用户程序需要执行系统调用,那么会通过陷阱(trap)指令跳入内核,并将特权级别提升到内核模式;完成系统调用,操作系统调用一个特殊的从陷阱返回(return-from-trap)指令返回到发起发起调用的用户程序,同时将特权级别降低到用户模式
    • 陷阱表(trap table)告诉硬件在发生某些异常事件时要运行哪些代码
  • 在进程之间切换
    • 协作方式:等待系统调用
    • 非协作方式:操作系统进行控制
      • 利用时钟中断。产生中断时,当前正在运行的进程停止,操作系统中预先配置的中断处理程序(interrupt handler)会运行。此时,操作系统重新获得CPU 的控制权,因此可以做它想做的事:停止当前进程,并启动另一个进程。
    • 保存和恢复上下文
      • 切换进程的操作称为上下文切换(context swtich)
        • 操作系统为当前正在执行的进程保存一些寄存器的值,为即将执行的进程恢复一些寄存器的值,这样从陷阱返回时不会回到之前运行的进行。

作业(测量)

1

系统调用时间测量,反复调用read 0字节:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>

int N = 10000;

int main() {
    struct timeval start;
    struct timeval end;
    double t = 0;
    char buf[1024];

    gettimeofday(&start, NULL);
    for (int i = 0; i < N; i++) {
        read(STDIN_FILENO, buf, 0);
    }
    gettimeofday(&end, NULL);
    
    t = end.tv_sec * 1000000.0 + end.tv_usec - start.tv_sec * 1000000.0 - start.tv_usec;
    printf("系统调用执行时间为:%f微秒\n", t / N);

    return 0;
}

运行:

╰─○ make sys_call && ./sys_call 
make: 'sys_call' is up to date.
系统调用执行时间为:0.626900微秒

下上文切换时间测量:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/wait.h>
#include <sys/time.h>

// 需要添加
#define __USE_GNU
#include <sched.h>
#include <pthread.h>

int N = 10000; //00000;

int main() {
    // I/O
    int fd1[2];
    int fd2[2];
    // 保存结果
    int fd[0];
    // CPU核的mask
    cpu_set_t mask;
    CPU_ZERO(&mask);
    // 设置第一个CPU
    CPU_SET(0, &mask);
    // 保证子进程先运行
    const struct sched_param param = {sched_get_priority_min(SCHED_FIFO)};
    // 时间
    struct timeval start;
    struct timeval end;

    // 管道1
    int r1 = pipe(fd1);
    if (r1 < 0) {
        printf("pipe failed\n");
        exit(1);
    }
    // 管道2
    int r2 = pipe(fd2);
    if (r2 < 0) {
        printf("pipe failed\n");
        exit(1);
    }
    // 保存结果
    int r = pipe(fd);
    if (r < 0) {
        printf("pipe failed\n");
        exit(1);
    }

    int rc = fork();
    if (rc < 0) {
        fprintf(stderr, "fork failed\n");
        exit(1);
    } else if (rc == 0) {
        // 子进程
        // 保证子进程先运行, 需要sudo
        if (sched_setscheduler(getpid(), SCHED_FIFO, &param) == -1) {
            printf("set scheduler failed!\n");
            exit(1);
        }
        // 设置CPU
        if (sched_setaffinity(getpid(), sizeof(cpu_set_t), &mask) == -1) {
            printf("Set child process affinity failed!\n");
            exit(1);
        }
        // 关闭读端
        close(fd[0]);
        // 得到起始时间
        gettimeofday(&start, NULL);
        for (int i = 0; i < N; i++) {
            // 子进程向pipe1写入
            write(fd1[1], "a", 1);
            // 子进程从pipe2读取
            read(fd2[0], NULL, 0);
        }
        // 传递给父进程
        write(fd[1], &start, sizeof(start));
        // 关闭写端
        close(fd[1]);
    } else {
        // 父进程
        // 设置CPU
        if (sched_setaffinity(getpid(), sizeof(cpu_set_t), &mask) == -1) {
            printf("Set parent process affinity failed!\n");
            exit(1);
        }
        // 关闭写端
        close(fd[1]);
        for (int i = 0; i < N; i++) {
            // 父进程向pipe2写入
            write(fd2[1], "a", 1);
            // 父进程从pipe1读取
            read(fd1[0], NULL, 0);
        }
        gettimeofday(&end, NULL);
        // 获得子进程中的时间
        read(fd[0], &start, sizeof(start));
        double t = end.tv_sec * 1000000.0 + end.tv_usec - start.tv_sec * 1000000.0 - start.tv_usec;
        printf("上下文切换执行时间为:%f微秒\n", t / N);
        // 关闭读端
        close(fd[0]);
    }
    
    return 0;
}

运行:

╰─○ make cont_switch && sudo ./cont_switch
make: 'cont_switch' is up to date.
上下文切换执行时间为:2.055700微秒