本文参考书籍

1.操作系统真相还原
2.Linux内核完全剖析:基于0.12内核
3.x86汇编语言  从实模式到保护模式
4.Linux内核设计的艺术
ps:基于x86硬件的pc系统

Linux0.12初始化续

在上一篇博文中根据main函数的执行;

void main(void)     /* This really IS void, no error here. */
{           /* The startup routine assumes (well, ...) this */...sti();                                              // 开启中断move_to_user_mode();                                // 移到用户态执行if (!fork()) {      /* we count on this going ok */  // 在新建的子进程中执行init()函数init();}
/**   NOTE!!   For any other task 'pause()' would mean we have to get a* signal to awaken, but task0 is the sole exception (see 'schedule()')* as task 0 gets activated at every idle moment (when no other tasks* can run). For task0 'pause()' just means we go check if some other* task can run, and if not we return here.*/for(;;)__asm__("int $0x80"::"a" (__NR_pause):"ax");   // 任务0倍调度时执行系统调用pause
}

此时我们继续从开启中断往下执行。

开启中断

sti函数主要是宏定义

#define sti() __asm__ ("sti"::)

内联汇编直接就开启中断;
当开启中断后,此时再前面初始化流程中的,如时间中断、硬盘中断、系统调用等中断都可以得到响应。

切换到用户态执行

move_to_user_mode函数就是切换到用户态执行,即从特权级0代码转移到特权级3的代码去运行;

除了进程0之外,所有进程都是由一个父进程在用户态下完成创建的,为了遵守这一规则,在进程0正式创建进程1之前,要将进程0由内核态转变为用户态。该函数使用的方法就是模拟中断调用返回过程,即利用iret指令来实现特权级的变更和堆栈的切换,从而把CPU执行控制流移动到进程0的环境中去运行,使用这种方法进行控制的转移是因为CPU保护机制造成,CPU允许低特权级的代码通过调用们或中断、陷阱门来调用或转移到高级别的代码中运行,但是从高特权级向低特权级转移则不行,所以内核采用了模拟iret返回低级别代码的方法。

#define move_to_user_mode() \
__asm__ ("movl %%esp,%%eax\n\t" \               //  保存堆栈指针esp到eax寄存器中"pushl $0x17\n\t" \                        //  将堆栈段选择符(SS)入栈"pushl %%eax\n\t" \                         //  保存堆栈指针值(esp)入栈"pushfl\n\t" \                              //  将标志寄存器内容入栈"pushl $0x0f\n\t" \                        //  将Task0代码段选择符(cs)入栈"pushl $1f\n\t" \                          //  将标号为1的偏移地址入栈"iret\n" \                                  //  执行返回中断命令,则会跳入标号为1处执行"1:\tmovl $0x17,%%eax\n\t" \               //  此时开始执行Task0"mov %%ax,%%ds\n\t" \                       //  初始化段寄存器"mov %%ax,%%es\n\t" \"mov %%ax,%%fs\n\t" \"mov %%ax,%%gs" \:::"ax")

在‘’pushl $0x17‘’代表是ss段选择符,0x17中的7用二进制表示为00010111,最后两位是11为3,所以是用户特权级,倒数第三位为1,表示从LDT中获取描述符,第4到5位从LDT的第3项中得到有关用户栈的描述符,同理“pushl $0f”代表的是cs段选择符,也是用户特权级,从LDT的第2项取得用户态代码段描述符,当itet时,硬件把压栈的值恢复,此时返回执行的程序特权级就是用户态特权级。

进程0是一个特殊的进程,它的数据段和代码段直接映射到内核代码和数据空间,即从物理地址0开始的640KB的内存空间,其堆栈地址即是内核代码所使用的堆栈,因此堆栈中原SS的eip是将现有内核堆栈指针直接压入堆栈的。并且进程0任务设置成0可运行状态。

创建进程1

此时进行系统调用fork(),此时通过宏定义展开,此时会执行’int 0x80, 2’,此时会调用已经注册的system_call函数,该函数位于kernel/sys_call.s中;

.align 2
bad_sys_call:pushl $-ENOSYSjmp ret_from_sys_call
.align 2
reschedule:pushl $ret_from_sys_calljmp _schedule
.align 2
_system_call:push %ds                                    # 保存原段寄存器值push %espush %fspushl %eax      # save the orig_eax         # 保存eax原值pushl %edx      pushl %ecx      # push %ebx,%ecx,%edx as parameters  将ebx,ecx,edx三个寄存器保存传入参数值pushl %ebx      # to the system call                    # ebx为第一个参数,ecx为第二个,edx为第三个movl $0x10,%edx        # set up ds,es to kernel space      # 将ds、es指向内核数据段mov %dx,%dsmov %dx,%esmovl $0x17,%edx        # fs points to local data space     # fs指向本次调用的用户程序的数据段mov %dx,%fscmpl _NR_syscalls,%eax                                  # 如果调用号超出范围就跳转jae bad_sys_call                                        # 跳转到错误系统调用处理函数call _sys_call_table(,%eax,4)                           # 调用sys_call_table中的函数为[_sys_call_table + eax*4]处对应的函数pushl %eax                                              # 系统调用号入栈
2:movl _current,%eax                                      # 取当前任务的结构指针给eaxcmpl $0,state(%eax)        # state                         # 获取当前任务的状态jne reschedule                                          # 如果不等于0(运行态),就重写调用cmpl $0,counter(%eax)      # counter                   # 如果时间片用完页执行调用je reschedule
ret_from_sys_call:movl _current,%eaxcmpl _task,%eax         # task[0] cannot have signals   # 比较当前任务是否是任务0,因为任务0在数组第一个je 3f                                                   #  如果是任务0则直接跳转到3处返回cmpw $0x0f,CS(%esp)        # was old code segment supervisor ?  # 比较调用程序是否是用户态程序jne 3f                                                       # 如果不是则直接跳转返回cmpw $0x17,OLDSS(%esp)     # was stack segment = 0x17 ?     # 比较堆栈选择符不是0x17,则直接跳转返回jne 3fmovl signal(%eax),%ebx                                       # 获取当前任务的信号位图movl blocked(%eax),%ecx                                      # 获取当前任务的阻塞位图notl %ecx                                                    # 每位取反andl %ebx,%ecxbsfl %ecx,%ecxje 3f                               btrl %ecx,%ebxmovl %ebx,signal(%eax)incl %ecxpushl %ecx                                                  # 信号值入栈作为do_signal参数call _do_signal                                             # 调用kernel/signal.c中的do_signal处理popl %ecx                                                   # 弹出入栈的信号值testl %eax, %eax                                            # 测试返回值jne 2b      # see if we need to switch tasks, or do more signals
3:  popl %eax                                                   # 弹出保存的入栈值popl %ebxpopl %ecxpopl %edxaddl $4, %esp  # skip orig_eax                             # 跳过原有的eax值pop %fspop %espop %dsiret 

通过该处理函数查找到_sys_fork函数;

.align 2
_sys_fork:call _find_empty_process                    # 为新进程取得进程号testl %eax,%eax                             # 测试取得的进程号如果返回为负数则跳转返回js 1fpush %gs                                    # 压入参数pushl %esipushl %edipushl %ebppushl %eaxcall _copy_process                          # 调用copy_process函数addl $20,%esp                                  # 丢弃压栈的五个参数值
1:  ret

此时我们继续查看kernel/fork.c代码中的find_empty_process函数;

int find_empty_process(void)
{int i;repeat:if ((++last_pid)<0) last_pid=1;                         // last_pid全局变量,排除last_pid为0for(i=0 ; i<NR_TASKS ; i++)                             // 操作系统最多支持64个任务if (task[i] && ((task[i]->pid == last_pid) ||(task[i]->pgrp == last_pid)))           // 如果查找到的任务号再使用,就继续查找goto repeat;for(i=1 ; i<NR_TASKS ; i++)if (!task[i])                                           // 返回除了任务0之外的可用任务号return i;return -EAGAIN;
}

此时,先查找到可用的任务号,即task数组中可用的项,当找到任务号之后调用copy_process函数;

int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,long ebx,long ecx,long edx, long orig_eax, long fs,long es,long ds,long eip,long cs,long eflags,long esp,long ss)
{struct task_struct *p;int i;struct file *f;p = (struct task_struct *) get_free_page();   // 为新任务数据结构分配内存if (!p)return -EAGAIN;task[nr] = p;                                // 在find_empty_process中找到的空闲任务下标*p = *current;  /* NOTE! this doesn't copy the supervisor stack */ p->state = TASK_UNINTERRUPTIBLE;             // 设置成不可中断p->pid = last_pid;                           // 设置进程的pidp->counter = p->priority;                    // 运行时间片的值p->signal = 0;                               // 信号位图p->alarm = 0;                                // 报警定时值p->leader = 0;      /* process leadership doesn't inherit */p->utime = p->stime = 0;                     // 用户态和核心态的运行时间p->cutime = p->cstime = 0;                   // 子进程用户态和核心态的运行时间p->start_time = jiffies;                     // 进程开始运行时间p->tss.back_link = 0;                        p->tss.esp0 = PAGE_SIZE + (long) p;          // 任务内核态栈指针p->tss.ss0 = 0x10;                           // 内核态栈的段选择符(与内核态数据段相同)p->tss.eip = eip;                            // 指令代码指针p->tss.eflags = eflags;                      // 标志寄存器p->tss.eax = 0;                              // 这就是fork返回时新进程会返回0p->tss.ecx = ecx;                            // 设置相应的寄存器值p->tss.edx = edx;p->tss.ebx = ebx;p->tss.esp = esp;p->tss.ebp = ebp;p->tss.esi = esi;p->tss.edi = edi;p->tss.es = es & 0xffff;                     // 设置段寄存器值p->tss.cs = cs & 0xffff;p->tss.ss = ss & 0xffff;p->tss.ds = ds & 0xffff;p->tss.fs = fs & 0xffff;p->tss.gs = gs & 0xffff;p->tss.ldt = _LDT(nr);                       // 设置任务局部表描述符的选择符p->tss.trace_bitmap = 0x80000000;            if (last_task_used_math == current)          // 是否使用数字协处理器__asm__("clts ; fnsave %0 ; frstor %0"::"m" (p->tss.i387));if (copy_mem(nr,p)) {                        // 复制进程页表,即在线性地址空间中设置新任务代码段和数据段描述符中的基址和限长,并复制页表task[nr] = NULL;                         // 如果复制失败,则置空free_page((long) p);                     // 失败并释放页表return -EAGAIN;}for (i=0; i<NR_OPEN;i++)                     // 如果父进程中有文件打开,则将对应文件的打开次数增1if (f=p->filp[i])                        // 因为子进程会与父进程共享打开的文件f->f_count++;if (current->pwd)                            // 父进程的pwd,root,executable都加一current->pwd->i_count++;if (current->root)current->root->i_count++;if (current->executable)current->executable->i_count++;if (current->library)current->library->i_count++;set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));   // 设置tss在全局描述符中set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));   // 设置ldt在全局描述符中p->p_pptr = current;                                   // 设置子进程的父进程p->p_cptr = 0;                                         // 复位子进程的最新子进程指针p->p_ysptr = 0;                                        // 复位子进程的比邻兄弟进程p->p_osptr = current->p_cptr;                          // 设置子进程的兄弟指针if (p->p_osptr)p->p_osptr->p_ysptr = p;                           // 如果父进程有兄弟进程,则让兄弟进程指向新进程current->p_cptr = p;                                   // 设置父进程的最新子进程p->state = TASK_RUNNING;    /* do this last, just in case */   // 此时当前子进程加入调用return last_pid;
}

在系统调用完成后,会返回到sys_call.s中的ret_from_sys_call,继续执行相关信号位图的相关处理后,就完成系统调用。

此时系统调用fork完成后就生成了一个子进程执行,此时该子进程执行的任务就是执行init()函数,父进程就执行;

    for(;;)__asm__("int $0x80"::"a" (__NR_pause):"ax");  

进程0被调度的时候就直接执行sys_pause的系统调用,该系统调用如下;

int sys_pause(void)
{current->state = TASK_INTERRUPTIBLE;     // 设置当前任务为可中断调度schedule();                              // 重新调度任务return 0;
}

此时进程0就是空闲的时候的被调度的任务,子进程执行init()的流程留待下文分析。

操作系统学习:Linux0.12初始化详细流程-首个子进程相关推荐

  1. 操作系统学习:Linux0.12初始化详细流程-打开文件与加载可执行程序

    本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 4.Linux内核设计的艺术 ps:基于x86硬件的pc系统 Linux0.1 ...

  2. 操作系统学习:Linux0.12初始化详细流程-进程1加载虚拟盘和根文件系统安装

    本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 4.Linux内核设计的艺术 ps:基于x86硬件的pc系统 Linux0.1 ...

  3. 操作系统学习:Linux0.12初始化详细流程-进程1调度与读取硬盘数据

    本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 4.Linux内核设计的艺术 ps:基于x86硬件的pc系统 Linux0.1 ...

  4. 操作系统学习:Linux0.12初始化详细流程-进程退出与系统进入怠速

    本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 4.Linux内核设计的艺术 ps:基于x86硬件的pc系统 Linux0.1 ...

  5. 操作系统学习:系统调用与Linux0.12初始化详细流程

    本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 4.Linux内核设计的艺术 ps:基于x86硬件的pc系统 系统调用 系统调 ...

  6. 操作系统学习:进程、线程与Linux0.12初始化过程概述

    本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 ps:基于x86硬件的pc系统 进程 进程是一种控制流集合,集合中至少包含一条 ...

  7. 操作系统学习体会之进程管理篇

    计算机基础知识的学习中,操作系统则是重中之重.继对微机原理和计算机组成原理的基础知识了解和学习后,对硬件和基础原理的理论有了初步的了解,结合在所在公司的项目中开发应用的经历和体会,进行了操作系统的学习 ...

  8. 操作系统学习:Linux0.12文件异步IO

    本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 4.Linux内核设计的艺术 ps:基于x86硬件的pc系统 Linux0.1 ...

  9. TCP/IP学习(30)——L2数据链路层的数据包处理详细流程

    原文地址:TCP/IP学习(30)--L2数据链路层的数据包处理详细流程 作者:GFree_Wind 本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝 ...

最新文章

  1. TCP拥塞控制算法内核实现剖析(二)
  2. Windows~KinectV2开发
  3. Python面向对象程序设计之抽象工厂模式之二-一个更加pythonic的抽象工厂
  4. php之判断点在多边形内的api
  5. stm32for循环几个机械周期_波浪理论之五:循环周期理论
  6. LT8920无线通讯程序
  7. float,double等精度丢失问题
  8. 脚本在流程中的性能影响
  9. 判断语句_如何学好C语言判断语句?攻略if语句是第一步
  10. (21)FPGA资源共享
  11. SVN太旧,要更新问题
  12. Xamarin 技术全解析
  13. mac 文字识别软件ocr_树洞OCR文字识别for Mac-树洞OCR文字识别Mac版下载 V1.2.0-PC6苹果网...
  14. 织梦列表页list标签调用支持flag属性方法
  15. 重难点详解-关系代数表达式
  16. 和周杰讨论:DB2连接问题
  17. HDLBits刷题Day6
  18. Unity网页插件Embedded Browser(ZFBrowser)打包文件无法加载本地网页或网站网址解决方法
  19. 工业互联网新发展:基于 HTML5 WebGL 的高炉炼铁厂可视化系统!
  20. CAEE2023精密冲压件/五金件、精密冲床、模具展览会

热门文章

  1. 做 Java 工程师,挺!好!
  2. 重磅日程公布!与百名大咖在线交流技术,2天20个AI论坛不可错过
  3. 一包烟钱买到电动剃须刀,小米有品告诉你什么叫性价比
  4. 只服这篇“神文”:基于老子哲学、相对论的超级人工智能模型
  5. 通俗理解PCA降维作用
  6. 能在不同的深度学习框架之间转换模型?微软的MMdnn做到了
  7. 京东金融晒 “打黑成绩单”:一年内避免用户损失上亿元
  8. 又出现依赖冲突?试试 IDEA 解决 Maven 依赖冲突的高能神器!
  9. Netty结合Protostuff传输对象案例,单机压测秒级接收35万个对象
  10. SpringBoot配置文件放在jar外部