南大ics课程

cs
Author

dd21

Published

December 5, 2022

P1- 课程介绍

http://jyywiki.cn/ 该课程主要是通过手把手编写一个模拟器, 来了解计算机底层原理(编译,链接….) # P2-C语言拾遗 >总结: > - 编译链接过程 > - 预编译指令 > - 宏的使用 > - 内存模型 >
在这里插入图片描述

gcc hello.c                             // complier c file
vim a.out                               // see the bin file
:%!xxd                                  // show with hex

we can see .ELF at the begin, this tell us this is an executeable file 我们可以看到开头的.ELF, 这表示这是一个(linux中)可执行文件 在这里插入图片描述 >可执行文件可以通过file打开

file a.out

在这里插入图片描述

上述过程直接通过gcc 实现了从.c.out 的过程 上述过程拆解: - gcc -S hello.c => 生成.s汇编文件 - gcc -c hello.c => 生成.o文件,但是没有链接 - objdump -d hello.o => 将.o文件反汇编成我们可以看的懂的文件

在这里插入图片描述

预编译指令#

#include 表示将代码复制粘贴进来

eg1

  • hello.inc
printf("hello world\n");
  • hello.c
#include <stdio.h>    
    
int main(int argc, char* argv[])    
{    
    
    #include "hello.inc"    
    return 0;    
} 

gcc -E hello.c

在这里插入图片描述 gcc --verbose hello.c 查看include文件是否在包含的目录中

可以看到#include "xxx"#include <xxx>"的位置 在这里插入图片描述 >使用#include <xxx>"进行包含文件挥发现找不到这个文件, 系统目录中没有包含该文件. > 可以使用gcc -I./进行将目录添加到系统目录中

问题演示 在这里插入图片描述 解决方案 在这里插入图片描述 gcc -I. --verbose hello.c查看日志中是否包含目录 在这里插入图片描述 gcc -m32 hello.c 将项目编译为32位机器的目标文件

有趣的预编译, 因为aa和bb都没有定义所以都是NULL, NULL==NULL 得到True,所以输出yes

#include <stdio.h>      
                                                                             
int main(int argc, char* argv[])                                             
{                                                                            
    #if aa == bb                                                             
    ¦   printf("yes\n");                     
    #else                                                                                                                     
    ¦   printf("no\n");                            
    #endif                                                                                                                                                                       
}  

在这里插入图片描述 gcc -E aabb.c 查看预编译后的代码. 在这里插入图片描述 直接放入extern int printf (const char *__restrict __format, ...);不用全部include也是可以的

extern int printf (const char *__restrict __format, ...);     
int main(int argc, char* argv[])    
{                  
    #if aa == bb    
    ¦   printf("yes\n");    
    #else    
    ¦   printf("no\n");    
    #endif    
} 

编译32位项目,出现报错:https://blog.csdn.net/wzzushx/article/details/119453099

sudo apt-get install build-essential module-assistant  
sudo apt-get install gcc-multilib g++-multilib  

预编译参数依旧添加进去了

extern int printf (const char *__restrict __format, ...);    
    
int main(int argc, char* argv[])    
{    
    #ifdef __x86_64__     
    ¦   printf("__x86_64__\n");    
    #else    
    ¦   printf("__x86__\n");    
    #endif    
    
    
    #if aa == bb    
    ¦   printf("yes\n");    
    #else    
    ¦   printf("no\n");    
    #endif    
}  

调用系统接口

system.c

  #define SYSTEM sys ## tem      
                     
  int main(){        
     SYSTEM("echo hello world\n");    
  }
gcc system.c                        // 虽然这会报错, 但是没有影响
./a.out

hello world

宏定义的展开

// 奇数行输出ture
#define true    (__LINE__%2!=0)

X-Macros

names.c

#include <stdio.h>    
#define NAMES(X) X(TOM) X(Jerry) X(Lcc) X(Hig) X(lcu)    
#define HELLO(M) M(Hello) M(world) M(\n)    
    
int main(int argc, char* argv[])    
{    
    #define PRINT(x) puts("hello," #x "!");    
    NAMES(PRINT)    
    HELLO(PRINT)    
    return 0;    
} 

gcc -E names.c 使用gcc -E 进行查看预编译的代码。

# 5 "namex.c"
int main(int argc, char* argv[])
{

    puts("hello," "TOM" "!"); puts("hello," "Jerry" "!"); puts("hello," "Lcc" "!"); puts("hello," "Hig" "!"); puts("hello," "lcu" "!");
    puts("hello," "Hello" "!"); puts("hello," "world" "!"); puts("hello," "\n" "!");
    return 0;
}

编译&链接

连个函数文件,一个sum.cmain.c先对这两个文件进行编译,没有链接, 然后手动指定链接,最后运行a.out同样产生相同结果。

sum.c

int foo(int n){                                                             
    int sum = 0;                                                                                   
    for(int i=0;i<=n;i++){                                                                         
    ¦   sum+=i;                                                                                     
    }                                                                                               
    return sum;                                                                                     
}

main.c

#include <stdio.h>    
    
int foo(int n);    
    
    
int main(int argc, char* argv[])    
{    
    printf("%d\n",foo(100));    
    return 0;    
}

在这里插入图片描述 ### 链接

 gcc {main,sum}.o -static

在这里插入图片描述 ## 内存 >总结 > - 代码存储的位置最低, 然后是只读变量(const修饰的变量), RO上面就是非零的变量, RW上边就是0变量和为被初始化的变量.上面就是堆栈. > 在这里插入图片描述 > 参考代码

#include <stdio.h>
void printptr(void *p){
    printf("p = %p; *p = %016lx\n",p, *(long *)p);
}
 
char bss_1[10];
 
static int _static_1=65535;// ffff
static int _static_2;
 
int glab_1=65535;
int glab_2;
int glab_3=0;
 
const char _const = 'a';
int main(int argc,char *argv[]){
    int b;
    static int _static_3=65535;
    static int _static_4=0;
    
    printf("------- [code] ------\n");
    printf("main:\t");
    printptr(main); // 代码
    printf("main:\t");
    printptr(&main);
 
    printf("------- [data] ------\n");
    printf("_____________[data-RO]_____________\n");
    printf("&const:\t");
    printf("p = %p;\n",&_const);
    printf("_____________[data-RW]_____________\n");
    printf("stac_1:\t");
    printptr(&_static_1);
    printf("stac_2:\t");
    printptr(&_static_2);
    printf("stac_3:\t");
    printptr(&_static_3);
    printf("stac_4:\t");
    printptr(&_static_4);
 
    printf("glab_1:\t");
    printptr(&glab_1); // 数据
    printf("glab_2:\t");
    printptr(&glab_2); // 数据
    printf("glab_3:\t");
    printptr(&glab_3);
    printf("&bss_1:\t");
    printf("p = %p;\n",&bss_1);
    printf("b:\t");
    printptr(&b);
    printf("------- [heap] ------\n");

    printf("\n");

    printf("------- [stack] ------\n");
    printf("argc:\t");
    printptr(&argc); // 堆栈
    printf("argv\t");
    printptr(argv); 
    printf("&argv:\t");
    printptr(&argv); 
    printf("argv[0:\t");
    printptr(argv[0]);
}

在这里插入图片描述 # p3- C 语言拾遗(2): 编程实践 > 总结: > - 提升代码的可读性(Readable),明确代码的可维护性 > - 函数指针 > - 实战yemu(计算机系统运行原理)

代码的可读性和可维护性

通过signal的例子来说明如何提升代码的可读性.

// 这样的代码可读性就比较差(顺时针螺旋法则)
void (*signal (int sig, void (*func)(int)))(int);

优化后的代码, 大大提升了代码的可阅读性

// 定义一个函数指针类型 sighandler_t, 表示他是一个接收参数数int的函数
typedef void (*sighandler_t)(int);
// 返回值是sighandler_t类型(返回函数类型),接收参数为一个int和一个接收为int的一个函数.
sighandler_t signal(int, sighandler_t);

YEMU的原理

NEMU就是YEMU的增强.(通过YEMU的案例进一步说明代码的可读性和可维护性的重要性)

  • 第2,3位表示 rt -> register target(猜测)目标寄存器,
  • 第0,1位表示 rs -> register source (猜测)源寄存器
  • 0123 表示的是指令的位置(第0,1,2,3位,4567类似)
  • 0000 表示mov
  • 0001 表示add
  • 1110 表示load(默认加载到RA(R0)寄存器)
  • 1111 表示store(默认将RA(R0)中的数据存储到制定地址)

在这里插入图片描述 > ## extern 将一个文件内的变量扩展到其他文件也可以使用

.
├── a.c    // 变量所在位置
├── a.h
├── a.out
└── main.c  // 将要使用的位置

method-1

a.h

void hello(int num);
extern int num;  // 添加外部变量

a.c

#include <stdio.h>

int num = 100;

void hello(int num){
    printf("%d\n",num);
}

main.c

#include "a.h"


int main(void){
    // method 2:在头文件中声明exter所需变量 
    // method 1: 解除下面的注释.
    //    extern int num;
    int a = num;
    hello(a);
    return 0;
}

method-2

a.h

void hello(int num);

a.c

#include <stdio.h>

int num = 100;

void hello(int num){
    printf("%d\n",num);
}

main.c

#include "a.h"

int main(void){
    extern int num;
    int a = num;
    hello(a);
    return 0;
}

union

将变量类型组合, 公用一个结构,个人感觉有点像是一个大的结构体(但是其所占用的大小是最大的那个变量的大小),将所有类型进行组合,大大提升代码的可阅读性和可维护性.

#include <stdio.h>

union data{
    long age;
    char ch;
};

int main(int argc, char* argv[])
{
      
    union data stu1;
    stu1.age = 18;
    stu1.ch = 'a';
    printf("union.age:%ld,sizeof:%ld\n",stu1.age,sizeof(stu1));
    printf("union.ch:%d,sizeof:%ld\n",stu1.ch,sizeof(stu1));
    return 0;
}

对bit位进行操作,

#include <stdio.h>

typedef union inst{
    //  type(int is 16bit)  part1:size, part2:size
    struct {int a:8, b:8; } atype;
    struct {int a:8, b:8; } btype;
}intst_t;

struct {int a:8, b:8; } ctype;

int main(int argc, char* argv[])
{
    ctype.a = 0b01111111;
    ctype.b = 0b10000011;
    printf("%d %d size:%ld\n",ctype.a,ctype.b,sizeof(ctype));   
    return 0;
}

MakeFile

使用makefile进行文件的构建对整个项目进行托管, 提升开发效率

.PHONY: run clean test

CFLAGS = -Wall -Werror -std=c11 -O2
CC = gcc
LD = gcc

# 构建yemu所需要yemu.o 和idex.o
yemu: yemu.o idex.o
    # 使用的命令为:gcc -Wall -Werror -std=c11 -O2 -o yemu yemu.o idex.o
    $(LD) $(LDFLAGS) -o yemu yemu.o idex.o

yemu.o: yemu.c yemu.h
    $(CC) $(CFLAGS) -c -o yemu.o yemu.c

idex.o: idex.c yemu.h
    $(CC) $(CFLAGS) -c -o idex.o idex.c

# make run
run: yemu
    # @表示将该cmd不输出到终端
    @./yemu
# make clean
clean:
    # 使用make clean所执行的命令
    rm -f test yemu *.o

# make test
test: yemu
    $(CC) $(CFLAGS) -o test idex.o test.c && ./test

p4 - NEMU框架选讲(1): 编译运行

总结: - unix 的优劣 - git的使用 一本说unix优劣书:https://web.mit.edu/~simsong/www/ugh.pdf

unix的关键字作为参数的问题

例如rm -rf xxxx如果该命令删除一个名为-rf的文件该如何解决. 为了解决这个问题, linux的命令中出现了-- xxx - 尽管unix有些问题, 但是还是给我们带来了更多的便利

rm -rf -- -rf               // 后续在linux上无法创建-开头的文件

git的使用

git的出现正是由于程序员的工作需求诞生, 需要对代码进行版本管理,以及社区的维护都有非常好的管理能力. git学习地址(对git有一定了解提升下理解):https://marklodato.github.io/visual-git-guide/index-en.html 可视化git 在这里插入图片描述 工作区: 编辑代码的时候 暂存区: 执行git add 后的区域 仓库区: 执行git commit 后的区域

每次修改代码后可直接使用git diff 查看修改的地方, 如果使用git add 后则表示提交暂存区该代码保留, 此时再执行git diff 就没有任何不同了. git commit 将代码提交到了仓库, 表示作为一个版本.

交互模式

git {reset,checkout,add} -p     // y(es), n(o), a(ll), q(quit),

git diff

在这里插入图片描述 >### git commit git会使用与当前提交相同的父节点进行一次新提交,旧的提交会被替换。

 git commit --amend
 git commit --allow-empty

git checkout

checkout切换分支,切换文件。

将HEAD指到HEAD的前一个分支(前1个:^/~,前2个:^^/~~),但是没有创建分支,如果直接切换到其他分支,当前所指位置的文件修改将会丢失,如果需要保存,则需要进行创建新的分支(git checkout -b xxx

git checkout HEAD^    

不保存

git restore 

分离指针如果想要保存就需要进行创建新的分支。

git checkout -b test
git add .
git commit 

git merge

git merge 就是进行连个分支间进行迁移, 将当前的分支指向需要合并的分支。

git checkout master
git merge test              // 将test分支的内容迁移到master分支

在这里插入图片描述

git rebase

rebase 就是将数据分支进行统一为线性,merge是将修改结果迁移,rebase是将所有的版本进行迁移。

在这里插入图片描述 >### git redo >- git reset 回到xxx节点 >- git revert 将xxx节点新建为后一个节点

Makefile的使用和解读

多线程编译

多个线程同时编译,大大多段时间

# 8线程同时编译
make -j8 

强制编译

  • 没有新的文件修改或产生也执行编译
  • 多线程并行编译,加速编译
# 8线程同时编译
make -B 
make -B -j8
# 计算时间,强制执行,8线程
time make -B -j8

basename

获取当前文件名(获取路径的最后一个参数)

basename $(pwd)

vi内置的bash

使用方法

:!echo hello

make -n

打印执行的命令

make -B

强制执行

解读NEMU的Makefile

make -nB | greap -ve '^(\#\|echo\mkdir\)' | vim - 在这里插入图片描述 > ### 筛选数据

# -n 表示打印执行的命令
# -B always make
make -nB \

# grep 筛选      
#  ^   行首
# -e   扩展正则
# -v   不包含
| grep -ve '^\(\#\|echo\|mkdir\)' \ 

# vim 从标准输出获取数据
| vim -

调整格式

  • set nowrap(不折叠)

将选中的部分:空格替换成换行 在这里插入图片描述

处理完格式

  • -D (definition) 就是在编译的过程中加上的宏

在这里插入图片描述 >### 链接

gcc                                                                                                                                                                
 -O2               # 编译优化选项                                            
 -rdynamic         # 动态链接                                      
                                                  
 -o build/x86-nemu-interpreter   # 最后输出到                         
 build/obj-x86-interpreter/device/audio.o         
 build/obj-x86-interpreter/device/io/map.o        
 build/obj-x86-interpreter/device/io/port-io.o    
 build/obj-x86-interpreter/device/io/mmio.o
 ....
 
 -lSDL2             # 图形库
 -lreadline         # 读行库
 -ldl               # 动态链接库

Makefile转换成文档

doxygen

P5 - 框架代码选讲 (2):代码导读

查看文件树

tree .
.
├── build
│   ├── obj-x86-interpreter
│   │   ├── device
│   │   │   ├── alarm.d
│   │   │   ├── alarm.o
│   │   │   ├── audio.d
│   │   │   ├── audio.o
│   │   │   ├── device.d
│   │   │   ├── device.o
│   │   │   ├── intr.d
│   │   │   ├── intr.o
│   │   │   ├── io
│   │   │   │   ├── map.d
│   │   │   │   ├── map.o
│   │   │   │   ├── mmio.d
│   │   │   │   ├── mmio.o
│   │   │   │   ├── port-io.d
│   │   │   │   └── port-io.o
│   │   │   ├── keyboard.d
│   │   │   ├── keyboard.o
│   │   │   ├── serial.d
....

查找.c.h文件

find . -name "*.c" -o -name "*.h"

统计代码行数

  • xargs cat 将前面的参数传递xargs然后作为cat的输入
  • wc -l 统计行数
# xargs cat 将前面的参数传递xargs然后作为cat的输入 
find . -name "*.c" -o -name "*.h" | xargs cat | wc -l

找到main函数

  • grep -n [ with line num ]
  • grep -e [ re ]
  • grep -s [ no messge ]

查找main函数位置

find . -name "*.c" -o -name "*.h"| xargs grep --color -nse '\<main\>'

在这里插入图片描述 > ### fzf 查找( 奈斯 ) > - 可以配合其他命令使用

vim $(fzf)
cat $(fzf)
...

static 解读

  • static 所修饰的变量和函数作用域scope是当前的文件, 不可以跨越文件, 所以实现了可以重名的函数,和变量。

如下的案例,加深static 修饰时, f函数只能够调用到当前文件中的变量,或函数,无调用到b.c中的函数或者是static修饰的变量。所以能加上static修饰的函数尽量加上static进行修饰,否则实现跨文件的调用比较麻烦。

在这里插入图片描述 >### gcc -Wall -Werror > - 将warning转换为error 每一个报错都有意义, 身为合格的cser需要解决掉所有的warning和error

这里之所以会报错, 是因为在b.c中和函数没有使用,原本应该是warning这里因为加上了Werror所以将warning转换成了error

在这里插入图片描述

inline 解读

可以直接定义在头文件中,编译的时候后直接将代码粘贴过去,对性能需求有较高要求的应用比较适合。空间换时间

项目框架解读

进入到nemu的src/main.c

init_monitor(argc, argv);

随后进入到init_monitor里面的parse_args

  • 这里调用了getopt这个库, 需要查看getopt这个手册–> man getopt后详细信息查看man getopt.3或者man 3 getopt

static inline void parse_args(int argc, char *argv[]) {
  const struct option table[] = {
    {"batch"    , no_argument      , NULL, 'b'},
    {"log"      , required_argument, NULL, 'l'},
    {"diff"     , required_argument, NULL, 'd'},
    {"port"     , required_argument, NULL, 'p'},
    {"help"     , no_argument      , NULL, 'h'},
    {0          , 0                , NULL,  0 },
  };
  int o;
 
  while ( (o = getopt_long(argc, argv, "-bhl:d:p:", table, NULL)) != -1) {
    switch (o) {
      case 'b': batch_mode = true; break;
      case 'p': sscanf(optarg, "%d", &difftest_port); break;
      case 'l': log_file = optarg; break;
      case 'd': diff_so_file = optarg; break;
      case 1:
        if (img_file != NULL) Log("too much argument '%s', ignored", optarg);
        else img_file = optarg;
        break;
      default:
        printf("Usage: %s [OPTION...] IMAGE\n\n", argv[0]);
        printf("\t-b,--batch              run with batch mode\n");
        printf("\t-l,--log=FILE           output log to FILE\n");
        printf("\t-d,--diff=REF_SO        run DiffTest with reference REF_SO\n");
        printf("\t-p,--port=PORT          run DiffTest with port PORT\n");
        printf("\n");
        exit(0);
    }
  }
}

make run

第一次运行大多会遇到x86-nemu-interpreter: src/isa/x86/reg.c:20: reg_test: Assertion reg_w(i) == (sample[i] & 0xffff)' failed.

解决方法: 查看报错位置代码