Linux源码版本:0.11
1. 内核模式与体系结构
1.1 操作系统的结构
1.2 操作系统的工作方式
1.把操作系统从用户态切换到内核态(用户应用程序到内核的流程)
2.实现操作系统的系统调用(操作系统服务层)
3.应用操作系统提供的底层函数,进行功能实现
4.退出后从内核态切换到用户态
1.3 操作系统内核中各级模块的相互关联
1.Linux内核的整体模块:进程调度模块、内存管理模块、文件系统模块、进程间通信模块、驱动管理模块
2.每个模块间的关系
1.4 操作系统结构的独立性
分两层:管理层 实现层 优点:易于升级和维护 高版本的内核与低版本的内核之间的区别: 多的是内核驱动的种类 而内核驱动的管理模式并没有巨大的改变(零散型、分层型、设备树) 进程的调度算法发生了改变 而进程的管理方式并没有巨大的改变
2. 内核中断概括
2.1 目的
1.硬件的中断响应 --> 内核驱动中的中断 2.系统调用的函数响应(sys_call) --> 系统调用 3.自定义中断–>软件的软中断模式 4.信号中断(kill -signalnum)–>对了解信号的使用创建等有帮助 5.系统的异常和错误–>系统的异常获取 了解系统异常的作用
2.1 Linux的中断机制
2.1.1分类
硬中断:由电脑主机的8259A类似的硬件中断控制芯片发出的中断 ARM中断控制器发出的中断 软中断:异常 第一类:CPU自行保留的中断 第二类:系统调用异常
2.1.2 代码结构
中断前的处理过程,中断的回复过程 中断的执行过程 asm. s trap.c system_ call.s fork.csignal.c exit.c sys.c
2.2 中断的工作流程
2.2.1 回忆
- 做CPU工作模式的转换
- 进行寄存器的拷贝与压栈
- 设置中断异常向量表
- 保存正常运行的函数返回值
- 跳转到对应的中断服务函数上运行
- 进行模式的复原以及寄存器的复原
- 跳转回正常工作的函数地址继续运行
2.2.2 Linux中中断的工作流程
- 将所有的寄存器值入栈
(8086中)SS EFLAGS ESP CS EIP (ARM中) r0-r15 - 将异常码入栈(中断号)
- 将当前的函数返回值进行入栈(为了能后还原现场)
- 调用对应的中断服务函数
- 出栈函数返回值,返回所有入栈的寄存器值
- 返回所有入栈的寄存器值
如图所示: 中断前的处理过程,中断的回复过程 中断的执行过程 asm. s trap.c system_ call.s fork.csignal.c exit.c sys.c
2.3 中断的代码实现过程
分析源码中asm.s得到下图:
3. 内核进程管理
3.1 系统进程的运转方式
系统时间:(jiffies 系统嘀嗒) CPU内部有一个RTC,会在上电的时候调用kernel_mktime函数算出从1970年1月1日0时到当前开机点所过的秒数。给kernel_mktime函数传来的时间结构体的赋值是由初始化时从RTC中读出来的参数转化为时间存入全局变量中,并且会为jiffies 所用 jiffies 是一个系统的时钟滴答,一个系统滴答是10ms 10ms一个系统滴答----->每隔10ms引发一个定时器中断(中断服务函数_timer_interrupt中,进行自增,然后调用do_timer) do_timer函数
if (cpl)
current->utime++;
else
current->stime++;
if (next_timer) {
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)();
}
}
if (current_DOR & 0xf0)
do_floppy_timer();
if ((--current->counter)>0) return;
current->counter=0;
if (!cpl) return;
schedule();
在内核初始化的过程中,先sched init(),后调用move_to_user mode把内核状态从内核态切换到用户态,然后会手动创建0号进程,0号进程是所有进程的父进程
1.sched init();做了什么事情? 初始化了GDT描述符
2.在0号进程中: 1.打开标准输入输出错误的控制台句柄 2.创建1号进程,如果创建成功,则在一号进程中 首先打开了 “/etc/rc"文件 执行SHELL程序“/bin/sh” 3.0号进程不可能结束,他会在没有其他进程调用的时候调用,只会执行for(;😉 pause() ;
3.2 如何进行创建一个新的进程
进程创建:fork 1.在task链表中找一个进程空位存放当前的进程 2.创建一个task_struct 3.设置task_struct
进程的创建就是对0号进程或者当前进程的复制 0号进程复制就是结构体的复制 把task[0]对应的task_struct复制给新创建的task_struct 对于堆栈的拷贝 当进程做创建的时候要复制原有的堆栈 1.进程的创建是系统调用
.align 2
_sys_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
2.进程的创建主体:copy_process函数
int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
long ebx,long ecx,long edx,
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;
*p = *current;
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;
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++;
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;
return last_pid;
}
3.3 进程状态
运行状态 可以被运行 只有在这个状态才能进行进程切换 可中断睡眠状态 可以被信号中断 变成running 不可中断睡眠状态 只能被wakeup唤醒 变成running 暂停状态 收到SIGSTOP SIGTSTP SIGTTIN这几个信号就会暂停 僵尸状态 进程停止运行 但是父进程还未将其清空
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define TASK_ZOMBIE 3
#define TASK_STOPPED 4
3.4 进程调度:优先级时间片轮转调度
void schedule(void) 进程调度函数 进程调度也是系统调用
reschedule:
pushl $ret_from_sys_call
jmp schedule
void schedule(void)
{
int i,next,c;
struct task_struct ** p;
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p) {
if ((*p)->alarm && (*p)->alarm < jiffies) {
(*p)->signal |= (1<<(SIGALRM-1));
(*p)->alarm = 0;
}
if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
(*p)->state==TASK_INTERRUPTIBLE)
(*p)->state=TASK_RUNNING;
}
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 = (*p)->counter, next = i;
}
if (c) break;
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) +
(*p)->priority;
}
switch_to(next);
}
switch_to(next) 上下文切换宏函数
#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])); \
}
void sleep_on(struct task_struct **p) 当某个进程想访问CPU资源,但是CPU资源被占用访问不到,就会休眠 其实核心就是把state置为TASK_UNINTERRUPTIBLE不 可中断休眠
void sleep_on(struct task_struct **p)
{
struct task_struct *tmp;
if (!p)
return;
if (current == &(init_task.task))
panic("task[0] trying to sleep");
tmp = *p;
*p = current;
current->state = TASK_UNINTERRUPTIBLE;
schedule();
if (tmp)
tmp->state=0;
}
等待队列的实现:
void math_state_restore() 协处理器的恢复函数 协处理器其实就想象成是CPU,切换进程时,协处理器的一些寄存器栈堆等数据也要进行切换
3.4 进程的销毁的流程
1.1 exit是销毁函数-----一个系统调用----do exit 首先该函数会释放进程的代码段和数据段占用的内存 1.2关闭进程打开的所有文件,对当前的目录和i节点进行同步(文件操作) 1.3如果当前要销毁的进程有子进程,那么就让1号进程作为新的父进程(init进程) 1.4如果当前进程是一个会话头进程,则会终止会话中的所有进程 1.5改变当前进程的运行状态,变成TASK_ZONBIE僵死状态,并且向其父进程发送SIGCHLD信号。
2.1父进程在运行子进程的时候一般都会运行wait waitpid这两个函数(父进程等待某个子进程终止的函数)当父进程收到SIGCHLD信号时父进程会终止僵死状态的子进程 2.2首先父进程会把子进程的运行时间累加到自己的进程变量中 2.3把对应的子进程的进程描述结构体进行释放,置空任务数组中的空槽
void release(struct task struct * p) 完成清空了任务描述表中的对应进程表项,释放对应的内存页(代码段 数据段)
static inline int send_sig (long sig,struct task_struct * p,int priv)给制定的p进程发送对应的sig信号
static void kill_session(void) 终止会话,终止当前进程的会话给其发送SIGHUP
int sys_kill(int pid,int sig) kill-不是杀死的意思,向对应的进程号或者进程组号发送任何信号 pid { pid>0给对应的pid发送sig pid=0给当前进程的进程组发送sig pid=-1给任何进程发送 pid<-1给进程组号为-pid的进程组发送信号) }
//告诉父进程要死了 static void tell_father(int pid)
static void tell_father(int pid)
{
int i;
if (pid)
for (i=0;i<NR_TASKS;i++) {
if (!task[i])
continue;
if (task[i]->pid != pid)
continue;
task[i]->signal |= (1<<(SIGCHLD-1));
return;
}
printk("BAD BAD - no father found\n\r");
release(current);
}
3.5 进程间通信
|