[ 1] linux0.11引导程序阅读注释。
[ 2] linux0.11由实模式进入保护模式程序阅读注释 。
[ 3] linux0.11护模式初始化程序阅读注释。
[ 4] linux0.11主存管理程序阅读注释。
[ 5] linux0.11中断/异常机制初始设置相关程序阅读注释。
[ 6] linux0.11缓冲区管理程序阅读注释。
[ 7] linux0.11文件系统管理程序阅读注释。
[ 8] linux0.11块设备驱动及访问请求管理程序阅读注释。
[ 9] linux0.11字符设备驱动及访问请求管理程序阅读注释。
篇幅较长,可通过浏览器的搜索功能(Ctrl + f)搜索函数名了解相应函数的实现机制,如 sleep_on。
比如系统调用fork会返回两次的原理;创建子进程过程;创建子进程并加载可执行程序执行过程……
下一步计划阅读信号处理部分,再下一步阅读linux0.11剩余部分。
/* head.s完成保护模式的初始化工作后,便跳转到此处。*/
void main(void)
{
/* ... */
/* 为初始任务LDT表和TSS描述符设置GDT描述符,
* 初始化定时器, 定时器约每10ms发生一次中断,
* 每次中断会检查任务调度。
*
* 其中还包含系统调用80h中断描述符的设置。
*
* 留意下用户程序下esp的值。*/
sched_init();
/* 设置标志寄存器IF位为1,
* 置位后, 在下一条指令执行后将开始响应外部中断。
* 若下一条指令仍保持IF置位的话。*/
sti(); /* 开启中断 */
/* 从CPU内核模式转移到CPU用户模式以初始任务init_task 名义执行,
* move_to_user_mode执行完毕后,之后程序代码将在CPU用户模式下执行。*/
move_to_user_mode();
/* fork定义在本文件中开始部分: static inline _syscall0(int,fork),
* 根据宏_syscall0看看fork的执行轨迹及其会返回两次的原理吧,这也是
* 需定义fork为内联函数的原因。*/
if (!fork()) { /* we count on this going ok */
/* 在init_task的子进程中调用init()函数,父进程
* init_task将执行后续"for(;;) pause();"语句。*/
init();
/* 若init()函数返回也会执行后续"for(;;) pause();"语句。*/
}
/*
* 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.
*/
/* 系统调用 pause()将本进程置准备就绪状态并调度任务函数进行任务调度。
* 对于调用pause()的初始进程,若当前系统无其他进程则立即返回;否则唤醒
* 其他进程运行。对于调用pause()的非初始进程,需用信号唤醒该进程。
*
* pause()定义在本文件开头部分,static inline _syscall0(int,pause),
* 其对应的内核函数为sched.c/ sys_pause(). */
for(;;) pause();
}
/* printf,
* 写标准输出设备的可变参数函数。*/
static int printf(const char *fmt, ...)
{
va_list args;
int i;
/* 让args指向fmt之后一个参数的栈地址 */
va_start(args, fmt);
/* 将printffmt之后的参数写往标准输出设备显示 */
write(1,printbuf,i=vsprintf(printbuf, fmt, args));
va_end(args);
return i;
}
/* sh程序的命令行参数,环境变量参数 */
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 };
/* init,
* init进程,该进程创建子进程并加载可执行程序sh运行。
*
* 若子进程sh被终止,则init进程会回收sh进程所创建子进
* 程资源,并会立即再创建子进程并加载sh程序运行。*/
void init(void)
{
int pid,i;
/* setup,
* 根据存储在drive_info中的硬盘参数,获取硬盘分区参数,
* 并设置根文件系统和RAMDISK(若有)。
*
* 系统调用setup定义在本文件开头处,
* static inline _syscall1(int, setup, void *, BIOS),
* 其对应的内核程序为hd.c/int sys_setup(void * BIOS)。*/
setup((void *) &drive_info);
/* 以读写属性打开文件/dev/tty0。由于此处是首次打开文件,
* /dev/tty0的文件描述符将为0,即/dev/tty0即是与控制终端
* 关联的文件哦。*/
(void) open("/dev/tty0",O_RDWR,0);
/* 通过open打开或创建/dev/tty0时,根据底层函数实现,目录/dev
* 应存在。/dev目录(甚至是/dev/tty0)可能是在格式化MINIX文件
* 系统静态创建的。*/
/* dup(0),
* 让本进程中后续空闲描述符(1和2)指向文件描述符0所关联
* 文件/dev/tty0。
*
* dup定义在lib/dup.c中,其对应的内核函数为sys_dup()。*/
(void) dup(0);
(void) dup(0);
/* 此处创建文件/dev/tty0与控制终端关联,由文件描述符0,1,2关联,
* 即系统层面所提到stdin,stdeout,stderr,即对应标准输入,标准输
* 出,错误输出。*/
/* 打印缓冲区和主存大小 */
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);
/* 在init进程中创建子进程 */
if (!(pid=fork())) {
/* 在init子进程中关闭标准输入。以用文件描述
* 符0关联以只读方式打开的/etc/rc文件。close
* 函数定义在lib/close.c文件中。*/
close(0);
if (open("/etc/rc",O_RDONLY,0))
_exit(1);
/* 加载/bin/sh"覆盖本进程"并执行sh程序,argv_rc
* 和envp_rc分别充当sh程序的命令行和环境变量参数。
*
* execve函数定义在lib/execve.c中。*/
execve("/bin/sh",argv_rc,envp_rc);
/* 若execve加载可执行程序/bin/sh成功则该语句不会被执行,若
* 失败则会执行_exit(2)退出本进程。另外,此处传给/bin/sh程
* 序的参数使得/bin/sh为非交互模式运行,其运行完毕后会退出。*/
_exit(2);
}
/* 等待子进程退出返回,定义在lib/wait.c中。
* 可以不用pid >0 的判断,子进程不会执行以下代码。*/
if (pid>0)
while (pid != wait(&i))
/* nothing */;
/* 非交互式/bin/sh运行退出时,重新创建子进程以交互式执行/bash/sh程序 */
while (1) {
if ((pid=fork())<0) {
printf("Fork failed in init\r\n");
continue;
}
/* 在子进程中关闭标准输入输出和错误输出;
* 新建会话组;重新打开控制终端并将控制终
* 端复制给标准输出和错误输出文件描述符;
* 并重新启动/bin/sh程序以交互模式运行。*/
if (!pid) {
close(0);close(1);close(2);
/* 创建会话组;
* 定义在lib/setsid.c中,
* 其内核函数为kernel/sys.c/sys_setsid() */
setsid();
(void) open("/dev/tty0",O_RDWR,0);
(void) dup(0);
(void) dup(0);
_exit(execve("/bin/sh",argv,envp));
}
/* 等待子进程结束,子进程结束则提示并刷新缓冲区然后
* 再回到循环开始处再次创建子进程运行/bin/sh程序。*/
while (1)
if (pid == wait(&i))
break;
printf("\n\rchild %d died with code %04x\n\r",pid,i);
/* 同步缓冲区内容到设备上。
*
* 其定义在本文件开始部分,
* static inline _syscall0(int,sync),
* 其内核函数为fs/buffer.c/sys_sync()。*/
sync();
}
_exit(0); /* NOTE! _exit, not exit() */
}
/*
* linux/kernel/sched.c
*
* (C) 1991 Linus Torvalds
*/
/*
* 'sched.c' is the main kernel file. It contains scheduling primitives
* (sleep_on, wakeup, schedule etc) as well as a number of simple system
* call functions (type getpid(), which just extracts a field from
* current-task
*/
/* sched.c 是主要的内核文件。其包含了任务调度的基础函数(sleep_on,wake_up,schedule等)
* 和一些简单的系统调用函数(如 getpid()),这些系统调用差不多仅从管理进程结构体中获取某
* 成员状态并返回。*/
#include
#include
#include
#include
#include
#include
#include
#include
/* _S(nr),将nr转换为位号;
* _BLOCKABLE,将SIGKILL和SIGSTOP位号置为0。*/
#define _S(nr) (1<<((nr)-1))
#define _BLOCKABLE (~(_S(SIGKILL) | _S(SIGSTOP)))
/* show_task,
* 显式任务号为nr进程相关信息。*/
void show_task(int nr,struct task_struct * p)
{
int i,j = 4096-sizeof(struct task_struct);
/* 打印任务号, 进程id, 进程当前状态 */
printk("%d: pid=%d, state=%d, ",nr,p->pid,p->state);
/* (p + 1)使得p指向进程内核栈底,由此计数并打印内核栈空闲字节数。*/
i=0;
while (i TR --> GDT[TR] --> TSS;
* TSS.ldt --> GDT[TSS.ldt] --> LDT */
static union task_union init_task = {INIT_TASK,};
/* jiffies用于记录系统开机所运行的时间片。jiffies在定时器中断处理函数
* 中会被增1,定时器中断约每10ms发生一次,即jiffies * 10ms即为开机时常。*/
long volatile jiffies=0;
/* 用于记录系统开机时间(见main.c/time_init) */
long startup_time=0;
/* current始终指向 管理当前运行进程的结构体,初始任务最先被运行 */
struct task_struct *current = &(init_task.task);
/* 指向刚使用协处理器的进程 */
struct task_struct *last_task_used_math = NULL;
/* 指向进程管理结构体的全局指针数组。
* task[0] = &init_task.task即指向管理初始进程的结构体。
* linux0.11最多能支持64个进程的管理(NR_TASKS=64)。
* task数组的下标充当了任务号,比如初始任务的任务号为0,依次类推。*/
struct task_struct * task[NR_TASKS] = {&(init_task.task), };
/* head.s中层用stack_start初始化ss:esp以将user_stack内存段作为内存栈内存 */
long user_stack [ PAGE_SIZE>>2 ] ;
struct {
long * a;
short b;
} stack_start = { & user_stack [PAGE_SIZE>>2] , 0x10 };
/*
* 'math_state_restore()' saves the current math information in the
* old math state array, and gets the new ones from the current task
*/
/* math_state_restore,
* 备份上一个进程在协处理器上的运
* 行状态,以让当前进程使用协处理器。*/
void math_state_restore()
{
if (last_task_used_math == current)
return;
/* 在向协处理器发送命令前需先发送fwait命令 */
__asm__("fwait");
/* 在当前进程使用协处理器之前,先为上一个进程备份其协处理器状态 */
if (last_task_used_math) {
__asm__("fnsave %0"::"m" (last_task_used_math->tss.i387));
}
/* 若当前进程使用过协处理器,则将当前进程在协处理器上
* 的运行状态写入协处理器,若当前进程首次使用协处理器
* 则初始设置协处理器并标识当前进程使用协处理器标志。*/
last_task_used_math=current;
if (current->used_math) {
__asm__("frstor %0"::"m" (current->tss.i387));
} else {
__asm__("fninit"::);
current->used_math=1;
}
}
/*
* 'schedule()' is the scheduler function. This is GOOD CODE! There
* probably won't be any reason to change this, as it should work well
* in all circumstances (ie gives IO-bound processes good response etc).
* The one thing you might take a look at is the signal-handler code here.
*
* NOTE!! Task 0 is the 'idle' task, which gets called when no other
* tasks can run. It can not be killed, and it cannot sleep. The 'state'
* information in task[0] is never used.
*/
/* schedule,
* 任务(进程)调度函数。*/
void schedule(void)
{
int i,next,c;
struct task_struct ** p;
/* check alarm, wake up any interruptible tasks that have got a signal */
/* 遍历管理进程的结构体, 检查为各进程所设置的报警是否超时, 若超时则为当
* 前进程设置报警超时信号。若当前进程被设置了不可屏蔽信号或未被屏蔽信号
* 且当前进程的状态为准备就绪则为当前进程设置可运行状态, 让其加入可被调
* 度进程的行列中。
*
* jiffies在系统开机时为0,在定时器中断处理函数中每约10ms自增1。在为进程
* 设置报警超时值alarm时是基于jiffies来设置的, 比如为当前进程设置30ms后
* 报警超时,alarm=jiffies + 3。所以当alarm < jiffes时表示报警已超时。
*
* 为进程设置的超时信号将在系统调用完成后被处理(见ret_from_sys_call)。
*
* 在进程信号中,有两个信号不可被blocked屏蔽(SIGKILL和SIGSTOP),所以此处
* 做了判断: 若当前进程需要处理不可屏蔽信号和需要处理未被blocked屏蔽信
* 号时则将处于就绪状态的进程置于可运行状态,好让该进程在后续处理信号。*/
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;
}
/* this is the scheduler proper: */
/* 调度处于运行状态的进程。遍历处于运行状态的进程,从当前进程切换到时间片
* 最大的进程。若当前除初始进程外无其他进程运行,则next将保持原本指向初始
* 进程索引0。若除初始进程外的其余就绪进程的时间片皆为0,则用进程的优先级
* 设置进程的时间片,然后切换到首次遍历到的时间片为0的进程中运行,该运行进
* 程的时间片数等于其优先级数。*/
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); /* 从当前进程切换到时间片最大的进程中运行 */
}
/* sys_pause,
* 将当前进程置于准备就绪状态,
* 调用进程调度函数调度时间片最大的进程运行。*/
int sys_pause(void)
{
current->state = TASK_INTERRUPTIBLE;
schedule();
return 0;
}
/* sleep_on,
* 让当前进程进入睡眠,*p最初指向NULL。
*
* 将当前进程结构体地址赋给p,然后将当前进
* 程设置为未就绪状态。直到其他进程调用函
* 数wake_up(p)重新将当前进程设置就绪状态。*/
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指向当前进程结构体。*/
tmp = *p;
*p = current;
/* 将当前进程设置为未就绪状态然后调度
* 时间片最大的其他进程运行。*/
current->state = TASK_UNINTERRUPTIBLE;
schedule();
/* 调用schedule()函数切换其他进程运行后,
* 本进程不会再被CPU运行。本进程运行上下
* 文将被备份到其TSS中, 包括本函数栈帧状
* 态。在本进程被其他进程调用wake_up设置
* 为就绪状态且被函数 schedule()切换运行
* 后,本进程才会被重新执行即从 switch_to
* 中ljmp之后的指令开始执行, 然后从函数
* schedule()函数返回到此处从而执行判断
* 语句if (tmp)。
*
* tmp->state=0即将tmp所指向进程即上一个
* 调用sleep_on()的进程设置为就绪状态。*/
if (tmp)
tmp->state=0;
}
/* interruptible_sleep_on,
* 让当前进程进入睡眠。
* 与sleep_on不同的是,经本函数进入睡眠进程的状态为准备就绪
* 状态,该状态除可由wake_up唤醒后,还可由信号唤醒即置可运行
* 状态。如1->2->3->4四个进程依次调用本函数进入睡眠,假设进
* 程2收到信号,则进程被置就绪状态的顺序为4->3->2->1。*/
void interruptible_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指向当前进程结构体。*/
tmp=*p;
*p=current;
/* 将当前进程设置为准备就绪状态然后调度
* 时间片最大的其他进程运行。准备就绪状
* 态除了能被其他进程调用wake_up唤醒外,
* 其他进程通过为本进程发送信号的方式将
* 本进程再次设置为就绪状态,见schdule()。*/
repeat: current->state = TASK_INTERRUPTIBLE;
schedule();
/* 当本进程被唤醒且再次切换到本进程
* 中时, 后续语句才会被执行。因为调
* 用本函数进入睡眠的进程可被信号唤
* 醒(见schedule)。所以被唤醒进程有
* 可能不是最后一次调用本函数进入睡
* 眠的进程, 所以在此处将最后调用本
* 函数的进程唤醒并再次将本进程置为
* 准备就绪状态后进行进程调度。
*
* struct task_struct *tmp = NULL;
* 假设进程1,2,3,4通过实参tmp 依次
* 调用了本函数 1 -> 2 -> 3 -> 4即
* interruptible_sleep_on(&tmp);
*
* 若通过wake_up(&tmp)陆续唤醒4个进
* 程, 该过程同wake_up 唤醒通过调用
* sleep_on进入睡眠的进程一样。
*
* 看看通过信号如何唤醒这4个进程的,
* 假设进程2 收到某信号被唤醒后执行
* 到此处, 此时*p即tmp指向进程4结构
* 体,此处则将最后调用本函数即进程4
* 置为就绪状态并重新进入睡眠。待进
* 程被运行后, 进程3,2,1 会被依次置
* 就绪状态(同调用wake_up唤醒)。*/
if (*p && *p != current) {
(**p).state=0;
goto repeat;
}
/* 最后调用本函数的进程被唤醒后, 将唤
* 醒上一个调用本函数的进程, 依次类推
* 直到tmp = NULL即第一个调用本函数的
* 进程为止。*/
*p=NULL; /* 复位实参在本进程中所指向的上一个进入睡眠的进程 */
if (tmp)
tmp->state=0;
}
/* wake_up,
* 唤醒由参数*p指向结构体所管理进程,即
* 为该进程设置为已就绪状态。由wake_up
* 唤醒的进程为最后调用sleep_on(p)的进
* 程,该进程将会在sleep_on()函数中唤醒
* 在他之前调用sleep_on的进程,依次类推
* 直到唤醒所有调用过sleep_on(p)的进程。*/
void wake_up(struct task_struct **p)
{
if (p && *p) {
(**p).state=0;
/* 复位实参在本进程中所指向的上一个进入睡眠的进程 */
*p=NULL;
}
}
/*
* OK, here are some floppy things that shouldn't be in the kernel
* proper. They are here because the floppy needs a timer, and this
* was the easiest way of doing it.
*/
/* 不懂点软盘相关专业知识,这些程序读起来还真是一头雾水,携带一下参考下书籍吧。*/
/* 等待软驱A-D马达启动并到达正常转速的进程指针数组 */
static struct task_struct * wait_motor[4] = {NULL,NULL,NULL,NULL};
/* 存储A-D软驱启动所需时间片数的数组 */
static int mon_timer[4]={0,0,0,0};
/* 存储A-D软驱停止所需时间片数的数组 */
static int moff_timer[4]={0,0,0,0};
/* A-D软驱控制器数字输出寄存器值,
* bit[7..4]: 标识D-A马达是否启动,1-启动;0-关闭。
* bit[3]: 1/0 - 允许DMA和中断请求/禁止DMA和中断请求。
* bit[2]: 1/0 - 启动/复位软盘控制器。
* bit[1..0]: 标识当前选择的软驱,[(00)2..(11)2]对应A-D。*/
unsigned char current_DOR = 0x0C;
/* ticks_to_floppy_on,
* 指定软驱nr(0-3对应A-D)启动和停止所需等待时间。*/
int ticks_to_floppy_on(unsigned int nr)
{
extern unsigned char selected;
unsigned char mask = 0x10 << nr;
if (nr>3)
panic("floppy_on: nr>3");
moff_timer[nr]=10000; /* 100 s = very big :-) */
cli(); /* use floppy_off to turn it off */
mask |= current_DOR;
if (!selected) {
mask &= 0xFC;
mask |= nr;
}
/* 若软盘输出寄存器当前值与要求值不同,
* 若软驱没有启动则置0.5s的等待启动时
* 间,若软驱已启动则再置20ms等待时间。*/
if (mask != current_DOR) {
outb(mask,FD_DOR);
if ((mask ^ current_DOR) & 0xf0)
mon_timer[nr] = HZ/2;
else if (mon_timer[nr] < 2)
mon_timer[nr] = 2;
current_DOR = mask;
}
sti();
/* 软盘定时值在do_floppy_timer中会被递减 */
return mon_timer[nr];
}
/* floppy_on,
* 等待软盘启动。*/
void floppy_on(unsigned int nr)
{
cli();
/* 当等待软盘启动时间未完毕时,
* 则等待相应软驱的进程睡眠等待。*/
while (ticks_to_floppy_on(nr))
sleep_on(nr+wait_motor);
sti();
}
/* floppy_off,
* 设置停止软盘马达的等待时间。*/
void floppy_off(unsigned int nr)
{
moff_timer[nr]=3*HZ;
}
/* do_floppy_timer,
* 软盘定时器超时C处理函数,该函数被
* do_timer函数调用,即约每10ms就会被调用一次。*/
void do_floppy_timer(void)
{
int i;
unsigned char mask = 0x10;
for (i=0 ; i<4 ; i++,mask <<= 1) {
if (!(mask & current_DOR))
continue;
/* 若等待马达启动定时到达则唤醒等待马达启动的进程 */
if (mon_timer[i]) {
if (!--mon_timer[i])
wake_up(i+wait_motor);
/* 若等待软盘马达停止超时则复位马达启动
* 位并更新记录软盘数字输出寄存器的变量 */
} else if (!moff_timer[i]) {
current_DOR &= ~mask;
outb(current_DOR,FD_DOR);
} else
moff_timer[i]--; /* 等待软盘马达停止时间片递减 */
}
}
#define TIME_REQUESTS 64
/* static struct timer_list,
* 定时器结构体。*/
static struct timer_list {
long jiffies; /* 定时器时间片 */
void (*fn)(); /* 定时器超时回调函数 */
struct timer_list * next; /* 指向timer_list数组中的上一元素 */
} timer_list[TIME_REQUESTS], * next_timer = NULL;
/* add_timer,
* 往定时器数组timer_list中加入超时时间片为jiffies
* 超时回调函数为fn的定时器。
*
* 如往timer_list数组中依次加入3个时间片分别为
* 10,3,33的定时器的过程
* |——————————|
* |jiffies=10|
* |fn10 |
* |next=NULL |
* |——————————|
* timer_list[0]
* next_timer = &timer_list[0]
*
* 添加3个时间片超时的定时器
* |——————————| |———————————————————|
* |jiffies=10| |jiffies=3 |
* |fn10 | |fn3 |
* |next=NULL | |next=&timer_list[0]|
* |——————————| |———————————————————|
* timer_list[0] timer_list[1]
* next_timer = &timer_list[1]
* add_timer在处理加入时间片较小定时器时似乎是
* 有问题的, 应该将timer_list[0]设置为定时器标
* 兵,该定时器标兵时间片保持为0.
*
* 添加33个时间片超时的定时器
* |——————————| |——————————| |———————————————————|
* |jiffies=20| |jiffies=10| |jiffies=3 |
* |fn33 | |fn10 | |fn10 |
* |next=NULL | |next=NULL | |next=&timer_list[1]|
* |——————————| |——————————| |———————————————————|
* timer_list[0] timer_list[1] timer_list[2]
* next_timer = &timer_list[2] */
void add_timer(long jiffies, void (*fn)(void))
{
struct timer_list * p;
if (!fn)
return;
cli(); /* 禁止中断 */
/* 若时间片小于等于0则理解执行定时器的超时回调函数 */
if (jiffies <= 0)
(fn)();
/* 否则将定时器加入到定时器数组timer_list中的合理位置上 */
else {
/* 遍历一个空闲的timer_list元素,将该定时器记录在该空闲元素中 */
for (p = timer_list ; p < timer_list + TIME_REQUESTS ; p++)
if (!p->fn)
break;
if (p >= timer_list + TIME_REQUESTS)
panic("No more time requests free");
p->fn = fn;
p->jiffies = jiffies;
p->next = next_timer; /* 指向链表头 */
next_timer = p;
/* 调整新加入定时器在timer_list中的位置,将
* timer_list[0]设置为标兵后以下表达式中的"<"应为"<=" */
while (p->next && p->next->jiffies < p->jiffies) {
/* 将插入定时器时间片减去当前定时器时间片
* 并交换两个定时器, 直到插入定时器时间片
* 比下一个定时器时间片小为止。*/
p->jiffies -= p->next->jiffies;
fn = p->fn;
p->fn = p->next->fn;
p->next->fn = fn;
jiffies = p->jiffies;
p->jiffies = p->next->jiffies;
p->next->jiffies = jiffies;
p = p->next;
}
}
sti();
}
/* do_timer,
* 定时中断C处理函数。由定时器中断入口处理函
* 数_timer_interrupt调用,即约每10ms调用一次。*/
void do_timer(long cpl)
{
extern int beepcount;
extern void sysbeepstop(void);
/* 当beepcount计数为0时关闭扬声器 */
if (beepcount)
if (!--beepcount)
sysbeepstop();
/* 更新当前进程在用户态和内核态运行时间片数 */
if (cpl)
current->utime++;
else
current->stime++;
if (next_timer) {
next_timer->jiffies--;
/* 调用时间片为已0的定时器的回调函数, 并将当前定
* 时器的回调函数指针清空,以表该数组元素空闲状态。*/
while (next_timer && next_timer->jiffies <= 0) {
void (*fn)(void);
/* next_timer指向下一个时间片最小的定时器 */
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();
/* 调用schedule()切换到其他进程中运行后,本进程
* 将阻塞在switch_to()中ljmp之后的一条语句处。
* 直到所有进程时间片都运行完毕, 各进程在调度函
* 数schedule()中重新根据各自优先级获得时间片后,
* 且本进程再被调度运行时才会从阻塞处返回到此处,
* 从而才结束本进程的定时器中断, 并依次返回到应
* 用程序发生中断处继续运行。*/
}
/* sys_*, 进程系统调用系列 */
/* sys_alarm,
* 设置当前进程seconds后报警。*/
int sys_alarm(long seconds)
{
int old = current->alarm;
/* 计算原超时值还有多少秒 */
if (old)
old = (old - jiffies) / HZ;
/* 将seconds换算成毫秒后基于当前系统时间片将seconds赋值
* alarm以实现当前进程在seconds后超时报警(见schedule())。*/
current->alarm = (seconds>0)?(jiffies+HZ*seconds):0;
return (old);
}
/* sys_getpid,
* 获取当前进程id。*/
int sys_getpid(void)
{
return current->pid;
}
/* sys_getppid,
* 获取当前进程父进程id。*/
int sys_getppid(void)
{
return current->father;
}
/* sys_getuid,
* 获取当前进程用户id。*/
int sys_getuid(void)
{
return current->uid;
}
/* sys_geteuid,
* 获取当前进程有效用户id。*/
int sys_geteuid(void)
{
return current->euid;
}
/* sys_getgid,
* 获取当前进程组id。*/
int sys_getgid(void)
{
return current->gid;
}
/* sys_getegid,
* 获取当前进程有效组id */
int sys_getegid(void)
{
return current->egid;
}
/* sys_nice,
* 将当前进程优先级降低increment。*/
int sys_nice(long increment)
{
if (current->priority-increment>0)
current->priority -= increment;
return 0;
}
/* sched_init,
* 任务调度初始化。
*
* 开启定时器中断并设置用于进程调度定时器的中断处理入口程序,
* 在GDT中设置初始进程的TSS和LDT,将初始任务的TSS和LDT分别加
* 载到TR和LDTR寄存器中。顺便设置系统调用处理入口程序(int 80h)。*/
void sched_init(void)
{
int i;
struct desc_struct * p;
if (sizeof(struct sigaction) != 16)
panic("Struct sigaction MUST be 16 bytes");
/* 在GDT中设置初始进程init_task的TSS和LDT。
* LDT用于保护应用程序内存段;TSS用于保存进程运行上下文。*/
set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));
set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));
/* 初始化GDT未用表项;初始化task数组 */
p = gdt+2+FIRST_TSS_ENTRY;
for(i=1;ia=p->b=0;
p++;
p->a=p->b=0;
p++;
}
/* Clear NT, so that we won't have troubles with that later on */
/* 复位标志寄存器NT位(若NT置位,执行IRET时会进行任务切换 80386_P7.5) */
__asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");
/* 分别加载初始进程TSS和LDT到TR和LDTR寄存器,
* 在进入用户态时,CPU会执行TR所指TSS所描述的进程。*/
ltr(0);
lldt(0);
/* 粗略理解用户模式下的多任务切换过程。
* CPU --> TR-->GDT[TR] --> TSS;
* GDT[TSS.ldt]-->LDT[TSS.cs+TSS.ds]-->CS:EIP(DS:ESI) */
/* 配置定时器控制器8254,分配给定时器控制器的端口地
* 址范围[0x40, 0x5f],8254使用端口地址最低两位用于
* 选择3通道和1寄存器,所以只用到了前三个端口地址。
*
* 0x43地址低2位为1, 使用out指令时写方式控制寄存器,
* 0x36: 选择通道0计数器, 计数器读写操作:先读/写LSB后MSB;
* 方式3方波计数, 16位二进制;
*
* 0x40地址低两位为1,out指令时表示装入通道0计数器,先低8位后高8位。
*
* 对通道0计数器中赋予初始值(计数器产生方波序列到8259A-1 IRQ0
* 8259A配置的中断方式是沿上升沿触发中断,减法计数器减1计数,中
* 断定时器频率为1.1931816)。此处配置通道0计数器初始值为频率值/100,
* 即约0.01s(10ms)发生一次定时器中断。
*
* 定时器计数频率为1.1931816MHz, 即1s约1193182次计数,
* 当设定计数器初值为fs/100时, 从fs减到0所花时间约为10ms。*/
outb_p(0x36,0x43); /* binary, mode 3, LSB/MSB, ch 0 */
outb_p(LATCH & 0xff , 0x40); /* LSB */
outb(LATCH >> 8 , 0x40); /* MSB */
/* 设置定时器中断处理函数,
* 允许8259A-1的IRQ0中断,即开启定时器中断。
*
* 粗略走一下timer_interrupt函数后返回到这里,
* 最好浏览到调度函数为止。*/
set_intr_gate(0x20,&timer_interrupt);
outb(inb_p(0x21)&~0x01,0x21);
/* 设置系统调用中断处理函数,即CPU执行
* "int 80h"指令时会跳转执行system_call */
set_system_gate(0x80,&system_call);
/* 依次粗略地跟着timer_interrupt和system_call函数的流程走一圈吧;
* 粗略了解任务(进程)管理数据结构体类型和管理初始任务结构体吧。
* 了解这两个流程对后续多进程管理程序的理解有铺垫。
*
* 粗略跟踪timer_interrupt和system_call后,此文了解到
* [1] 在linux0.11内核态,除显式调用schedule()进行任务调度外,不会发生任务调度切换;
* [2] 在用户态,除通过系统调用(如pause)显式调用schedule()进行任务调度外,只有以下
* 两种情况会调用schedule()进行任务切换,
* [2-1] 在当前任务时间片运行完毕后在定时器中断(约每10ms发生一次)处理程序中;
* [2-2] 在系统调用完毕检查到当前进程处于非运行状态(见_system_call)。*/
}
#ifndef _UNISTD_H
#define _UNISTD_H
/* ok, this may be a joke, but I'm working on it */
#define _POSIX_VERSION 198808L
#define _POSIX_CHOWN_RESTRICTED /* only root can do a chown (I think..) */
#define _POSIX_NO_TRUNC /* no pathname truncation (but see in kernel) */
#define _POSIX_VDISABLE '\0' /* character to disable things like ^C */
/*#define _POSIX_SAVED_IDS */ /* we'll get to this yet */
/*#define _POSIX_JOB_CONTROL */ /* we aren't there quite yet. Soon hopefully */
/* 标准输入文件描述符;
* 标准输出文件描述符;
* 错误输出文件描述符。*/
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
#ifndef NULL
#define NULL ((void *)0)
#endif
/* access */
#define F_OK 0
#define X_OK 1
#define W_OK 2
#define R_OK 4
/* lseek */
#define SEEK_SET 0
#define SEEK_CUR 1
#define SEEK_END 2
/* _SC stands for System Configuration. We don't use them much */
#define _SC_ARG_MAX 1
#define _SC_CHILD_MAX 2
#define _SC_CLOCKS_PER_SEC 3
#define _SC_NGROUPS_MAX 4
#define _SC_OPEN_MAX 5
#define _SC_JOB_CONTROL 6
#define _SC_SAVED_IDS 7
#define _SC_VERSION 8
/* more (possibly) configurable things - now pathnames */
#define _PC_LINK_MAX 1
#define _PC_MAX_CANON 2
#define _PC_MAX_INPUT 3
#define _PC_NAME_MAX 4
#define _PC_PATH_MAX 5
#define _PC_PIPE_BUF 6
#define _PC_NO_TRUNC 7
#define _PC_VDISABLE 8
#define _PC_CHOWN_RESTRICTED 9
#include
#include
#include
#include
#ifdef __LIBRARY__
/* 系统调用号,__NR_系统调用名 */
#define __NR_setup 0 /* used only by init, to get system going */
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
#define __NR_link 9
#define __NR_unlink 10
#define __NR_execve 11
#define __NR_chdir 12
#define __NR_time 13
#define __NR_mknod 14
#define __NR_chmod 15
#define __NR_chown 16
#define __NR_break 17
#define __NR_stat 18
#define __NR_lseek 19
#define __NR_getpid 20
#define __NR_mount 21
#define __NR_umount 22
#define __NR_setuid 23
#define __NR_getuid 24
#define __NR_stime 25
#define __NR_ptrace 26
#define __NR_alarm 27
#define __NR_fstat 28
#define __NR_pause 29
#define __NR_utime 30
#define __NR_stty 31
#define __NR_gtty 32
#define __NR_access 33
#define __NR_nice 34
#define __NR_ftime 35
#define __NR_sync 36
#define __NR_kill 37
#define __NR_rename 38
#define __NR_mkdir 39
#define __NR_rmdir 40
#define __NR_dup 41
#define __NR_pipe 42
#define __NR_times 43
#define __NR_prof 44
#define __NR_brk 45
#define __NR_setgid 46
#define __NR_getgid 47
#define __NR_signal 48
#define __NR_geteuid 49
#define __NR_getegid 50
#define __NR_acct 51
#define __NR_phys 52
#define __NR_lock 53
#define __NR_ioctl 54
#define __NR_fcntl 55
#define __NR_mpx 56
#define __NR_setpgid 57
#define __NR_ulimit 58
#define __NR_uname 59
#define __NR_umask 60
#define __NR_chroot 61
#define __NR_ustat 62
#define __NR_dup2 63
#define __NR_getppid 64
#define __NR_getpgrp 65
#define __NR_setsid 66
#define __NR_sigaction 67
#define __NR_sgetmask 68
#define __NR_ssetmask 69
#define __NR_setreuid 70
#define __NR_setregid 71
/* _syscall0(type,name),
* 用于定义名为name返回值类型为type的无参类型系统调用。
*
* IDT[80h]为系统调用的陷阱门描述符,系统调用
* 的入口处理函数为system_call(见sched_init)。
* (可访问条件IDT[80h].DPL=CPL,CPL>GDT[IDT[80h].SEL].DPL,所以会发生栈切换
* 即CPU执行int 80h指令后,往栈中写入的内容为ss esp eflag(并设置eflag.tf =
* eflag.if=0) cs eip。其中eip="if (__res >= 0)"语句偏移地址处。
*
* 内联汇编入参。
* "0" (__NR_##name), eax=__NR_name,__NR_name为系统调用号,如__NR_fork为2
*
* 内联汇编出参。
* "=a" (__res), __res=eax。*/
#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name)); \
/* 当int 80h系统调用即system_call执行完毕后以下语句执行 */ \
if (__res >= 0) \
return (type) __res; \
errno = -__res; \
return -1; \
}
/* _syscall1(type,name,atype,a),
* 用于定义带一个参数的系统调用,
* 如static inline _syscall1(int, void *, BIOS)定义的是
* static inline int setup(void *BIOS)
* {
* ...
* }
*
* "0" (__NR_##name)跟%0使用同一个寄存器eax作为输入参数,
* 第二个参数为ebx = a,
* 经过中断机制进入system_call中断调用, 依据eax调用sys_setup
* 返回后将执行"=a"(__res)语句及后续语句。*/
#define _syscall1(type,name,atype,a) \
type name(atype a) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(a))); \
if (__res >= 0) \
return (type) __res; \
errno = -__res; \
return -1; \
}
/* _syscall2(type,name,atype,a,btype,b),
* 用于定义带2个参数的系统调用,
*
* 其余同_syscall1。*/
#define _syscall2(type,name,atype,a,btype,b) \
type name(atype a,btype b) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b))); \
if (__res >= 0) \
return (type) __res; \
errno = -__res; \
return -1; \
}
/* _syscall3,
* 该宏用于定义含三个参数的系统调用,该系统调用的原型为
* type name(atype a, btype b, ctype c)。
*
* 在内联汇编输入部分,
* eax=系统调用号;
* ebx=系统调用第1个参数;
* ecx=系统调用第2个参数;
* edx=系统调用第3个参数。
*
* 系统调用返回结果将由eax传递给__res。*/
#define _syscall3(type,name,atype,a,btype,b,ctype,c) \
type name(atype a,btype b,ctype c) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b)),"d" ((long)(c))); \
if (__res>=0) \
return (type) __res; \
errno=-__res; \
return -1; \
}
#endif /* __LIBRARY__ */
extern int errno;
int access(const char * filename, mode_t mode);
int acct(const char * filename);
int alarm(int sec);
int brk(void * end_data_segment);
void * sbrk(ptrdiff_t increment);
int chdir(const char * filename);
int chmod(const char * filename, mode_t mode);
int chown(const char * filename, uid_t owner, gid_t group);
int chroot(const char * filename);
int close(int fildes);
int creat(const char * filename, mode_t mode);
int dup(int fildes);
int execve(const char * filename, char ** argv, char ** envp);
int execv(const char * pathname, char ** argv);
int execvp(const char * file, char ** argv);
int execl(const char * pathname, char * arg0, ...);
int execlp(const char * file, char * arg0, ...);
int execle(const char * pathname, char * arg0, ...);
volatile void exit(int status);
volatile void _exit(int status);
int fcntl(int fildes, int cmd, ...);
int fork(void);
int getpid(void);
int getuid(void);
int geteuid(void);
int getgid(void);
int getegid(void);
int ioctl(int fildes, int cmd, ...);
int kill(pid_t pid, int signal);
int link(const char * filename1, const char * filename2);
int lseek(int fildes, off_t offset, int origin);
int mknod(const char * filename, mode_t mode, dev_t dev);
int mount(const char * specialfile, const char * dir, int rwflag);
int nice(int val);
int open(const char * filename, int flag, ...);
int pause(void);
int pipe(int * fildes);
int read(int fildes, char * buf, off_t count);
int setpgrp(void);
int setpgid(pid_t pid,pid_t pgid);
int setuid(uid_t uid);
int setgid(gid_t gid);
void (*signal(int sig, void (*fn)(int)))(int);
int stat(const char * filename, struct stat * stat_buf);
int fstat(int fildes, struct stat * stat_buf);
int stime(time_t * tptr);
int sync(void);
time_t time(time_t * tloc);
time_t times(struct tms * tbuf);
int ulimit(int cmd, long limit);
mode_t umask(mode_t mask);
int umount(const char * specialfile);
int uname(struct utsname * name);
int unlink(const char * filename);
int ustat(dev_t dev, struct ustat * ubuf);
int utime(const char * filename, struct utimbuf * times);
pid_t waitpid(pid_t pid,int * wait_stat,int options);
pid_t wait(int * wait_stat);
int write(int fildes, const char * buf, off_t count);
int dup2(int oldfd, int newfd);
int getppid(void);
pid_t getpgrp(void);
pid_t setsid(void);
#endif
/*
* linux/kernel/system_call.s
*
* (C) 1991 Linus Torvalds
*/
/*
* system_call.s contains the system-call low-level handling routines.
* This also contains the timer-interrupt handler, as some of the code is
* the same. The hd- and flopppy-interrupts are also here.
*
* NOTE: This code handles signal-recognition, which happens every time
* after a timer-interrupt and after each system call. Ordinary interrupts
* don't handle signal-recognition, as that would clutter them up totally
* unnecessarily.
*
* system_call.s 包含了系统调用入口处理程序。定时器,硬盘中断,软盘中断入口处理函数
* 也在本文件中,因为他们有部分代码和系统调用是相同的。
*
* 注,在每次定时器中断和系统调用完毕后会检查并处理进程信号。不在其他中断中处理检查
* 或处理信号,以免把信号处理整得很凌乱。
*
* Stack layout in 'ret_from_system_call':
* 在 ret_from_sys_call 中时,栈中内容如下:
*
* 0(%esp) - %eax
* 4(%esp) - %ebx
* 8(%esp) - %ecx
* C(%esp) - %edx
* 10(%esp) - %fs
* 14(%esp) - %es
* 18(%esp) - %ds
* 1C(%esp) - %eip
* 20(%esp) - %cs
* 24(%esp) - %eflags
* 28(%esp) - %oldesp
* 2C(%esp) - %oldss
*/
/* 在用户程序中(CPL=3)发生中断进入内核代码段涉及特权级变化,
* 所以在中断发生时CPU进行现场保护时涉及栈变换,即CPU的现场
* 保护相当于 push ss; push esp; pushf; push cs; push eip */
SIG_CHLD = 17
/* 执行到ret_from_system_call程序时,寄存器在栈中的备份情况如下 */
EAX = 0x00 /* ss:esp处备份了eax */
EBX = 0x04 /* ss:esp[0x04]处备份了ebx */
ECX = 0x08 /* ... */
EDX = 0x0C
FS = 0x10
ES = 0x14
DS = 0x18
EIP = 0x1C
CS = 0x20
EFLAGS = 0x24
OLDESP = 0x28
OLDSS = 0x2C
/* struct task_struct结构体内各成员偏移 */
state = 0 # these are offsets into the task-struct.
counter = 4
priority = 8
signal = 12
sigaction = 16 # MUST be 16 (=len of sigaction)
blocked = (33*16)
# offsets within sigaction
/* struct sigaction 结构体内各成员偏移 */
sa_handler = 0
sa_mask = 4
sa_flags = 8
sa_restorer = 12
/* 系统调用个数 */
nr_system_calls = 72
/*
* Ok, I get parallel printer interrupts while using the floppy for some
* strange reason. Urgel. Now I just ignore them.
*/
.globl _system_call,_sys_fork,_timer_interrupt,_sys_execve
.globl _hd_interrupt,_floppy_interrupt,_parallel_interrupt
.globl _device_not_available, _coprocessor_error
/* 非法系统调用,置-1于eax(返回-1)然后中断返回 */
.align 2
bad_sys_call:
movl $-1,%eax
iret
/* 跳转执行任务调度函数 */
.align 2
reschedule:
# schedule函数返回执行ret语句时将从栈中弹出此处压入
# 栈中的地址从而跳转执行ret_from_sys_call以从内核中返回
pushl $ret_from_sys_call
jmp _schedule
# _system_call系统调用入口函数
# 当通过int 80h指令从用户程序中调用系统API时,
# CPU在进行现场保护时会将以下寄存器依次压入栈中
# ss esp eflag cs eip
# -------------------
.align 2
_system_call:
/* 在进行系统调用时,eax=系统调用号。
* 此处判断eax是否超过系统调用最大个数,
* 若超过则跳转执行bad_sys_call */
cmpl $nr_system_calls-1,%eax
ja bad_sys_call
/* 依次备份用户程序的数据段寄存器和普通寄存器,
* 除fs段寄存器仍指向用户程序内存段外,其余段寄
* 存器皆指向内核段;即在内核态时, 通过fs段寄存
* 器可以实现用户内存段和内核内存段的数据拷贝。*/
push %ds
push %es
push %fs
pushl %edx
pushl %ecx # push %ebx,%ecx,%edx as parameters
pushl %ebx # to the system call
movl $0x10,%edx # set up ds,es to kernel space
mov %dx,%ds
mov %dx,%es
movl $0x17,%edx # fs points to local data space
mov %dx,%fs
/* call (4 * eax + _sys_call_table),
* 根据系统调用号eax调用相应系统调用,
* 如系统调用号eax=2将调用sys_fork。*/
call _sys_call_table(,%eax,4)
pushl %eax # 将系统调用返回值压入栈中备份
/* 若当前任务为不可运行状态则调用任务调度函数进行任务切换 */
movl _current,%eax
cmpl $0,state(%eax) # if (0 != _current->state) reschedule();
jne reschedule
/* 检查当前任务时间片,若运行完毕则进行任务调度 */
cmpl $0,counter(%eax)
je reschedule
/* 这是每个系统调用和中断处理程序执行完毕后都会跳转执
* 行的子程序,该子程序用于处理当前任务所需处理的信号。*/
ret_from_sys_call:
/* 若当前任务为初始任务,则向前跳转到3标号处 */
movl _current,%eax
cmpl _task,%eax
je 3f
/* 若进入内核态前的任务为超级任务(内核段选择符不为0xf)则向前跳转到标号3处 */
cmpw $0x0f,CS(%esp) # was old code segment supervisor
jne 3f
/* 若进入内核态前的任务栈段不为用户态栈(不等于0x17)则向前跳转到标号3处 */
cmpw $0x17,OLDSS(%esp) # was stack segment = 0x17
jne 3f
/* 若进入内核的任务为应用程序任务,则处理任务中非
* 被屏蔽的信号。blocked nr位为1时表屏蔽nr信号。*/
movl signal(%eax),%ebx # task[0].signal
movl blocked(%eax),%ecx # task[0].blocked
notl %ecx # blocked各位取反
andl %ebx,%ecx # 取没被阻塞的signal
bsfl %ecx,%ecx # 从低到高(0->31)取第一位为1的位的索引存到ecx中
je 3f # 若全为0则不进行信号处理向前跳转到标号3处
btrl %ecx,%ebx # 将ebx的ecx清0, 并存入signal中即清信号 ecx + 1
movl %ebx,signal(%eax)
incl %ecx # 对应的信号值 ecx + 1
pushl %ecx # 信号值入栈作为_do_signal函数最后/左一个参数
call _do_signal # 处理任务的信号,kernel/signal.c
popl %eax
# 恢复存在栈中的寄存器
3: popl %eax
popl %ebx
popl %ecx
popl %edx
pop %fs
pop %es
pop %ds
iret # 恢复系统调用和中断现场
/* _coprocessor_error,
* 协处理器中断处理入口程序。
* 当协处理器发生错误时向CPU发送中断号为16的中断信息,
* 让CPU调用IDT[16]中的中断处理程序。*/
.align 2
_coprocessor_error:
push %ds
push %es
push %fs
pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
movl $0x10,%eax # 内核数段描述符
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax # 用户数据段描述符
mov %ax,%fs
# _math_error执行RET指令时将跳转执行ret_from_sys_call
pushl $ret_from_sys_call
jmp _math_error
/* _device_not_available,
* 若CR0中EM置位,CPU执行协处理器指令时会处理IDT[7]中断
* 以让device_not_available模拟协处理器执行协处理器指令。*/
.align 2
_device_not_available:
push %ds
push %es
push %fs
pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax
mov %ax,%fs
pushl $ret_from_sys_call
clts # clear TS so that we can use math
movl %cr0,%eax
testl $0x4,%eax # EM (math emulation bit)
je _math_state_restore
pushl %ebp
pushl %esi
pushl %edi
call _math_emulate
popl %edi
popl %esi
popl %ebp
ret
/* 经过对定时器8254和中断控制器8259A的配置,
* 定时器中断(IDT[20h])约10ms产生一次。*/
.align 2
_timer_interrupt:
/* 备份中断前的数据段寄存器和普通寄存器 */
push %ds # save ds,es and put kernel data space
push %es # into them. %fs is used by _system_call
push %fs
pushl %edx # we save %eax,%ecx,%edx as gcc doesn't
pushl %ecx # save those across function calls. %ebx
pushl %ebx # is saved as we use that in ret_sys_call
pushl %eax
/* 除fs段寄存器外,其余段寄存器指向内核段;
* 通过指向用户态的fs段寄存器可实现内核和
* 用户程序之间的数据拷贝。*/
movl $0x10,%eax # 加载内核数据段描述符到数据段寄存器。
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax # 0x17为用户程序数据段描述符。
mov %ax,%fs
/* 系统时间片增1,即系统时间片时间又增加10ms */
incl _jiffies # _jiffies定义在kernel/sched.c中(jiffies, 初始值为0)。
/* 往PIC发送EOI命令以结束本次定时器中断,见setup.s中对PIC的设置 */
movb $0x20,%al # EOI to interrupt controller
outb %al,$0x20
/* 检查定时器中断时所在任务的特权级 */
movl CS(%esp),%eax # 发生定时器中断时被压入栈时的cs寄存器。
andl $3,%eax # %eax is CPL (0 or 3, 0=supervisor)
/* 将eax入栈作为do_timer的参数并调用do_timer函数 */
pushl %eax # 请求特权级入栈作为do_timer的参数
call _do_timer # 'do_timer(long CPL)' does everything from
addl $4,%esp # task switching to accounting ...
/* 跳转执行ret_from_sys_call*/
jmp ret_from_sys_call
/* _sys_execve,
* 系统调用execve函数入口处。*/
.align 2
_sys_execve:
/* 取 发生系统调用execve()时eip寄存器被CPU备份在栈中的 地址,
* 并将该地址压入栈中作为do_execve函数最后一个参数,前三个函数
* 在 _system_call 中被入栈。
lea EIP(%esp),%eax
pushl %eax
call _do_execve # fs/exec.c/ do_execve
addl $4,%esp # 回收do_execve参数栈内存
ret
/* _sys_fork,
* 系统调用fork()内核代码入口处。*/
.align 2
_sys_fork:
/* 调用fork.c/find_empty_process以获取未用进程
* 号和管理进程的空闲结构体元素下标于eax。若当
* 前无管理进程的空闲结构体则向前跳转标号1处。*/
call _find_empty_process
testl %eax,%eax
js 1f
/* 将gs esi等寄存器压入栈中以作为fork.c/copy_process参数 */
push %gs
pushl %esi
pushl %edi
pushl %ebp
pushl %eax
call _copy_process
addl $20,%esp # 回收copy_process实参栈内存
# 返回到_system_call中call _sys_call_table(,%eax,4)之后语句处
1: ret
/* _hd_interrupt,
* 设置在IDT[0x2e]中的硬盘中断入口程序。
* 当通过hd_out函数向硬盘下发读写等命令后,
* 在硬盘准备好被读写后就会向PIC IRQ14输出
* 相应的硬盘中断,从而让CPU执行IDT[0x2e]中
* 的处理函数即此处的_hd_interrupt。
*
* CPU跳转执行_hd_interrupt时,在栈中依次保存
* 了以下寄存器(即中断现场保护) ss esp flag cs eip。
*
* 在_hd_interrupt中继续依次入栈的寄存器有
* eax ecx edx ds es fs。*/
_hd_interrupt:
pushl %eax
pushl %ecx
pushl %edx
push %ds
push %es
push %fs
movl $0x10,%eax # ds和es切换到内核数据段GDT[1]
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax # 加载用户数据段LDT[2]到fs
mov %ax,%fs
movb $0x20,%al # 见setup.s中对8259A的设置
outb %al,$0xA0 # EOI to interrupt controller #1
jmp 1f # give port chance to breathe
1: jmp 1f
1: xorl %edx,%edx # 异或操作将edx清0
xchgl _do_hd,%edx # 交换do_hd和edx的内容,do_hd的赋值见hd_out函数
testl %edx,%edx
jne 1f # 若hd_out非空则向前跳转到标号1处
movl $_unexpected_hd_interrupt,%edx # 若do_hd函数指针为NULL,则赋值此函数给do_hd。
1: outb %al,$0x20
call *%edx # "interesting" way of handling intr,如read_intr, write_intr etc.
# 中断函数执行完毕后, 从栈中恢复寄存器的值, 同时执行iret恢复中断现场。
pop %fs
pop %es
pop %ds
popl %edx
popl %ecx
popl %eax
iret
/* _floppy_interrupt,
* 设置在IDT[26h]中软盘中断入口处理程序。
*
* 在向软盘下发复位,校正,DMA读写命令并在软盘完成
* 这些命令后,软盘将引发软盘中断即会让CPU直行本函数,
* 本函数将调用do_floppy函数,do_floppy函数挂载了复位,
* 校正,DMA读写等中断处理C函数。
*
* 其余一些信息同_hd_interrupt。*/
_floppy_interrupt:
pushl %eax
pushl %ecx
pushl %edx
push %ds
push %es
push %fs
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax
mov %ax,%fs
movb $0x20,%al
outb %al,$0x20 # EOI to interrupt controller #1
xorl %eax,%eax
xchgl _do_floppy,%eax
testl %eax,%eax
jne 1f
movl $_unexpected_floppy_interrupt,%eax
1: call *%eax # "interesting" way of handling intr.
pop %fs
pop %es
pop %ds
popl %edx
popl %ecx
popl %eax
iret
/* _parallel_interrupt,
* 并行口中断处理入口程序IDT[39]。
* 该程序向PIC发送EOI命令以结束并口中断,其他功能还未实现。*/
_parallel_interrupt:
pushl %eax
movb $0x20,%al
outb %al,$0x20
popl %eax
iret
/*
* linux/kernel/fork.c
*
* (C) 1991 Linus Torvalds
*/
/*
* 'fork.c' contains the help-routines for the 'fork' system call
* (see also system_call.s), and some misc functions ('verify_area').
* Fork is rather simple, once you get the hang of it, but the memory
* management can be a bitch. See 'mm/mm.c': 'copy_page_tables()'
*/
/* fork.c 包含了 fork 系统调用的内核代码(见 system_call.s)和一些其他函数
* (如 verify_area)。一旦掌握 fork 的关键点, fork还是挺简单的,但是内存管
* 理程序就有些难以被理解,见 mm/mm.c中的copy_page_tables.*/
#include
#include
#include
#include
#include
extern void write_verify(unsigned long address);
/* 用于保存新建进程id号 */
long last_pid=0;
/* verify_area,
* 基于当前进程数据段基址dbase,以4Kb将内存段
* [dbase + addr, dbase + addr + size)对齐。
*
* 然后写时拷贝所对齐内存段: 当前进程被创建时
* 以页机制的方式共享父进程物理内存页。在当前
* 进程写与父进程共享的内存页时, 则将该共享内
* 存页内容拷贝到一块空闲未用的内存页中, 并将
* 被拷贝内容的内存页映射到当前进程页表中。如
* 果当前进程数据内存段引用计数为0,则表明该进
* 程还未创建任何子进程,可写原内存段。*/
void verify_area(void * addr,int size)
{
unsigned long start;
/* 使得(addr + size)以4Kb对齐 */
start = (unsigned long) addr;
size += start & 0xfff;
start &= 0xfffff000;
/* 让addr的段基址为当前进程数据段基址 */
start += get_base(current->ldt[2]);
/* 以4Kb为单位写时拷贝内存段
* [dbase + addr, dbase + addr + size) */
while (size>0) {
size -= 4096;
write_verify(start);
start += 4096;
}
}
/* copy_mem,
* 为(任务号为nr的)进程分配逻辑地址空间并设置在
* 其LDT中,通过页机制共享父进程代码和数据内存段。*/
int copy_mem(int nr,struct task_struct * p)
{
unsigned long old_data_base,new_data_base,data_limit;
unsigned long old_code_base,new_code_base,code_limit;
/* 根据选择符bit[2]=1时选择LDT段描述符, 0x0f和0x17分别为
* LDT段描述符GDT[LDTR][1]和GDT[LDTR][2]的选择符即分别选
* 中父进程LDT[1](代码段描述符)和LDT[2](数据段描述符)。
*
* 获取父进程代码段和数据段的基址。
*
* 父进程代码段和数据段的基址和限长见init_task赋值处, 初始进程
* 代码段基址=数据段基址=0x0;代码段限长=数据段限长=0x9ffff。第
* 2个用户进程的父进程为初始进程,所有用户进程LDT表内容将相同。*/
code_limit=get_limit(0x0f);
data_limit=get_limit(0x17);
old_code_base = get_base(current->ldt[1]);
old_data_base = get_base(current->ldt[2]);
if (old_data_base != old_code_base)
panic("We don't support separate I&D");
if (data_limit < code_limit)
panic("Bad data_limit");
/* 进程逻辑地址空间为[nr * 0x4000000, (nr+1) * 0x4000000 - 1]每个
* 进程的逻辑地址空间为64Mb,64进程逻辑空间共64 * 0x4000000 = 4Gb。
* 将进程代码段和数据段逻辑基址设置到其LDT中。*/
new_data_base = new_code_base = nr * 0x4000000;
p->start_code = new_code_base;
set_base(p->ldt[1],new_code_base);
set_base(p->ldt[2],new_data_base);
/* 将内存地址空间[old_data_base, old_data_base + data_limit]所对应页表
* 拷贝到内存地址空间[new_data_base, new_data_base + data_limit]对应页
* 表中。如此, 当访问[new_data_base, new_data_base + data_limit]中内存
* 地址时,(根据页机制,见head.s)将访问到父进程代码和数据所在内存段。*/
if (copy_page_tables(old_data_base,new_data_base,data_limit)) {
free_page_tables(new_data_base,data_limit);
return -ENOMEM;
}
return 0;
}
/*
* Ok, this is the main fork-routine. It copies the system process
* information (task[nr]) and sets up the necessary registers. It
* also copies the data segment in it's entirety.
*/
/* ss
* esp
* flag
* cs
* eip
* ds
* es
* fs
* edx
* ecx
* ebx
* call时入栈eip
* gs
* esi
* edi
* ebp
* eax */
/* copy_process,
* 创建子进程。
*
* nr - 管理即将创建子进程结构体的下标;
* ebp,...,gs为_sys_fork为其传递参数,这些寄存器值为父进程调用fork时的值;
* ebx,..ds为_system_call所传递实参, 这些寄存器值为父进程调用fork时的值;
* eip,...,ss为CPU执行父进程fork("int 80h指令")时往栈中写入的。*/
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; /* NOTE! this doesn't copy the supervisor stack */
p->state = TASK_UNINTERRUPTIBLE;
p->pid = last_pid; /* 子进程id(见find_empty_process) */
p->father = current->pid; /* 父进程id */
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; /* 上一个进程TSS选择符 */
p->tss.esp0 = PAGE_SIZE + (long) p; /* 子进程内核态下的栈顶, 见struct union task */
p->tss.ss0 = 0x10; /* 子进程内核态栈段 */
/* 回看栈中内容吧: eip=fork函数中"if (__res >= 0)"处偏移地址;
* fork函数中有提到, 父进程通过"int 80h"指令完成系统调用后将
* 执行"if (__res >= 0)"处及后续语句;此处亦将"if (__res >= 0)"
* 语句处偏移地址赋给子进程TSS.eip,当子进程被调度运行时也会执
* "if (__res >= 0)"处及后续语句,所以fork函数会在父子进程中各
* 返回1次。
*
* 另外,为了不让RET语句破坏子进程正确的栈内容,fork函数需为内联
* 函数(内联函数指令被直接嵌在被调用处,无CALL-RET对栈暗中操作)。*/
p->tss.eip = eip;
/* 使用栈中备份的标志寄存器,真实标志寄存器TF&&IF位已置0 */
p->tss.eflags = eflags;
/* 当调度子进程运行时, eax=TSS.eax,
* eax将作为"fork返回值":__res=eax,
* 这也是fork在子进程中返回0的原因。*/
p->tss.eax = 0;
/* 子进程各寄存器继承父进程调fork之前各寄存器值 */
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); /* 将本进程LDT选择符赋予TSS.ldt字段 */
p->tss.trace_bitmap = 0x80000000; /* I/O许可位图偏移 */
/* 若父进程使用了协处理器,则清CR0TS标志并将协处理器转态备份 */
if (last_task_used_math == current)
__asm__("clts ; fnsave %0"::"m" (p->tss.i387));
/* 通过页机制将[nr * 0x4000000, (nr + 1) * 0x4000000)内存地址空间映
* 射父进程数据和代码内存段,并将基址nr*0x4000000设置在子进程的LDT中。*/
if (copy_mem(nr,p)) {
task[nr] = NULL;
free_page((long) p);
return -EAGAIN;
}
/* 因*p = *current,子进程已继承父进程所打开文
* 件和各目录i节点,此处增加共享对象的引用计数。*/
for (i=0; ifilp[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++;
/* 在GDT中为新建进程设置TSS和LDT,可以再结合schedule
* 函数粗略理解进程切换过程: _TSS(nr)将得到task[nr]
* 进程TSS的选择符, 使用ljmp指令跳转GDT[_TSS(nr)]时
* CPU会将_TSS(nr)加载到TR并由此访问到进程TSS即获取
* 到进程运行上下文;随后将进程LDT选择符GDT[TSS.ldt]
* 加载到LDTR并访问进程LDT,即由此访问到由LDT所描述代
* 码和数据内存段,由此运行task[nr]所管理的进程。*/
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 */
/* 再继续跟踪下fork函数的返回流程吧,直到main调用fork处。
* --> _sys_fork --> _system_call --> fork
* last_pid将被返回到父进程中,fork在子进程中的返回值为0。
*
* 似乎反复跟踪fork执行过程才是理解fork函数的真实渠道哦。*/
return last_pid;
}
/* find_empty_process,
* 为新进程编译唯一进程id和空闲未用进程结构体。
* 该函数由_sys_fork调用。*/
int find_empty_process(void)
{
int i;
/* 全局变量last_pid用于记录新进程id */
repeat:
if ((++last_pid)<0) last_pid=1;
for(i=0 ; ipid == last_pid) goto repeat;
/* 在进程管理结构体中遍历空闲元素并返回空闲元素下标 */
for(i=1 ; i
extern int sys_setup();
extern int sys_exit();
extern int sys_fork();
extern int sys_read();
extern int sys_write();
extern int sys_open();
extern int sys_close();
extern int sys_waitpid();
extern int sys_creat();
extern int sys_link();
extern int sys_unlink();
extern int sys_execve();
extern int sys_chdir();
extern int sys_time();
extern int sys_mknod();
extern int sys_chmod();
extern int sys_chown();
extern int sys_break();
extern int sys_stat();
extern int sys_lseek();
extern int sys_getpid();
extern int sys_mount();
extern int sys_umount();
extern int sys_setuid();
extern int sys_getuid();
extern int sys_stime();
extern int sys_ptrace();
extern int sys_alarm();
extern int sys_fstat();
extern int sys_pause();
extern int sys_utime();
extern int sys_stty();
extern int sys_gtty();
extern int sys_access();
extern int sys_nice();
extern int sys_ftime();
extern int sys_sync();
extern int sys_kill();
extern int sys_rename();
extern int sys_mkdir();
extern int sys_rmdir();
extern int sys_dup();
extern int sys_pipe();
extern int sys_times();
extern int sys_prof();
extern int sys_brk();
extern int sys_setgid();
extern int sys_getgid();
extern int sys_signal();
extern int sys_geteuid();
extern int sys_getegid();
extern int sys_acct();
extern int sys_phys();
extern int sys_lock();
extern int sys_ioctl();
extern int sys_fcntl();
extern int sys_mpx();
extern int sys_setpgid();
extern int sys_ulimit();
extern int sys_uname();
extern int sys_umask();
extern int sys_chroot();
extern int sys_ustat();
extern int sys_dup2();
extern int sys_getppid();
extern int sys_getpgrp();
extern int sys_setsid();
extern int sys_sigaction();
extern int sys_sgetmask();
extern int sys_ssetmask();
extern int sys_setreuid();
extern int sys_setregid();
/* 系统调用子程序静态数组,该数组中包含了各个系统调用的在内核段中的偏移
* 地址,sys_call_table[2]为系统调用sys_fork在内核代码段中的偏移地址,该
* 函数的定义在system_call.s中(_sys_fork)。*/
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 };
/*
* linux/lib/open.c
*
* (C) 1991 Linus Torvalds
*/
#define __LIBRARY__
#include
#include
/* open,
* 以flag标识属性打开文件filename,若文件filename
* 不存在时则以第三个参数所指标识属性创建文件。
*
* 为什么定义成可变参函数而不定义成含三个参数的函数。*/
int open(const char * filename, int flag, ...)
{
register int res;
va_list arg;
va_start(arg,flag);
/* 内联函数输入。
* "0" (__NR_open), eax=__NR_open;
* "b" (filename), ebx=filename;
* "c" (flag), ecx=flag;
* "d" (va_arg(arg,int)), edx=open第三个整型参数。
*
* 内联函数输出。
* res=eax。
*
* int 80h执行后CPU将调用fs/open.c/sys_open系统调用,
* 该函数以flag所标识属性将打开或创建名为filename的
* 文件(若filename不存在被创建则将文件属性设置为open
* 第三个参数所标识属性)。*/
__asm__("int $0x80"
:"=a" (res)
:"0" (__NR_open),"b" (filename),"c" (flag),
"d" (va_arg(arg,int)));
/* 漏了以下语句? va_end(arg); */
if (res>=0)
return res;
errno = -res;
return -1;
}
/*
* linux/lib/close.c
*
* (C) 1991 Linus Torvalds
*/
#define __LIBRARY__
#include
/* 定义系统调用接口 int close(int fd)通过int 80h
* 指令将调用内核函数 fs/open.c/ sys_close 。*/
_syscall1(int,close,int,fd)
/*
* linux/fs/exec.c
*
* (C) 1991 Linus Torvalds
*/
/*
* #!-checking implemented by tytso.
*/
/*
* Demand-loading implemented 01.12.91 - no need to read anything but
* the header into memory. The inode of the executable is put into
* "current->executable", and page faults do the actual loading. Clean.
*
* Once more I can proudly say that linux stood up to being changed: it
* was less than 2 hours work to get demand-loading completely implemented.
*/
/* 根据需求加载可执行文件到内存即首次加载可执行文件时除可执行文件头部外可暂
* 不读取其他内容到内存中,该功能实现于01.12.91。可执行文件i节点由进程管理结
* 构体的executable成员指向,可执行文件内容由缺页异常加载。
*
* 另外,此文在此吹个有底气的牛:实现按需(基于页异常)加载可执行文件到内存执行
* 的功能,我只花了不到2小时时间。*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
extern int sys_exit(int exit_code);
extern int sys_close(int fd);
/*
* MAX_ARG_PAGES defines the number of pages allocated for arguments
* and envelope for the new program. 32 should suffice, this gives
* a maximum env+arg of 128kB !
*/
#define MAX_ARG_PAGES 32
/*
* create_tables() parses the env- and arg-strings in new user
* memory and creates the pointer tables from them, and puts their
* addresses on the "stack", returning the new stack pointer value.
*/
/* create_tables,
* 在进程内存段末端组织进程环境(变量)参数和命令行参数信息;
* 该函数返回参数信息首地址。*/
static unsigned long * create_tables(char * p,int argc,int envc)
{
unsigned long *argv,*envp;
unsigned long * sp;
/* 环境参数末端地址以4字节对齐 */
sp = (unsigned long *) (0xfffffffc & (unsigned long) p);
/* 空出用来存各环境参数和命令行参数的地址的空间 */
sp -= envc+1;
envp = sp;
sp -= argc+1;
argv = sp;
/* 将存各环境参数的地址的内存段首地址,
* 存各命令行参数的地址的内存段首地址,
* 命令行参数个数依次写入内存中。*/
put_fs_long((unsigned long)envp,--sp);
put_fs_long((unsigned long)argv,--sp);
put_fs_long((unsigned long)argc,--sp);
/* 将各命令行参数的地址依次写入argv内存段 */
while (argc-->0) {
put_fs_long((unsigned long) p,argv++);
/* 跳过命令行参数字符串串 */
while (get_fs_byte(p++)) /* nothing */ ;
}
/* 存储命令行参数的地址的内存段结束标志位0 */
put_fs_long(0,argv);
/* 将各环境参数的地址依次写入envp内存段 */
while (envc-->0) {
put_fs_long((unsigned long) p,envp++);
/* 跳过环境参数本身 */
while (get_fs_byte(p++)) /* nothing */ ;
}
/* 存储环境参数的地址的内存段结束标志位0 */
put_fs_long(0,envp);
return sp;
}
/* 经create_tables后,
* 进程跟参数相关的逻辑地址空间跟分布大体如下。
*64Mb| |
* |======| 环
* | | 境
* | | 参
* | | 数
* | | 段
* |======|
* | | 命
* | | 令
* | | 行
* | | 参
* | | 数
* | | 段
* |======|
* | | 各
* | | 环
* | | 境
* | | 参
* | | 数
* | | 的
* | | 地
* | | 址
* | | 的
* | | 段
* |======|
* | | 各
* | | 命
* | | 令
* | | 行
* | | 参
* | | 数
* | | 的
* | | 地
* | | 址
* | | 的
* |======| 段
* | envp |
* |======|
* | argv |
* |======|
* | argc |
* |======| sp
* | . |
* | . |
* 0| . | */
/*
* count() counts the number of arguments/envelopes
*/
/* count,
* 计算argv所指内存段中所包含的
* (char *)类型指针元素, 指针元
* 素以NULL结尾。*/
static int count(char ** argv)
{
int i=0;
char ** tmp;
/* count函数应用场景(32位地址线)。
*
* 指针数组存储字符串常量地址,以NULL结尾。
* char *arg[] = {"1","2","3",NULL};
*
* 内存中的字符串常量。
* -------------
* |"1"|"2"|"3"|
* -------------
* x y z
*
* 设arg所在内存首地址为w
* ---------------------------
* |x |y |z |NULL|
* ---------------------------
* w w+4 w+8 w+12
* arg[0] arg[1] arg[2] arg[3]
* arg+0 arg+1 arg+2 arg+3
*
* -----
* |w |
* -----
* tmp=argv
*
* tmp作为右值时为其内存中内容即w;
* *((unsigned long *)tmp)即从内存地址w处读取sizeof(long)=4字节内容即x。
*
* tmp++,作为指向(char *)类型的指针,tmp++对应w+4;
* *( (unsigned long *)tmp)即从内存地址(w+4)中读取sizeof(long)=4字节内容即y。
* ...
*
* 待*( (unsigned long *)tmp)为NULL时则退出循环,
* 便统计了指针数组arg中指针元素个数从而统计了指针数组所指字符串个数。*/
if (tmp = argv)
while (get_fs_long((unsigned long *) (tmp++)))
i++;
return i;
}
/*
* 'copy_string()' copies argument/envelope strings from user
* memory to free pages in kernel mem. These are in a format ready
* to be put directly into the top of new user memory.
*
* Modified by TYT, 11/24/91 to add the from_kmem argument, which specifies
* whether the string and the string array are from user or kernel segments:
*
* from_kmem argv * argv **
* 0 user space user space
* 1 kernel space user space
* 2 kernel space kernel space
*
* We do this by playing games with the fs segment register. Since it
* it is expensive to load a segment register, we try to avoid calling
* set_fs() unless we absolutely have to.
*/
/* copy_strings,
* 将argv中的argc个指针元素所指数据拷贝到内核中,拷贝后的目的内存由page中相应元素指向。
* from_kmem用于标识 *argv 和 **argv 是内核还是用户空间地址。该函数返回参数内存块首地址。*/
static unsigned long copy_strings(int argc,char ** argv,unsigned long *page,
unsigned long p, int from_kmem)
{
char *tmp, *pag;
int len, offset = 0;
unsigned long old_fs, new_fs;
if (!p)
return 0; /* bullet-proofing */
new_fs = get_ds();
old_fs = get_fs();
/* 若参数在内核数据段则置fs加载内核数据段描述符 */
if (from_kmem==2)
set_fs(new_fs);
/* 将argv中的argc个元素所指向字符串拷贝考内核内存中 */
while (argc-- > 0) {
/* 若 *argv 为内核空间地址则让fs加载内核数据段描述符 */
if (from_kmem == 1)
set_fs(new_fs);
/* 将(argv+argc)地址中的地址取出 */
if (!(tmp = (char *)get_fs_long(((unsigned long *)argv)+argc)))
panic("argc is wrong");
/* 若**argv为用户空间地址则恢复fs加载用户数据段描述符 */
if (from_kmem == 1)
set_fs(old_fs);
/* 求取argv[argc]所指字符串长度 */
len=0; /* remember zero-padding */
do {
len++;
} while (get_fs_byte(tmp++));
/* 判断字符串长度是否超过预留内存长度*/
if (p-len < 0) { /* this shouldn't happen - 128kB */
set_fs(old_fs);
return 0;
}
/* 将argv[argc]即tmp所指字符串拷贝到空闲内核内存中 */
while (len) {
--p; --tmp; --len;
if (--offset < 0) {
/* offset=(PAGE_SIZE*32-5)%PAGE_SIZE=PAGE_SIZE-5 */
offset = p % PAGE_SIZE;
/* 若曾在本函数中间加载内核数据段描述
* 符于fs则先恢复以让内存分配函数使用。*/
if (from_kmem==2)
set_fs(old_fs);
/* 分配一页内存由page相应元素和pag指向 */
if (!(pag = (char *) page[p/PAGE_SIZE]) &&
!(pag = (char *) page[p/PAGE_SIZE] =
(unsigned long *) get_free_page()))
return 0;
/* 恢复fs加载内核数据段描述符 */
if (from_kmem==2)
set_fs(new_fs);
}
/* 将tmp所指字符串拷贝到内存页中(逆向拷贝) */
*(pag + offset) = get_fs_byte(tmp);
}
}
/* 恢复fs加载用户数据段描述符 */
if (from_kmem==2)
set_fs(old_fs);
return p;
}
/* change_ldt,
* 更改当前进程的LDT,使其代码段限长为text_size,数据段限长为64Mb;
* 将进程数据段末端与page中保存环境变量和命令行等参数的内存页映射。*/
static unsigned long change_ldt(unsigned long text_size,unsigned long * page)
{
unsigned long code_limit,data_limit,code_base,data_base;
int i;
/* 代码段以页对齐;数据段大小为64Mb */
code_limit = text_size+PAGE_SIZE -1;
code_limit &= 0xFFFFF000;
data_limit = 0x4000000;
/* 基于当前进程代码段和数据段基址和所计算的限长,设置新的LDT表 */
code_base = get_base(current->ldt[1]);
data_base = code_base;
set_base(current->ldt[1],code_base);
set_limit(current->ldt[1],code_limit);
set_base(current->ldt[2],data_base);
set_limit(current->ldt[2],data_limit);
/* make sure fs points to the NEW data segment */
/* 确保fs寄存器加载用户数据段描述符 */
__asm__("pushl $0x17\n\tpop %%fs"::);
/* 将用户程序数据段末端与保存参数(环境、命令行等)的内存页映射 */
data_base += data_limit;
for (i=MAX_ARG_PAGES-1 ; i>=0 ; i--) {
data_base -= PAGE_SIZE;
if (page[i])
put_page(page[i],data_base);
}
return data_limit;
}
/*
* 'do_execve()' executes a new program.
*/
/* do_execve,
*
*
* eip,tmp在_sys_execve中被入栈,tmp为_sys_execve call do_execve
* 时入栈的eip寄存器;
* filename, argv, envp分别和_system_call中入栈的edx, ecx, ebx对应。*/
int do_execve(unsigned long * eip,long tmp,char * filename,
char ** argv, char ** envp)
{
struct m_inode * inode;
struct buffer_head * bh;
struct exec ex;
unsigned long page[MAX_ARG_PAGES];
int i,argc,envc;
int e_uid, e_gid;
int retval;
int sh_bang = 0;
unsigned long p=PAGE_SIZE*MAX_ARG_PAGES-4;
/* eip[1]即*(eip + 1)即系统调用execve时cs寄存器的值,
* 若cs段寄存器值不为0x0f则表明当时进程为内核程序。*/
if ((0xffff & eip[1]) != 0x000f)
panic("execve called from supervisor mode");
for (i=0 ; ii_mode)) { /* must be regular file */
retval = -EACCES;
goto exec_error2;
}
i = inode->i_mode;
/* 根据可执行文件属性标志,设置其用户id和组id,
* 当前进程为即将执行可执行程序的父进程。*/
e_uid = (i & S_ISUID) ? inode->i_uid : current->euid;
e_gid = (i & S_ISGID) ? inode->i_gid : current->egid;
/* 由于是有当前进程执行可执行文件,
* 所以看看当前进程对于可执行文件
* 的操作权限, 当当前进程是可执行
* 文件组员时获取权限, 当当前进程
* 是可执行文件宿主时获取其宿主权限。*/
if (current->euid == inode->i_uid)
i >>= 6;
else if (current->egid == inode->i_gid)
i >>= 3;
/* 当当前进程作为组员或宿主身份对可执行文件没有执行权限且
* 不满足可执行文件对所有进程都开放执行权限且当前进程为超
* 级进程(初始进程的euid为0)的条件 时则出错返回。*/
if (!(i & 1) &&
!((inode->i_mode & 0111) && suser())) {
retval = -ENOEXEC;
goto exec_error2;
}
/* 读可执行程序文件前1Kb内容进入内存 */
if (!(bh = bread(inode->i_dev,inode->i_zone[0]))) {
retval = -EACCES;
goto exec_error2;
}
/* 用作可执行文件头部的解析 */
ex = *((struct exec *) bh->b_data); /* read exec-header */
/* 若可执行文件为(shell)脚本文件 */
if ((bh->b_data[0] == '#') && (bh->b_data[1] == '!') && (!sh_bang)) {
/*
* This section does the #! interpretation.
* Sorta complicated, but hopefully it will work. -TYT
*/
char buf[1023], *cp, *interp, *i_name, *i_arg;
unsigned long old_fs;
/* 除去开头的#!字符,将脚本前1Kb内容拷贝到buf中并释放缓冲区块和i节点 */
strncpy(buf, bh->b_data+2, 1022);
brelse(bh);
iput(inode);
buf[1022] = '\0';
/* 处理第1行内容(如#!/bash/sh),跳过#!后所有空格和制表符 */
if (cp = strchr(buf, '\n')) {
*cp = '\0';
for (cp = buf; (*cp == ' ') || (*cp == '\t'); cp++);
}
/* 若没有回车符或已到行尾则表明脚本无内容需处理 */
if (!cp || *cp == '\0') {
retval = -ENOEXEC; /* No interpreter name found */
goto exec_error1;
}
/* 如脚本首行内容为#!/bash/sh,i_name将等于"sh",interp="/bash/sh" */
interp = i_name = cp;
i_arg = 0;
for ( ; *cp && (*cp != ' ') && (*cp != '\t'); cp++) {
if (*cp == '/')
i_name = cp+1;
}
if (*cp) {
*cp++ = '\0';
i_arg = cp;
}
/* 到此处,就解析出脚本首行中(#!/bash/sh)的不带参数的解释器(/bash/sh)了 */
/*
* OK, we've parsed out the interpreter name and
* (optional) argument.
*/
/* 将用户空间的环境变量参数和命令行参数拷贝到可执行程序用
* 于存储参数的内存中, 保存环境变量参数和命令行参数内存的
* 地址由page末尾元素指向, 即环境变量和命令行参数此处预分
* 配128Kb内存末端。*/
if (sh_bang++ == 0) {
p = copy_strings(envc, envp, page, p, 0);
p = copy_strings(--argc, argv+1, page, p, 0);
}
/*
* Splice in (1) the interpreter's name for argv[0]
* (2) (optional) argument to interpreter
* (3) filename of shell script
*
* This is done in reverse order, because of how the
* user environment and arguments are stored.
*/
/* 将filename拷贝到可执行文件用于存储参数的内存段中&filename
* 是内核内存空间地址,filename是用户内存空间地址。*/
p = copy_strings(1, &filename, page, p, 1);
argc++;
/* 将脚本首行中的参数拷贝到可执行文件用于存储参数的内存段中 */
if (i_arg) {
p = copy_strings(1, &i_arg, page, p, 2);
argc++;
}
/* 将解释器名拷贝到可执行文件用于存储参数的内存段中 */
p = copy_strings(1, &i_name, page, p, 2);
argc++;
if (!p) {
retval = -ENOMEM;
goto exec_error1;
}
/* -----------------------------------------------------------------------------------------------------------------
* ... interpreter_name interpreter_arg|filename|argv[1] argv[2] ... argv[argc-1]|envp[0] envp[1] ... envp[envc-1] |
* -----------------------------------------------------------------------------------------------------------------
* 0 0x1ffff
*
* 为进程命令行参数和环境变量预留的128Kb内存与page[32]对应。*/
/*
* OK, now restart the process with the interpreter's inode.
*/
/* 让fs加载内核数据段描述符 */
old_fs = get_fs();
set_fs(get_ds());
/* 获取interp所指向脚本可执行程序名(如/bash/sh)的i节点 */
if (!(inode=namei(interp))) { /* get executables inode */
set_fs(old_fs);
retval = -ENOENT;
goto exec_error1;
}
/* 恢复fs指向用户空间数据段描述符 */
set_fs(old_fs);
/* 跳转restart_interp处以启动执行脚本可执行程序
* (如/bash/sh),现在inode指向脚本程序(/bash/sh)。*/
goto restart_interp;
}
/* 释放可执行程序前1Kb缓冲区块 */
brelse(bh);
/* 解析可执行文件头部 */
if (N_MAGIC(ex) != ZMAGIC || ex.a_trsize || ex.a_drsize ||
ex.a_text+ex.a_data+ex.a_bss>0x3000000 ||
inode->i_size < ex.a_text+ex.a_data+ex.a_syms+N_TXTOFF(ex)) {
retval = -ENOEXEC;
goto exec_error2;
}
if (N_TXTOFF(ex) != BLOCK_SIZE) {
printk("%s: N_TXTOFF != BLOCK_SIZE. See a.out.h.", filename);
retval = -ENOEXEC;
goto exec_error2;
}
/* 若当前可执行程序文件不为脚本可执行文件则将环境变量
* 和命令行参数拷贝到可执行文件用于存储参数内存段末端。*/
if (!sh_bang) {
p = copy_strings(envc,envp,page,p,0);
p = copy_strings(argc,argv,page,p,0);
if (!p) {
retval = -ENOMEM;
goto exec_error2;
}
}
/* 略看filename各参数的存储,假设参数未超过一页内存。
* |<----------------------128Kb-------------------->|
* ---------------------------------------------------
* ...|........命令行参数+环境变量参数(+脚本程序参数)|
* ---------------------------------------------------
* 0 p 0x1ffff
* 用于保存各参数内存页的物理地址存在page[31]中。*/
/* OK, This is the point of no return */
/* 使用当前进程管理结构体current管理execve所加载可执行文件的运行 */
/* 覆盖可执行文件i节点 */
if (current->executable)
iput(current->executable);
current->executable = inode;
/* 复位信号处理函数指针 */
for (i=0 ; i<32 ; i++)
current->sigaction[i].sa_handler = NULL;
/* 关闭所打开文件 */
for (i=0 ; iclose_on_exec>>i)&1)
sys_close(i);
current->close_on_exec = 0;
/* 释放当前进程代码段和数据段所占内存段 */
free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
free_page_tables(get_base(current->ldt[2]),get_limit(0x17));
/* 使用协处理标志复位 */
if (last_task_used_math == current)
last_task_used_math = NULL;
current->used_math = 0;
/* 将当前进程LDT更改以描述可执行程序filename代码段和数据段,
* 并将可执行程序filename数据段末尾段内存空间地址与保存各参
* 数的page内存页相映射。*/
p += change_ldt(ex.a_text,page)-MAX_ARG_PAGES*PAGE_SIZE;
/* change_ldt执行完毕后,
* 略看filename进程跟环境变量等参数相关内存地址空间。
* |<---------------------64MB---------------->|
* ---------------------------------------------
* .............................|arguments.....|
* ---------------------------------------------
* 0 p=64Mb-128Kb+p 0x3ffffff
* 即将进程数据段末端映射到保存各参数的内存页。*/
/* 在filename进程内存段末端组织环境变量和命令行参数 */
p = (unsigned long) create_tables((char *)p,argc,envc);
current->brk = ex.a_bss +
(current->end_data = ex.a_data +
(current->end_code = ex.a_text));
current->start_stack = p & 0xfffff000; /* 栈顶地址 */
/* 可执行文件属性不同时有效id不同(宿主id或继承当前进程id ) */
current->euid = e_uid;
current->egid = e_gid;
i = ex.a_text+ex.a_data;
/* 若进程末端内存地址不以4Kb对齐则往该内存地址对应内存页写0 */
while (i&0xfff)
put_fs_byte(0,(char *) (i++));
/* 更改发生系统调用execve()时CPU往栈中备份的eip和esp寄存器的值,
* eip=filename可执行文件指令入口地址处,
* esp=p即进程命令行参数信息前4字节为进程栈顶。
*
* 若对寄存器部分不太熟悉,则回system_call.s中看看栈中寄存器的备份布局吧。*/
eip[0] = ex.a_entry; /* eip, magic happens :-) */
eip[3] = p; /* stack pointer */
/* 由于用filename可执行文件代码入口地址修改了发生
* 系统调用时CPU往栈中备份的eip寄存器,所以CPU会转
* 而执行filename可执行文件(加上对诸如current->xx
* 信息的修改,可执行文件的执行环境也是正确的啦)。
*
* 从调用系统调用execve开始到此处,就是execve加载
* 可执行文件覆盖原进程执行的全部秘密啦。
*
* 能阅读明白的钥匙似乎是内存页机制,进程管理等相关知识和反复阅读。*/
return 0;
exec_error2:
iput(inode);
exec_error1:
for (i=0 ; i
/* 从用户态到内核态时,_system_call让fs寄存器
* 保持了本进程用户态数据段描述符LDT[2]内容。
*
* 当然,fs也可以指向内核数据段,如 printk()。*/
/* 此处就暂不具体理解static inline, extern inline和inline了吧,
* 他们在不同阶段或不同编译器下的含义可能会有所不同。*/
/* get_fs_byte,
* 将用户空间内存地址addr中的1字节内容读出并返回。*/
extern inline unsigned char get_fs_byte(const char * addr)
{
unsigned register char _v;
__asm__ ("movb %%fs:%1,%0":"=r" (_v):"m" (*addr));
return _v;
}
/* get_fs_word,
* 将用户空间内存地址addr中的sizeof(short)字节内容读出并返回。*/
extern inline unsigned short get_fs_word(const unsigned short *addr)
{
unsigned short _v;
__asm__ ("movw %%fs:%1,%0":"=r" (_v):"m" (*addr));
return _v;
}
/* get_fs_long,
* 将用户空间内存地址addr中的sizeof(long)字节内容读出并返回。*/
extern inline unsigned long get_fs_long(const unsigned long *addr)
{
unsigned long _v;
__asm__ ("movl %%fs:%1,%0":"=r" (_v):"m" (*addr)); \
return _v;
}
/* put_fs_byte,
* 将1字节val拷贝到用户内存地址addr处。*/
extern inline void put_fs_byte(char val,char *addr)
{
__asm__ ("movb %0,%%fs:%1"::"r" (val),"m" (*addr));
}
/* put_fs_word,
* 将sizeof(short)字节val拷贝到用户内存地址addr处。*/
extern inline void put_fs_word(short val,short * addr)
{
__asm__ ("movw %0,%%fs:%1"::"r" (val),"m" (*addr));
}
/* put_fs_long,
* 将sizeof(long)字节val拷贝到用户内存地址addr处。*/
extern inline void put_fs_long(unsigned long val,unsigned long * addr)
{
__asm__ ("movl %0,%%fs:%1"::"r" (val),"m" (*addr));
}
/*
* Someone who knows GNU asm better than I should double check the followig.
* It seems to work, but I don't know if I'm doing something subtly wrong.
* --- TYT, 11/24/91
* [ nothing wrong here, Linus ]
*/
/* 如果有人比我更了解GNU的话就好好检查下以下代码吧,他们运行起来
* 似乎没问题,但我并不确定以下代码是否包含了一些微妙的错误。^_^*/
/* get_fs,
* 获取fs寄存器值并返回。*/
extern inline unsigned long get_fs()
{
unsigned short _v;
__asm__("mov %%fs,%%ax":"=a" (_v):);
return _v;
}
/* get_ds,
* 获取ds寄存器值并返回。*/
extern inline unsigned long get_ds()
{
unsigned short _v;
__asm__("mov %%ds,%%ax":"=a" (_v):);
return _v;
}
/* set_fs,
* 设置fs寄存器的值为val。*/
extern inline void set_fs(unsigned long val)
{
__asm__("mov %0,%%fs"::"a" ((unsigned short) val));
}
#ifndef _A_OUT_H
#define _A_OUT_H
/* a.out(Assembley out)目标(可执行文件)的格式,跟ELF是同层级的概念 */
/* [1] GNU GCC 目标(可执行)文件格式,即linux0.11 OS system部分的格式 */
#define __GNU_EXEC_MACROS__
/* 粗略理解gnu gcc目标(可执行)文件格式
* |------------------------------------------------------------------------------------------|
* |头部区域|代码区域|数据区域|代码重定位信息区域|数据重定位信息区域|符号表区域|字符串常量区域|
* |------------------------------------------------------------------------------------------| */
/* struct exec,
* 描述(gcc构建的)可执行文件头部信息的结构体类型。*/
struct exec {
unsigned long a_magic; /* Use macros N_MAGIC, etc for access */
unsigned a_text; /* length of text, in bytes */
unsigned a_data; /* length of data, in bytes */
unsigned a_bss; /* length of uninitialized data area for file, in bytes */
unsigned a_syms; /* length of symbol table data in file, in bytes */
unsigned a_entry; /* start address */
unsigned a_trsize; /* length of relocation info for text, in bytes */
unsigned a_drsize; /* length of relocation info for data, in bytes */
};
#ifndef N_MAGIC
#define N_MAGIC(exec) ((exec).a_magic)
#endif
/* 标识目标文件魔数;这3种目标文件格式稍有差异,
* 见后续计算目标文件中代码数据等区域的宏。*/
#ifndef OMAGIC
/* Code indicating object file or impure executable. */
#define OMAGIC 0407
/* Code indicating pure executable. */
#define NMAGIC 0410
/* Code indicating demand-paged executable. */
#define ZMAGIC 0413
#endif /* not OMAGIC */
/* N_BADMAG,_N_BADMAG,
* 不能能识别的(目标)文件格式 */
#ifndef N_BADMAG
#define N_BADMAG(x) \
(N_MAGIC(x) != OMAGIC && N_MAGIC(x) != NMAGIC \
&& N_MAGIC(x) != ZMAGIC)
#endif
#define _N_BADMAG(x) \
(N_MAGIC(x) != OMAGIC && N_MAGIC(x) != NMAGIC \
&& N_MAGIC(x) != ZMAGIC)
/* 计算目标文件一个段中除去描述头部信息结构体后的长度 */
#define _N_HDROFF(x) (SEGMENT_SIZE - sizeof (struct exec))
/* N_TXTOFF,
* ZMAGIC魔数所标识的目标文件头部信息占SEGMENT_SIZE
* 字节,非ZMAGIC标识目标文件不以SEGMENT_SIZE字节对齐。
*
* 由于目标文件代码区域在头部信息之后,所以该宏返回值
* 为代码区域在目标文件中的偏移地址。*/
#ifndef N_TXTOFF
#define N_TXTOFF(x) \
(N_MAGIC(x) == ZMAGIC ? _N_HDROFF((x)) + sizeof (struct exec) : sizeof (struct exec))
#endif
/* N_DATOFF,
* 数据区域在目标文件中的偏移地址(数据区域在代码区域之后)。*/
#ifndef N_DATOFF
#define N_DATOFF(x) (N_TXTOFF(x) + (x).a_text)
#endif
/* N_TRELOFF,
* 目标文件代码重定位信息偏移地址(代码重定位信息在数据区域之后)。*/
#ifndef N_TRELOFF
#define N_TRELOFF(x) (N_DATOFF(x) + (x).a_data)
#endif
/* N_DRELOFF,
* 目标文件数据重定位信息偏移地址(数据重定位信息在代码重定位信息之后)。*/
#ifndef N_DRELOFF
#define N_DRELOFF(x) (N_TRELOFF(x) + (x).a_trsize)
#endif
/* N_SYMOFF,
* 目标文件符号表偏移地址(符号表在数据重定位信息知之后)。*/
#ifndef N_SYMOFF
#define N_SYMOFF(x) (N_DRELOFF(x) + (x).a_drsize)
#endif
/* N_STROFF,
* 目标文件字符串常量偏移地址(字符串常量在符号表之后)。*/
#ifndef N_STROFF
#define N_STROFF(x) (N_SYMOFF(x) + (x).a_syms)
#endif
/* [2] 可执行文件加载(内存地址)相关宏。
* 在可执行文件被加载到内存中后,其中诸如符号表,可重定位等区域将被移除。*/
/* Address of text segment in memory after it is loaded. */
/* linux0.11 代码区域被加载的内存基址 */
#ifndef N_TXTADDR
#define N_TXTADDR(x) 0
#endif
/* Address of data segment in memory after it is loaded.
Note that it is up to you to define SEGMENT_SIZE
on machines not listed here. */
/* linux0.11 数据区域被加载的内存基址;
* 可以在此处定义其他CPU所支持的SEGMEN_SIZE和PAGE_SIZE */
#if defined(vax) || defined(hp300) || defined(pyr)
#define SEGMENT_SIZE PAGE_SIZE
#endif
#ifdef hp300
#define PAGE_SIZE 4096
#endif
#ifdef sony
#define SEGMENT_SIZE 0x2000
#endif /* Sony. */
#ifdef is68k
#define SEGMENT_SIZE 0x20000
#endif
#if defined(m68k) && defined(PORTAR)
#define PAGE_SIZE 0x400
#define SEGMENT_SIZE PAGE_SIZE
#endif
#define PAGE_SIZE 4096
#define SEGMENT_SIZE 1024
/* _N_SEGMENT_ROUND(x),
* 将x以SEGMENT_SIZE大小对齐。*/
#define _N_SEGMENT_ROUND(x) (((x) + SEGMENT_SIZE - 1) & ~(SEGMENT_SIZE - 1))
/* _N_TXTENDADDR,
* linux0.11 代码区域末端即数据区域起始基址。*/
#define _N_TXTENDADDR(x) (N_TXTADDR(x)+(x).a_text)
/* N_DATADDR,计算linux0.11 数据区被加载基址,非OMAGIC所标识
* 目标文件数据区域被加载内存基址以SEGMENT_SIZE大小对齐。*/
#ifndef N_DATADDR
#define N_DATADDR(x) \
(N_MAGIC(x)==OMAGIC? (_N_TXTENDADDR(x)) \
: (_N_SEGMENT_ROUND (_N_TXTENDADDR(x))))
#endif
/* Address of bss segment in memory after it is loaded. */
/* linux0.11 bss区域被加载基址 */
#ifndef N_BSSADDR
#define N_BSSADDR(x) (N_DATADDR(x) + (x).a_data)
#endif
/* [3] 供编译链接器使用的相关宏 */
#ifndef N_NLIST_DECLARED
/* struct nlist,符号表表项
* 用于描述目标文件中一个符号的结构体类型。*/
struct nlist {
union {
char *n_name;
struct nlist *n_next;
long n_strx;
} n_un;
unsigned char n_type; /* 符号类型,见后续N_*系列宏常量 */
char n_other;
short n_desc; /* 对符号的描述 */
unsigned long n_value; /* 符号的值 */
};
#endif
/* 粗略领略标识符号特性/类型的宏,即
* 以下N_*系列宏常量用于标识一个符号的类型,
* 是目标文件符号表中各表项n_type字段的值。*/
#ifndef N_UNDF
#define N_UNDF 0 /* 标识符号未定义 */
#endif
#ifndef N_ABS
#define N_ABS 2 /* ... */
#endif
#ifndef N_TEXT
#define N_TEXT 4 /* 代码符号(代码地址) */
#endif
#ifndef N_DATA
#define N_DATA 6 /* 数据符号(数据地址) */
#endif
#ifndef N_BSS
#define N_BSS 8 /* 位于bss区域数据的地址 */
#endif
#ifndef N_COMM
#define N_COMM 18 /* ... */
#endif
#ifndef N_FN
#define N_FN 15 /* 链接器链接的文件名 */
#endif
#ifndef N_EXT
#define N_EXT 1 /* 标识是否为外部符号 */
#endif
#ifndef N_TYPE
#define N_TYPE 036 /* 符号类型 */
#endif
#ifndef N_STAB
#define N_STAB 0340 /* 符号表类型 */
#endif
/* The following type indicates the definition of a symbol as being
an indirect reference to another symbol. The other symbol
appears as an undefined reference, immediately following this symbol.
Indirection is asymmetrical. The other symbol's value will be used
to satisfy requests for the indirect symbol, but not vice versa.
If the other symbol does not have a definition, libraries will
be searched to find a definition. */
/* 以下定义的常量0xa用于标识一个间接引用另一个符号值的符号。该符号之后
* 的符号(被引用符号)为未定义引用状态。
*
* 间接引用不是对称的,间接引用符号可以引用另一个符号的值,反过来则不行。
* 若没有在当前文件中找到间接引用符号所引用符号的定义,则会尝试继续在库
* 中查找。*/
#define N_INDR 0xa
/* The following symbols refer to set elements.
All the N_SET[ATDB] symbols with the same name form one set.
Space is allocated for the set in the text section, and each set
element's value is stored into one word of the space.
The first word of the space is the length of the set (number of elements).
The address of the set is made into an N_SETV symbol
whose name is the same as the name of the set.
This symbol acts like a N_DATA global symbol
in that it can satisfy undefined external references. */
/* 以下宏常量用于标识集合,值相同的符号构成一个集合。目标文件代码段为集合分配了
* 空间,每个集合元素的值存储在1个字大小内存空间中。第1个字中存储了集合的长度。
*
* 集合基址会被一个被标识为N_SETV的跟集合同名的符号记录。该N_SETV符号就相当于
* 一个N_DATA全局符号,可以匹配一个外部全局引用。*/
/* These appear as input to LD, in a .o file. */
/* 这些宏值由编译器在编译阶段标识在目标文件的符号表中,以进一步输入给链接器处理 */
#define N_SETA 0x14 /* Absolute set element symbol */
#define N_SETT 0x16 /* Text set element symbol */
#define N_SETD 0x18 /* Data set element symbol */
#define N_SETB 0x1A /* Bss set element symbol */
/* This is output from LD. */
/* 该宏常量由链接器标识在(数据)符号表中 */
#define N_SETV 0x1C /* Pointer to set vector in data area. */
#ifndef N_RELOCATION_INFO_DECLARED
/* This structure describes a single relocation to be performed.
The text-relocation section of the file is a vector of these structures,
all of which apply to the text section.
Likewise, the data-relocation section applies to the data section. */
/* struct relocation_info结构体描述如何重定向一个符号。
* 目标文件中的代码重定向区域就是由多个此结构体组成的(结构体数组);
* 同理,目标文件中的数据重定向区域也是由struct relocation_info数组构成的。*/
struct relocation_info
{
/* Address (within segment) to be relocated. */
/* (段内)重定位地址 */
int r_address;
/* The meaning of r_symbolnum depends on r_extern. */
/* r_symbolnum 的含义取决于r_extern */
unsigned int r_symbolnum:24;
/* Nonzero means value is a pc-relative offset
and it should be relocated for changes in its own address
as well as for changes in the symbol or section specified. */
/* 该位非0表示重定位类型为指令指针相对偏移,在符号或段(section)
* 地址改变时需重定位。*/
unsigned int r_pcrel:1;
/* Length (as exponent of 2) of the field to be relocated.
Thus, a value of 2 indicates 1<<2 bytes. */
/* 2^r_length用以指定重定位长度 */
unsigned int r_length:2;
/* 1 => relocate with value of symbol.
r_symbolnum is the index of the symbol
in file's the symbol table.
0 => relocate with the address of a segment.
r_symbolnum is N_TEXT, N_DATA, N_BSS or N_ABS
(the N_EXT bit may be set also, but signifies nothing). */
/* 1 => 重定位 r_symbolnum 在符号表中所索引符号的值;
* 0 => 重定位段的地址,此时r_symbolnum为N_TEXT或N_DATA或N_BSS或N_ABS
* (N_EXT位也可能被设置,但在这里不会解析该位) */
unsigned int r_extern:1;
/* Four bits that aren't used, but when writing an object file
it is desirable to clear them. */
/* 以下4比特位没有使用,但最好将其清0 */
unsigned int r_pad:4;
};
#endif /* no N_RELOCATION_INFO_DECLARED. */
#endif /* __A_OUT_GNU_H__ */
/*
* linux/kernel/sys.c
*
* (C) 1991 Linus Torvalds
*/
#include
#include
#include
#include
#include
#include
#include
int sys_ftime()
{
return -ENOSYS;
}
int sys_break()
{
return -ENOSYS;
}
int sys_ptrace()
{
return -ENOSYS;
}
int sys_stty()
{
return -ENOSYS;
}
int sys_gtty()
{
return -ENOSYS;
}
int sys_rename()
{
return -ENOSYS;
}
int sys_prof()
{
return -ENOSYS;
}
/* sys_setregid,
* 设置当前进程实际组id和有效组id。*/
int sys_setregid(int rgid, int egid)
{
if (rgid>0) {
/* 超级进程可以设置其组id为实际组id */
if ((current->gid == rgid) ||
suser())
current->gid = rgid;
else
return(-EPERM);
}
if (egid>0) {
/* 超级进程或满足组id为有效组id..则设置进程有效组id为egid */
if ((current->gid == egid) ||
(current->egid == egid) ||
(current->sgid == egid) ||
suser())
current->egid = egid;
else
return(-EPERM);
}
return 0;
}
/* sys_setgid,
* 设置当前进程组id。*/
int sys_setgid(int gid)
{
return(sys_setregid(gid, gid));
}
int sys_acct()
{
return -ENOSYS;
}
int sys_phys()
{
return -ENOSYS;
}
int sys_lock()
{
return -ENOSYS;
}
int sys_mpx()
{
return -ENOSYS;
}
int sys_ulimit()
{
return -ENOSYS;
}
/* sys_time,
* 获取自1970年1月1号0时0分0秒
* 到现在的描述于tloc所指内存中。
*
* 该函数时系统调用time的内核函数。*/
int sys_time(long * tloc)
{
int i;
i = CURRENT_TIME;
if (tloc) {
/* 写时拷贝tloc所在内存页 */
verify_area(tloc,4);
/* 将当前时间(秒数)写到tloc所指用户内存中 */
put_fs_long(i,(unsigned long *)tloc);
}
return i;
}
/*
* Unprivileged users may change the real user id to the effective uid
* or vice versa.
*/
/* sys_setreuid,
* 设置进程实际和有效的用户id。*/
int sys_setreuid(int ruid, int euid)
{
int old_ruid = current->uid;
if (ruid>0) {
/* 为超级进程或若进程有效用户id/用户id
* 等于实际用户id则设置进程用户id为ruid。*/
if ((current->euid==ruid) ||
(old_ruid == ruid) ||
suser())
current->uid = ruid;
else
return(-EPERM);
}
if (euid>0) {
/* 为超级进程或若当前进程用户id/有效用户
* id为euid则设置进程有效用户id为euid。*/
if ((old_ruid == euid) ||
(current->euid == euid) ||
suser())
current->euid = euid;
else {/* 否则当前用户id不变 */
current->uid = old_ruid;
return(-EPERM);
}
}
return 0;
}
/* sys_setuid,
* 设置进程用户id。*/
int sys_setuid(int uid)
{
return(sys_setreuid(uid, uid));
}
/* sys_stime,
* 用于超级进程设置系统开机时间为tptr所指时间。*/
int sys_stime(long * tptr)
{
if (!suser())
return -EPERM;
startup_time = get_fs_long((unsigned long *)tptr) - jiffies/HZ;
return 0;
}
/* sys_times,
* 获取当今进程及其子进程用户态,内核态运行时间(单位为10ms)。*/
int sys_times(struct tms * tbuf)
{
if (tbuf) {
/* 写时拷贝tbuf所指内存段所在内存页 */
verify_area(tbuf,sizeof *tbuf);
/* 将时间拷贝到用户内存空间 */
put_fs_long(current->utime,(unsigned long *)&tbuf->tms_utime);
put_fs_long(current->stime,(unsigned long *)&tbuf->tms_stime);
put_fs_long(current->cutime,(unsigned long *)&tbuf->tms_cutime);
put_fs_long(current->cstime,(unsigned long *)&tbuf->tms_cstime);
}
return jiffies;
}
/* sys_brk,
* 设置进程数据段大小(逻辑上)。*/
int sys_brk(unsigned long end_data_seg)
{
/* 数据段大小需要满足
* 大于进程代码段 && 能预留16Kb栈内存 */
if (end_data_seg >= current->end_code &&
end_data_seg < current->start_stack - 16384)
current->brk = end_data_seg;
return current->brk;
}
/*
* This needs some heave checking ...
* I just haven't get the stomach for it. I also don't fully
* understand sessions/pgrp etc. Let somebody who does explain it.
*/
/* sys_setpgid,
* 设置进程id为pid进程的组id。*/
int sys_setpgid(int pid, int pgid)
{
int i;
if (!pid)
pid = current->pid;
if (!pgid)
pgid = current->pid;
for (i=0 ; ipid==pid) {
if (task[i]->leader) /* 不能设置会话领导进程的组id */
return -EPERM;
/* 当前进程与目标进程不属同一会话不能设置 */
if (task[i]->session != current->session)
return -EPERM;
task[i]->pgrp = pgid;
return 0;
}
return -ESRCH;
}
/* sys_getpgrp,
* 获取当前进程组id。*/
int sys_getpgrp(void)
{
return current->pgrp;
}
/* sys_setsid,
* 在当前进程中创建会话,会话领导id为1。*/
int sys_setsid(void)
{
/* 已是会话领导且不为超级进程则返回 */
if (current->leader && !suser())
return -EPERM;
current->leader = 1;
/* 当前进程会话号和当前进程组id=当前进程id*/
current->session = current->pgrp = current->pid;
current->tty = -1;
return current->pgrp;
}
/* sys_uname,
* 获取linux版本信息于name所指内存段中。*/
int sys_uname(struct utsname * name)
{
static struct utsname thisname = {
"linux .0","nodename","release ","version ","machine "
};
int i;
/* 写时拷贝name地址映射的内存页 */
if (!name) return -ERROR;
verify_area(name,sizeof *name);
/* 将内核空间的thisname内存段拷贝到name所指用户内存段 */
for(i=0;iumask;
current->umask = mask & 0777;
return (old);
}