OS-Pre-Lab

Dracary 发布于 2026-03-10 487 次阅读


比较基础的Pre教程,主要分为四个部分

  • Git基本操作
  • c-exercise
  • make-exercise
  • mips-exercise

Git基本操作

在进入正式的练习之前,首先是前置知识点,我们需要在任务列表处初始化分支后,在评测机上通过命令行进入工作目录:

cd ~/12345678(你的学号)

此时应该会显示 /home/git/12345678(你的学号)

确认所处位置无误以后,可以使用git branch -a来查看你所处的分支和远端分支,正常情况下会显示:

*protected
remotes/origin/HEAD -> origin/protected
remotes/origin/protected

其中*protected代表你处于的本地分支是protectedremotes/origin/protected代表远端origin仓库下存在一个protected分支,而remotes/origin/HEAD -> origin/protected代表远端的默认分支是origin/protected,意思就是你使用git clone repo.git的时候,本地默认处于protected分支,这是系统根据origin/HEAD为你创建的本地分支

现在你需要进行本次实验,要做的就是拉取远端新初始化的分支,使用git fetch拉取所有远端分支,以c-exercise为例,此时再使用git branch -a你应该会看到:

*protected
remotes/origin/HEAD -> origin/protected
remotes/origin/protected
remotes/origin/c-exercise

现在我们想要在c-exercise分支上面开发,我们可以使用:

git checkout -b c-exercise origin/c-exercise

此时你再次使用git branch -a确认你会看到:

protected
*c-exercise
remotes/origin/HEAD -> origin/protected
remotes/origin/protected
remotes/origin/c-exercise

如果你不小心创建了不想要的分支,可以使用git branch -D branch-name来删除你不需要的本地分支

此外,你也可以使用git checkout -b branch-name来创建本地的新分支,如果此时你需要同步远端分支的内容,可以使用git reset --hard origin/branch来完成,以c-exercise为例:

git checkout -b c-exercise
git reset --hard origin/make-exercise

如果需要切换不同的分支可以使用

git checkout branchName

之后你就可以进行相应的开发了,后续就不再详细介绍该过程

c-exercise

本题的主要任务就是使用C语言实现一些基本的库函数

第一部分是实现size_t strlen(const char *s),遍历字符串直到遇'\0'即可

size_t strlen(const char *s) {
    size_t i=0;
    while(*(s+i)!='\0')i++;
    return i;
}

第二部分则是实现char *strcat(char *dst, const char *src),可以先判断两个字符串是不是NULL进行处理,接着把dst移动至末尾'\0',然后执行*dst=*src即可,别忘了最后加上'\0'

char *strcat(char *dst, const char *src) {
    if (dst == NULL || src == NULL)
        return dst;
    char *res = dst;
    while (*dst != '\0')
        dst++;
    while (*src != '\0') {
        *dst = *src;
        dst++;
        src++;
    }
    *dst = '\0';
    return res;
}

第三部分则是实现char *strncat(char *dst, const char *src, size_t n),相比于上一题,只要在while循环加入n的条件控制即可

char *strncat(char *dst, const char *src, size_t n){
    if(dst==NULL||src==NULL)return dst;
    char *res = dst;
    while(*dst!='\0')dst++;
    while(*src!='\0'&&n--){
        *dst++=*src++;
    }
    *dst='\0';
    return res;
}

第四部分则是实现char* strsep(char** stringp, const char* delim),题目初看可能有些不好懂,意思其实就是delim所指向的字符串是一个字符合集,这里面的每个字符都可以被当作分隔符,你需要在stringp中查找这些字符,把找到的第一个字符合集中的字符置'\0'并把源字符串指针后移一位,然后返回被分割的原字符串的指针头,所以初始时需要保存stringp的指针头,之后我是使用了两层while进行查找

char* strsep(char** stringp, const char* delim){
    if(*stringp==NULL)return NULL;
    char *start = *stringp;
    int flag=0;
    while(**stringp!='\0'){
        char *destr = delim;
        while(*destr!='\0'){
            if(*destr==**stringp){
                **stringp='\0';
                (*stringp)++;
                flag=1;
                break;
            }
            destr++;    
        }
        if(flag)break;
        (*stringp)++;
    }
    if(!flag){
        *stringp=NULL;
    }                                             
    return start;
}

make-exercise

本题主要任务就是学习Makefile的基本语法

给定代码段如:

all: target
target: target.c
        gcc target.c -o target

如果我们执行make all,它会先去寻找它后面的依赖target(不过依赖也可能不止一个,示例只给出了一种的情况),而target的依赖是target.c是目录下的C语言源码,不用再继续往下找了,执行后续命令行即可

此外,只使用make时,会默认构建第一个目标

明白了这一点,我们就可以完成任务了

题目要求执行make或者make out时利用 calc 计算 case_all,并将结果输出到 out 文件中

所以我们Makefile的第一个目标应该是out,它依赖于calccase_all,于是我们得到

out: calc case_all
    ./calc < case_all > out 
case_add: casegen
    ./casegen add 100 > case_add
case_sub: casegen
    ./casegen sub 100 > case_sub
case_mul: casegen
    ./casegen mul 100 > case_mul
case_div: casegen
    ./casegen div 100 > case_div
case_all: case_add case_sub case_mul case_div
    cat case_add > case_all
    cat case_sub >> case_all
    cat case_mul >> case_all
    cat case_div >> case_all
calc: calc.c
    gcc calc.c -o calc
casegen: casegen.c
    gcc casegen.c -o casegen

最后补上clean

clean:
    rm -f out calc casegen case_* *.o   

值得注意的是作业给出的文件开头

.PHONY: clean

这是一个特殊的目标,有了它以后我们每次make clean系统就不会去目录下寻找有没有clean这个文件,而是直接执行

mips-exercise

本题主要利用汇编的知识完成代码补全

先看第一部分

str:
.asciiz "Hello World\n"  # Null-terminated string "Hello World" stored at label 'str'
.align 2 # align to 4-byte boundary (2^2) now at 0x1010 based on 0x1000

字符串"Hello World\n"占了13个字节,此时使用.align 2代表对齐2的2次方,也即4字节,所以此时地址偏移了16字节

.byte 3
.align x
.word 7,8,9

接着题目要求3和7是直接相邻的,但是3此时的地址偏移量是17,而word会自动对齐4字节,也就是会在3后面补齐3个0,为了阻止系统自动补0,我们使用.align 0对齐2的0次方即1,从而阻止后面的word补0

la      a0, str
addiu sp, sp, x
/*Nonleaf function 'print_str'*/
jal     print_str

函数print_str需要调用其他函数,所以需要栈空间来保存返回地址$ra

而即使原本只要4字节的空间,栈空间要对齐8字节,所以补齐为8,故x=-8

addiu sp, sp, x 

返回后恢复栈空间原位置即可x=8

/* Set the first four parameters. */
li      a0, 0
li      a1, 1
li      a2, 2
li      a3, 3
/* use 'addiu  sp, sp, <x>' to push a proper-sized frame onto the stack for Nonleaf function 'hello'. */
/* Your code here. (4/6) */
addiu sp, sp, -24 
lw      t1, var 
li      t2, 5
/* use 'sw  t1, <x>(sp)' to store t1 at the proper place of the stack */
/* so that t1 is 5th argument of function hello. */
/* Your code here. (5/6) */
sw t1, 16(sp)
/* use 'sw  t2, <x>(sp)' to store t2 at the proper place of the stack */
/* so that t2 is 6th argument of function hello. */
/* Your code here. (6/6) */
sw t2, 20(sp)
/* use 'j' to call the function 'hello', we use 'j' instead of 'jal' because 'hello' is 'noreturn' */
j       hello

对于第4问,GPT老师给出的解释是参数量大于4,所以需要预留至少16字节的空间,加上要保存另外两个参数,所以是24字节

第五、六问则是因为栈是向下生长的,越往后的参数在越上面

这里是紫罗兰的永恒花园
最后更新于 2026-03-22