最全Linux操作系统(一)系统初始化_linux系统初始化(2),2024年最新写给即将正在找工作的C C++攻城狮

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 在 32 位处理器中, 有 32 根地址总线,可以访问 2^32=4G 的内存。
  • “开放” : 意味着有大量其他公司的软硬件是基于这个架构来实现的 , 存在限制
  • “兼容” : 8 个 16 位的扩展到 8 个 32 位的 。 个数不变 只扩展了空间 。
  • 段寄存器:弄了一个不上不下的 20 位的地址 , 这 样每次都要左移四位,也就意味着段的起始地址不能是任何一个地方,只是能整除 16 的地 方。
  • CS、SS、DS、ES 仍然是 16 位的。表格中的一项一项是段描述 符(Segment Descriptor)。这里面才是真正的段的起始地址。而段寄存器里面保存的是 在这个表格中的哪一项,称为选择子(Selector)。 16位以及不是以前的初始位置了。
  • 表格中的一项一项是段描述符(Segment Descriptor)。这里面才是真正的段的起始地址。而段寄存器里面保存的是 在这个表格中的哪一项,称为选择子(Selector)。
  • 实模式(Real Pattern): 从段寄存器直接拿到的段起始地址。
  • 保护模式(Protected Pattern) : 先间接地从段寄存器找到表格中 的一项,再从表格中的一项中拿到段起始地址。

在这里插入图片描述

系统刚刚启动 的个体户模式

BIOS 时期

在这里插入图片描述

如果你自己安装过操作系统,刚启动的时候,按某个组合键,显示器会弹出一个蓝色的界 面。能够调整启动顺序的系统,就是我说的 BIOS,然后我们就可以先执行它。

  • 在主板上,有一个东西叫ROM(Read Only Memory,只读存储器)
  • 内存RAM(Random Access Memory,随机存取存储器)
  • BIOS(Basic Input and Output System,基本输入输出系统)
    在这里插入图片描述
    在 x86 系统中,将 1M 空间最上面的 0xF0000 到 0xFFFFF 这 64K 映射给 ROM, 启动电脑的时候 会进行重置 将 CS 设置为 0xFFFF,将 IP 设置为 0x0000,所以第一条指令就会指向 0xFFFF0,正是在 ROM 的范围内。在这里,有一个 JMP 命令会跳到 ROM 中做初始化工作的代码,于是,BIOS 开始进行初始化的工作。

创业指导手册第一条,BIOS 要检查一下系统的硬件是不是都好着呢。
创业指导手册第二条,要有个办事大厅,只不过自己就是办事员。这个时期你能提供的服务很简单,但也会有零星的客户来提要求。
这个时候,要建立一个中断向量表和中断服务程序,因为现在你还要用键盘和鼠标,这些都 要通过中断进行的。

在这里插入图片描述

bootloader 时期

  • Grub2(Grand Unified Bootloader Version 2) linux的一个工具,用于系统启动。
  • grub2-mkconfig -o /boot/grub2/grub.cfg 来配置系统启动的选项

在这里插入图片描述

  • 使用 grub2-install /dev/sda,可以将启动程序安装到相应的位置。
  • grub2 第一个要安装的就是 boot.img,它由 boot.S 编译而成,一共 512 字节,正式安装 到启动盘的第一个扇区。这个扇区通常称为MBR(Master Boot Record,主引导记录 / 扇区)。
  • 由于 512 个字节实在有限,boot.img 做不了太多的事情。它能做的最重要的一个事情就 是加载grub2 的另一个镜像 core.img(由 lzma_decompress.img、diskboot.img、kernel.img 和一系列的模块组成)。

在这里插入图片描述

  • 如果从硬盘启动的话,这个扇区里面是 diskboot.img,对应的代码是 diskboot.S。
  • boot.img 将控制权交给 diskboot.img 后,diskboot.img 的任务就是将 core.img 的其他 部分加载进来,先是解压缩程序 lzma_decompress.img,再往下是 kernel.img,最后是 各个模块 module 对应的映像。这里需要注意,它不是 Linux 的内核,而是 grub 的内核。
  • lzma_decompress.img 对应的代码是 startup_raw.S,本来 kernel.img 是压缩过的,现 在执行的时候,需要解压缩。
  • 实模式: 1M 的地址空间 (只能满足比较小的程序)所以在真正的解压缩之前,lzma_decompress.img 做了一个重要的决定,就是调用 real_to_prot,切换到保护模式,这样就能在更大的寻址空间里面,加载更多的东西。

从实模式切换到保护模式

切换到保护模式要干很多工作,大部分工作都与内存的访问方式有关。
第一项是启用分段,就是在内存里面建立段描述符表,将寄存器里面的段寄存器变成段选择 子,指向某个段描述符,这样就能实现不同进程的切换了。第二项是启动分页。能够管理的 内存变大了,就需要将内存分成相等大小的块.
在这里插入图片描述

内核初始化

内核的启动从入口函数 start_kernel() 开始。在 init/main.c 文件中,start_kernel 相当于 内核的 main 函数。打开这个函数,你会发现,里面是各种各样初始化函数 XXXX_init

在这里插入图片描述

  • 在操作系统里面,先要有个创始进程,有一行指令set_task_stack_end_magic(&init_task)。这里面有一个参数 init_task,它的定义是 struct task_struct init_task = INIT_TASK(init_task)。它是系统创建的第一个进程,我们称为0 号进程。这是唯一一个没有通过 fork 或者 kernel_thread 产生的进程,是进程列表的第一个。
  • 32位系统 trap_init(),里面设置了很多中断门(Interrupt Gate),用于处理各 种中断。其中有一个 set_system_intr_gate(IA32_SYSCALL_VECTOR, entry_INT80_32), 这是系统调用的中断门。
  • mm_init() 就是用来初始化内存管理模块
  • sched_init() 就是用于初始化调度 模块
  • vfs_caches_init() 会用来初始化基于内存的文件系统 rootfs。为了兼容各种各样的文件系统,我们需要将文件的相关数据 结构和操作抽象出来,形成一个抽象层对上提供统一的接口,这个抽象层就是 VFS(Virtual File System),虚拟文件系统。
  • 1 号进程对于操作系统来讲,有“划时代”的意义。因为它将运行一个用户进程,然后会继承很多子进程,形成一棵进程树。 有了进程也就有了权限
  • x86 提供了分层的权限机制,把区域分成了四个 Ring,越往里权限越高,越往外权限越低.

在这里插入图片描述
操作系统很好地利用了这个机制,将能够访问关键资源的代码放在 Ring0,我们称为内核态(Kernel Mode);将普通的程序代码放在 Ring3,我们称为用户态(User Mode)

在这里插入图片描述

当一个用户态的程序运行到一半,要访问一个核心资源,例如访问网卡发一个网络包,就需 要暂停当前的运行,调用系统调用,接下来就轮到内核中的代码运行了。
首先,内核将从系统调用传过来的包,在网卡上排队,轮到的时候就发送。发送完了,系统 调用就结束了,返回用户态,让暂停运行的程序接着运行.
在这里插入图片描述

从内核态到用户态

kernel_thread 的参数是一个函数 kernel_init,也就是这个进程会运行这个函数。在 kernel_init 里面,会调用 kernel_init_freeable(),里面有这样的代码:

if (!ramdisk_execute_command)  //如果不为空 就初始化
 ramdisk_execute_command = "/init";

kernel_init:
if (ramdisk_execute_command) {
 ret = run\_init\_process(ramdisk_execute_command);
  ...... }
   ......
if (!try\_to\_run\_init\_process("/sbin/init") ||
 !try\_to\_run\_init\_process("/etc/init") ||
  !try\_to\_run\_init\_process("/bin/init") ||
   !try\_to\_run\_init\_process("/bin/sh")) 
return 0;  

  • 1 号进程运行的是一个文件。如果我们打开 run_init_process 函数,会发现它 调用的是 do_execve。execve 是一个系统调用,它的作 用是运行一个执行文件。加一个 do_ 的往往是内核系统调用的实现。没错,这就是一个系 统调用,它会尝试运行 ramdisk 的“/init”,或者普通文件系统上 的“/sbin/init”“/etc/init”“/bin/init”“/bin/sh”。不同版本的 Linux 会选择不同的 文件启动,但是只要有一个起来了就可以,而咱们刚才运行 init,是调用 do_execve,正是上面的过程的后半部分,从内核态执行系统调用开始。
//do\_execve->do\_execveat\_common->exec\_binprm->search\_binary\_handler
int search\_binary\_handler(struct linux\_binprm \*bprm) {
......
struct linux\_binfmt \*fmt; ......
retval = fmt->load\_binary(bprm); ......
}

我要运行一个程序,需要加载这个二进制文件,它是有一定格式的。Linux 下一个常用的格式是ELF(Executable and Linkable Format,可执行与可链接格式)。

//二进制 格式
static struct linux\_binfmt elf_format = { 
.module = THIS_MODULE, .load_binary
= load_elf_binary, .load_shlib = load_elf_library,
.core_dump = elf_core_dump,
.min_coredump = ELF_EXEC_PAGESIZE, 
};




void start\_thread(struct pt\_regs \*regs, unsigned long new_ip, unsigned long new_sp) {
set\_user\_gs(regs, 0);  // register,就是寄存器
regs->fs= 0; 
regs->ds= __USER_DS;  //设置为用户态
regs->es = __USER_DS; 
regs->ss = __USER_DS; 
regs->cs = __USER_CS; 
regs->ip  = new_ip;  //恢复
regs->sp  = new_sp;  // 恢复
regs->flags= X86_EFLAGS_IF;
force\_iret(); 
}
EXPORT\_SYMBOL\_GPL(start_thread);


**ramdisk 的作用:**内核就太大了,需要一个基于内存的文件系统,内存访问是不需要驱动的,这个就是 ramdisk。 这个时候,ramdisk 是根文件系统。ramdisk 上的 /init 会启动文件系统上的 init ,形成了用户态所有进程的祖 先

2号进程:

  • rest_init 第二大事情就是第三个进程,就是 2 号进程。kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES) 又一次使用 kernel_thread 函数创建进程。
  • 函数名 thread 可以翻译成“线程”
  • 有多个人并 行执行不同的部分,这就叫多线程(Multithreading),如果只有一个人,那它就是这个 项目的主线程。
  • 从内核态来看,无论是进程,还是线程,我们都可以统称为任务(Task),都使用相 同的数据结构,平放在同一个链表中。
  • 函数 kthreadd,负责所有内核态的线程的调度和管理,是内核态所有线程运行的祖先。

glibc 对系统调用的封装

Linux 还提供了glibc 这个中介。它更熟悉系统调用的细节,并且可以封装成更加友好的接口。

  • glibc 里面的 open 函数int open(const char *pathname, int flags, mode_t mode)
  • 在 glibc 的源代码中,有个文件 syscalls.list,里面列着所有 glibc 的函数对应的系统调用。 (下图只显示 open 的)
    在这里插入图片描述
  • glibc 还有一个脚本 make-syscall.sh.可以根据上面的配置文件,对于每一个封装 好的系统调用,生成一个文件。这个文件里面定义了一些宏,例如 #define SYSCALL_NAME open
T\_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS) ret  //伪代码 符号 名字 参数
T\_PSEUDO\_END (SYSCALL_SYMBOL) #define T\_PSEUDO(SYMBOL, NAME, N)
PSEUDO (SYMBOL, NAME, N)

PSEUDO 也是一个宏

#define PSEUDO(name, syscall\_name, args) .text;
ENTRY (name)
DO\_CALL (syscall_name, args); 
cmpl $-4095, %eax;
jae SYSCALL_ERROR_LABEL 


  • 里面对于任何一个系统调用,会调用 DO_CALL。这也是一个宏,这个宏 32 位和 64 位的 定义是不一样的

32 位系统调用过程

在这里插入图片描述

  • ENTER_KERNEL : # define ENTER_KERNEL int $0x80 int 就是 interrupt,也就是“中断”的意思。int $0x80 就是触发一个软中断,通过它就可以陷入(trap)内核.
set\_system\_intr\_gate(IA32_SYSCALL_VECTOR, entry_INT80_32);  // 系统启动时的 trap\_init


ENTRY(entry_INT80_32)//接收到一个系统调用的时候,entry\_INT80\_32 就被调用了
        ASM_CLAC
        pushl   %eax                    /\* pt\_regs->orig\_ax \*/
        SAVE_ALL pt_regs_ax=$-ENOSYS    /\* save rest \*/ //通过 push 和 SAVE\_ALL 将当前用户态的寄存器,保存在 pt\_regs 结构里面。
        movl    %esp, %eax  //内核之前,保存所有的寄存
        call    do_syscall_32_irqs_on  // 调用 do\_syscall\_32\_irqs\_on
/\*
//下面是 do\_syscall\_32\_irqs\_on

static \_\_always\_inline void do\_syscall\_32\_irqs\_on(struct pt\_regs \*regs) //
{
 struct thread\_info \*ti = current\_thread\_info();
 unsigned int nr = (unsigned int)regs->orig\_ax; // 将系统调用号从 eax 里面取出来
......
 if (likely(nr < IA32\_NR\_syscalls)) {
 //然后根据系统调用号,在系统调用 表中找到相应的函数进行调用,并将寄存器中保存的参数取出来,作为函数参数。
 regs->ax = ia32\_sys\_call\_table[nr]( //#define ia32\_sys\_call\_table sys\_call\_table,系统调用就是放在这个表里 面。
 (unsigned int)regs->bx, (unsigned int)regs->cx,
 (unsigned int)regs->dx, (unsigned int)regs->si,
 (unsigned int)regs->di, (unsigned int)regs->bp);
 }
 syscall\_return\_slowpath(regs);
}

\*/
.Lsyscall_32_done:
......
.Lirq_return:
	INTERRUPT_RETURN  //紧接着调用的是 INTERRUPT\_RETURN,我们能够找到它的定义,也就是 iret。
	//#define INTERRUPT\_RETURN iret
//iret 指令将原来用户态保存的现场恢复回来,包含代码段、指令指针寄存器等。这时候用户 态进程恢复执行。

32 位的系统调用的执行过程
在这里插入图片描述

64位

x86_64 下的 sysdep.h 文件

/\* The Linux/x86-64 kernel expects the system call parameters in
 registers according to the following table:
 syscall number rax
 arg 1 rdi
 arg 2 rsi
 arg 3 rdx
 arg 4 r10
 arg 5 r8
 arg 6 r9
......
\*/
#define DO\_CALL(syscall\_name, args) \


![img](https://img-blog.csdnimg.cn/img_convert/8ac4875a05cc356d54f7d8b3aa235ee3.png)
![img](https://img-blog.csdnimg.cn/img_convert/3b7cfa4654b0db94a2bc34ca369ae38b.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

 args) \


[外链图片转存中...(img-prTCiPpN-1715816986090)]
[外链图片转存中...(img-rLQQA2DW-1715816986091)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 27
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值