西南交大2022操作系统实验笔记

Posted on   林安静  阅读(879)  评论(0编辑  收藏  举报

记录下,问题挺多的,但能用

 

# 操作系统实验

1.观察Linux行为

实验目的

Ø通过本实验,了解LINUX系统的组织和行为,观察各种存储系统状态信息的内核变量

实验要求

Ø理解Linux操作系统功能和结构,重点掌握/proc文件

Ø熟悉gcc、gdb、codeblocks或者其它IDE,比较Linux下C程序编译、调试技术

Ø使用给定的程序观察cpuinfo、version、stat、uptime

Ø编程实现对meminfo、loadavg、interrupts、filesystem的观测

test1代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

FILE *fp = NULL;
char string[1024];

void meminfo();

void meminfo_sys();

void loadavg();

void loadavg_sys();

void interrupts();

void interrupts_sys();

void filesystems();

void filesystems_sys();

int main(int argc, char *argv[]) {
    meminfo_sys();
    loadavg_sys();
    interrupts_sys();
    filesystems_sys();
    /*
     * 以下为通过文件流的方式输出
     * */
//    meminfo();
//    loadavg();
//    interrupts();
//    filesystems();
    return 0;
}

void meminfo() {
    printf("---------------------\nmeminfo:\n");
    fp = fopen("/proc/meminfo", "r");
    if (fp == NULL) {
        printf("error");
        return;
    }
    while (fgets(string, 512, fp)) {
        printf("%s", string);
    }
    printf("---------------------\n");
    fclose(fp);
}


void loadavg() {
    printf("---------------------\nloadavg:\n");
    fp = fopen("/proc/loadavg", "r");
    if (fp == NULL) {
        printf("error");
        return;
    }
    while (fgets(string, 512, fp)) {
        printf("%s", string);
    }
    printf("---------------------\n");
    fclose(fp);
}


void interrupts() {
    printf("---------------------\ninterrupts:\n");
    fp = fopen("/proc/interrupts", "r");
    if (fp == NULL) {
        printf("error");
        return;
    }
    while (fgets(string, 512, fp)) {
        printf("%s", string);
    }
    printf("---------------------\n");
    fclose(fp);
}

void filesystems() {
    printf("---------------------\nfilesystems:\n");
    fp = fopen("/proc/filesystems", "r");
    if (fp == NULL) {
        printf("error");
        return;
    }
    while (fgets(string, 512, fp)) {
        printf("%s", string);
    }
    printf("---------------------\n");
    fclose(fp);
}

void meminfo_sys() {
    printf("---------------------\nmeminfo:\n");
    system("cat /proc/meminfo");
    printf("---------------------\n");
}

void loadavg_sys() {
    printf("---------------------\nloadavg:\n");
    system("cat /proc/loadavg");
    printf("---------------------\n");
}

void interrupts_sys() {
    printf("---------------------\ninterrupts:\n");
    system("cat /proc/interrupts");
    printf("---------------------\n");
}

void filesystems_sys() {
    printf("---------------------\nfilesystems:\n");
    system("cat /proc/filesystems");
    printf("---------------------\n");
}

效果图:

image
image


2.软中断通信

实验目的

本实验要求学生了解什么是信号,掌握软中断的基本原理;掌握中断信号的使用、进程的创建以及系统计时器的使用。

通过对本实验的学习,学生能够学会进程的创建方法,更能加深对Linux中的信号机制的认识,并会使用软中断信号来实现进程间的通信。

实验要求

1.学生根据test2.c理解以下内容:

父进程接受到软中断信号(SIGQUIT)后,向其子进程分别发送整数值为16的软中断信号,子进程获得对应软中断信号后,终止运行;

父进程调用wait()函数等待子进程终止,然后自我终止。

由父进程创建一个子进程,通过终端输入Crtl+\组合键向父进程发送SIGQUIT软中断信号发送给父进程;

2.编程实现以下内容:

由一个父进程创建两个子进程,之后通过终端输入Crtl+\组合键向父进程发送软中断信号,终止两个子进程以及父进程。

由一个父进程创建一个子进程,之后该子进程再创建一个孙进程,通过终端输入Crtl+\组合键向父进程发送软中断信号,依次终止孙进程、子进程、父进程。

test2-2代码
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include <sys/wait.h>

void waiting();

void stop();

int wait_mark;

int main() {
    int p1, p2;
    while ((p1 = fork()) == -1);
    if (p1 > 0) //if p1 is parent process
    {
        lockf(1, 1, 0);
        printf("parent process is %d \n", getpid());
        lockf(1, 0, 0);

        wait_mark = 1;
//        signal(SIGQUIT, stop);
//        waiting();
//        kill(p1, 16); //send signal 16 to end the process p1
        while ((p2 = fork()) == -1);
        if (p2 > 0) //if p2 is parent process
        {
//            lockf(1, 1, 0);
//            printf("parent process is %d \n", getpid());
//            lockf(1, 0, 0);

            wait_mark = 1;
            signal(SIGQUIT, stop);
            waiting();
            kill(p2, 16); //send signal 16 to end the process p2
            kill(p1, 16); //send signal 16 to end the process p2

            wait(0); //waiting for the ending of p2

        } else //if p2 is child process
        {
            lockf(1, 1, 0);
            printf("child process %d is created by the parent %d \n", getpid(), getppid());
            lockf(1, 0, 0);

            signal(SIGQUIT, SIG_IGN);
            wait_mark = 1;
            signal(16, stop);
            waiting();

            lockf(1, 1, 0);
            printf("child process %d is killed by parent %d \n", getpid(), getppid());
            lockf(1, 0, 0);

            exit(0); // p2 quit
        }
        
        lockf(1, 1, 0);
        printf("parent process is killed!\n");
        lockf(1, 0, 0);

        exit(0); //quit from the parent process

    } else //if p1 is child process
    {
        lockf(1, 1, 0);
        printf("child process %d is created by the parent %d \n", getpid(), getppid());
        lockf(1, 0, 0);

        signal(SIGQUIT, SIG_IGN);
        wait_mark = 1;
        signal(16, stop);
        waiting();

        lockf(1, 1, 0);
        printf("child process %d is killed by parent %d \n", getpid(), getppid());
        lockf(1, 0, 0);

        exit(0); // p1 quit
    }

    return 0;
}

void waiting() {
    while (wait_mark != 0);
}

void stop() {
    wait_mark = 0;
}

效果图:

image

test2-3代码
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include <sys/wait.h>

void waiting();

void stop();

int wait_mark;

int main() {
    int p1, p2;
    while ((p1 = fork()) == -1);
    if (p1 > 0) //if p1 is parent process
    {
        lockf(1, 1, 0);
        printf("parent process is %d \n", getpid());
        lockf(1, 0, 0);

        wait_mark = 1;
        signal(SIGQUIT, stop);
        waiting();
        kill(p1, 16); //send signal 16 to end the process p1
        wait(0); //waiting for the ending of p1

        lockf(1, 1, 0);
        printf("parent process is killed!\n");
        lockf(1, 0, 0);

        exit(0); //quit from the parent process

    } else //if p1 is child process
    {
        while ((p2 = fork()) == -1);
        if (p2 > 0) //if p2 is parent process
        {
            lockf(1, 1, 0);
            printf("child process %d is created by the parent %d \n", getpid(), getppid());
            lockf(1, 0, 0);
            signal(SIGQUIT, SIG_IGN);
            wait_mark = 1;
            signal(16, stop);
            waiting();
//            signal(SIGQUIT, stop);
//            waiting();
            kill(p2, 16); //send signal 16 to end the process p2
            wait(0); //waiting for the ending of p2
            lockf(1, 1, 0);
            printf("child process %d is killed by parent %d \n", getpid(), getppid());
            lockf(1, 0, 0);
            exit(0); // p1 quit
        } else //if p2 is child process
        {
            lockf(1, 1, 0);
            printf("grandson process %d is created by the parent %d \n", getpid(), getppid());
            lockf(1, 0, 0);

            signal(SIGQUIT, SIG_IGN);
            wait_mark = 1;
            signal(16, stop);
            waiting();

            lockf(1, 1, 0);
            printf("grandson process %d is killed by parent %d \n", getpid(), getppid());
            lockf(1, 0, 0);
            exit(0); // p2 quit
        }
    }
    return 0;
}

void waiting() {
    while (wait_mark != 0);
}

void stop() {
    wait_mark = 0;
}

效果图:

image


3.进程调度实验

实验目的

撑握进程调度的概念

学习Linux内核源码编写风格,重点理解进程调度策略算法,包括FCFS、RR、SRT、Feedback的调度算法。

实验要求

本实验要求在Linux的用户态下编程实现进程调度策略算法的模拟程序

实现以下调度算法:

  1. FCFS(先到先服务)

  2. RR(轮循)

test4代码
#include<stdio.h>
#include<time.h>
#include<malloc.h>
#include<stdlib.h>

#define  NR_TASKS             64   //系统支持的进程个数
#define  TASK_RUNNING          0   //就绪态
#define  TASK_UNINTERRUPTIBLE  2   //不可中断的睡眠状态
#define  TASK_ZOMBIE           3   //僵死态

//进程表项
struct task_struct{
    long pid;        //进程号
    long state;      //进程运行状态
    long priority;   //优先数
    long counter;    //进程剩余时间片
    long start_time;  //进程开始时间
    long excute_time; //进程执行时间
};

// 初始化一个全局结构体变量init_task,赋值风格是Linux内核风格
struct task_struct init_task = {
    .pid = 0,
    .state = 0,
    .priority = 0,
    .counter = 0,
    .start_time = 0,
    .excute_time = 0
};

struct task_struct *current = &init_task; // 一个进程指针,初始化时指向init_task
unsigned long volatile jiffies = 0; //系统滴答数 (volatile会严格规定编译器,防止优化过度,如偷懒读取寄存器而不重新读内存)
struct task_struct* task[NR_TASKS] = {&init_task,}; //进程指针数组
#define  FIRST_TASK    task[0]
#define  LAST_TASK     task[NR_TASKS-1]

struct run_q {       //进程就绪队列
    struct task_struct *data;
    struct run_q *next;
};
struct run_q *head=NULL,*end=NULL,*r_temp;

#define  N_PROCESS             5   //进程个数
#define  MAX_RUNTIME         100   //最长运行时间
int process[N_PROCESS][2]={{0,3},{2,6},{4,4},{6,5},{8,2}};//进程初始值
int totalExcuteTime = 0;             //cpu总的运行时间
int runState[N_PROCESS][MAX_RUNTIME] = {0};  //进程运行状态的记录

void checkProcessCome();  //判断是否有进程到达,如果有则创建进程
void pause();             //0号进程的运行体
void schedule_f();        //FCFS调度程序
void schedule_r();        //RR调度程序
void switch_to(int pid);  //进程切换
void init();              //基于优先级调度器的初始化
void run(int pid);        //普通进程的运行体
void myfork(int pid);     //进程创建
void delete(int pid);     //进程清除

typedef void funtype(void);
funtype *schedule = NULL;

int main(int argc,char **argv)
{
    int i,j;
    int choice;

    while(1){
        printf("please choice the schedule measure:\n");
        printf("f : 先到先服务的调度策略\n");
        printf("r : 轮循的调度策略\n");
        printf("q : 退出\n");
        printf("choice = ");
        choice = getchar();
        if(choice == '\n')
            choice = getchar();
        switch(choice){
            case 'f': schedule = schedule_f;break;
            case 'r': schedule = schedule_r;break;
            case 'q': return 0;
            default : {
                schedule = NULL;
                printf("please input the true symbol(p or f or r)!\n\n");
                continue;
            }
        }
        printf("task id   Start  Service time\n"); // 注意:process已经初始化了,只是个二维数组
        for(i=0;i<N_PROCESS;i++){
            printf("task %2d: %6d %6d\n",i+1,process[i][0],process[i][1]);
            totalExcuteTime+=process[i][1]; // 累加进程的CPU服务总时间
        }

		//初始化
        init();

        //打印进程调度情况
		printf("Scheduling result\n");
        printf("time   : 0%*c%d\n",totalExcuteTime-2,' ',totalExcuteTime);
        for(i=0;i<N_PROCESS;i++){
            printf("task %2d: ",i+1);
            for(j=0;j<totalExcuteTime;j++){
                if(runState[i][j]==1) printf("#");
                else printf(" ");
                runState[i][j] = 0;
            }
            printf("\n");
        }
        while((head!=NULL)&&(head!=end)){
            r_temp = head;
            head = head->next;
            free(r_temp);
        }
        if(head){
            free(head);
            head = NULL;
            end = NULL;
        }
        current = &init_task;
        jiffies = 0;
        totalExcuteTime = 0;
        printf("\n");
    }
    return 0;
}

void schedule_f(){ //本质上应该用到就绪队列,但是本算法进行了简化
    int i,next,c;
    struct task_struct **p;
    c = 9999;
    next = 0;
    i = NR_TASKS;
    p = &task[NR_TASKS];
    while(--i){
        if(!*--p)
            continue;
        if((*p)->state == TASK_RUNNING && (*p)->start_time < c) // 判断该进程处于就绪态且开始时间可控
            c = (*p)->start_time,next = i; // c值约束当前p指向就绪态且进程有最早的进程创建时间,保证先到先服务 FCFS
    }
    switch_to(next); // next代表最早的可运行进程pid,若非0,则消耗1时间片
}

void schedule_r(){ //用到了就绪队列,循环队列(默认时间片长度1)
    int next;
    next = 0;
    if(current->state != TASK_RUNNING){
        r_temp = head;
        if(head==end){
            head = NULL;
            end = NULL;
        }else{ //若当前指向的进程指针非就绪态且就绪队列非空(即该进程已运行完成,僵尸态),则出队队头
            head = head->next;
            end->next = head;
        }
        free(r_temp);
    }else if(head){ //若当前进程处于就绪态且队头存在,则轮转递进
        head = head->next;
        end = end->next;
    }
    if(head) next = head->data->pid; //next指向队头pid
    switch_to(next);//递归
}

void switch_to(int pid){
    if(pid)
        run(pid); // 进入run递归
    else
        pause(); // pid为初始进程init_task则暂停一时间片时间,即当前时间无进程需要运行
}

void myfork(int pid){
    struct timeval now; // linux的时间库提供的时间变量
    struct run_q *p;
    task[pid] = (struct task_struct*)malloc(sizeof(struct task_struct)); //根据pid分配进程空间
    task[pid]->state = TASK_UNINTERRUPTIBLE; // 创建的默认运行态是不可中断的睡眠态(实质为阻塞态
    task[pid]->pid = pid;
    gettimeofday(&now,0); // 获取当前时间,赋值给now
    srand(now.tv_usec); // 随机化种子 ,usec是微秒变量
    task[pid]->priority = 2 + (int)(4.0f*rand()/(RAND_MAX+1.0f)); // 随机化优先级
    task[pid]->counter = task[pid]->priority; // 随机化优先级,偷懒把优先级变为剩余时间片
    task[pid]->start_time = jiffies;
    task[pid]->excute_time = 0;
    task[pid]->state = TASK_RUNNING; // 调整进程为就绪态,仅等待CPU资源

    p = (struct run_q*)malloc(sizeof(struct run_q)); // 创建就绪队列数据结构
    p->data = task[pid];
    if(head==NULL){ // 入队操作,不含初始进程init_task
        head = end = p;
        head->next = end;
        end->next = head;
    }else{
        end->next = p;
        end = p;
        end->next = head;
    }
}

void delete(int pid){
    free(task[pid]);
}

void checkProcessCome(){
    int i;
    for(i=0;i<N_PROCESS;i++){
    if(process[i][0]==jiffies) // process[i][0]是该进程的到达时间 , 即到达了该时间,创建对应的进程
        myfork(i+1);
    }
}

void init(){
    int i;
    for(i=1;i<NR_TASKS;i++){
        task[i] = NULL; // 初始化进程数组(仅用作记录所有进程)
    }
    checkProcessCome(); // 完成进程创建(实例化),以及就绪队列初始化
    schedule(); // 调用函数指针指向的调度函数
}

void pause(){
    current = task[0];
    jiffies++;
    totalExcuteTime++;
    checkProcessCome();
    schedule();
}

void run(int pid){
    int i;
    current = task[pid];
    runState[pid-1][jiffies] = 1; // 标记该进程在某时间片下的flag
    jiffies++; // 时间片自增
    task[pid]->counter--;
    task[pid]->excute_time++;
    //判断进程是否运行完,如果是则将进程杀死
    if(task[pid]->excute_time==process[pid-1][1]){
        //task[pid]->end_time = jiffies;
        task[pid]->state = TASK_ZOMBIE;
    }
    //判断所有进程是否都运行完,如果是则结束
    if(jiffies>=totalExcuteTime) return;
    checkProcessCome(); // 查询是否抵达进程创建时间,是则创建新进程
    schedule(); // 递归
}

效果图:

image


4.线程同步实验

实验目的

掌握操作系统并发的概念

理解并发中的生产消费者问题

熟悉Linux线程编程机制,掌握线程同步的实现方式

实验要求

完成Linux线程程序,完成利用Linux线程信号量及PV操作实现消费者的同步关系的程序

test3代码
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>

#define TOTAL_NUM 3
#define INIT_NUM 0

sem_t p_sem, c_sem, sh_sem;

void *producer(void *arg);

void *consumer(void *arg);

int n = 0; // 生产的现存面包数目

int main() {
    pthread_t tid_1, tid_2;

    sem_init(&p_sem, 0, TOTAL_NUM - INIT_NUM);
    sem_init(&c_sem, 0, INIT_NUM);
    sem_init(&sh_sem, 0, 1);

    pthread_create(&tid_1, NULL, producer, NULL);
    pthread_create(&tid_2, NULL, consumer, NULL);

    pthread_join(tid_1, NULL);
    pthread_join(tid_2, NULL);

    sem_destroy(&p_sem);
    sem_destroy(&c_sem);
    sem_destroy(&sh_sem);

    return 0;
}

void *producer(void *arg) {
    while (1) {
        printf("生产!!\n");
        sem_wait(&p_sem);
        sem_wait(&sh_sem);
        usleep(1000*1000);
        printf("生产面包:%d\n", ++n);
        sem_post(&sh_sem);
        sem_post(&c_sem);
    }
}

void *consumer(void *arg) {
    while (1) {
        sem_wait(&c_sem);
        sem_wait(&sh_sem);
        usleep(1000*500);
        printf("抢面包-1,剩余:%d\n",--n);
        sem_post(&sh_sem);
        sem_post(&p_sem);
        printf("开吃面包!\n");
    }
}

效果图:

image


5.内存管理实验

实验目的

了解内存管理的概念,掌握分页、分段操作过程

掌握虚拟内存技术的概念,重点理解替换策略

理解选择替换页算法OPT、FIFO、LRU、CLOCK

实验要求

(用户态)在Linux下编程实现虚存页面替换算法的模拟程序。主要包含:

根据用户输入参数,包含进程大小(页数),进程地址(页地址)顺序,页表大小

验证test5.c中FIFO、LRU替换算法。

新建工程replace,编写并运行OPT和CLOCK算法。

test5代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define random(x) (rand() % x)
#define MULTIPLE 3

typedef struct page_s //页的数据结构
{
    int n; // number
    int v; // visit flag
} page;

char *menu[] = {
    "f - FIFO",
    "r - LRU",
    "o - OPT",
    "c - CLOCK",
    "q - QUIT",
    NULL};

int getchoice(char *greet, char *choices[]) //实现菜单选择功能
{
    int chosen = 0;
    int selected;
    char **option;

    do
    {
        printf("Choice: %s\n", greet);
        option = choices;
        while (*option)
        {
            printf("%s\n", *option);
            option++;
        }
        do
        {
            selected = getchar();
        } while (selected == '\n');
        option = choices;
        while (*option)
        {
            if (selected == *option[0])
            {
                chosen = 1;
                break;
            }
            option++;
        }
        if (!chosen)
        {
            printf("Incorrect choice, select again\n");
        }
    } while (!chosen);
    return selected;
}

void buildPageReference(int size, page **reference, page *program)
{
    int i;
    int n;
    printf("Page reference : ");
    for (i = 0; i < size; i++)
    {
        n = random(size / MULTIPLE);
        reference[i] = &program[n]; //随机取一个进程的页放进预分配数组中
        program[n].n = n;
        program[n].v = 0;
        printf("| %d ", n);
    }
    printf("\n");
}

void print(int n, page *frame, int size)
{
    int i;

    printf("no. %d step: ", n);
    for (i = 0; i < size; i++)
    {
        printf("| %d ", frame[i].n);
    }
    printf("\n");
}

int Search(int n, page *list, int size)
{
    int i;
    for (i = 0; i < size; i++)
    {
        if (list[i].n == n)
            return i;
    }
    return -1;
}

int findNext(int n, page **list, int start, int size)
{
    int count = size;
    int i;
    for (i = start; i < size; i++)
    {
        if (list[i]->n == n)
            break;
        else
            count++;
    }
    return count;
}

int findLastMax(page *frame, int size)
{
    int tmp = 0, s, i, j = 0;
    for (i = 0; i < size; i++)
    {
        s = frame[i].v;
        if (s > tmp)
        {
            tmp = s;
            j = i;
        }
    }
    return j;
}

int findLastMin(page *frame, int size)
{
    int tmp = frame[0].v, s, i, j = 0;
    // printf("| %d ", tmp);
    for (i = 1; i < size; i++)
    {
        s = frame[i].v;
        // printf("| %d ", s);
        if (s < tmp)
        {
            tmp = s;
            j = i;
        }
    }
    // printf("\n");
    return j;
}

void fifo(int fsize, page *frame, int rsize, page **pageR)
{
    int i, j = 0, p = 0;
    int f = 0; //记录page fault,缺页中断
    for (i = 0; i < fsize; i++)
        frame[i].n = -1;
    for (i = 0; i < rsize; i++)
    {
        if (Search(pageR[i]->n, frame, fsize) != -1) //三段式,先判断是否已经有这个页,有直接访问,若无再判断是否有新页框,若都没有再开始本算法
            ;
        else if (i < fsize || p < fsize)
        {
            frame[p].n = pageR[i]->n;
            p++;
        }
        else
        {
            frame[j % fsize].n = pageR[i]->n;
            j++; // j维护循环队列指向
            f++;
        }
        print(i, frame, fsize);
    }
    printf("page fault : %d\n", f);
}

void lru(int fsize, page *frame, int rsize, page **pageR)
{
    int i, j, p = 0, q;
    int f = 0; //记录page fault,缺页中断
    for (i = 0; i < fsize; i++)
    {
        frame[i].n = -1;
        frame[i].v = 0;
    }
    for (i = 0; i < rsize; i++)
    {
        for (j = 0; j < fsize; j++)
        {
            if (frame[j].n != -1)
                frame[j].v++; //将访问位自增,等同于增加它的存在时间
        }
        q = Search(pageR[i]->n, frame, fsize);
        if (q != -1) //三段式,先判断是否已经有这个页,有直接访问,若无再判断是否有新页框,若都没有再开始本算法
            frame[q].v = 0;
        else if (i < fsize || p < fsize)
        {
            frame[p].n = pageR[i]->n; //有未使用的页框,直接访问
            p++;
        }
        else
        {
            q = findLastMax(frame, fsize); //普通的顺序查找算法,找出最长的存在时间的页框(用v值比较)
            frame[q].n = pageR[i]->n;
            frame[q].v = 0;
            f++;
        }
        print(i, frame, fsize);
    }
    printf("page fault : %d\n", f);
}

void opt(int fsize, page *frame, int rsize, page **pageR)
{
    int i, j, p = 0, q;
    int f = 0; //记录page fault,缺页中断
    for (i = 0; i < fsize; i++)
    {
        frame[i].n = -1;
        frame[i].v = 0;
    }
    for (i = 0; i < rsize; i++)
    {
        q = Search(pageR[i]->n, frame, fsize);
        if (q != -1) //三段式,先判断是否已经有这个页,有直接访问,若无再判断是否有新页框,若都没有再开始本算法
            ;
        else if (i < fsize || p < fsize)
        {
            frame[p].n = pageR[i]->n; //有未使用的页框,直接访问
            p++;
        }
        else
        {
            for (int j = 0; j < fsize; j++)
            {
                frame[j].v = findNext(frame[j].n, pageR, i, rsize); //迭代更新v值,即计算下次找到该页时的间距
            }
            q = findLastMax(frame, fsize); //普通的顺序查找算法,找出最长的间隔时间的页框(用v值比较)
            frame[q].n = pageR[i]->n;
            f++;
        }
        print(i, frame, fsize);
    }
    printf("page fault : %d\n", f);
}

void clk(int fsize, page *frame, int rsize, page **pageR)
{
    int i, j = 0, p = 0,q;
    int f = 0; //记录page fault,缺页中断
    for (i = 0; i < fsize; i++)
    {
        frame[i].n = -1;
        frame[i].v = 0;
    }
    for (i = 0; i < rsize; i++)
    {
        if (q = Search(pageR[i]->n, frame, fsize) != -1) //三段式,先判断是否已经有这个页,有直接访问,若无再判断是否有新页框,若都没有再开始本算法
            frame[q].v = 1;
        else if (i < fsize || p < fsize)
        {
            frame[p].n = pageR[i]->n;
            frame[p].v = 1;
            j++;
            p++;
        }
        else
        {
            while (1)
            {
                if (frame[j % fsize].v == 0) //类似于FIFO,但是多了1位v,用于在触发缺页中断时标记该页框是否已被访问过
                {
                    frame[j % fsize].n = pageR[i]->n;
                    frame[j % fsize].v = 1;
                    j++; // j维护循环队列指向
                    f++;
                    break;
                }
                else
                {
                    frame[j % fsize].v = 0;
                    j++; // j维护循环队列指向
                }
            }
        }
        print(i, frame, fsize);
    }
    printf("page fault : %d\n", f);
}

int main()
{
    int choice = 0;
    int logSize; //逻辑内存大小
    int phySize; //物理内存大小(实际的页框)
    page *program;
    page **pageR;
    page *frame;
    int prSize;

    srand((int)time(0));
    printf("Enter number of pages in program: ");
    scanf("%d", &logSize);

    printf("Enter number of frames in physical memory: ");
    scanf("%d", &phySize);

    program = (page *)malloc(sizeof(int) * 2 * logSize);
    frame = (page *)malloc(sizeof(int) * 2 * phySize);

    prSize = logSize * MULTIPLE;                     //预分配逻辑内存大小
    pageR = (page **)malloc(sizeof(int *) * prSize); //预分配逻辑内存
    buildPageReference(prSize, pageR, program);      //初始化预分配逻辑内存的数组

    do
    {
        choice = getchoice("Please select an action", menu);
        printf("You have chosen: %c\n", choice);
        switch (choice)
        {
        case 'f':
            fifo(phySize, frame, prSize, pageR);
            break;
        case 'r':
            lru(phySize, frame, prSize, pageR);
            break;
        case 'o':
            opt(phySize, frame, prSize, pageR);
            break;
        case 'c':
            clk(phySize, frame, prSize, pageR);
            break;
        default:
            break;
        }
    } while (choice != 'q');
    exit(0);
}

效果展示:

image

image


6.系统调用

实验目的

学习如何产生一个系统调用,以及怎样通过往内核中增加一个新函数,从而在内核空间中实现对用户空间的读/写。学习重建内核。

实验要求

(1)设计并实现一个新的系统调用pedagogictime() ,该函数通过使用一个引用参数的调用返回当前的系统时间。

(2)编写一个用户空间程序来测试pedagogictime()。

由于我用的Ubuntu20.04,kernel5.13已经和PPT要求差别过大,没有do_gettimeofday()函数,故更改了实验步骤:

(1) 该系统调用有1个整型参数或字符串型参数,接收输入自己的学号;

(2) 若参数为奇数,则返回自己学号的最后5位。如你的学号为16130120101 ,则返回20101;

(3) 若参数为偶数,则返回自己的学号的最后6位。如你的学号为16130120102 ,则返回120102 。
test6代码
 //内核代码:
SYSCALL_DEFINE1(mysyscall,unsigned long long,num)
{
	int mod;
	unsigned long long result,x;
	long m;
	x=2;
	result=num;
	mod=do_div(result,x);
	if(mod)
	{
		x=100000;
		m=do_div(num,x);
		printk("result is %05ld\n",m);
		return m;
	}
	else
	{
		x=1000000;
		m=do_div(num,x);
		printk("result is %06ld\n",m);
		return m;
	}
}

//测试代码
#include<linux/kernel.h>
#include<sys/syscall.h>
#include<unistd.h>
#include<math.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
int main(){
    printf("result is %ld\n",syscall(335,20212123766));
    return 0;
}

效果图:

image

image


7.系统缺页次数统计实验

实验目的

理解内存管理中缺页的概念

综合运用实验1, 实验5, 实验6中/proc文件系统、内存管理、系统调用、内核编译的知识

掌握向/proc文件系统中增加文件的方法

掌握Linux内核模块的概念和操作方法

实验要求

通过在内核中自建变量并利用/proc文件系统作为中介的方法来统计系统缺页次数

由于我用的Ubuntu20.04,kernel5.13已经和PPT要求差别过大,没有do_page_fault函数,故实验失败


操作系统实验解析

实验1

  1. Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。

  2. cat命令的用途是连接文件或者标准输入并打印。这个命令常用来显示文件内容,或者将几个文件拼接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用。

实验2

fork()函数调用后,父进程会接收返回值子进程PID,子进程则返回0,失败返回-1

test2-2

  1. 在while循环中调用fork生成子进程p1(不断循环直到创建成功)

  2. (1)判断若是父进程,进入第一段代码块,对打印资源加锁,自旋锁flag置1(n = 25)。继续判断并执行1,接受ctrl\信号输入,接收到则执行stop函数( flag置0)。自旋等待。kill函数向两个子进程发送16信号。等待子进程退出。打印,退出n=63。

    (2)若是子进程进入,则调至第二段,对打印资源加锁,注册signal函数,判断ctrl\信号输入。若接收到了信号(无动作),则自旋锁flag置1 。注册接受自定义信号量16,接收到则执行stop函数( flag置0)。自旋等待,直到接受自定义信号量16再释放,最后退出n=81。

    signal函数,参数1为信号量,参数2为接受信号后行为

    特别地,子进程的第一个signal函数需要先屏蔽sigquit信号,因为ctrl\信号属于quit信号,如果不屏蔽,会被用户直接杀死,而不是被父进程杀死

实验3

  1. 主程序开始,读取输入流的值,根据选择赋予函数指针schedule值(调用哪个调度算法函数)
  2. 打印已预设的process进程信息,包含开始时间戳与作业完成需要的时间片数量;累加所需时间得出总运行时间 totalExcuteTime
  3. 进入初始化函数init n=93,将作业数组task初始化置空
  4. 进入函数checkProcessCome 实例化进程创建,以及就绪队列初始化 n = 213
  5. 遍历并检查滴答是否到达某个进程的初试时间,是则调用myfork函数传入参数pid并创建实例进程 n=204
  6. 进入myfork,为该pid的进程分配内存空间并放入task数组存放,随机分配优先级、剩余时间片 ;完成实例化
  7. 创建就绪队列,初始化。退出myfork
  8. 退出checkProcessCome ,回到init调用调度算法的函数指针。f对应9. FCFS算法,r对应10.Round Robin 轮转算法
  9. FCFS:从task数组尾部反向遍历,找到处于就绪态且开始时间最早的进程,得到其pid,传入switch_to函数进一步运行,调度
  10. Round Robin:若当前进程current非就绪态;若队列为空,则将头尾置空;若不为空(代表队列该进程已运行完成,处于僵尸态),则从队列删除该进程。若当前进程current是就绪态,则轮转递进。传入switch_to函数进一步运行,调度
  11. 进入switch_to,若pid非空,传入pid并进入run函数模拟运行并进一步调度;若为空则进入pause函数
  12. 若进入run函数,将该pid进程标记为current,标记该进程在某时间片下的flag二维数组runState,时间片自增,进程执行进程时间自增,判断进程是否运行完,如果是则将进程杀死,判断所有进程是否都运行完,如果是则结束,否则继续查询是否抵达进程创建时间,是则创建新进程,递归调度
  13. 若进入pause,单纯的自增时间片,再查询是否抵达进程创建时间,递归调度

实验4

  1. 调用linux内核的信号量初始化函数完成信号量p,c,sh的初始化,p=3 c=0 sh=1,p、c同步缓冲区资源,sh完成互斥锁
  2. 创建两条线程运行函数对应生产者和消费者,pthread_join表示该线程必须等待指定线程结束后才能继续,否则挂起并线程阻塞
  3. 在生产者线程,每次生产先将psem执行p操作,即占用一个缓冲区块,再加互斥锁防止临界区冲突完成生产,完成后v操作释放csem
  4. 在消费者线程,每次消费前对csem执行p操作,即吃面包释放了一个缓冲区块,再加互斥锁防止临界区冲突完成消费,完成后v操作释放psem

实验5

prSize:预分配逻辑内存的大小,pageR:预分配的逻辑内存,program对应逻辑页,页框对应物理实在的内存

若逻辑页未命中已有的页框,则示为触发一次pagefault

  1. 在主函数分配内存空间,进入buildPageReference函数完成page引用的初始化构建
  2. 进入buildPageReference,用随机值赋值完成page引用数组的初始化
  3. 循环给出提示选择调用的内存置换算法
  • FIFO:三段式,先判断是否已经有这个页,有则直接访问,若无再判断是否有新页框,若都没有再开始本算法;在循环队列入队并赋值
  • LRU:least recently used,最近最少使用算法,将.v属性当做存在时间的变量,三段式,先判断是否已经有这个页,有直接访问,若无再判断是否有新页框,若都没有再开始本算法;顺序查找出v最大的页框,将其置换
  • OPT:Optimal 最佳算法,三段式,先判断是否已经有这个页,有直接访问,若无再判断是否有新页框,若都没有再开始本算法;循环迭代更新v值,即计算下次找到该页时的间距(此处用v表示),迭代本轮之后,再顺序查找出v最大的页框,将其置换
  • Clock:三段式,先判断是否已经有这个页,有直接访问,若无再判断是否有新页框,若都没有再开始本算法;类似于FIFO,但是多了1位v,用于在触发缺页中断时标记该页框是否已被访问过,v为0则可置换,否则v置为0(只有这种情况)并跳过该页框。

实验六

  1. 先下载内核(要合适自己的linux,不能版本差距过大),解压(Ubuntu增加一个系统调用(20.04)西电软工OS实验专题二_李黎玖的博客-CSDN博客_ubuntu添加系统调用

  2. cd arch/x86/entry/syscalls

  3. kate syscall_64.tbl编辑syscall系统调用表

  4. 注意该表对应的用户可自定义的号码段,不要搞错;添加自己的系统调用号和名字

  5. 添加系统调用声明:

    cd /usr/src/linux-5.15.40 kate include/linux/syscalls.h

  6. 添加:asmlinkage long sys_mysyscall(unsigned long long);//sys_后面的函数名要和上面的对应

  7. 再添加系统调用的定义:kate kernel/sys.c,写上自己的函数:

     //内核代码:
    SYSCALL_DEFINE1(mysyscall,unsigned long long,num)
    {
    	int mod;
    	unsigned long long result,x;
    	long m;
    	x=2;
    	result=num;
    	mod=do_div(result,x);
    	if(mod)
    	{
    		x=100000;
    		m=do_div(num,x);
    		printk("result is %05ld\n",m);
    		return m;
    	}
    	else
    	{
    		x=1000000;
    		m=do_div(num,x);
    		printk("result is %06ld\n",m);
    		return m;
    	}
    }
    
    1. 保存后退出,之后就是内核编译并加载在系统启动grub中,重启长按shift切换该内核

    2. 运行 (运行sudo dmesg可查看内核调用情况)

      #include<linux/kernel.h>
      #include<sys/syscall.h>
      #include<unistd.h>
      #include<math.h>
      #include<stdio.h>
      #include<stdlib.h>
      #include<sys/types.h>
      int main(){
         printf("result is %ld\n",syscall(335,20212123766));
         return 0;
      }
      
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示