大家好,欢迎来到IT知识分享网。
最后的话
最近很多小伙伴找我要Linux学习资料,于是我翻箱倒柜,整理了一些优质资源,涵盖视频、电子书、PPT等共享给大家!
资料预览
给大家整理的视频资料:
给大家整理的电子书资料:
如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以点击这里获取!
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
- 使用 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 $0x80int 就是 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 指令将原来用户态保存的现场恢复回来,包含代码段、指令指针寄存器等。这时候用户 态进程恢复执行。
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) \ lea SYS\_ify (syscall\_name), %rax; \ syscall
与32位的区别 放到寄存器 rax
- 在系统初始化的时候,
trap_init除了初始化上面的中断模式,这里面还会调用cpu_init>syscall_init。这里面有这样的代码:`
wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64); rdmsr 和 wrmsr 是用来读写特殊模块寄存器 的 MSR_LSTAR 一个特殊的寄存器专门用来做系统调用 entry_SYSCALL_64 如下 ENTRY(entry_SYSCALL_64) /\* Construct struct pt\_regs on stack \*/ //这里先保存了很多寄存器到 pt\_regs 结构里面,例如用户态的代码段、数据段、保存参数 的寄存器 pushq $__USER_DS /\* pt\_regs->ss \*/ pushq PER\_CPU\_VAR(rsp_scratch) /\* pt\_regs->sp \*/ pushq %r11 /\* pt\_regs->flags \*/ pushq $__USER_CS /\* pt\_regs->cs \*/ pushq %rcx /\* pt\_regs->ip \*/ pushq %rax /\* pt\_regs->orig\_ax \*/ pushq %rdi /\* pt\_regs->di \*/ pushq %rsi /\* pt\_regs->si \*/ pushq %rdx /\* pt\_regs->dx \*/ pushq %rcx /\* pt\_regs->cx \*/ pushq $-ENOSYS /\* pt\_regs->ax \*/ pushq %r8 /\* pt\_regs->r8 \*/ pushq %r9 /\* pt\_regs->r9 \*/ pushq %r10 /\* pt\_regs->r10 \*/ pushq %r11 /\* pt\_regs->r11 \*/ sub $(6\*8), %rsp /\* pt\_regs->bp, bx, r12-15 not saved \*/ movq PER\_CPU\_VAR(current_task), %r11 testl $_TIF_WORK_SYSCALL_ENTRY|_TIF_ALLWORK_MASK, TASK\_TI\_flags(%r11) jnz entry_SYSCALL64_slow_path //然后调用 entry\_SYSCALL64\_slow\_pat->do\_syscall\_64 如下 ...... /\*调用 entry\_SYSCALL64\_slow\_pat->do\_syscall\_64 和 32位的差不多 \_\_visible void do\_syscall\_64(struct pt\_regs \*regs) { struct thread\_info \*ti = current\_thread\_info(); unsigned long nr = regs->orig\_ax; //从 rax 里面拿出系统调用号 ...... if (likely((nr & \_\_SYSCALL\_MASK) < NR\_syscalls)) { regs->ax = sys\_call\_table[nr & \_\_SYSCALL\_MASK]( //查表 regs->di, regs->si, regs->dx, regs->r10, regs->r8, regs->r9); //这些参数所对应的寄存器,和 Linux 的注释又是一样的。 } syscall\_return\_slowpath(regs); } \*/ entry_SYSCALL64_slow_path: /\* IRQs are off. \*/ SAVE_EXTRA_REGS movq %rsp, %rdi call do_syscall_64 /\* returns with IRQs disabled \*/ return_from_SYSCALL_64: RESTORE_EXTRA_REGS TRACE_IRQS_IRETQ movq RCX(%rsp), %rcx movq RIP(%rsp), %r11 movq R11(%rsp), %r11 ...... syscall_return_via_sysret: /\* rcx and r11 are already restored (see code above) \*/ RESTORE_C_REGS_EXCEPT_RCX_R11 movq RSP(%rsp), %rsp USERGS_SYSRET64 //64 位的系统调用返回的时候,执行的是 USERGS\_SYSRET64 //#define USERGS\_SYSRET64 swapgs; sysretq; 返回用户态的指令变成了 sysretq。
系统调用表
5 i386 open sys_open compat_sys_open
64 位的系统调用定义在另一个文件 arch/x86/entry/syscalls/syscall_64.tbl 里。例如 open 是这样定义的
2 common open sys_open
- 系统调用在内核中的实现函数要有一个
声明。声明往往在include/linux/syscalls.h文件 中。
asmlinkage long sys\_open(const char __user \*filename, int flags, umode\_t mode);
最全的Linux教程,Linux从入门到精通
======================
- linux从入门到精通(第2版)
- Linux系统移植
- Linux驱动开发入门与实战
- LINUX 系统移植 第2版
- Linux开源网络全栈详解 从DPDK到OpenFlow
第一份《Linux从入门到精通》466页
====================
内容简介
====
本书是获得了很多读者好评的Linux经典畅销书《Linux从入门到精通》的第2版。本书第1版出版后曾经多次印刷,并被51CTO读书频道评为“最受读者喜爱的原创IT技术图书奖”。本书第﹖版以最新的Ubuntu 12.04为版本,循序渐进地向读者介绍了Linux 的基础应用、系统管理、网络应用、娱乐和办公、程序开发、服务器配置、系统安全等。本书附带1张光盘,内容为本书配套多媒体教学视频。另外,本书还为读者提供了大量的Linux学习资料和Ubuntu安装镜像文件,供读者免费下载。
本书适合广大Linux初中级用户、开源软件爱好者和大专院校的学生阅读,同时也非常适合准备从事Linux平台开发的各类人员。
需要《Linux入门到精通》、《linux系统移植》、《Linux驱动开发入门实战》、《Linux开源网络全栈》电子书籍及教程的工程师朋友们劳烦您转发+评论
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以点击这里获取!
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
mg_convert/9d4aefb6a92edea27b825e59aa1f2c54.png)
本书适合广大Linux初中级用户、开源软件爱好者和大专院校的学生阅读,同时也非常适合准备从事Linux平台开发的各类人员。
需要《Linux入门到精通》、《linux系统移植》、《Linux驱动开发入门实战》、《Linux开源网络全栈》电子书籍及教程的工程师朋友们劳烦您转发+评论
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以点击这里获取!
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/117190.html











