Harbin Institute of Technology operating system experiment summary

The experimental address,  https://www.lanqiao.cn/courses/115/learning/?id=374 , is doing the experiment now. The advantage is that the environment is configured in advance. The disadvantage is that there is a network delay when typing the code, and the environment cannot be saved. Generally speaking, it is more convenient.

Textbook, <<Linux Kernel Fully Annotated Revised Version V5.0 >> (Zhao Jiong), hereinafter referred to as <<Notes>>, <<Assembly Language>> (Wang Shuang)

 

Experiment 1 Familiar with the experimental environment

This experiment mainly introduces the basic situation of the experimental environment. The host machine uses ubuntu to simulate the operation of Linux0.11 through the boch emulator.

The following commands are mainly used:

cd /home/shiyanlou/oslab/  #cd 是change directory 表示更换文件夹, 后面跟的是更改的文件地址, 如果不是以斜杠 '/'开头的话默认是当前文件夹, 和'./'表示相同的意思, '/'开头表示根目录

tar -zxvf hit-oslab.tar.gz -C /home/shiyanlou  #tar是打包命令, 在"鸟哥的linux私房菜"里有详细的介绍, 这里的-z表示用zip格式压缩或则解压, -x表示extract就是解压, -v表示显示压缩或者解压的文件细节, -f后面跟处理的tar文件(无论是压缩还是解压), -C后面跟的是指定的解压地址, C为大写

make all  #这里的make是一个编译命令, 当前所在文件夹必须有Makefile文件, 关于Makefile文件在<<linux内核完全注释(第五版)>>里3.6节有详细的介绍, 这个知识个人认为非常有必要掌握

sudo ./mount-hdc  # 挂载hdc硬盘命令, 必须使用sudo (super user do)来完成, 否则权限不够; 并且当前目录下的hdc文件夹的主要文件映射是在 /hdc/usr/root/ -> /root; ./run进入boch模拟之后的默认其实目录是/home/usr/root, 所以为了交换文件方便, 尽量放在/hdc/usr/root里

sudo umount hdc 跟上一个命令对应, 在./run运行boch前要umount

 

Experiment 2 booting the operating system

This experiment mainly introduces the whole process of the operating system from power-on to startup, which involves the modification of .s files (that is, assembly files), so you need to have a certain understanding of assembly. To understand this, you can use Wang Shuang's <<Assembly Language>> (mainly introduces some assembly operations in real mode), and Chapter 3 in <<Notes>> talks about the difference between AT&T syntax and 8086 real-mode assembly syntax, and the format of C language embedded assembly. I can't understand the language

The following are some of my own questions and thoughts about the experimental process

#6.2完成bootsect.s的屏幕输出功能
#.org 510    这一句的意思是下面的代码从第510个字节开始算, 后面跟了一个.word 0xAA55, 正好印证了引导扇区是512个字节
#.word 0xAA55    这一句是约定的引导扇区的地址, 没有别的意思

dd bs=1 if=bootsect of=Image skip=32     #这里的dd是linux的备份命令, 查了一下是disk destroyer的缩写, 在<<鸟哥>>里第九章有介绍, bs表示block size 一个块的大小, if 是 input file, of是output file, skip 32 表示跳过前32个字节, 这句命令在linux0.11目录下的Makefile里也有, 所以手工编译链接的时候需要你进行这一步处理

jnc     # 汇编命令, jump no carry, 没有进位时跳转
jmpi    # 段间跳转命令, 所以后面必须有两个操作数, 一个表示段, 一个表示段内偏移
lds si, memory    # load ds, 从memory中获取32位的信息到 ds:si中


 

The key point of this experiment is to understand the whole process of booting the operating system, which is described in detail in Chapter 6 of the book <<Notes>>, and the core of my personal feeling is this picture

Experiment 3 system call

This experiment introduces how to enter the kernel mode from the user mode and call the kernel code, and how to modify and add the kernel code

The five-paragraph theory of the operating system to implement system calls, combined with an example, is expressed as follows:

1.应用程序调用库函数(API);
2.API 将系统调用号存入 EAX,然后通过中断调用使系统进入内核态;
3.内核中的中断处理函数根据系统调用号,调用对应的内核函数(系统调用);
4.系统调用完成相应功能,将返回值存入 EAX,返回到中断处理函数;
5.中断处理函数返回到 API 中;
API 将 EAX 返回给应用程序

# 以系统调用 close.c 为例
第一步, 应用程序调用 int close(int fd)     # 这里是用户态, 用户调用close()函数是系统调用的API

第二步, API将系统调用号NR_close存到eax里作为系统中断int 0x80的第一个参数, 根据参数调用_NR_table里对应的系统调用        # 这里是通过_syscall1(int, close, int , fd)这个宏定义将close变成__NR_close, __NR_close在文件sys.h中定义了其序号以及相应的处理函数, 这个时候刚进入内核状态, 后面进行内核栈的切换操作, 切换操作就是让 ds, es 指向核心地址空间 0x10, 用户地址空间是0x17

第三步, 查sys_call_table, 调用对应的内核函数sys_close        #  这时候已经进入内核态, 并进行内核栈的切换
第四步, sys_close在内核态处理, 返回的结果在eax中
第五步, 内核态切换为用户态, 此时返回值是用户态的返回值

This experiment has a lot of small bugs on the Internet. Before assigning values ​​in iam.c, the value of buffer must be set to 0, otherwise it will leave traces of the last setting

 

Experiment 4 Tracking and statistics of process running trajectory

This experiment began to enter the core part of the operating system, multi-process programming,

The documents and personal understandings involved in the experiment are as follows:

process.c     # 这是个样本程序, 实现了一个cpuio_bound函数, 用来模拟一个进程是cpu耗时为主还是IO耗时为主, 从而来观察得出一个进程的不同状态的持续时间

/var/process.log      # 通过在main.c的init()之前, 将文件描述符为3的输出关联到process.log, 因此后面用到fprintk就可以将输出指定在process.log里

(void) open("/var/process.log", O_CREAT | O_TRUNC | O_WRONLY, 0666)      # 这里的 O_CREAT是打开文件的flag设置, 定义在/usr/include/asm-generic/fcntl.h  O_CREAT 表示文件不存在就创建, O_TRUNC 表示文件存在就截为0(即清空), O_WRONLY 表示只写write only

/kernel/printk.c         # 这里面的门道有点多, 一个一个来

va_list args         # 这个是用来专门对应 const char* fmt 里的输出格式 , 如 "%d %s", ... 
struct file* file    # 这个struct file* 是在后面的文件系统里会学习到, 是一个文件类型的指针
struct m_inode* inode    # 这个inode是磁盘里的一个struct m_inode数据结构指针, 是一个文件节点, 里面存有当前文件对应的数据块block索引以及下一级目录的索引

count = vsprintf(logbuf, fmt, args)        # 这里是将fmt, args的结果输入到定义的内核缓存logbuf中, vsprintf返回输入的字节数, 这里的count表示输入了多少个字节到logbuf里

addl $8, %%esp\n\t        # 这里是栈的回滚, 前面pushl %logbuf 和 pushl %1正好占据了8个字节, 将esp加8就相当于抹去这两个数据, 后面的addl $12 也是相同的道理

/6.4jiffies 滴答
set_intr_gate(0x20, &timer_interrupt)        # 这里就是吧timer_interrupt这个时钟中断设置到IDT的0x20位置

/6.5寻找状态切换点
struct task_struct *p        # 这个struct task_struct 就是老师课件里提到的PCB(进程控制块 process control block), 里面有一个进程的各种信息, 栈, 寄存器, 上下文等, 定义在/include/linux/sched.h中

*p = current        # current是一个全局变量, 表示当前的进程控制块PCB  

The places where mistakes are easy to make in the experiment are

1. Process 0 will call sys_pause continuously. If you do not deal with this, the python file will report an error and repeat lines, that is, the state of a certain process is the same as the last time

2. switch_to(next) also compares the pid of the next task and the current task, if it is the same task, no change is required

 

Experiment 5 Process switching based on kernel stack switching

This experiment is relatively difficult, mainly because the amount of code is large, and you must have a certain understanding of the kernel stack. If you are not careful, you will make mistakes. 

After this experiment, remember the overall structure of the GDT table

Some code comments are as follows:

/6.3 schedule 与 switch_to
if ((*p)->state == TASK_RUNNING && (*p)->counter > c)    #    (*p)->counter > c表示p的时间片比当前最大的时间片大, 所以当前p的优先级最高, 下一个调度的任务就是*p, 
    c = (*p)->counter, next = i, pnext = *p;        # next 是下一个任务的编号, 所有的任务是定义的一个NR_TASKS

//.......

switch_to(pnext, LDT(next));
/6.4 实现switch_to
pushl %ebp
movl %esp, %ebp        # 这两句是栈帧操作, %ebp保存当前栈的栈帧位置, 因为栈是往下扩展的, 一个栈的栈帧是地址的最高值

movl 8(%ebp), %ebx        # %ebp是当前栈帧的地址, %ebp+4是保存的上一个栈帧的地址, %ebp+8就是switch_to的第一个参数, 回想一下上一节switch_to(pnext, LDT(next)), 因此这里是把pnext的值赋给了%ebx, 也就是要切换的下一个任务的指针

je 1f        # 这里的1是一个标号, 对应的下面的1:    , f表示forward,向前跳转到1的位置

movl tss, %ecx
addl $4096, %ebx    # %ebx表示下一个任务的指针, 增加4096相当于开辟了4096个字节的空间, 也就是一页的大小, 可以作为内核PCB的空间
movl %ebx, ESP0(%ecx)    # 这里就把%ebx也就是任务的指针存到了任务0的内核栈指针那里, 所有的内核任务都共用这一个地址

movl %esp,KERNEL_STACK(%eax)    #    这个%eax其实也是pnext指针, KERNEL_STACK(%eax)就是下一个任务的内核栈地址

What also needs to be understood is the structure of the task state segment TSS (task state segment)

As well as the structure of PCB (task_struct), you can see it in the source code, or read another blogger's blog post, which is quite detailed

https://blog.csdn.net/qq_29503203/article/details/54618275

 

Experiment 6 Implementation and application of semaphore

This experiment is probably a relatively helpful experiment for postgraduate entrance examinations and job hunting. It mainly uses semaphores to describe the principles of inter-process communication and locks.

First of all, we need to design a sem_t data structure. According to the video, a semaphore structure must have two basic members, one is the name of the semaphore (char* name), and the other is the value in the semaphore (int value). The communication between processes is blocked and awakened through this semaphore;

There is also a task queue, where a pointer to sem_t_queue can be added to the semaphore sem_t, or the implicit queue in the experimental guide can be used directly;

Secondly, we need to understand the PV order of the producer-consumer program. In the mutex semaphore MUTEX (critical section), we should try to use as few other semaphore resources as possible. The more you use, the easier it is to cause deadlock. The experimental thinking question is a case of this problem 

/6.1 信号量
Producer()
{
    // 生产一个产品 item;

    // 空闲缓存资源
    P(Empty);

    // 互斥信号量
    P(Mutex);

    // 将item放到空闲缓存中;
    V(Mutex);

    // 产品资源
    V(Full);
}

Consumer()
{
    P(Full);
    P(Mutex);

    //从缓存区取出一个赋值给item;
    V(Mutex);

    // 消费产品item;
    V(Empty);
}
/6.2 多进程共享文件

使用标准 C 的文件操作函数要注意,它们使用的是进程空间内的文件缓冲区,父进程和子进程之间不共享这个缓冲区        # 这句话其实隐含的说明了进程的内存空间是独立的, 父进程和子进程之间不会共享, 所以需要写完之后马上fflush一下, 手动把要写入的东西立即写入磁盘

建议直接使用系统调用进行文件操作    # 这句话说明系统调用可以使用统一的内核空间, 这样相当于直接使用了共享内存

/6.4 原子操作, 睡眠和唤醒

这里用到的原子操作是开关中断(sti(), cli()), 缺点是只能在单核上有效, 因为开关中断是CPU单核上的一个引脚的置1或者0
课堂上还讲了利用硬件的原子操作test&set(sem), 也就是P,V操作

strcmp is used in string.h to compare semaphore names (sem_t ->name)

 

Experiment 7 address mapping and sharing

The first part of this experiment is to visually show how a logical address is mapped to physical memory layer by layer

The ds on the logical address is a segment selector (segment selector), which is the offset above the LDT. If you want to find the LDT, you have to find the GDT, so the process is to check the GDTR (register) --> find the address of the GDT --> check the number of digits corresponding to the LDTR (register) --> find the offset of the LDT in the GDT address --> find the 64-bit data at the GDT offset, and take out the 32-bit information containing the physical address of the LDT, which is the real segment base address --> according to the segment base address +Segment offset 3004 can get the physical address of logical address ds:3004;

The value of the segment selector ds is actually very particular. It is said that ds=0x17 means user mode, mainly because the binary value of 0x17 is 00010111, and the last two digits indicate the privilege level. Here, 11 means 3, which happens to be user mode privilege level 3. The second bit means TI. TI is 1, which means to check data in LDT, so it just happens that user mode goes to LDT to check data, and kernel mode goes to GDT to check data; so 0x10 means kernel mode makes sense Yes, the binary value of 0x10 is 00010000, the last two digits indicate the privilege level is 0, the privilege level of the kernel mode, the TI bit is 0, which means that the data is checked on the GDT, which confirms that the data in the kernel mode is checked on the GDT

It is easy to find the physical address through the linear address. Using the page table knowledge, the 32 bits of the linear address are divided into page directory + page table entry + page offset. The size of the page directory entry and page table entry is a 32-bit number, so the corresponding page table should be found according to the page directory base address + page directory number * 4, the page table base address + page table number * 4 to get the page number, and finally + the page offset to get the physical address

The core part of the second experiment is actually to find a free physical page, store the physical page in a shared space table of the kernel, and let the two processes obtain the same piece of shared kernel memory according to the shmid, so as to achieve the purpose of shared memory. The rest of the experiment is similar to that of Experiment 6. Here, we learned how to use the character & and how the 64M memory in the process space is distributed.

 

Experiment 8 Control of terminal equipment

This experiment is mainly to learn how to deal with keyboard input and display output. In order to make all characters '*' by pressing F12, you have to find out where the output of '*' is. According to the teacher's introduction in the video, you need to modify it in the file console.c

It is also necessary to find out the corresponding input code of F12, and judge the input code of F12 in the input buffer (ESC, [, [, L seems to be), once it appears, the character written to the console in the console_write function is "*". This method ensures that only pressing F12 can change to '*'

Refer to this blogger's practice https://blog.csdn.net/weixin_45666853/article/details/105278345

 

Experiment 9 Implementation of PROC file system

This experiment is mainly to learn the concept of block and inode in the file system, and realize the virtual file PROC by mounting the corresponding inode. 

The main thing is to know the usage of the two functions mkdir() and mknod() to create nodes, and then to know the structure of task_struct to obtain the state, start_time, count and other information corresponding to each pid

The method of obtaining hdinfo is still a little unclear. On the whole, it is through traversal to count all used blocks and inodes, and then use the total blocks and inodes to subtract

The rest of the steps are not particularly difficult to understand, and the results can be obtained step by step according to the experimental guidance

 

Summarize

It took almost a month for the 9 experiments, and the first half of the month was spent on Wang Shuang's <<Assembly Language>>. I have a certain understanding of the compilation of real mode. But in fact, the requirements of this series of experiments on assembly can be found in the book <<Notes>>. In fact, it can be completed faster. I also referred to many other people's methods during the completion process. When I first came into contact with this thing, I was still at a loss. , can write a large piece of code independently, the only headache is debugging, it is really difficult to find bugs with GDB.

 

Guess you like

Origin blog.csdn.net/Mint2yx4/article/details/113740871