printf执行流程:
1、调用printf;2、调用库函数printf;3、调用库函数write;4、系统调用write。
系统调用write进一步是这样一句宏定义
// linux-0.11/lib/write.c
#include
_syscall3(int,write,int,fd,const char *,buf,off_t,count)
// linux-0.11/include/unistd.h
#define __NR_write 4
#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; \
}
上述宏定义的整体解释:
1、初始化输入寄存器eax(4),ebx(fd),ecx(buf),edx(count)
2、执行指令int 0x80,执行完成后并返回值到寄存器eax中
3、输出eax的值到__res中,并退出系统调用write函数
1、初始化输入部分:得到立即数0xee00,得到IDT表中下标为0x80对应的那块内存A,得到IDT表中下标为0x80再加偏移地址4对应的那块内存B,edx(&system_call),eax(0x00080000)
2、movw %%dx,%%ax\n\t – 此时ax = &system_call
3、movw %0,%%dx\n\t – 此时dx = 0xee00
4、movl %%eax,%1\n\t – 此时A = 0x80000 + &system_call
5、movl %%edx,%2 – 此时B = &system_call << 16 + 0xee00
上述的A和B两块内存区共8个字节构成一个idt表,代码目的初始化这个IDT表,将DPL设置为3,段描述符置为8,调用接口设置为system_call
// linux-0.11/init/main.c
void main(void)
{
sched_init(); // 系统初始化时会调用这个函数
}
// linux-0.11/kernel/sched.c
void sched_init(void)
{
// 调度函数初始化时会设置系统门,设定
// 系统调用的中断接口
set_system_gate(0x80,&system_call);
}
// linux-0.11/include/asm/system.h
#define set_system_gate(n,addr) \
_set_gate(&idt[n],15,3,addr)
#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \
"movw %0,%%dx\n\t" \
"movl %%eax,%1\n\t" \
"movl %%edx,%2" \
: \
: "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
"o" (*((char *) (gate_addr))), \
"o" (*(4+(char *) (gate_addr))), \
"d" ((char *) (addr)),"a" (0x00080000))
// linux-0.11/kernel/system_call.s
nr_system_calls = 72
system_call:
cmpl $nr_system_calls-1,%eax #此时eax = __NR_write = 4
ja bad_sys_call
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 sys_call_table(,%eax,4) #相当于往后偏移4*eax个字节
pushl %eax
movl current,%eax
cmpl $0,state(%eax) # state
jne reschedule
cmpl $0,counter(%eax) # counter
je reschedule
ret_from_sys_call:
movl current,%eax # task[0] cannot have signals
cmpl task,%eax
je 3f
cmpw $0x0f,CS(%esp) # was old code segment supervisor ?
jne 3f
cmpw $0x17,OLDSS(%esp) # was stack segment = 0x17 ?
jne 3f
movl signal(%eax),%ebx
movl blocked(%eax),%ecx
notl %ecx
andl %ebx,%ecx
bsfl %ecx,%ecx
je 3f
btrl %ecx,%ebx
movl %ebx,signal(%eax)
incl %ecx
pushl %ecx
call do_signal
popl %eax
3: popl %eax
popl %ebx
popl %ecx
popl %edx
pop %fs
pop %es
pop %ds
iret
// linux-0.11/include/linux/sys.h
fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
sys_setreuid,sys_setregid };
可以看出sys_write正好处于系统调用表中下标为4的位置,所以上述执行int 0x80时确实调用了sys_write接口。
那sys_write又是怎样把字符写入到显示屏中去的呢?这个问题,我们日后再说。