Lab0实验报告

思考题

Thinking 0.1

题面

1
2
3
4
5
6
7
思考下列有关 Git 的问题:
• 在前述已初始化的 ~/learnGit 目录下,创建一个名为 README.txt 的文件。执行命令 git status > Untracked.txt(其中的 > 为输出重定向,我们将在 0.6.3 中详细介绍)。
• 在 README.txt 文件中添加任意文件内容,然后使用 add 命令,再执行命令 git status > Stage.txt。
• 提交 README.txt,并在提交说明里写入自己的学号。
• 执行命令 cat Untracked.txt 和 cat Stage.txt,对比两次运行的结果,体会README.txt 两次所处位置的不同。
• 修改 README.txt 文件,再执行命令 git status > Modified.txt。
• 执行命令 cat Modified.txt,观察其结果和第一次执行 add 命令之前的 status 是否一样,并思考原因。

回答

1.cat Untracked.txtcat Stage.txt命令输出内容的区别在于前者显示README.txt文件未跟踪,而后者则显示该文件已暂存需要提交,区别于README.txt文件的状态。同时由于在git add README.txtgit commit -m “<message>”这之间创建了Stage.txt文件,所以在前后者显示的输出中都有Untracked.txt未被跟踪,而后者此外还有Stage.txt文件未被跟踪

Untracked.txt文件内容如下:

Untracked.txt

Stage.txt文件内容如下:

Stage.txt

2.cat Modified.txt命令的输出与第一次add之前的输出不一样,至于原因,我认为是由于修改后,该文件版本同最近一次提交的版本中的并不相同了,但是这个文件曾经被git add进入暂存区,也就是被Git跟踪过了,因此该文件被修改后处于的是被修改的状态而非未跟踪状态,但是根据Git提供的提示,仍需要通过git add <filename>命令来更新暂存区文件

Modified.txt文件内容如下:

Modified.txt

Thinking 0.2

题面

1
仔细看看0.10,思考一下箭头中的 add the file 、stage the file 和 commit 分别对应的是 Git 里的哪些命令呢?

回答

Git中的四种状态转换关系

(摘自Git - 记录每次更新到仓库

图中的Add the file代表了git add <filename>命令表示将未跟踪的文件添加到暂存区中,Stage the file也是git add <filename>命令表示将处于修改状态的文件更新至暂存区中,commitgit commit -m "<message>"命令表示将暂存区中的文件提交到HEAD区(也就是最近的一次提交)中。

Thinking 0.3

题面

1
2
3
4
思考下列问题:
1. 代码文件 print.c 被错误删除时,应当使用什么命令将其恢复?
2. 代码文件 print.c 被错误删除后,执行了 git rm print.c 命令,此时应当使用什么命令将其恢复?
3. 无关文件 hello.txt 已经被添加到暂存区时,如何在不删除此文件的前提下将其移出暂存区?

回答

1.假如print.c文件并未被跟踪,被错误删除后,将无法采用命令来恢复。假如print.c被跟踪,也就是被放入暂存区或者是被提交到HEAD后,可以使用git restore print.c丢弃在工作区中对于print.c文件的修改,将print.c重新恢复回来。本质是将暂存区的版本重新赋给工作区

2.同1,若print.c未被跟踪,错误删除时就不可再用命令来恢复。若被跟踪,则可以先使用git reset HEAD print.c撤销对暂存区的修改,也就是恢复到仅在工作区删除print.c的状态,然后再使用git restore print.c将暂存区赋给工作区,恢复print.c文件。

3.可以采用git rm --cached hello.txt,将该文件从暂存区删除但并没有在工作区中删除,仅起到移除的作用

Thinking 0.4

题面

Git 的问题:
1
2
3
4
5
6
7
8
9
10
11
12
13
 找到在 /home/22xxxxxx/learnGit 下刚刚创建的 README.txt 文件,若不存在则新建该文件。

• 在文件里加入 Testing **1**,git add,git commit,提交说明记为 **1**。

• 模仿上述做法,把 1 分别改为 2 和 3,再提交两次。

• 使用 git log 命令查看提交日志,看是否已经有三次提交,记下提交说明为 3 的哈希值*a*。

• 进行版本回退。执行命令 git reset --hard HEAD^ 后,再执行 git log,观察其变化。

• 找到提交说明为 1 的哈希值,执行命令 git reset --hard <hash> 后,再执行 git log,观察其变化。

• 现在已经回到了旧版本,为了再次回到新版本,执行 git reset --hard <hash>,再执行 git log,观察其变化。

回答

1.使用git reset --hard HEAD^HEAD会回退到前一个git commit之前的状态,也就是回退到上一个版本,此时使用git log会显示当前处于第二次git commit后的状态。

2.再使用git reset --hard <hash>会直接切换到哈希值为<hash>的提交状态,因此每次使用该命令移动的都是HEAD这个指针,因此使用该命令可以进行版本回退和前进。因此,每次使用该命令后执行git log显示的就是以当前HEAD所指向的提交版本及此之前的版本组成的提交日志。

git reset

Thinking 0.5

题面

1
2
3
4
5
执行如下命令, 并查看结果
echo first
echo second > output.txt
echo third > output.txt
echo forth >> output.txt

回答

echo first将输出first到终端上;

echo second > output.txt将会将second输出到output.txt文件中;

echo third > output.txt将会用third覆盖掉原有的second

echo forth >> output.txt将会将forth追加到output文件中third之后。

echo

Thinking 0.6

题面

1
使用你知道的方法(包括重定向)创建下图内容的文件(文件命名为 test),将创建该文件的命令序列保存在 command 文件中,并将 test 文件作为批处理文件运行,将运行结果输出至 result 文件中。给出 command 文件和 result 文件的内容,并对最后的结果进行解释说明(可以从 test 文件的内容入手). 具体实现的过程中思考下列问题: echo echo Shell Start 与 echo `echo Shell Start` 效果是否有区别;echo echo $c>file1与 echo `echo $c>file1` 效果是否有区别.

回答

command文件内容:

command

result文件内容:

result

结果解释:test文件内容如下:

test

当以shell脚本批处理时,第4、6、8行代码会分别将a、b、c赋值为1、2、3.再者经由11、13、15行代码将c、b、a的结果输入到file1file2file3中,第15、16、17行代码再将file1file2file3的内容输出到file4中,其中file1覆盖输入,file2file3均是追加输入,第19行代码再将file4的内容追加输入到result文件中。

因此,运行后,result文件中的每行内容分别为file1file2file3的内容,也就是c、b、a,即3、2、1。

Thinking A.1

题面

1
2
3
在现代的 64 位系统中,提供了 64 位的字长,但实际上不是 64 位页式存储系统。假设在 64 位系统中采用三级页表机制,页面大小 4KB。由于 64 位系统中字长为8B,且页目录也占用一页,因此页目录中有 512 个页目录项,因此每级页表都需要 9 位。因此在 64 位系统下,总共需要 3 × 9 + 12 = 39 位就可以实现三级页表机制,并不需要 64位。现考虑上述 39 位的三级页式存储系统,虚拟地址空间为 512 GB,若三级页表的基地址为 PTbase,请计算:
• 三级页表页目录的基地址。
• 映射到页目录自身的页目录项(自映射)。

回答

三级页表思路图

1.由于页面大小为4KB,虚拟地址空间为512GB,则一共有128M个页表项,且这些页表项同虚拟地址空间线性映射,因此PTbase对应的应该是第(PTbase >> 12)个页表项(其中一个页表项代表一个页目录项)。因为一个页表项占8B,则第(PTbase >> 12)个页表项相对于页表基地址的偏移为(PTbase >> 12) * 8,则二级页表页目录的基地址为(PTbase >> 12) * 8 +PTbase = PTbase >> 9 + PTbase。同理可得:三级页表页目录基地址为(PTbase >>18 + PTbase >>8 + PTbase)。

2.映射到页目录自身的页目录项(自映射),题目要求不明,并不知道是要要求什么?是要求页目录项的内容还是地址?如果是内容的话,未知,无法求解。如果是地址的话,同第一问:应该是(PTbase>>27 + 3 * PTbase>>18 + 3 * PTbase>>9 + PTbase)。

lab0课下的一些可能的易错点和踩的坑

Exercise 0.1

1.palindrome.c的编写,回文数的判断,我的思路是先将输入的n按每一位分解到一个数组中,然后从数组头尾进行比较,一旦发现不同就输出N这个应该都不会出错吧

2.将.c文件编译成可执行文件的方式为:gcc xxx.c -o xxx;也可以先只编译然后再链接:gcc -c xxx.c -o xxx.ogcc xxx.o -o xxx

3.获取某个文件的某一行的内容可以使用sed命令,通过sed -n '{num}p' xxx.txt可以从xxx.txt文件获取其{num}行的内容并显示在终端上,当然你可以通过重定向将输出内容输出到文件中,注意:>是覆盖式输出,而>>才是追加式输出

Exercise 0.2

1.shell里内声明定义的变量需要前置$才可使用,并且使用的时候是直接替换,部分用法和宏有点**”类似”**。

例如:

1
2
3
4
5
6
7
8
9
#!/bin/bash
a=1
while [ $a -le 100 ]
do
if [ $a -gt 70 ]
then
mv oldfile$a file$a #假如a是1,则该行代码等价于mv oldfile1 file1
...
done

2.注意,循环里循环变量的改变,在shell中,可以通过如下方式进行:a=$[$a+1],通过询问GPT,我得知了[$a+1]是旧式的算术式,其将告诉shell[$a+1]当作算术式计算,最终得到”2”的结果后再赋给a,但是GPT并不推荐它,确实令人很难以理解,此外其推荐了如下的新式算术扩展:

1
2
3
4
5
((a=a+1))
((a+=1))
((a++))
a=$((a+1))
#这四种方式都是等价的,都是变量自增一

3.删除非空的目录,可不能再用rmdir了,它只能删除空的,那该怎么办呢?注意到rm-r选项好像也可以删除目录啊,因此我采用rm -rf filex这里要注意了哈,从打出-rf高亮为红色,也可以看出,这是个危险的命令选项,因此,我们只要删除要我们删除的目录即可,**切勿执行rm -rf *rm -rf ./***,否则后果不堪设想。

Exercise 0.3

1.可以使用grep命令查找有xxx字符的地方,通过加上**-n选项可以显示相应的行数,利用通道将输出作为输入给awk命令,此前,可以先执行一下grep命令的输出格式,观察到相应使用的分隔符后,即可在修改分隔符的基础上利用awk命令输出行数**。

Tips:传参的时候如果最终的命令形式是文件或字符串一类的,可以用双引号括起来,这样可能会减少”歧义”。

Exercise 0.4

1.使用sed命令可以实现字符串的替换,记得加上-i选项才会对源文件修改啊-n只是安静模式哟,只会输出改后的效果,但是并不改(我会输出,但是我不改)。此外,带上/g才会全部替换,否则,替换的只会是每行的第一个匹配的

2.code文件夹内的fibo.c依赖于本文件夹内的main.c和另一个文件夹的fibo.h,因此在相应的编译的过程中需要使用-I选项来指定头文件的路径,例如:-I../csc就是告诉shell有个头文件在前一个目录的csc文件夹内。此外,最终生成的时候一定要把所有有参与到达文件一块链接,否则,大有出现找不到入口的bug的情况。

3.不确定的地方一律使用绝对路径,当然由于评测机的根路径会与我们的本地有所不同,因此,应该确保你使用的决对路径里没有题目提供的文件树以外的东西,否则,会出现编译错误的情况,都找不到了可不出错了

4.make内近似于并行执行,因此,你原先设定的顺序并不一定是最终的执行顺序,可以使用以下三种方式解决:

此处摘自Make 命令教程 - 阮一峰的网络日志)

1.把两行命令写在一起,中间用分号分隔:

1
2
var-kept:
export foo=bar; echo "foo=[$$foo]"

2.在换行前加反斜杠转义\:

1
2
3
var-kept:
export foo=bar; \
echo "foo=[$$foo]"

3.加上.ONESHELL:命令:

1
2
3
4
.ONESHELL:
var-kept:
export foo=bar;
echo "foo=[$$foo]"

上机前需要注意的

  • 最好在每个脚本文件末尾手动写一个exit 0来避免报错,$?是上一条语句返回值,$#代表传参个数,$*代表内容包含所有参数的字符串。

  • 文件处理三剑客grepsedawk,一定要熟练掌握,例:如何输出hello.c中所有包含"os_hello"(区分大小写)的行的”os_hello”左边的内容,若一行出现多个,则输出第一次出现的左边的内容

    grep+sed:

    1
    grep -E '.*os_hello' hello.c | sed -E 's/(.*)os_hello.*/\1/'

    (不推荐,太过于复杂了,如果你能轻松编写的话,那当我没说)

    awk

    1
    2
    3
    4
    5
    awk -F 'os_hello' '{print $1}' hello.c #这是一种错误的写法,这样会导致没有'hello'的那一行将会整行输出。
    #如下是正确的写法(即先利用grep命令找到有hello的每一行,然后再利用awk来输出以'hello'为分隔的第一块内容):
    grep 'os_hello' hello.c | awk -F 'os_hello' '{print $1}'

    awk '/os_hello/ { sub(/os_hello.*/,"");print}' hello.c #也不是很推荐,过于复杂了
  • #var获取变量var长度,${#var}获取长度值

  • ${variable:start:count}表示从变量variablestart开始截取count个字符(超出末尾,截取至末尾)

  • if需要fi进行闭合

  • >>在文件中追加会自动换行,不换行可以采用echo -n xx >> xx

  • 在脚本文件中,输出文件内容,一般采用cat,不能用more,后者是交互式命令,在终端使用。

  • awk用法补充:

    1
    2
    #awk+if
    awk '{if($2=="'$PID'") print $3}' $FILE
    • 可以通过"'$VAR'"的形式在awk中使用shell中定义的变量。

    • 可以使用倒引号将awk结果赋值给变量,例如:

      1
      var=`echo $result|awk '{print substr($result,16,3c)}'`

课上感受和踩坑教训

1.一开始由于没有初始化分支,导致后续的fetchcheckout等操作都毫无作用,耽搁了4、5分钟,还影响了心态,一定要先初始化分支!!!

2.第一个Makefile的编写还算顺利,就是后续提交的时候,clean目标明明本地不加-f都可以进行有效的操作,但是交到评测机上就是不行,一开始还以为是什么奇怪的地方出了问题,结果是没加-f,QAQ。(不过试后,一位舍友没加-f也能过,形式还与我没加-f相同,不知道怎么回事()。

3.第二个bash脚本的最后一个编写出现了些许小问题,导致被卡了很久。也就是并未充分掌握好sed命令的用法,主要是其中单形参的考虑。题目要求在单形参时,输出相应文件从第形参行开始到最后一行每一行的内容,显然应该使用sed命令来实现,由于''中一切皆字符,所以使用""方便来引用输入的形参,根据$表示最后一行,得出了第一版想法:

1
sed “$1,$p” <filename>

但是如你所见,bash认为$p连在一起应该指代一个变量,于是就水灵灵的报错了。

在百般尝试后,(包括但不限于使用,分隔)最终得到了以下两种方式:

1
2
3
sed "$1,$ p" <filename> #使用空格强行将$和p分隔开,让bash知道这是两个东西

sed "$1,\$p" <filename> #使用\将$反义,使之只代表最后一行,失去获取变量的能力

4.extra题量大,考量全面,拼尽全力,无法拿分,没有办法,菜就多练

致谢

页表那道题,是在和舍友kxq同学讨论后的结果,特此致谢。