linux-0.11分析:init文件 main.c的第六个time_in()和第七个sched_init()初始化函数 第八篇随笔
6、第六个初始化函数, time_in()
参考 [github这个博主的 厉害][ https://github.com/sunym1993/flash-linux0.11-talk ]
先来看看这个函数吧
init文件 -> mian.c
static void time_init(void) { struct tm time; do { time.tm_sec = CMOS_READ(0); time.tm_min = CMOS_READ(2); time.tm_hour = CMOS_READ(4); time.tm_mday = CMOS_READ(7); time.tm_mon = CMOS_READ(8); time.tm_year = CMOS_READ(9); } while (time.tm_sec != CMOS_READ(0)); BCD_TO_BIN(time.tm_sec); BCD_TO_BIN(time.tm_min); BCD_TO_BIN(time.tm_hour); BCD_TO_BIN(time.tm_mday); BCD_TO_BIN(time.tm_mon); BCD_TO_BIN(time.tm_year); time.tm_mon--; startup_time = kernel_mktime(&time); }
可以看出这几行代码比较简单,大部分都是CMOS_READ、BCD_TO_BIN
所以现在需要找到这两个宏定义;就这个函数time_init
的上面
#define CMOS_READ(addr) ({ \ outb_p(0x80|addr,0x70); \ inb_p(0x71); \ }) #define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)
CMOS_READ
这个就是在对一个端口,out写一下,从另一个端口in读出来;
然后再看看最常见的就是读硬盘了,我们看硬盘的端口表。
端口 | 读 | 写 |
---|---|---|
0x1F0 | 数据寄存器 | 数据寄存器 |
0x1F1 | 错误寄存器 | 特征寄存器 |
0x1F2 | 扇区计数寄存器 | 扇区计数寄存器 |
0x1F3 | 扇区号寄存器或 LBA 块地址 0~7 | 扇区号或 LBA 块地址 0~7 |
0x1F4 | 磁道数低 8 位或 LBA 块地址 8~15 | 磁道数低 8 位或 LBA 块地址 8~15 |
0x1F5 | 磁道数高 8 位或 LBA 块地址 16~23 | 磁道数高 8 位或 LBA 块地址 16~23 |
0x1F6 | 驱动器/磁头或 LBA 块地址 24~27 | 驱动器/磁头或 LBA 块地址 24~27 |
0x1F7 | 命令寄存器或状态寄存器 | 命令寄存器 |
上面的CMOS_READ
也就在读取不同的端口号,来获取秒、分、时、日、月、年
然后在借助BCD_TO_BIN
转化成BIN,从CMOS获取的 是BCD码值需要转换为二进制数
然后就是这个函数kernel_mktime
通过时间戳的形式来获取时间,也就刚刚获取的这个时间数据,通过与1970年1月1日0时来比较获取时间戳
看看kernel_mktime
这段代码;具体的算法
kernel文件 -> mktime.c
long kernel_mktime(struct tm * tm) { long res; int year; year = tm->tm_year - 70; /* magic offsets (y+1) needed to get leapyears right.*/ res = YEAR*year + DAY*((year+1)/4); res += month[tm->tm_mon]; /* and (y+2) here. If it wasn't a leap-year, we have to adjust */ if (tm->tm_mon>1 && ((year+2)%4)) res -= DAY; res += DAY*(tm->tm_mday-1); res += HOUR*tm->tm_hour; res += MINUTE*tm->tm_min; res += tm->tm_sec; return res; }
返回的res就是时间戳
到这里time_in()
就结束了,那之后会被谁拿到这个时间戳就是看看后面那里需要了
7、第七个初始化函数,sched_init()
这个就是 大名鼎鼎的进程调度初始化
先看看这个函数
kernel文件 -> sched.c
void sched_init(void) { int i; struct desc_struct * p; if (sizeof(struct sigaction) != 16) panic("Struct sigaction MUST be 16 bytes"); set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss)); set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt)); p = gdt+2+FIRST_TSS_ENTRY; for(i=1;i<NR_TASKS;i++) { task[i] = NULL; p->a=p->b=0; p++; p->a=p->b=0; p++; } /* 清除NT,这样我们以后就不会有麻烦了 */ __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl"); ltr(0); lldt(0); outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */ outb_p(LATCH & 0xff , 0x40); /* LSB */ outb(LATCH >> 8 , 0x40); /* MSB */ set_intr_gate(0x20,&timer_interrupt); outb(inb_p(0x21)&~0x01,0x21); set_system_gate(0x80,&system_call); }
先看第一部分:
...... if (sizeof(struct sigaction) != 16) panic("Struct sigaction MUST be 16 bytes"); set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss)); set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
判断了一个东西,不等于就打印这一串字符
然后就是重要的部分,设置了TSS,LDT
先看看这两个set_tss_desc,set_ldt_desc
函数的宏定义
include文件 -> asm文件 -> system.h
#define set_tss_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x89") #define set_ldt_desc(n,addr) _set_tssldt_desc(((char *) (n)),addr,"0x82")
可以看出他们调用了同一个函数_set_tssldt_desc
知识最后参数不同;
继续看看这个函数_set_tssldt_desc
#define _set_tssldt_desc(n,addr,type) \ __asm__ ("movw $104,%1\n\t" \ "movw %%ax,%2\n\t" \ "rorl $16,%%eax\n\t" \ "movb %%al,%3\n\t" \ "movb $" type ",%4\n\t" \ "movb $0x00,%5\n\t" \ "movb %%ah,%6\n\t" \ "rorl $16,%%eax" \ ::"a" (addr), "m" (*(n)), "m" (*(n+2)), "m" (*(n+4)), \ "m" (*(n+5)), "m" (*(n+6)), "m" (*(n+7)) \ )
又是一段内嵌汇编代码
这是向前面的哪种gdt表
(全局描述符表)后面追加数据
再来看一下这个结构图吧
TSS 叫任务状态段,就是保存和恢复进程的上下文的 ;也就是当进程切换时把进程相关的数据也就是一些寄存器都放在这个TSS中
再看看tss_struct结构体
include文件 -> linux文件 -> sched.h
struct tss_struct { long back_link; /* 16 high bits zero */ long esp0; long ss0; /* 16 high bits zero */ long esp1; long ss1; /* 16 high bits zero */ long esp2; long ss2; /* 16 high bits zero */ long cr3; long eip; long eflags; long eax,ecx,edx,ebx; long esp; long ebp; long esi; long edi; long es; /* 16 high bits zero */ long cs; /* 16 high bits zero */ long ss; /* 16 high bits zero */ long ds; /* 16 high bits zero */ long fs; /* 16 high bits zero */ long gs; /* 16 high bits zero */ long ldt; /* 16 high bits zero */ long trace_bitmap; /* bits: trace 0, bitmap 16-31 */ struct i387_struct i387; }; struct task_struct { /* these are hardcoded - don't touch */ long state; /* -1 unrunnable, 0 runnable, >0 stopped */ long counter; long priority; long signal; struct sigaction sigaction[32]; long blocked; /* bitmap of masked signals */ /* various fields */ int exit_code; unsigned long start_code,end_code,end_data,brk,start_stack; long pid,father,pgrp,session,leader; unsigned short uid,euid,suid; unsigned short gid,egid,sgid; long alarm; long utime,stime,cutime,cstime,start_time; unsigned short used_math; /* file system info */ int tty; /* -1 if no tty, so it must be signed */ unsigned short umask; struct m_inode * pwd; struct m_inode * root; struct m_inode * executable; unsigned long close_on_exec; struct file * filp[NR_OPEN]; /* ldt for this task 0 - zero 1 - cs 2 - ds&ss */ struct desc_struct ldt[3]; /* tss for this task */ struct tss_struct tss; }; #define FIRST_TSS_ENTRY 4 #define FIRST_LDT_ENTRY (FIRST_TSS_ENTRY+1)
通过这个可以也看出来它储存了所有寄存器的信息方便另一个进程使用了,好把自己的执行的位置存储起来
而 LDT
叫局部描述符表,是与 GDT 全局描述符表
相对应的,内核态的代码用 GDT
里的数据段和代码段,而用户进程的代码用每个用户进程自己的LDT
里得数据段和代码段。
第二部分:
#define NR_TASKS 64 struct task_struct * task[NR_TASKS] = {&(init_task.task), }; ...... int i; struct desc_struct * p; ...... p = gdt+2+FIRST_TSS_ENTRY; for(i=1;i<NR_TASKS;i++) { task[i] = NULL; p->a=p->b=0; p++; p->a=p->b=0; p++; }
这个部分主要用循环干了两件事,
-
先是把长度为64的
task_struct
的全部赋值为NULL,task_struct
这个结构体比较复杂,它就代表着每一个进程的数据信息 -
然后把
gdt
后的数据全部置为0;也就是把剩余的TSS和LDT全部赋值为空;后面创建进程时候再使用
那么刚刚创建这一个TSS和LDT
会在将来给正在执行的这个代码作为一个进程指令
include文件 -> linux文件 -> head.h
typedef struct desc_struct { unsigned long a,b; } desc_table[256];
第三部分:
__asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl"); ltr(0); lldt(0);
ltr(0)和lldt(0)
;
ltr
是在给tr
寄存器赋值,tr
指向的是TSS
lldt
是在给ldt寄存器赋值,
,ldt
指向的LDT
如下图所示:
这样cpu
就可以获取到是哪个进程在执行了
第四部分:
outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */ outb_p(LATCH & 0xff , 0x40); /* LSB */ outb(LATCH >> 8 , 0x40); /* MSB */ set_intr_gate(0x20,&timer_interrupt); outb(inb_p(0x21)&~0x01,0x21); set_system_gate(0x80,&system_call);
四行outb_p
在读代码,两行set_xx
在设置中断
第一个中断是在设置时钟中断:0x20,timer_interrupt
这个程序;方便后面用户强制取消一下进程
第二个中断是在设置系统调用:0x80,system_call
这个程序;每一个用户需要调用内核程序提供的方法,都需通过这个中断进入
到目前为止,中断已经设置了不少了,我们现在看看所设置好的中断有哪些。
中断号 | 中断处理函数 |
---|---|
0 ~ 0x10 | trap_init 里设置的一堆 |
0x20 | timer_interrupt |
0x21 | keyboard_interrupt |
0x80 | system_call |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)