kernel源码(九)main.c
0 __asm__
参考1:中的嵌入式汇编部分
参考2:https://blog.csdn.net/yt_42370304/article/details/84982864
00 系统调用int 0x80
在保护模式下,内核采用中断的方式实现系统调用,中断向量号为0x80;
具体哪种系统调用,是由eax中的值决定的,称为功能号,功能号在include/linux/sys.h中定义的
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
sys_setreuid,sys_setregid };
这里定义了一个数组,数组中的元素是相关系统调用函数地址,数组下标就是功能号,比如我们调用int 0x80时eax中存放2的话,就是sys_fork系统调用。
1 源码
/* * linux/init/main.c * * (C) 1991 Linus Torvalds */ #define __LIBRARY__ #include#include /* * we need this inline - forking from kernel space will result * in NO COPY ON WRITE (!!!), until an execve is executed. This * is no problem, but for the stack. This is handled by not letting * main() use the stack at all after fork(). Thus, no function * calls - which means inline code for fork too, as otherwise we * would use the stack upon exit from 'fork()'. * * Actually only pause and fork are needed inline, so that there * won't be any messing with the stack from main(), but we define * some others too. */ static inline _syscall0(int,fork) static inline _syscall0(int,pause) static inline _syscall1(int,setup,void *,BIOS) static inline _syscall0(int,sync) #include #include #include #include #include #include #include #include #include #include #include static char printbuf[1024]; extern int vsprintf(); extern void init(void); extern void blk_dev_init(void); extern void chr_dev_init(void); extern void hd_init(void); extern void floppy_init(void); extern void mem_init(long start, long end); extern long rd_init(long mem_start, int length); extern long kernel_mktime(struct tm * tm); extern long startup_time; /* * This is set up by the setup-routine at boot-time */ #define EXT_MEM_K (*(unsigned short *)0x90002) #define DRIVE_INFO (*(struct drive_info *)0x90080) #define ORIG_ROOT_DEV (*(unsigned short *)0x901FC) /* * Yeah, yeah, it's ugly, but I cannot find how to do this correctly * and this seems to work. I anybody has more info on the real-time * clock I'd be interested. Most of this was trial and error, and some * bios-listing reading. Urghh. */ #define CMOS_READ(addr) ({ \ outb_p(0x80|addr,0x70); \ inb_p(0x71); \ }) #define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10) 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); } static long memory_end = 0; static long buffer_memory_end = 0; static long main_memory_start = 0; struct drive_info { char dummy[32]; } drive_info; void main(void) /* This really IS void, no error here. */ { /* The startup routine assumes (well, ...) this */ /* * Interrupts are still disabled. Do necessary setups, then * enable them */ ROOT_DEV = ORIG_ROOT_DEV; drive_info = DRIVE_INFO; memory_end = (1<<20) + (EXT_MEM_K<<10); memory_end &= 0xfffff000; if (memory_end > 16*1024*1024) memory_end = 16*1024*1024; if (memory_end > 12*1024*1024) buffer_memory_end = 4*1024*1024; else if (memory_end > 6*1024*1024) buffer_memory_end = 2*1024*1024; else buffer_memory_end = 1*1024*1024; main_memory_start = buffer_memory_end; #ifdef RAMDISK main_memory_start += rd_init(main_memory_start, RAMDISK*1024); #endif mem_init(main_memory_start,memory_end); trap_init(); blk_dev_init(); chr_dev_init(); tty_init(); time_init(); sched_init(); buffer_init(buffer_memory_end); hd_init(); floppy_init(); sti(); move_to_user_mode(); if (!fork()) { /* we count on this going ok */ 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(;;) pause(); } static int printf(const char *fmt, ...) { va_list args; int i; va_start(args, fmt); write(1,printbuf,i=vsprintf(printbuf, fmt, args)); va_end(args); return i; } static char * argv_rc[] = { "/bin/sh", NULL }; static char * envp_rc[] = { "HOME=/", NULL }; static char * argv[] = { "-/bin/sh",NULL }; static char * envp[] = { "HOME=/usr/root", NULL }; void init(void) { int pid,i; setup((void *) &drive_info); (void) open("/dev/tty0",O_RDWR,0); (void) dup(0); (void) dup(0); printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS, NR_BUFFERS*BLOCK_SIZE); printf("Free mem: %d bytes\n\r",memory_end-main_memory_start); if (!(pid=fork())) { close(0); if (open("/etc/rc",O_RDONLY,0)) _exit(1); execve("/bin/sh",argv_rc,envp_rc); _exit(2); } if (pid>0) while (pid != wait(&i)) /* nothing */; while (1) { if ((pid=fork())<0) { printf("Fork failed in init\r\n"); continue; } if (!pid) { close(0);close(1);close(2); setsid(); (void) open("/dev/tty0",O_RDWR,0); (void) dup(0); (void) dup(0); _exit(execve("/bin/sh",argv,envp)); } while (1) if (pid == wait(&i)) break; printf("\n\rchild %d died with code %04x\n\r",pid,i); sync(); } _exit(0); /* NOTE! _exit, not exit() */ }
开头
#define __LIBRARY__ #include#include
接下来,定义了4个内联函数,而且是在unistd.h中的宏定义。
static inline _syscall0(int,fork) static inline _syscall0(int,pause) static inline _syscall1(int,setup,void *,BIOS) static inline _syscall0(int,sync)
我们看一下unistd.h中是如何定义_syscall0的
#define _syscall0(type,name) \ type name(void) \ { \ long __res; \ __asm__ volatile ("int $0x80" \ : "=a" (__res) \ : "0" (__NR_##name)); \ //##为C中的语法,这里表示连接__NR_和name,这里是__NR_fork,这个是在本头文件中定义的宏(#define __NR_fork 2) if (__res >= 0) \ return (type) __res; \ errno = -__res; \ return -1; \ }
在这个宏定义中,嵌入了汇编代码。
int 0x80:int表示中断,0x80表示系统调用,int 0x80表示发起一个系统调用
"0" (__NR_##name) 为asm的输入,__NR_##name为2,这里意思是把2放入0的位置处,即把2放入eax寄存器中(asm规定,0到9分别表示特定的位置,不明白可以查看:https://blog.csdn.net/yt_42370304/article/details/84982864)
"=a" (__res) 为asm的输出,表示把eax中的内容写到变量__res中。
上面这段代码表示发起一个系统调用,功能号是2,功能号2对应的是fork系统调用。
我们再回过头来看main.c中的这段代码
static inline _syscall0(int,fork) static inline _syscall0(int,pause) static inline _syscall1(int,setup,void *,BIOS) static inline _syscall0(int,sync)
这里定义了4个内联函数,作用分别是系统调用fork、系统调用pause、系统调用setup、系统调用sync
下面引入了一些头文件
#include#include #include #include #include #include #include #include #include #include #include
下面定义了一个char数组
static char printbuf[1024];
下面是一些函数,他们的原型分别在不同的c文件中。
extern int vsprintf(); extern void init(void); extern void blk_dev_init(void); extern void chr_dev_init(void); extern void hd_init(void); extern void floppy_init(void); extern void mem_init(long start, long end); extern long rd_init(long mem_start, int length); extern long kernel_mktime(struct tm * tm); extern long startup_time;
我们在setup.s中在0x90000开始的位置放入了一些数据(),这里我们开始使用这些数据
/* * This is set up by the setup-routine at boot-time */ #define EXT_MEM_K (*(unsigned short *)0x90002) //0x90002处存放的是扩展内存大小。这里我们把扩展内存大小赋值给宏EXT_MEM_K #define DRIVE_INFO (*(struct drive_info *)0x90080) //第一个硬盘的信息 #define ORIG_ROOT_DEV (*(unsigned short *)0x901FC) //根设备信息
下面代码是读取cmos实时时钟信息。
#define CMOS_READ(addr) ({ \ outb_p(0x80|addr,0x70); \ //0x70是写端口号,0x80|addr 是要读取的CMOS 内存地址 inb_p(0x71); \ //0x71 是读端口号。 })
outb_p是一个宏,在io.h中定义,汇编中outb指令是向特定io端口写一字节数据。inb是特定io端口向源头读入1字节数据。
#define outb(value,port) \ __asm__ ("outb %%al,%%dx"::"a" (value),"d" (port)) //无输出,输入:value写入eax,port写入edx。这个嵌入汇编意思是把value发到port端口 #define inb(port) ({ \ unsigned char _v; \ __asm__ volatile ("inb %%dx,%%al":"=a" (_v):"d" (port)); \ //从port端口读取1字节数据放入eax,并把eax值存入_v;返回_v _v; \ }) #define outb_p(value,port) \ __asm__ ("outb %%al,%%dx\n" \ "\tjmp 1f\n" \ //jmp 1f意思是跳转到标号1处,方向是向前跳转(因为这里有两个标号1,所以要区分向前还是向后) "1:\tjmp 1f\n" \ "1:"::"a" (value),"d" (port)) #define inb_p(port) ({ \ unsigned char _v; \ __asm__ volatile ("inb %%dx,%%al\n" \ "\tjmp 1f\n" \ "1:\tjmp 1f\n" \ "1:":"=a" (_v):"d" (port)); \ _v; \ })
下面代码定义了一个宏,作用是把BCD码转换为二进制的值
#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)
下面代码实现时间初始化。
static void time_init(void) { struct tm time; //在time.h中定义的 do { time.tm_sec = CMOS_READ(0); //读取cmos时间,秒 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)); //如果读取时间超过1秒,则重新读取。保证误差在1秒之内 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--; //因为tm_mon从1-12,这里做减1操作 startup_time = kernel_mktime(&time); //创建时间,赋值给start_time }
定义了一个结构体
struct drive_info { char dummy[32]; } drive_info;
下面是main函数,在介绍head.s时我们讲过,head.s中设置了调用c中main函数的入口,就是指的这里(注意:汇编调用c中的函数时要加_,即_main)
下面的代码需要对照这个图查看脉络将会更清晰。
void main(void) /* This really IS void, no error here. */ { /* The startup routine assumes (well, ...) this */ /* * Interrupts are still disabled. Do necessary setups, then * enable them */ ROOT_DEV = ORIG_ROOT_DEV; //ROOT_DEV是在其他文件中定义的宏,讲到时我们再回来看这地方 drive_info = DRIVE_INFO; //DRIVE_INFO前面定义的宏,这里赋值给driver_info memory_end = (1<<20) + (EXT_MEM_K<<10); //计算总的内存大小,单位为字节,1左移20位表示1M基本内存地址,EXT_MEM_K是前面定义的宏表示扩展内存大小(kb),这里左移10位转换为字节 memory_end &= 0xfffff000; //因为内存是分页的,1页是4kb,这里把最后不足1页的内存去掉 if (memory_end > 16*1024*1024) //如果内存大于16M, memory_end = 16*1024*1024; if (memory_end > 12*1024*1024) //如果内存大于12M,缓冲区大小设置为4M buffer_memory_end = 4*1024*1024; else if (memory_end > 6*1024*1024) //如果内存大于6M,缓冲区大小设置为2M buffer_memory_end = 2*1024*1024; else buffer_memory_end = 1*1024*1024; main_memory_start = buffer_memory_end; //缓冲区后面是主内存的地址 #ifdef RAMDISK //如果定义了虚拟内存,则主内存开始位置还要往后延一点 main_memory_start += rd_init(main_memory_start, RAMDISK*1024); #endif mem_init(main_memory_start,memory_end); //初始化主内存 trap_init(); //中断门初始化 blk_dev_init(); //块设备初始化 chr_dev_init(); //字符设备初始化 tty_init(); //tty的初始化 time_init(); //时间的初始化 sched_init(); //调度程序的初始化 buffer_init(buffer_memory_end); //缓冲区管理的初始化 hd_init();//硬盘初始化 floppy_init();//软盘初始化 sti();//打开中断 move_to_user_mode();//在system.h中定义的宏,作用是初始化数据段、附加段 if (!fork()) { //fork是前面定义的宏,作用是新建子进程 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(;;) pause(); //这一段的意思是,定义了一个死循环,每循环一次,执行一次pause,如果此时有其他进程在等待,将获取cpu时间片执行;如果没有其他进程,将一直执行for循环;这里就是那个idel进程 }
其中下面这些初始化是非常重要的部分,后面博客会一一讲到。
mem_init(main_memory_start,memory_end); //初始化主内存 trap_init(); //中断门初始化 blk_dev_init(); //块设备初始化 chr_dev_init(); //字符设备初始化 tty_init(); //tty的初始化 time_init(); //时间的初始化 sched_init(); //调度程序的初始化 buffer_init(buffer_memory_end); //缓冲区管理的初始化 hd_init();//硬盘初始化 floppy_init();//软盘初始化
下面程序,创建一个init进程
if (!fork()) { //fork是前面定义的宏,作用是新建子进程 init(); }
我们看一下init()函数
void init(void) { int pid,i; setup((void *) &drive_info); //设置硬盘,在hd.c中定义 (void) open("/dev/tty0",O_RDWR,0); //以只读的方式打开/dev/tty0 (void) dup(0); (void) dup(0); printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS, NR_BUFFERS*BLOCK_SIZE); printf("Free mem: %d bytes\n\r",memory_end-main_memory_start); if (!(pid=fork())) { //再创建一个子进程(任务2) close(0);//关闭描述符0 if (open("/etc/rc",O_RDONLY,0))//打开/etc/rc文件 _exit(1); execve("/bin/sh",argv_rc,envp_rc);//执行/bin/sh _exit(2); //退出子进程 } if (pid>0) //如果子进程创建成功 while (pid != wait(&i)) //等待子进程结束 /* nothing */; while (1) { //如果子进程结束,才轮到这里执行 if ((pid=fork())<0) { //再创建一个进程 printf("Fork failed in init\r\n"); continue; } if (!pid) { close(0);close(1);close(2); setsid(); (void) open("/dev/tty0",O_RDWR,0); (void) dup(0); (void) dup(0); _exit(execve("/bin/sh",argv,envp)); } while (1) if (pid == wait(&i)) break; printf("\n\rchild %d died with code %04x\n\r",pid,i); sync(); //刷新缓冲区 } _exit(0); /* NOTE! _exit, not exit() */ }
我们对main.c程序讲解了一个框架,里面一些细节我们会在接下来的文章中逐步深化。