标签:red inf 只读 eth 指令 怎么办 sem lookup link
初始化ds,es和ss等段寄存器为0
使能A20门,其中seta20.1写数据到0x64端口,表示要写数据给8042芯片的Output Port;seta20.2写数据到0x60端口,把Output Port的第2位置为1,从而使能A20门。
gdt:
SEG_NULLASM # null seg
SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff) # code seg for bootloader and kernel
SEG_ASM(STA_W, 0x0, 0xffffffff) # data seg for bootloader and kernel
使用lgdt gdtdesc
将gdt的地址加载到GDTR寄存器中
movl %cr0, %eax
orl $CR0_PE_ON, %eax
movl %eax, %cr0
执行ljmp $PROT_MODE_CSEG, $protcseg
从而进入保护模式,其中PROT_MODE_CSEG为8,即代码段的段选择子,执行ljmp时会将cs寄存器设置为PROT_MODE_CSEG=8.
# Set up the protected-mode data segment registers
movw $PROT_MODE_DSEG, %ax # Our data segment selector
movw %ax, %ds # -> DS: Data Segment
movw %ax, %es # -> ES: Extra Segment
movw %ax, %fs # -> FS
movw %ax, %gs # -> GS
movw %ax, %ss # -> SS: Stack Segment
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x001000 0xc0100000 0xc0100000 0x15650 0x15650 R E 0x1000
LOAD 0x017000 0xc0116000 0xc0116000 0x05000 0x05f28 RW 0x1000
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10
readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
以及内核文件的program headers信息,可知bootloader分别将代码段和数据段加载到物理地址为0x100000和0x116000的位置。调用pic_init函数,初始化8259A可编程中断控制器芯片,包括主片和从片,需要严格按照一定的顺序写入ICW1~ICW4这四个初始化命令字
for (pos = 0; pos < 256; pos++) {
SETGATE(idt[pos], 0, GD_KTEXT, __vectors[pos], DPL_KERNEL);
}
SETGATE(idt[T_SYSCALL], 1, GD_KTEXT, __vectors[T_SYSCALL], DPL_USER);
lidt(&idt_pd);
加载IDT的地址到IDTR寄存器中无论是外部中断、异常还是系统调用,都统一采用了中断机制进行处理。简言之,就是CPU检查到有中断发生后,根据中断号索引中断向量表,得到中断处理例程的地址,跳到中断处理例程中进行处理。
vectors (kern/trap/vectors.S 中断向量表,存有所有中断向量的地址) ->
vector[k] (第k个中断的入口处理函数) ->
__alltraps (kern/trap/trapentry.S) ->
trap (kern/trap/trap.c) ->
trap_dispatch
__trapret
struct trapframe {
struct pushregs tf_regs;
uint16_t tf_gs;
uint16_t tf_padding0;
uint16_t tf_fs;
uint16_t tf_padding1;
uint16_t tf_es;
uint16_t tf_padding2;
uint16_t tf_ds;
uint16_t tf_padding3;
uint32_t tf_trapno;
/* below here defined by x86 hardware */
uint32_t tf_err;
uintptr_t tf_eip;
uint16_t tf_cs;
uint16_t tf_padding4;
uint32_t tf_eflags;
/* below here only when crossing rings, such as from user to kernel */
uintptr_t tf_esp;
uint16_t tf_ss;
uint16_t tf_padding5;
} __attribute__((packed));
系统调用初始化:在idt_init函数实现中,可以看到在执行加载中断描述符表lidt指令前,专门设置了一个特殊的中断描述符idt[T_SYSCALL],它的特权级设置为DPL_USER,中断向量处理地址在__vectors[T_SYSCALL]处。这样建立好这个中断描述符后,一旦用户进程执行“INTT_SYSCALL”后,由于此中断允许用户态进程产生(注意它的特权级设置为DPL_USER) ,所以CPU就会从用户态切换到内核态,保存相关寄存器,并跳转到__vectors[T_SYSCALL]处开始执行。
系统调用接口封装:在操作系统中初始化好系统调用相关的中断描述符、中断处理起始地址等后,还需在用户态的应用程序中初始化好相关工作,简化应用程序访问系统调用的复杂性。为此在用户态建立了一个中间层,即简化的libc实现,在user/libs/ulib.[ch]和user/libs/syscall.[ch]中完成了对访问系统调用的封装。用户态最终的访问系统调用函数是syscall。
safe_read(int fd, void *data, size_t len) ->
read(fd, data, len) ->
sys_read(fd, data, len) ->
syscall(SYS_read, fd, data, len) ->
"int %1;"
: "=a" (ret)
: "i" (T_SYSCALL),
"a" (num),
"d" (a[0]),
"c" (a[1]),
"b" (a[2]),
"D" (a[3]),
"S" (a[4])
: "cc", "memory");
执行int指令后,硬件检测到中断,保存ss/esp/eflags/cs/eip后,进入软件处理流程(详见上节):vectors.S -> __alltraps -> trap -> trap_dispatch,最终进入trap_dispatch函数。
int num = tf->tf_regs.reg_eax;
if (num >= 0 && num < NUM_SYSCALLS) {
if (syscalls[num] != NULL) {
arg[0] = tf->tf_regs.reg_edx;
arg[1] = tf->tf_regs.reg_ecx;
arg[2] = tf->tf_regs.reg_ebx;
arg[3] = tf->tf_regs.reg_edi;
arg[4] = tf->tf_regs.reg_esi;
tf->tf_regs.reg_eax = syscalls[num](arg);
return ;
}
}
outb(TIMER_MODE, TIMER_SEL0 | TIMER_RATEGEN | TIMER_16BIT);
outb(IO_TIMER1, TIMER_DIV(100) % 256);
outb(IO_TIMER1, TIMER_DIV(100) / 256);
在80386系统中,通常采用Intel 8042(或8742)单片机作为主机的键盘接口安置在主机上。8042内部含有1个8位的CPU、2个8位的并行端口、2KB ROM、128B RAM、1个输入缓冲寄存器、1个输出缓冲寄存器、1个状态缓冲寄存器、1个命令缓冲寄存器。其中,输出并行端口有一位是输出缓冲器满IRQ,用来向主机发中断请求。
键盘中断初始化,主要是使能键盘中断对应的掩码。
键盘中断响应:在trap_dispatch函数中,当检查到trap no等于键盘中断对应的中断号33时,说明发生了键盘中断,此时读取键盘输入的字符并打印。
通过BIOS中断调用INT 15
来探测可用的物理内存空间,中断调用时需要设置eax等寄存器,返回值也保存在这些寄存器中。最终探测到的物理内存空间如下所示:
e820map:
memory: 0009fc00, [00000000, 0009fbff], type = 1.
memory: 00000400, [0009fc00, 0009ffff], type = 2.
memory: 00010000, [000f0000, 000fffff], type = 2.
memory: 07ee0000, [00100000, 07fdffff], type = 1.
memory: 00020000, [07fe0000, 07ffffff], type = 2.
memory: 00040000, [fffc0000, ffffffff], type = 2.
bootloader加载完内核OS后,进入到内核OS的入口kern_entry
创建一个页目录表boot_pgdir。一个页目录表刚好占用一个物理页,其中每个页目录项占用4字节,共有1K个页目录项。在kern/init/entry.S文件中只设置了两个有效页目录项,分别将0 ~ 0x00400000, 0xc0000000 ~ 0xc0400000映射到0 ~ 0x00400000.
创建一个页表boot_pt1。在kern/init/entry.S文件中创建了一个页表boot_pt1,并且初始化其中每一个页表项:将其物理地址设置为0~4M物理内存中对应的物理页的地址,并且标记对应物理页的属性为已存在(PTE_P)并且可写(PTE_W)。
kern_entry的开头将页目录表的地址加载到cr3寄存器,并设置cr0寄存器以使能保护模式。
设置好cr0和cr3寄存器后,将页目录表的第一项清零,以取消虚拟地址0~4M到物理地址0~4M的映射。(为什么要取消这个映射?)
通过查看memmap结构体的内容,来确认最大可用物理内存地址maxpa。gdb调试时观察到maxpa = 0x07fe0000.
创建一个pages数组,其起始位置为内核文件的.bss段的后面(亦即end对应的地址),元素数目npage为0~maxpa这段内存空间对应的页数。并且将freemem设置为空闲内存的起始位置。gdb调试观察到npage=0x7fe0,freemem=0x001bc000.
将pages数组的每个元素的reserved标志位均设置为1,即将每一页初始化为预留给内核使用,后面会重新设置可用的物理页的reserved标志位为0.
从freemem对应的地址开始寻找所有可用的空闲内存块。由上文可知探测到的可用物理内存共2块,范围分别为0x0~0x0009fc00和0x00100000~0x07fe0000.由于第一个内存块在freemem前面,因此实际上只找到一块空闲物理内存,范围是0x00100000~0x07fe0000。在这块空闲物理内存的第一页对应的Page结构中设置页数等属性,并添加到空闲列表free_list中。
pmm_init中调用boot_map_segment函数,为0xc0000000~0xf8000000这段虚拟地址的每个虚拟页创建对应的页目录项和页表项,从而将0~0x38000000这段物理内存映射到0xc0000000~0xf8000000.由于entry.S只创建了一个页表,当一个虚拟页的起始地址找不到对应的页表时,需要分配一个物理页来建立对应的页表。这个是在get_pte函数中实现的。
get_pte函数根据虚拟地址返回对应的页表项。首先根据虚拟地址的最高10位索引页目录表,确认对应的页表是否存在。若存在,则根据虚拟地址的中间10位,返回该页表中对应页表项的地址;若不存在,则先分配一个物理页作为页表,再返回该页表项的地址。
page_remove_pte根据虚拟地址删除对应的页表项。如果对应的页表项指向的物理页的引用计数减至0,则释放对应物理页。然后将对应页表项清零以表示无效。
首先判断待申请的页数n是否大于空闲页的总数nr_free,若是则返回NULL
遍历空闲列表free_list,若无法找到一个页数不小于n的空闲内存块,则返回NULL
若找到的空闲块的页数刚好等于n,则直接从free_list删除当前空闲块的page_link;若空闲的页数大于n,则在free_list中当前空闲块的Page_link后面先插入一个新的page_link,其对应的内存块的页数为当前空闲块的页数减去n,然后再删除当前空闲块的page_link。
注:这里先插入新page_link再删除当前page_link的原因是:为了保证空闲列表中的内存块按照地址从小到大排序,需要将当前空闲块分配n页剩余的空闲块仍插回到原位置,如果先删除当前空闲块,将无法直接得到插入位置,因此先插入再删除。
找到空闲块后,还要将其Property属性清零,并更新空闲页的数目nr_free(减少n页)
遍历待释放内存块的每一页,将其flags和property清零
遍历空闲列表,若找到与待释放内存块相邻的内存块,则将它们合并。注意一共有4种情况:仅左边有相邻空闲块、仅右边有相邻空闲块、左右均有相邻空闲块、左右均无相邻空闲块。
释放内存块后,还需要更新空闲页的数目nr_free(增加n页)
ts.ts_esp0 = esp0;
,可见是设置trap frame中的esp0字段。esp0是指内核态下(此时特权级为0)的esp寄存器的值。如果在用户态下发生中断,会将特权级由3切换到0,这时CPU需要知道特权级0下的内核栈在哪里,而trap frame提供了这个信息。因此需要提前初始化trap frame,并在gdt中设置好TSS段描述符,方便找到trap frame;为了加速寻找trap frame的过程,CPU还专门提供一个寄存器TR来保存TSS的段地址,因此在初始化时也要提前将TSS的段地址加载到TR寄存器中。疑问:为什么判断到n > 1或swap_init_ok == 0时退出while循环?
if (page != NULL || n > 1 || swap_init_ok == 0) break;
// the control struct for a set of vma using the same PDT
struct mm_struct {
list_entry_t mmap_list; // linear list link which sorted by start addr of vma
struct vma_struct *mmap_cache; // current accessed vma, used for speed purpose
pde_t *pgdir; // the PDT of these vma
int map_count; // the count of these vma
void *sm_priv; // the private data for swap manager
};
// the virtual continuous memory area(vma), [vm_start, vm_end),
// addr belong to a vma means vma.vm_start<= addr <vma.vm_end
struct vma_struct {
struct mm_struct *vm_mm; // the set of vma using the same PDT
uintptr_t vm_start; // start addr of vma
uintptr_t vm_end; // end addr of vma, not include the vm_end itself
uint32_t vm_flags; // flags of vma
list_entry_t list_link; // linear list link which sorted by start addr of vma
};
vma_struct是一个连续的虚拟内存块,mm_struct是一系列连续的内存块的集合,而且这些内存块使用的是同一个页目录表。通过往mm_struct的mmap_list链表添加或删除vma_struct的list_link来实现对vma的管理。
insert_vma_struct实现以下功能:在mm->mmap_list链表中找到vm_start不大于vma的vm_start的最后一个节点,把vma插到该节点后面,以保证mmap_list仍然按照地址从小到大排序。此外,在插入时还要检查vma之间地址不重叠。
创建一个mm,将其页目录表地址设置为boot_pgdir的地址
创建一个vma,虚拟地址范围为0~4M,然后将其插入到mm的mmap_list中
访问虚拟地址为0x100~0x163的内存空间,这时由于页目录表中不存在虚拟地址为0~4M的页目录项,换言之尚未建立虚拟地址为0~4M到物理地址的映射,因此会导致缺页异常。
CPU访问的虚拟地址尚未与物理地址建立映射,从而导致缺页异常。比如check_pgfault中访问虚拟地址0x100.
缺页异常对应的中断向量号为14,CPU根据中断向量表得到中断向量号14对应的中断处理例程。
其实所有中断处理例程都是先将中断号入栈,然后跳转到alltraps函数。alltraps函数只是将通用寄存器和段寄存器等入栈,然后调用trap函数。trap函数直接调用trap_dispatch函数。
trap_dispatch函数根据不同中断号trapno进行不同的处理。当判断到中断号为T_PGFLT(14)时,则调用pgfault_handler函数。
pgfault_handler然后调用do_pgfault进行具体的缺页异常处理。注意cr2寄存器保存引起缺页异常的内存地址。
备份内存环境变量,包括空闲内存块数目count、空闲页面数目total。
创建mm和vma,其中vma对应的虚拟地址范围为0x1000~0x6000,共5个虚拟页面。
为虚拟地址0x1000建立页表,其间需要申请一个物理页。
setup Page Table for vaddr 0X1000, so alloc a page setup Page Table vaddr 0~4MB OVER!
申请分配4个物理页,然后又将其释放。
check_content_set:分别访问a,b,c,d等4个虚拟页,每个虚拟页访问两个地址。访问每个虚拟页的第一个地址时,由于该虚拟页尚未与物理页建立映射,导致缺页异常。在缺页异常处理函数中,申请分配物理页,填写页表,建立虚拟页到物理页的映射后,重新访问一次虚拟页就能成功了。
set up init env for check_swap begin! page fault at 0x00001000: K/W [no page found]. page fault at 0x00002000: K/W [no page found]. page fault at 0x00003000: K/W [no page found]. page fault at 0x00004000: K/W [no page found]. set up init env for check_swap over!
检查页表项是否正确建立
fifo_check_swap:检查页面置换是否成功。依次访问页面c,a,d,b,e等5个虚拟页,当访问到虚拟页e时,同样由于该虚拟页尚未与物理页建立映射,导致缺页异常。在缺页异常处理函数,申请分配物理页。但由于物理页只有4个,且已经分别与虚拟页a,b,c,d建立映射,无法为虚拟页e申请物理页,这时需要将一个物理页换出到磁盘中。
write Virt Page c in fifo_check_swap write Virt Page a in fifo_check_swap write Virt Page d in fifo_check_swap write Virt Page b in fifo_check_swap write Virt Page e in fifo_check_swap page fault at 0x00005000: K/W [no page found].
swap_out:首先选出要置换的页面,将其内容写回到磁盘(疑问:无论页面是否修改都进行写回,这样是否必要?)。根据FIFO算法,将最早进入物理内存的虚拟页a写到swap区域2.
swap_out: i 0, store page in vaddr 0x1000 to disk swap entry 2
swap_in:当再次访问虚拟页a时,发现物理页已用完且找不到a对应的物理页。因此首先根据FIFO算法,将当前最早进入物理内存的虚拟页b写到swap区域3(疑问:怎么确保swap区域3尚未被占用?),再从swap区域2将虚拟页a读入到物理内存。
为什么idleproc不需要设置进程现场保存信息(context和tf)?
为什么idleproc不需要添加到进程链表proc_list和哈希链表hash_list中?
第二个内核线程initproc是由idleproc线程在proc_init函数中调用kernel_thread函数来创建的,调用kernel_thread函数时,传入的前两个参数分别为init_main线程的处理函数地址init_main及其输入参数"Hello world!!"
// kernel_thread:
tf.tf_cs = KERNEL_CS;
tf.tf_ds = tf.tf_es = tf.tf_ss = KERNEL_DS;
tf.tf_regs.reg_ebx = (uint32_t)fn;
tf.tf_regs.reg_edx = (uint32_t)arg;
tf.tf_eip = (uint32_t)kernel_thread_entry;
// copy_thread:
proc->tf->tf_regs.reg_eax = 0;
proc->tf->tf_esp = esp;
proc->tf->tf_eflags |= FL_IF;
proc->context.eip = (uintptr_t)forkret;
proc->context.esp = (uintptr_t)(proc->tf);
加入proc_list和hash_list:后面调度器是通过遍历proc_list来挑选下一个要运行的线程的,因此需要将initproc先插入到proc_list和hash_list中。注:hash_list用于find_proc函数中根据pid快速找到对应的进程控制块,因此这里也要将initproc加入hash_list。
完成初始化后,将proc->state设置为RUNNABLE,从而唤醒该进程。
当内核线程idleproc从kern_init的开头运行到最后一个函数cpu_idle时,发生第一次线程切换,由idleproc切换到initproc
while (1) {
if (current->need_resched) {
schedule();
}
}
为什么schedule函数开头要设置current->need_resched = 0;
?答:避免循环调用schedule函数。
schedule按照FIFO的顺序来选择下一个将要运行的线程。具体而言,如果当前正在运行的线程是idleproc,则从线程链表的开头开始搜索;否则从当前线程的下一个元素开始搜索,一旦找到一个state为RUNNABLE,立即退出搜索,并运行之。如果找不到,则继续运行idleproc。
load_esp0(next->kstack + KSTACKSIZE);
lcr3(next->cr3);
switch_to(&(prev->context), &(next->context));
切换上下文时,把eip和esp的值分别设置为initproc->context.eip和initproc->context.esp,然后跳转到context.eip指定的函数地址,也就是forkret
forkret把栈指针指向initproc->tf,然后通过pop指令将tf保存的内容依次赋给各寄存器,接着通过iret指令跳转到tf_eip指定的入口kernel_thread_entry
kernel_thread_entry: # void kernel_thread(void)
pushl %edx # push arg
call *%ebx # call fn
pushl %eax # save the return value of fn(arg)
call do_exit # call do_exit to terminate current thread
cprintf("kernel panic at %s:%d:\n ", file, line);
vcprintf(fmt, ap);
cprintf("\n");
cprintf("stack trackback:\n");
print_stackframe();
va_end(ap);
panic_dead:
intr_disable();
while (1) {
kmonitor(NULL);
}
内核线程initproc在创建完成用户态进程userproc后,调用do_wait函数,do_wait函数在确认存在RUNNABLE的子进程后,调用schedule函数。
forkret函数直接调用forkrets函数,forkrets先把栈指针指向userproc->tf的地址,然后跳到__trapret
__trapret先将userproc->tf的内容pop给相应寄存器,然后通过iret指令,跳转到userproc->tf.tf_eip指向的函数,即kernel_thread_entry
kernel_thread_entry先将edx保存的输入参数(NULL)压栈,然后通过call指令,跳转到ebx指向的函数,即user_main
user_main先打印userproc的pid和name信息,然后调用kernel_execve
kernel_execve执行exec系统调用,CPU检测到系统调用后,会保存eflags/ss/eip等现场信息,然后根据中断号查找中断向量表,进入中断处理例程。这里要经过一系列的函数跳转,才真正进入到exec的系统处理函数do_execve中:vector128 -> __alltraps -> trap -> trap_dispatch -> syscall -> sys_exec -> do_execve
do_execve首先检查用户态虚拟内存空间是否合法,如果合法且目前只有当前进程占用,则释放虚拟内存空间,包括取消虚拟内存到物理内存的映射,释放vma,mm及页目录表占用的物理页等。
load_icode返回到do_exevce,do_execve设置完当前用户进程的名字为“exit”后也返回了。这样一直原路返回到__alltraps函数时,接下来进入__trapret函数
__trapret函数先将栈上保存的tf的内容pop给相应的寄存器,然后跳转到userproc->tf.tf_eip指向的函数,也就是应用程序的入口(exit.c文件中的main函数)。注意,此处的设计十分巧妙:__alltraps函数先将各寄存器的值保存到userproc->tf中,接着将userproc->tf的地址压入栈后,然后调用trap函数;trap返回后再将current->tf的地址出栈,最后恢复current->tf的内容到各寄存器。这样看来中断处理前后各寄存器的值应该保存不变。但事实上,load_icode函数清空了原来的current->tf的内容,并重新设置为应用进程的相关状态。这样,当__trapret执行iret指令时,实际上跳转到应用程序的入口去了,而且特权级也由内核态跳转到用户态。接下来就开始执行用户程序(exit.c文件的main函数)啦。
执行完用户程序后,继续原路返回到kernel_thread_entry函数。接下来将保存在eax的返回值压栈,然后调用do_exit。
上文说到userproc调用do_exit函数,接着往下分析。do_exit函数首先释放userproc占用的内存空间,包括取消虚拟地址到物理地址的映射,释放mm、vma、pgdir等占用的内存。然后唤醒内核线程initproc,接着调用schedule函数,这时由于userproc的state已经设置为ZOMBIE,因此只能选择initproc来运行。
切换进程上下文后,会回到initproc之前执行的do_wait函数。do_wait函数返回到repeat标签,重新找到子进程userproc,检查到其状态为ZOMBIE后,将其从hash_list和proc_list中删除,并释放userproc对应的内核栈空间和进程控制块空间,此时用户进程userproc彻底over了。最后do_wait返回0.
由于do_wait返回0,init_main再次执行schedule,由于proc_list中只有initproc,因此还是继续调用do_wait,这次由于找不到子进程,最终返回-E_BAD_PROC,从而退出init_main中的while循环。
init_main打印一些字符串信息,然后返回。
执行完init_main函数后,会返回到kernel_thread_entry,先将保存在eax的返回值压栈,然后调用do_exit。
do_exit判断到当前进程为initproc后,调用panic函数打印一些字符串,然后停留在内核调试界面,等待用户输入命令。
调用mm_create分配一个mm结构体并初始化
调用setup_pgdir分配一个物理页,作为页目录表的内存空间,并拷贝内核页目录表的内容到新页目录表,从而建立内核虚拟地址空间。
调用dup_mmap,首先复制父进程的vma链表到子进程的mm->mmap_list中,然后调用copy_range将父进程使用到的虚拟内存空间的全部内容拷贝到子进程。
为什么用户进程需要加载ELF文件来执行,内核线程则不需要?
sudo make qemu
时,不会定义TEST,因此走的是else分支,KERNEL_EXECVE宏中又包含__KERNEL_EXECVE宏,exit在这个宏中被修饰为_binary_obj_user_exit_out_start,也就是说用户进程将要执行的程序的地址保存在_binary_obj_user_exit_out_start中。可是,我找遍整个目录下的所有文件,都找不到这个变量被定义的地方,why?#define KERNEL_EXECVE(x) ({ extern unsigned char _binary_obj___user_##x##_out_start[], _binary_obj___user_##x##_out_size[]; __KERNEL_EXECVE(#x, _binary_obj___user_##x##_out_start, _binary_obj___user_##x##_out_size); })
exit.c文件中的main函数为啥子进程反复七次调用yield?
proc.c文件中load_icode函数在拷贝ELF的section时为啥不一次性拷完,而是逐页拷贝?是因为本来就只设计了分配一页的接口吗?
kmalloc/kfree函数看不明白?spin_lock, slob是什么?
do_exit尚未看明白?cptr,optr和yptr的设置?
lock_mm和unlock_mm看不明白?
user目录下各个文件的功能是什么?
struct sched_class stride_sched_class = {
.name = "stride_scheduler",
.init = stride_init,
.enqueue = stride_enqueue,
.dequeue = stride_dequeue,
.pick_next = stride_pick_next,
.proc_tick = stride_proc_tick,
};
何时设置进程的优先级?
如何设置BIG_STRIDE?
没理解stride溢出问题的解决方案?使用无符号整数表示,但比较时又看作是有符号?
我的PC是如何运行多进程的?我的PC含有一个四核CPU,跑多进程时,会把进程分到四个核上面跑吗?
sudo make debug
为什么能加载并执行priority.c文件?是在哪里设置TEST=priority的?
kern_init ->
fs_init ->
vfs_init
dev_init
sfs_init
void vfs_init(void) {
sem_init(&bootfs_sem, 1);
vfs_devlist_init();
}
void dev_init(void) {
init_device(stdin);
init_device(stdout);
init_device(disk0);
}
void dev_init_stdin(void) {
struct inode *node;
if ((node = dev_create_inode()) == NULL) {
panic("stdin: dev_create_node.\n");
}
stdin_device_init(vop_info(node, device));
int ret;
if ((ret = vfs_add_dev("stdin", node, 0)) != 0) {
panic("stdin: vfs_add_dev: %e.\n", ret);
}
}
stdout,disk0的初始化与stdin类似,只是在初始化inode里面的device时挂的函数表不同。
sfs_init ->
sfs_mount ->
vfs_mount ->
sfs_do_mount
sfs_lookup ->
sfs_namefile ->
sfs_lookup_once ->
sfs_do_mount ->
sfs_get_root ->
sfs_load_inode ->
sfs_create_inode ->
vop_init(sfs_get_ops) ->
sfs_node_dirops
关键问题在于:如何根据路径名找到文件在磁盘上的位置。
open (user/libs/file.c) -> // 用户接口
sys_open (user/libs/syscall.c) -> // 封装系统调用接口给用户
syscall(SYS_open, path, open_flags) -> // 系统调用统一实现接口,根据不同系统调用号从函数表中找到相应处理函数
sys_open (kern/syscall/syscall.c) -> // 发生open系统调用的实际处理接口
sysfile_open (kern/fs/sysfile.c) // VFS提供给系统调用的接口
sysfile_open (kern/fs/sysfile.c) ->
file_open (kern/fs/file.c) ->
vfs_open (kern/fs/vfs/vfsfile.c) -> // 根据文件名获取或生成一个inode
vfs_lookup (kern/fs/vfs/vfsloopup.c) ->
get_device -> // 根据文件名获取对应的inode
vop_lookup (kern/fs/vfs/inode.h) ->
sfs_lookup (kern/fs/sfs/sfs_inode.c)
vop_open (kern/fs/vfs/inode.h) ->
sfs_opendir (kern/fs/sfs/sfs_inode.c)
init_main ->
vfs_set_bootfs("disk0:") -> // 设置当前目录对应的inode为disk0
vfs_chdir("disk0:") ->
vfs_lookup("disk0:") ->
get_device("disk0:") ->
vfs_get_root // 由于初始化时已将disk0的vfs_dev_t结构添加到vdev_list中,这里遍历链表即可找到对应的inode
vfs_set_curdir ->
set_cwd_nolock
sfs_lookup (kern/fs/sfs/sfs_inode.c) ->
sfs_lookup_once ->
sfs_dirent_search_nolock -> // 读取当前目录下的每一个file entry,搜索与文件名name匹配的entry
sfs_dirent_read_nolock -> // 根据当前目录的inode及slot找到相应entry并读取其内容
sfs_bmap_load_nolock ->
sfs_bmap_get_nolock
sfs_rbuf ->
sfs_rwblock_nolock ->
dop_io -> disk0_io ->
disk0_read_blks_nolock ->
ide_read_secs
sfs_load_inode ->
lookup_sfs_nolock
疑问:
- 如何根据路径找到文件所在的磁盘位置?
- disk0对应的inode的fs为空指针?
- 一个文件可能由多个block组成,如何读取每个block的内容?如何读取sfs_disk_entry?
- sfs_dirent_search_nolock中判断到entry->ino为0则continue,为什么?ino不能为0吗?
read ->
sys_read ->
syscall(SYS_read) ->
sys_read ->
sysfile_read
sysfile_read ->
file_read ->
fd2file
iobuf_init
vop_read -> sfs_read
iobuf_used
copy_to_user
sys_read ->
sfs_io ->
sfs_io_nolock
疑问:
- 如何根据inode读取数据?
write (user/libs/file.c) ->
sys_write (user/libs/syscall.c) ->
syscall(SYS_write) (user/libs/syscall.c) ->
sys_write (kern/syscall/syscall.c) ->
sysfile_write (kern/fs/sysfile.c)
sysfile_write (kern/fs/sysfile.c) ->
file_write (kern/fs/file.c) ->
vop_write (kern/fs/vfs/inode.h) <=> __vop_op(node, write) ->
sys_write (kern/fs/sfs/sfs_inode.c)
sfs_write (kern/fs/sfs/sfs_inode.c) ->
sfs_io (kern/fs/sfs/sfs_inode.c) -> sfs_io_nolock ->
sfs_wbuf (kern/fs/sfs/sfs_io.c) ->
sfd_rwblock_nolock (kern/fs/sfs/sfs_io.c) ->
dop_io (kern/fs/devs/dev.h)
dop_io (kern/fs/devs/dev.h) -> dev->d_io ->
disk0_io (kern/fs/devs/dev_disk0.c) ->
disk0_write_blks_nolock (kern/fs/devs/dev_disk0.c) ->
ide_write_secs (kern/driver/ide.c) ->
outsl (kern/driver/ide.c)
进程控制块的结构体proc_struct包含文件控制信息结构体files_struct
struct files_struct {
struct inode *pwd; // inode of present working directory
struct file *fd_array; // opened files array
int files_count; // the number of opened files
semaphore_t files_sem; // lock protect sem
};
struct sfs_disk_entry {
uint32_t ino; /* inode number /
char name[SFS_MAX_FNAME_LEN + 1]; / file name */
};
4. sfs_inode结构体定义如下,其中sfs_disk_inode的内容跟硬盘中保存的inode信息基本一致。
struct sfs_inode {
struct sfs_disk_inode din; / on-disk inode /
uint32_t ino; / inode number /
bool dirty; / true if inode modified /
int reclaim_count; / kill inode if it hits zero /
semaphore_t sem; / semaphore for din /
list_entry_t inode_link; / entry for linked-list in sfs_fs /
list_entry_t hash_link; / entry for hash linked-list in sfs_fs */
};
5. sfs_disk_inode的定义如下。标记文件块的数目及位置。注意这里使用了二级索引。
struct sfs_disk_inode {
uint32_t size; /* size of the file (in bytes) /
uint16_t type; / one of SYS_TYPE_* above /
uint16_t nlinks; / # of hard links to this file /
uint32_t blocks; / # of blocks /
uint32_t direct[SFS_NDIRECT]; / direct blocks /
uint32_t indirect; / indirect blocks */
};
6. fs结构体定义如下
struct fs {
union {
struct sfs_fs __sfs_info;
} fs_info; // filesystem-specific data
enum {
fs_type_sfs_info,
} fs_type; // filesystem type
int (fs_sync)(struct fs fs); // Flush all dirty buffers to disk
struct inode (fs_get_root)(struct fs fs); // Return root inode of filesystem.
int (fs_unmount)(struct fs fs); // Attempt unmount of filesystem.
void (fs_cleanup)(struct fs *fs); // Cleanup of filesystem.???
};
7. sfs_fs结构体定义如下
struct sfs_fs {
struct sfs_super super; /* on-disk superblock /
struct device dev; /* device mounted on /
struct bitmap freemap; /* blocks in use are mared 0 /
bool super_dirty; / true if super/freemap modified /
void sfs_buffer; /* buffer for non-block aligned io /
semaphore_t fs_sem; / semaphore for fs /
semaphore_t io_sem; / semaphore for io /
semaphore_t mutex_sem; / semaphore for link/unlink and rename /
list_entry_t inode_list; / inode linked-list /
list_entry_t hash_list; /* inode hash linked-list */
};
7. file结构体定义如下
struct file {
enum {
FD_NONE, FD_INIT, FD_OPENED, FD_CLOSED,
} status;
bool readable;
bool writable;
int fd;
off_t pos;
struct inode *node;
```
标签:red inf 只读 eth 指令 怎么办 sem lookup link
原文地址:https://www.cnblogs.com/wuhualong/p/ucore_source_analysis.html