自顶向下的流程结构 |
---|
用户应用程序 |
操作系统的服务层(sys_call) |
操作系统内核 |
硬件系统(驱动) |
硬件 |
1.把操作系统从用户态 切换到 内核态(用户应用程序 到 内核的流程)
2.实现操作系统的系统调用sys_call(操作系统服务层)
3.应用操作系统提供的底层函数,进行功能实现
3.1 操作系统的驱动结构
4.退出后从内核态切换到用户态
Linux内核的整体模块:进程调度模块、内存管理模块、文件系统模块、进程间通信模块、驱动管理模块
每个模块间的关系:
管理层——实现层
易于升级和维护 1991-2022年,低版本的内核与高版本内核,它们之间的区别——内核驱动的种类大大增多,内核驱动的管理模式没有巨大的改变(也有3个阶段的跳跃:零散型 分层型 设备树);进程的调度算法发生了改变,进程的管理方式没有巨大的改变。
学习中断的目的:
硬中断:由电脑主机的8259A类似的硬件中断控制芯片发出的中断;ARM中断控制器发出的中断
软中断:又可称为异常 或 错误。CPU自行保留的中断、系统调用异常。
中断前的处理过程,中断回复过程 | 中断的执行过程 | |
---|---|---|
硬件中断的处理过程 | asm.s | trap.c |
软件及系统调用的处理过程 | system_call.s | fork.c signal.c exit.c sys.c |
做CPU工作模式的转化
进行寄存器的拷贝和压栈
设置中断异常向量表
保存正常运行的函数返回值
跳转到对应的中断服务函数上运行
进行模式和寄存器的复原
跳转回正常工作函数地址继续运行
将所有的寄存器值入栈 (8086中)SS EFLAGS ESP CS EIP (错误码) ARM中的(r0-r15)
将异常码入栈(中断号)
将当前的函数返回值进行入栈(为了在中断执行后能够找到在哪中断的,能够复原)
调用对应的中断服务函数
出栈函数返回值
出栈复原寄存器值
首先将对应异常的C语言函数入栈:
_divide_error:
pushl $_do_divide_error //把一个c语言函数入栈
如以下这些C语言函数:
_debug://这是一个中断
pushl $_do_int3 # _do_debug
jmp no_error_code//跳转
_nmi:
pushl $_do_nmi
jmp no_error_code
_int3:
pushl $_do_int3
jmp no_error_code
_overflow:
pushl $_do_overflow
jmp no_error_code
_bounds:
pushl $_do_bounds
jmp no_error_code
然后把中断处理服务函数的入口地址存给EAX
xchgl %eax,(%esp)
再压入所有寄存器
pushl %ebx
pushl %ecx
pushl %edx
pushl %edi
pushl %esi
pushl %ebp
push %ds
push %es
push %fs
有错误码压错误码,没错误码压0
pushl %eax # error code
pushl $0 # no error code
取原来的ESP(EIP)存给压进ESP(中断的返回地址)
lea 44(%esp),%edx //取原来的ESP(EIP)
pushl %edx
赋值
movl $0x10,%edx
mov %dx,%ds
mov %dx,%es
mov %dx,%fs
调用之前存在EAX中的C语言函数
call *%eax
加8位开始准备出栈
addl $8,%esp
把之前保存的全部出栈
pop %fs
pop %es
pop %ds
popl %ebp
popl %esi
popl %edi
popl %edx
popl %ecx
popl %ebx
popl %eax
1.trap_init
set_trap_gate 设置的权限较高,只能由用户程序调用
set_system_gate 设置的权限较低,能由用户和系统所 有的进程调用
2.system_call
所有的系统调用C函数放到了一个统一的sys_call_table
//定义系统调用的sys_call_table
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 };
系统调用的操作码
系统时间:(jiffies 系统滴答) CPU内部有一个RTC作为系统定时器,会在上电的时候调用mktime函数算出从1970年1月1日0时开始到当前开机时间点所过的秒数。(代码在mktime.c)
给mktime函数传来的时间结构体(struct tm)的赋值是初始化时从RTC(CMOS)中读出的参数,转化为时间存入全局变量startup_time中,并为JIFFIES所用。
JIFFIES 是一个系统的时钟滴答,一个系统滴答是10ms,它是系统的脉搏,是定时器。
10ms一个系统滴答----->每隔10ms会引发一个定时器中断(中断服务函数中,首先进行JIFFIES自加 ,再timer_interrupt)
call do_timer
调用do_timer函数
if (cpl)//CPL变量是内核中用来指示被中断程序的特定变量 0表示被中断的是内核进程,1表示被中断的是用户进程
current->utime++;//用户程序运行时间+1
else
current->stime++;//内核程序运行时间+1
if (next_timer) {// next_timer 是连接jiffies变量的所有定时器的事件链表
// 可以这样想象,jiffies是一个时间轴,然后这个时间轴上每个绳结上绑了一个事件,运行到该绳结就触发对应的事件
next_timer->jiffies--;
while (next_timer && next_timer->jiffies <= 0) {
void (*fn)(void);
fn = next_timer->fn;
next_timer->fn = NULL;
next_timer = next_timer->next;
(fn)();
}
}
current->counter ----->进程的时间片
if ((--current->counter)>0) return;
current->counter=0;//counter进程的时间片为0,task_struct[]是进程的向量表
//counter在哪里用? 进程的调度就是task_struct[]中检索,找时间片最大的进程对象来运行 直到时间片为0退出 之后再进行新一轮调用
//counter在哪里被设置? 当task_struct[]所有进程的counter都为0,就进行新一轮的时间片分配
if (!cpl) return;
schedule();//这个就是进行时间片分配
task_struct 一个进程 task_struct[] 进程向量表
优先级分配:
(*p)->counter = ((*p)->counter >> 1) + (*p)->priority;
优先级时间片轮转调度算法
进程的结构 |
---|
堆栈 |
TSS 进程的状态描述符 |
LDT 局部描述符(数据段、代码段) |
在内核初始化的过程中,会创建0号进程,0号进程是所有进程的父进程。
在0号进程中:
大致流程:
进程的创建就是对0号进程或者当前进程的复制,
结构体的复制 | 把task[0]对应的task_struct复制给新创建的进程的tas_struct |
对于栈堆的拷贝 | 进程创建时复制原有的堆栈 |
进程的创建是系统调用:
.align 2
_sys_fork://fork的系统调用
call _find_empty_process//调用这个函数
testl %eax,%eax
js 1f
push %gs
pushl %esi
pushl %edi
pushl %ebp
pushl %eax
call _copy_process//
addl $20,%esp
1: ret
struct task_struct *p;
//其实就是malloc分配内存
p = (struct task_struct *) get_free_page();//在内存分配一个空白页,让指针指向它
task[nr] = p;
//后面全是对这个结构体进行赋值相当于初始化赋值
p->state = TASK_UNINTERRUPTIBLE;
p->pid = last_pid;
p->father = current->pid;
p->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;//把寄存器的参数添加进来
p->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"::"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++)//
if (f=p->filp[i])//父进程打开过文件
f->f_count++;//就会打开文件的计数+1,说明会继承这个属性
if (current->pwd)//跟上面一样
current->pwd->i_count++;
if (current->root)
current->root->i_count++;
if (current->executable)
current->executable->i_count++;
set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
p->state = TASK_RUNNING;//把状态设定为可运行状态 /* do this last, just in case */
return last_pid;//返回新创建进程的id号
进程被创建到了链表中,如何进行进一步的调用和调度呢?
void schedule(void); 进程调度函数
switch_to(next); 进程切换函数
进程状态 | 宏定义运行状态 | 说明 |
---|---|---|
运行状态 | #define TASK_RUNNING 0 | 可以被运行,只有在这个状态才能进行进程切换 |
可中断睡眠状态 | #define TASK_INTERRUPTIBLE 1 | 可以被信号中断,变成running |
不可中断睡眠状态 | #define TASK_UNINTERRUPTIBLE 2 | 只能被wakeup唤醒,变成running |
暂停状态 | #define TASK_ZOMBIE 3 | 收到SIGSTOP SIGTSTP SIGTTIN这几个信号就会暂停 |
僵死状态 | #define TASK_STOPPED 4 | 进程停止运行,但是父进程还未将其清空 |
// 时间片分配和进程根据优先度调度
void schedule(void)
{
int i,next,c;
struct task_struct ** p;
/* check alarm, wake up any interruptible tasks that have got a signal */
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p) {//alarm是用来设置警告,比如jiffies有1000个可能其中一些需要警告那么就用alarm来实现
if ((*p)->alarm && (*p)->alarm < jiffies) {
(*p)->signal |= (1<<(SIGALRM-1));
(*p)->alarm = 0;
}
//~(_BLOCKABLE & (*p)->blocked
//&&(*p)->state==TASK_INTERRUPTIBLE
//用来排除非阻塞信号
//如果该进程为可中断睡眠状态 则如果该进程有非屏蔽信号出现就将该进程的状态设置为running
if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
(*p)->state==TASK_INTERRUPTIBLE)
(*p)->state=TASK_RUNNING;
}
/* this is the scheduler proper: */
// 以下思路,循环task列表 根据counter大小决定进程切换
while (1) {
c = -1;
next = 0;
i = NR_TASKS;
p = &task[NR_TASKS];
while (--i) {
if (!*--p)
continue;//进程为空就继续循环
if ((*p)->state == TASK_RUNNING && (*p)->counter > c)//找出c最大的task
c = (*p)->counter, next = i;
}
if (c) break;//如果c找到了,就终结循环,说明找到了
//如果c=0,说明所有进程的时间片都用完了,进行时间片的重新分配
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p)//这里很关键,在低版本内核中,是进行优先级时间片轮转分配,这里搞清楚了优先级和时间片的关系
//counter = counter/2 + priority
(*p)->counter = ((*p)->counter >> 1) +
(*p)->priority;
}
//切换到下一个进程 这个功能使用宏定义完成的
switch_to(next);
}
// 进程切换是用汇编宏定义实现的
//1. 将需要切换的进程赋值给当前进程的指针
//2. 将进程的上下文(TSS和当前堆栈中的信息)切换
#define switch_to(n) {\
struct {long a,b;} __tmp; \
__asm__("cmpl %%ecx,_current\n\t" \
"je 1f\n\t" \
"movw %%dx,%1\n\t" \
"xchgl %%ecx,_current\n\t" \
"ljmp %0\n\t" \
"cmpl %%ecx,_last_task_used_math\n\t" \
"jne 1f\n\t" \
"clts\n" \
"1:" \
::"m" (*&__tmp.a),"m" (*&__tmp.b), \
"d" (_TSS(n)),"c" ((long) task[n])); \
}
// 当某个进程想访问CPU资源,但是CPU资源被占用访问不到,就会休眠
void sleep_on(struct task_struct **p)
{
struct task_struct *tmp;
if (!p)//如果传进来的是空的 就返回
return;
if (current == &(init_task.task))//当前进程是0号
panic("task[0] trying to sleep");//就打印并且返回
tmp = *p;
*p = current;//这两步相当于用头插法给task_struct链表添加了一个新node
// 其实核心就是把当前在请求CPU资源的进程的task_struct的state置为TASK_UNINTERRUPTIBLE(只能由wakeup唤醒的不可中断睡眠状态)
current->state = TASK_UNINTERRUPTIBLE;
schedule();
if (tmp)
tmp->state=0;
}
syscall和do开头的基本都是中断服务调用函数
//释放内存页
free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
free_page_tables(get_base(current->ldt[2]),get_limit(0x17));
for (i=0 ; i<NR_OPEN ; i++)//每个进程能打开的最大文件数NR_OPEN=20
if (current->filp[i])
sys_close(i);//关闭文件
iput(current->pwd);
current->pwd=NULL;
iput(current->root);
current->root=NULL;
iput(current->executable);
current->executable=NULL;
//current->pid就是当前需要关闭的进程
for (i=0 ; i<NR_TASKS ; i++)
if (task[i] && task[i]->father == current->pid) {//如果当前进程是某个进程的父进程
task[i]->father = 1;//就让1号进程作为新的父进程
if (task[i]->state == TASK_ZOMBIE)//如果是僵死状态
/* assumption task[1] is always init */
(void) send_sig(SIGCHLD, task[1], 1);//给父进程发送SIGCHLD
}
if (current->leader && current->tty >= 0)
tty_table[current->tty].pgrp = 0;//清空终端 tty是控制台
if (last_task_used_math == current)
last_task_used_math = NULL;//清空协处理器
if (current->leader) //如果当前进程是会话头进程,则关闭会话的所有进程
kill_session();//清空session
current->state = TASK_ZOMBIE;//设为僵死状态
current->exit_code = code;
tell_father(current->father);
schedule();
int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options);
current->cutime += (*p)->utime;
current->cstime += (*p)->stime;
release(*p);
//完成清空了任务描述表task[]中对应task,释放对应内存页(代码段、数据段)
void release(struct task_struct * p);
//给指定的p进程发送对应的sig信号
static inline int send_sig(long sig,struct task_struct * p,int priv);
//终止当前进程的会话,给其发送SIGHUP
static void kill_session(void)
{
struct task_struct **p = NR_TASKS + task;
while (--p > &FIRST_TASK) {//从最后一个开始扫描(不包括0进程,因为0进程不能被关闭,它是所有进程的父进程)
if (*p && (*p)->session == current->session)
(*p)->signal |= 1<<(SIGHUP-1);
}
}
// 系统调用 向任何进程 发送任何信号(类比shell中的kill命令也是发送信号的意思)
int sys_kill(int pid,int sig)
{
struct task_struct **p = NR_TASKS + task;//指向最后
int err, retval = 0;
if (!pid) while (--p > &FIRST_TASK) {
if (*p && (*p)->pgrp == current->pid) //如果等于进程组号
if (err=send_sig(sig,*p,1))
retval = err;
} else if (pid>0) while (--p > &FIRST_TASK) {//pid>0给对应进程发送信号
if (*p && (*p)->pid == pid)
if (err=send_sig(sig,*p,0))
retval = err;
} else if (pid == -1) while (--p > &FIRST_TASK)//pid=-1给任何进程发送
if (err = send_sig(sig,*p,0))
retval = err;
else while (--p > &FIRST_TASK)//pid<-1 给进程组发送信息
if (*p && (*p)->pgrp == -pid)
if (err = send_sig(sig,*p,0))
retval = err;
return retval;
}
/*PID (
PID>0 给对应的PID发送SIG
PID=0 给当前进程的进程组发送SIG
PID=-1 给任何进程发送SIG
PID<-1 给进程组号为-PID的进程组发送SIG
)*/
//子进程告诉父进程自己要死了
static void tell_father(int pid);
本章探求三个问题:
trap.c trap_init()
mktime.c time_init()
sched.c sched_init()
由PC机的 BIOS ( 0XFFFF0 是 BIOS 存储的总线地址)把 bootsect 从某个固定的地址拿到内存中某个固定地址(每个版本Linux系统是不同的,如0.11是 0X90000 ),并进行一系列的硬件初始化和参数设置。
bootsect.s 是 磁盘引导块程序,是 在磁盘的第一个扇区中的程序( 0磁道 0磁头 1扇区 )
作用:
//设置操作系统的根文件
ROOT_DEV = ORIG_ROOT_DEV;
//设置操作系统驱动参数
drive_info = DRIVE_INFO;
//解析setup.s代码后获取系统内存参数
//设置系统的内存大小 系统本身内存(1MB)+扩展内存大小(参数*KB)
memory_end = (1<<20) + (EXT_MEM_K<<10);
//取整4k的内存大小
memory_end &= 0xfffff000;
if (memory_end > 16*1024*1024)//控制操作系统的最大内存为16M
memory_end = 16*1024*1024;
if (memory_end > 12*1024*1024)
buffer_memory_end = 4*1024*1024;//设置高速缓冲区的大小,跟块设备有关,跟设备交互的时候,充当缓冲区,写入到块设备中的数据先放在缓冲区里,只有执行sync时才真正写入;这也是为什么要区分块设备驱动和字符设备驱动;块设备写入需要缓冲区,字符设备不需要是直接写入的
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
系统初始化的起点:磁盘引导程序,需要将内核移入内存进行运行,并初始化多种模块和硬件
系统初始化的终点:运行第一个应用程序——系统的根文件系统
if (!fork()) { //创建0号进程 fork函数就是用来创建进程的函数 /* we count on this going ok */
//0号进程是所有进程的父进程
init();
void init(void)
{
int pid,i;
//设置有关硬件的驱动信息
setup((void *) &drive_info);
//打开标准输入控制台 句柄为0
(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);
//创建了1号进程,如果0号父进程创建进程成功则fork函数返回0,如果在子进程中fork则返回父进程PID
//如果fork返回值为0,则其在新进程中执行;如果返回值不为0,为子进程的进程号,则在父进程中执行
if (!(pid=fork())) {//这里创建1号进程
close(0);//关闭了0号进程的标准输入输出
//打开系统配置文件
if (open("/etc/rc",O_RDONLY,0))//如果1号进程创建成功打开/etc/rc这里面保存的大部分是系统配置文件 开机的时候要什么提示信息全部写在这个里面
_exit(1);
execve("/bin/sh",argv_rc,envp_rc);//挂接文件系统,运行shell程序
_exit(2);//shell程序一般不会关闭,所以这个exit不会执行
}
//在0号进程中等待子进程退出
if (pid>0)//如果这个不是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();//重新设置id
(void) open("/dev/tty0",O_RDWR,0);
(void) dup(0);
(void) dup(0);//重新打开新的控制台
//执行shell脚本
_exit(execve("/bin/sh",argv,envp));//这里不是上面的argv_rc和envp_rc了是因为怕上面那种创建失败,换了一种环境变量来创建,过程和上面是一样的其实
}
//如果还在父进程中,则进行等待子进程退出,并重新开始循环
//子进程执行shell,父进程等待回收
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() */
}
操作系统移植的过程分为2步:
BOOTLOADER的启动内核代码
创建theKernel函数指针
void (*theKernel)(int zero, int arch, uint params);
把指针移到ih_ep上去,Linux的启动入口
theKernel = (void (*) (int, int, uint))ntohl(hdr->ih_ep);
执行Linux并传入参数
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
//bd->bi_arch_number 称为 process id——CPU的架构号
//bd->bi_boot_params 称为 参数地址
theKernel跳转到head.S文件__lookup_processor_type
比对当前板子的CPU是否支持Linux,如果不支持就不启动直接退出,如果支持则继续运行
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
THUMB( it eq ) @ force fixup-able long branch encoding
beq __error_p @ yes, error 'p'
设置物理内存的页面
adr r3, 2f
ldmia r3, {r4, r8}
sub r4, r3, r4 @ (PHYS_OFFSET - PAGE_OFFSET)
add r8, r8, r4 @ PHYS_OFFSET
跳转到__vet_atags,验证参数是否完整(tag_list)
bl __vet_atags
创建虚拟内存映射表
bl __create_page_tables
把函数__mmap_switched的地址装载进链接寄存器
//创建完虚拟内存映射表后,要将旧的地址转化为虚拟地址,代码重定义,也就是__mmap_switched
ldr r13, =__mmap_switched @ address to jump to after
__mmap_switched:
adr r3, __mmap_switched_data
ldmia r3!, {r4, r5, r6, r7}
cmp r4, r5 @ Copy data segment if needed
1: cmpne r5, r6
ldrne fp, [r4], #4
strne fp, [r5], #4
bne 1b
mov fp, #0 @ Clear BSS (and zero fp)
1: cmp r6, r7
strcc fp, [r6],#4
bcc 1b
ARM( ldmia r3, {r4, r5, r6, r7, sp})
THUMB( ldmia r3, {r4, r5, r6, r7} )
THUMB( ldr sp, [r3, #16] )
str r9, [r4] @ Save processor ID
str r1, [r5] @ Save machine type
str r2, [r6] @ Save atags pointer
bic r4, r0, #CR_A @ Clear 'A' bit
stmia r7, {r0, r4} @ Save control register values
b start_kernel //跳转到start_kernel
ENDPROC(__mmap_switched)
进程初始化C函数的调用,跳转到start_kernel,这就是我们整个内核的启动函数了(在init文件夹的Main.c中)
asmlinkage void __init start_kernel(void);
这个函数有非常多的代码,进行了一系列的初始化。这里就不一一列举。
这里调用了一个重要的函数 setup_arch(&command_line);
setup_arch里面调用了另一个setup_processor()函数,而setup_processor()中
struct proc_info_list *list; //创建了一个CPU指令集描述结构体
list = lookup_processor_type(read_cpuid_id());//从指定的内存中获取到该描述结构体
cpu_name = list->cpu_name;//将获取到结构体的CPU名字赋值给一个全局变量
我们再回溯到setup_arch函数中setup_processor()的下一步,调用了一个 setup_machine_fdt 函数
//找到一个移植Linux时写的最适合的machine_desc结构体,并返回
mdesc = setup_machine_fdt(__atags_pointer);
setup_machine_fdt 函数又调用了一个宏定义for_each_machine_desc(mdesc)
for_each_machine_desc(mdesc) {
score = of_flat_dt_match(dt_root, mdesc->dt_compat);
if (score > 0 && score < mdesc_score) {
mdesc_best = mdesc;
mdesc_score = score;
}
#define for_each_machine_desc(p) \
for (p = __arch_info_begin; p < __arch_info_end; p++)
//这里我们可以分析出它实在遍历一个代码段
这个问题的答案:结构体 machine_desc
链接脚本:vmlinux.lds.S
.init.arch.info : {
__arch_info_begin = .;
*(.arch.info.init) //代码段
__arch_info_end = .;
}
ARCH.H 中的宏定义
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};
多种板子的BSP文件中出现的宏定义调用,以下是一块三星板子的例子
MACHINE_START(S3C2440, "SMDK2440")
/* Maintainer: Ben Dooks */
.atag_offset = 0x100,
.init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
.restart = s3c244x_restart,
MACHINE_END
结合上面三段代码展开:
#define MACHINE_START(S3C2440,"SMDK2440") \
static const struct machine_desc __mach_desc_S3C2440 \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_S3C2440, \
.name = "SMDK2440",
/* Maintainer: Ben Dooks */
.atag_offset = 0x100,
.init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
.restart = s3c244x_restart,
};
总结:
//想去搜索processor的id,调用__lookup_processor_type
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
//__lookup_processor_type函数把
//__lookup_processor_type_data这样一块地址存进r3
//并进行一系列判断,如果相同,则运行,不相同,则报错
__lookup_processor_type:
adr r3, __lookup_processor_type_data
//搜索得到__lookup_processor_type_data的定义,是
//从__proc_info_begin到__proc_info_end的一块地址
.type __lookup_processor_type_data, %object
__lookup_processor_type_data:
.long .
.long __proc_info_begin
.long __proc_info_end
.size __lookup_processor_type_data, . - __lookup_processor_type_data
//我们从链接脚本vmlinux.lds.S发现其中存的是.proc.info.init这块区域
VMLINUX_SYMBOL(__proc_info_begin) = .; \
*(.proc.info.init) \
VMLINUX_SYMBOL(__proc_info_end) = .;
//再继续搜索.proc.info.init得到了一堆proc-开头的arm内核的指令集
//这些东西是Linux写好的,也就是Linux支持的型号
//再回到开头,我们得到了设备的id。
mrc p15, 0, r9, c0, c0 @ get processor id
//通过层层深挖,我们发现它要和Linux支持的
//板子型号进行遍历检索对比,如果有相同的,则运行,不相同,则报错
bl __lookup_processor_type @ r5=procinfo r9=cpuid
__lookup_processor_type:
adr r3, __lookup_processor_type_data
ldmia r3, {r4 - r6}
sub r3, r3, r4 @ get offset between virt&phys
add r5, r5, r3 @ convert virt addresses to
add r6, r6, r3 @ physical address space
1: ldmia r5, {r3, r4} @ value, mask
and r4, r4, r9 @ mask wanted bits
teq r3, r4
beq 2f
add r5, r5, #PROC_INFO_SZ @ sizeof(proc_info_list)
cmp r5, r6
blo 1b
mov r5, #0 @ unknown processor
2: mov pc, lr
ENDPROC(__lookup_processor_type)
static noinline int init_post(void)
{
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
current->signal->flags |= SIGNAL_UNKILLABLE;
if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {//如果execute_command不为空 就运行这个命令
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}//如果execute_command为空则执行下面四个
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");//这几个必须由文件系统提供
//这个panic表达的就是如果文件系统没找到则linux启动失败
panic("No init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}
static noinline int init_post(void)
{
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
free_initmem();
mark_rodata_ro();
system_state = SYSTEM_RUNNING;
numa_default_policy();
current->signal->flags |= SIGNAL_UNKILLABLE;
if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s\n",
ramdisk_execute_command);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {//如果execute_command不为空 就运行这个命令
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults...\n", execute_command);
}//如果execute_command为空则执行下面四个
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");//这几个必须由文件系统提供
//这个panic表达的就是如果文件系统没找到则linux启动失败
panic("No init found. Try passing init= option to kernel. "
"See Linux Documentation/init.txt for guidance.");
}