<!-- @page { margin: 0.79in } P { margin-bottom: 0.08in } -->
汇编语言中也需要通过某些途径来使用操作系统提供的服务,也就是系统调用;系统调用就是通过与操作系统内核通信来完成;系统调用会把用户态程序的调用转换成对系统内核服务的调用;
Linux平台下有两种方式来使用系统调用:一种是利用封装后的C库(libc),另一种是通过汇编直接调用;其中,通过汇编语言来直接调用系统调用,是最高效地使用Linux内核服务的方法,因为最终生成的程序不需要与任何库进行连接,而是直接与内核通信;
与DOS一样,Linux下的系统调用也是通过中断(int 0x80)方式来实现的;
在执行"int $0x80"指令时,寄存器eax中存放的是系统调用的功能号(即:中断功能号,DOS下存放在AH中),所有的系统调用功能号都可以再文件/usr/include/bits/syscall.h中找到,为了便于使用,它们都是用"SYS_<name>"这样的宏来定义的;如:SYS_write、SYS_exit等;
系统调用的参数传递规则:
传递给系统调用的参数则必须按照参数顺序依次存放到寄存器ebx,ecx,edx,esi,edi中,当系统调用完成之后,返回值存放在eax中;
A.当系统调用所需参数的个数不超过5个的时候,执行"int $0x80"指令时,需在eax中存放系统调用的功能号,传递给系统调用的参数则按照参数顺序依次存放到寄存器ebx,ecx,edx,esi,edi中,当系统调用完成之后,返回值存放在eax中;
比如,经常用到的write函数的定义如下:
ssize_t write(int fd, const void* buf, size_t count);
该函数的功能最终通过SYS_write这一系统调用来实现的;根据上面的参数传递规则可知,参数fd存放在ebx中,参数buf存放在ecx中,参数count存放在edx中,而系统调用功能号SYS_write则存放在寄存器eax中;系统调用执行完成之后,返回值可以从eax中得到;
B.当系统调用的参数超过5个的时候,执行"int $0x80"指令,需在eax中存放系统调用的功能号,所不同的只是全部的参数应该依次存放在一块连续的内存区域里,同时在寄存器ebx中保存指向该内存区域的指针(即:该连续内存块的首地址),返回值仍然保存在寄存器eax中;由于只是需要一块连续的内存区域来保存系统调用所需要的参数,因此,完全可以像普通的函数调用一样使用栈来传递系统调用所需要的参数;但是要注意一点:Linux采用的是C语言的调用模式,这就意味着所有参数必须以相反的顺序进栈,即:最后一个参数最先进栈,而第一个参数最后进栈;如果采用栈来传递系统调用所需要的参数,在执行"int $0x80"指令时,还应将栈指针的当前值(栈顶地址)复制到寄存器ebx中;
例如,系统调用mmap()的参数个数就超过5个:
void* mmap(void* start, size_t length, int prot, int flags, int fd, off_t offset);
使用这个系统调用时,系统调用功能号保存到eax中,mmap()所需要的所有参数存放到一块连续的内存区域中,这块连续内存区域的首地址存放到ebx中,即可;
C库函数的参数传递规则:
1、参数从右向左依次入栈(push);
2、库函数的返回值存放在寄存器eax中;
3、系统调用exit()的参数值在exit()调用结束程序退出的时候会被传递给系统shell,通过打印$?的值可看到;
汇编函数的返回值:
只要是函数,一般都需要返回值;汇编语言中约定:对于32位的返回值,存放在寄存器eax中返回;对于64位的返回值,存放在寄存器eax和edx中返回,高位字放在edx中,低位字放在eax中;
例如:
sys_fork()调用,用于创建子进程,是system_call 功能2。原形在include/linux/sys.h 中。 首先调用C 函数find_empty_process(),取得一个进程号pid。若返回负数则说明目前任务数组 已满。然后调用copy_process()复制进程。
_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
* * 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. */ /* * OK,下面是主要的fork 子程序。它复制系统进程信息(task[n])并且设置必要的寄存器。 * 它还整个地复制数据段。 */ // 复制进程。 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; // 将新任务结构指针放入任务数组中。 // 其中nr 为任务号,由前面find_empty_process()返回。 *p = *current; /* NOTE! this doesn't copy the supervisor stack */ /* 注意!这样做不会复制超级用户的堆栈 */ (只复制当前进程内容)。 p->state = TASK_UNINTERRUPTIBLE; // 将新进程的状态先置为不可中断等待状态。 p->pid = last_pid; // 新进程号。由前面调用find_empty_process()得到。 p->father = current->pid; // 设置父进程号。 p->counter = p->priority; p->signal = 0; // 信号位图置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; // 当前滴答数时间。 // 以下设置任务状态段TSS 所需的数据(参见列表后说明)。 p->tss.back_link = 0; p->tss.esp0 = PAGE_SIZE + (long) p; // 堆栈指针(由于是给任务结构p 分配了1 页 // 新内存,所以此时esp0 正好指向该页顶端)。 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; // 段寄存器仅16 位有效。 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); // 该新任务nr 的局部描述符表选择符(LDT 的描述符在GDT 中)。 p->tss.trace_bitmap = 0x80000000; // 如果当前任务使用了协处理器,就保存其上下文。 if (last_task_used_math == current) __asm__ ("clts ; fnsave %0"::"m" (p->tss.i387)); // 设置新任务的代码和数据段基址、限长并复制页表。如果出错(返回值不是0),则复位任务数组中 // 相应项并释放为该新任务分配的内存页。 if (copy_mem (nr, p)) { // 返回不为0 表示出错。 task[nr] = NULL; free_page ((long) p); return -EAGAIN; } // 如果父进程中有文件是打开的,则将对应文件的打开次数增1。 for (i = 0; i < NR_OPEN; i++) if (f = p->filp[i]) f->f_count++; // 将当前进程(父进程)的pwd, root 和executable 引用次数均增1。 if (current->pwd) current->pwd->i_count++; if (current->root) current->root->i_count++; if (current->executable) current->executable->i_count++; // 在GDT 中设置新任务的TSS 和LDT 描述符项,数据从task 结构中取。 // 在任务切换时,任务寄存器tr 由CPU 自动加载。 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; // 返回新进程号(与任务号是不同的)。 }