栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Linux课程总结

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Linux课程总结

一、课程总结

本课程是理论和代码相结合的授课模式,在先讲解一般操作系统的通用运行逻辑后再结合Linux内核源码具体学习相关操作系统功能逻辑的实现。

计算机系统的基本工作原理

1. 计算机的三个关键性方法机制
当代所有计算机平台的基础–冯诺伊曼机
先了解一下什么是存储程序计算机?
主要思想是讲程序存放在计算机存储器中,然后按照存储器中的存储程序的首地址执行程序的第一条指令,按照该程序中编写好的指令执行,直至程序结 束。
冯诺伊曼机的三大特点:
包含运算器、存储器、控制器、I/O
内部采用二进制来表示指令和数据。
将编写好的程序和数据先放入计算机中,然后启动。
核心是存储程序计算机。
2. 堆栈
作用:记录调用框架,传递参数,保存返回值,暂时存储局部变量    
相关寄存器:ESP+EBP(堆栈相关寄存器),CS:EIP(函数调用相关),EAX(返回值地址),还有传递参数相关
运行机制:通过pop,push两种操作,结合以上各种寄存器,实现函数调用,返回地址的记录,并且在函数调用完之后可以返回到原文中继续执行。

3. 中断–系统调用(这里只写相关部分的中断内容)
系统调用的总过程:
触发系统调用时,用户态使用int$0x80指令触发中断,跳转到汇编代码system_call,触发中断机制,当前进程用户态的堆栈SS:ESP,CS:EIP EFLAGS保存在内核中,接下来执行到system_call的位置,将这段汇编用于保存现场,执行系统调用内核函数,函数调用完之后返回,恢复现场。最后iret将关键现场恢复到对应的寄存器中去,并回到int$0x80之后的下一条指令继续执行。
初始化过程:  
系统加载 start_kernel 开始,调用了 trap.c 中的 trap_init()函数对中断进行初始化。并且调用同一文件下的set_trap_gate()/set_system_gate()/set_intr_gate()等对中断描述符进行初始化。在进入保护模式之前, IDT 再次通过 setup_idt()函数进行初始化,在这里使用了 ignore_int()函数,是为了保护未初始化完成时发生异常不出错。然后调用 init_IRQ()函数,把中断描述附表的中断处理代码段地址设在在 interrupt 数组中,该数组指向同一个函数处理 common_interrup。    
为什么需要分级?
系统体系架构分为用户态和内核态,用户态的权限低于核心态,也就是说代码掌控的范围收到了限制,如果没有权限级别的划分,程序员所有代码都可以使用特权指令,系统很容易出现崩溃的情况。

操作系统实现的功能之一-进程管理

1. 进程的的概念:
运行一个程序的时候,操作系统需要描述其的运行过程,通过结构体task_struct{}来描述,统称为PCB。
2. 进程的描述信息有:
PID,进程状态,优先级,sp,上下文,内存指针。
3.创建进程–fork()
fork函数不同于其他函数,因为其返回次数的特殊性:fork会有两个返回值,一个为父亲进程的返回值,一个为子进程的
子进程返回0,父进程返回子进程的进程ID。
4.fork运行机制:
触发系统调用时,用户态使用int$0x80指令触发中断,跳转到汇编代码system_call,触发中断机制后把当前进程用户态的堆栈SS:ESP,CS:EIP EFLAGS,接下来执行到system_call的位置,将这段汇编用于保存现场,执行系统调用内核函数,函数调用完之后返回,恢复现场。最后iret将关键现场恢复到对应的寄存器中去,并回到int$0x80之后的下一条指令继续执行。
fork系统调用创建了一个子进程,子进程复制了父进程中所有的进程信息(内核堆栈,进程描述符),但有些信息是不一样的,比如pid和内核堆栈,还有将新的进程连接到各种链表中,要保存进程执行到哪个位置,父子进程共享存储空间。
5.相关函数:
fork,vfork,和clone3个系统调用和kernel_thread内核函数都是通过do_fork()来创建进程的。
6.进程状态:
阻塞态,运行态,就绪态。
7.进程调度策略:
7.1 实时调度策略:SCHED_FIFO和SCHED_RR,实时系统的优先级是静态设定的,在就绪队列没有实时进程的情况下,普通进程才能获得调度,二者分别为先  进先出和时间片轮转。
7.2 根据负载变化发生变化:SCHED_NORMAL,每个进程分配到的cpu时间占比有比例跟系统当前的负载有关。
8.调度算法:
CFS:基于权重的动态优先级调度算法,已使用的虚拟时间越少,进程排序就越靠前。
9.进程上下文切换
包括两个部分:切换页全局目录CR3和切换内核态堆栈和硬件上下文。

最后给出一个linux系统概念模型:

举例验证:
读写文件,在读写文件之前,必须使用oepn打开一个文件,打开文件首先open会执行到C库,C库里有INT $0x80指令,然后在中断向量表中找到128项,中断向量表里有中断描述符,可以找到中断处理程序入口,128项是系统调用处理函数,进入系统调用处理函数,保存现场,系统调用号存储eax中,根据系统调用号执行系统调用表中对应那一项的函数。系统调用表是相应的函数指针,这里会执行sys_open。sys_open进行命令查找,找到文件控制块,根据不同文件类型,调用文件打开函数,文件打开函数会创建一个系统文件打开表file,file的很多内容来自文件控制块,填完之后,进程也有一个进程文件打开表,这个结构里面有fd数组,fd数组是指针,找个空闲的,把它指向之前已经创建的file结构,最后返回那一项的索引号,即fd。

当进程使用read系统调用读这个文件,就会根据fd数组的下标 找到fd数组的对应项,然后找到指向之前创建的file结构的指针,再找到这个file结构,最后找到file结构里的file_operations里的具体的read函数来读取文件。

当进程使用write系统调用写数据到这个文件时也会根据fd数组的下标 找到fd数组的对应项,然后找到之前创建的file结构的指针,再找到这个file结构,进而找到file结构里的file_operations里的具体的write函数来写文件。

二、课程实验 2.1 实验一:mykernel

实验目的:搭建虚拟的x86-64 CPU实验平台mykernel

实验环境:VMware+Ubuntu1804

详细步骤:

//下载孟宁老师的mykernel2.0补丁
wget https://raw.github.com/mengning/mykernel/master/mykernel-2.0_for_linux-5.4.34.patch 

//安装axel工具
sudo apt install axel

//下载linux-5.4.34内核源码
axel -n 20 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.4.34.tar.xz

//解压
xz -d linux-5.4.34.tar.xz
tar -xvf linux-5.4.34.tar

//进入linux内核源码目录,进行后续操作
cd linux-5.4.34
//patch一下补丁,可以看到哪些文件被更改了
patch -p1 < ../mykernel-2.0_for_linux-5.4.34.patch

//安装调试工具
sudo apt install build-essential gcc-multilib
sudo apt install qemu # install QEMU
sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev

//配置文件
make defconfig # Default configuration is based on 'x86_64_defconfig'
//编译,需要五分钟左右时间,make -j2(核数)
make -j$(nproc)

qemu-system-x86_64 -kernel arch/x86/boot/bzImage

在qemu窗口可以看到my_start_kernel在执行,同时my_timer_handler时钟中断处理程序周期性执行。

2.2 实验二:debug mykernel

(1)安装开发工具

  • 安装vscode在官网https://code.visualstudio.com/Download下载并安装,注意,ubuntu下要下载.deb后缀的文件哦

  • 还要安装VSCode插件C/C++ Intellisense和C/C++ Themes。由于插件C/C++ Intellisense需要GNU Global,还需要使用如下命令安装GNU Global。

sudo apt install global

  • 修改VScode的配置文件c_cpp_properties.json使得VSCode能自动搜索跳转到函数定义了,阅读代码会方便很多。由于Linux内核代码非常庞大,为加速搜索速度还可以设置排除一些干扰文件,比如修改配置文件settings.json
    ​ 配置文件的路径是home/butterfly/.config/Code/User/seetings.json。配置文件参见https://github.com/mengning/linuxkernel/tree/master/src/kerneldebuging

  • 安装调试工具

sudo apt install build-essential gcc-multilib
sudo apt install qemu # install QEMU
sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev
(2)下载内核源代码
sudo apt install axel

axel -n 20 https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.4.34.tar.xz

xz -d linux-5.4.34.tar.xz

tar -xvf linux-5.4.34.tar

cd linux-5.4.34

由于 Linux 内核高度定制化,所以没有办法直接通过配置 includePath 等让 Intellisense 正常提示,这里借助一个 Python 脚本来生成 compile_commands.json 文件帮助 Intellisense 正常提示(包括头文件和宏定义等)。在Linux源代码目录下直接运行如下命令就可以生成 compile_commands.json 了。

python ./scripts/gen_compile_commands.py

//如果没有安装python,使用如下命令即可
sudo apt install python
(3)配置内核选项
make defconfig # Default configuration is based on 'x86_64_defconfig'

make menuconfig #打开debug相关选项

Kernel hacking —>
Compile-time checks and compiler options —>
[] Compile the kernel with debug info
[] Provide GDB scripts for kernel debugging
[*] Kernel debugging

关闭KASLR,否则会导致打断点失败

Processor type and features ---->
[] Randomize the address of the kernel image (KASLR)

(4)make [-j #] 编译内核
make -j$(nproc) # nproc 是核数   make -j2
# 测试一下内核能不能正常加载运行,因为没有文件系统最终会kernel panic
qemu-system-x86_64 -kernel arch/x86/boot/bzImage
(5)生成initramfs根文件系统镜像,也习惯于命名为rootfs

​ 电脑加电启动首先由bootloader加载内核,内核紧接着需要挂载内存根文件系统,其中包含必要的设备驱动和工具,bootloader加载根文件系统到内存中,内核会将其挂载到根目录/下,然后运行根文件系统中init脚本执行一些启动任务,最后才挂载真正的磁盘根文件系统。
​ 我们这里为了简化实验环境,仅制作内存根文件系统。这里借助BusyBox 构建极简内存根文件系统,提供基本的用户态可执行程序。

​ 首先从https://www.busybox.net下载 busybox源代码解压,解压完成后,跟内核一样先配置编译,并安装。

axel -n 20 https://busybox.net/downloads/busybox-1.31.1.tar.bz2

tar -jxvf busybox-1.31.1.tar.bz2

cd busybox-1.31.1

make menuconfig
# 记得要编译成静态链接,不用动态链接库。
Settings  --->
    [*] Build static binary (no shared libs)
    
# 然后编译安装,默认会安装到源码目录下的 _install 目录中。
make -j$(nproc) && make install

# 然后制作内存根文件系统镜像,大致过程如下:
mkdir rootfs

cd rootfs

cp ../_install/* ./ -rf

mkdir dev proc sys home

sudo cp -a /dev/{null,console,tty,tty1,tty2,tty3,tty4} dev/
# 准备init脚本文件放在根文件系统跟目录下(rootfs/init)
touch init

#添加如下内容到init文件。

#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
echo "Wellcome MengningOS!"
echo "--------------------"
cd home
/bin/sh

给init脚本添加可执行权限
chmod +x init
打包成内存根文件系统镜像
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../rootfs.cpio.gz 

测试挂载根文件系统,看内核启动完成后是否执行init脚本
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz
(6)命令行debug linux kernel

​下面具体看看如何使用gdb跟踪调试Linux内核。使用gdb跟踪调试内核,加两个参数,一个是-s,在TCP 1234端口上创建了一个gdb-server。可以另外打开一个窗口,用gdb把带有符号表的内核镜像vmlinux加载进来,然后连接gdb server,设置断点跟踪内核。若不想使用1234端口,可以使用-gdb tcp:xxxx来替代-s选项),另一个是-S代表启动时暂停虚拟机,等待 gdb 执行 continue指令(可以简写为c)。

# 启动虚拟机
qemu-system-x86_64 -kernel linux-5.4.34/arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s -nographic -append "console=ttyS0"

​ 用以上命令先启动,然后可以看到虚拟机一启动就暂停了。加-nographic -append "console=ttyS0"参数启动不会弹出QEMU虚拟机窗口,可以在纯命令行下启动虚拟机,此时可以通过“killall qemu-system-x86_64”命令强行关闭虚拟机。

​ 再打开一个窗口,启动gdb,把内核符号表加载进来,建立连接:

cd linux-5.4.34/

gdb vmlinux

(gdb) target remote:1234

(gdb) b start_kernel

c、bt、list、next、step....

(7)VScode debug linux kernel

配置.vscode/tasks.json和.vscode/launch.json。配置文件参见https://github.com/mengning/linuxkernel/tree/master/src/kerneldebuging,修改方法示例:

此时将断点设在init/main.c中的start_kernel函数中,然后Qemu 开启GDB调试,vscode start debug即可开始调试内核。

参考资料

《庖丁解牛Linux操作系统分析》 孟宁

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1002761.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号