【linux0.12】linux0.11下增加系统调用whoami

13 篇文章 0 订阅
10 篇文章 0 订阅

系统调用的初始化

在系统启动main函数之后,会调用kernel/sched.c中的sched_init,里面对系统调用的初始化就一句set_system_gate(0x80,&system_call);这个0x80就是我们的中断号,而system_call函数则是中断服务程序。
set_system_gate是一段宏替换,代码如下

1 #define _set_gate(gate_addr,type,dpl,addr) \
2 __asm__ ("movw %%dx,%%ax\n\t" \
3        "movw %0,%%dx\n\t" \
4        "movl %%eax,%1\n\t" \
5        "movl %%edx,%2" \
6        : \
7        : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
8        "o" (*((char *) (gate_addr))), \
9        "o" (*(4+(char *) (gate_addr))), \
10        "d" ((char *) (addr)),"a" (0x00080000))
        
#define set_system_gate(n,addr) \
        _set_gate(&idt[n],15,3,addr)

那么这段嵌入汇编的含义是什么呢,看起来好像是把中断处理程序的地址放入到0x80对应的idt表项中,那么是如何做到的呢?
要想理解这段嵌入汇编的含义,我们得了解IDT描述符的构成,其中有中断门描述符,陷阱门描述符,任务门描述符,它们三个大同小异。在这里我们只用到了中断门描述符,结构如下

	如图所示,两段offset组成了中断服务程序的在段内的偏移,selector即是段选择符,
和offset一起组成了中断服务程序的入口地址,剩下的即是一些标志位:p代表段是否存在于内存中,DPL表示描述符的特权级,剩下几位则代表所需设置的描述符类型。
	首先说明一下几个数字的含义,第三四五行中的%0 %1 %2代表了第几个输入,如%0
代表计算完毕后的数值,%1代表第八行的数值,以此类推。
	第七行的0x8000其实代表了P为1,其余位为0,和后面的偏移相加组成了上面的0-15位
	第十行的0x0008代表了存在于GDT表中的内核代码段选择符。
下面讲解这段嵌入汇编的执行过程:
	首先我们要知道,嵌入汇编都是由输入寄存器->汇编处理->输出寄存器->返回流程组成的
 1 输入
 	%1 计算完毕的结果代表了描述符结构第一行的0-15位
 	%2代表idt表项的的高4个字节
 	%3代表idt表项的的低4个字节
 	edx寄存器存放中断处理函数的入口地址
 	eax寄存器存放0x00080000
 2 计算
 	第二行 将edx的低16位赋值给eax的低16位,也就是将中断处理函数地址的低2个字节给了eax的低2个字节,
 组装好了中断描述符低4个字节的0-31位
 	第三行 将%0即计算好的描述符第二行0-15位赋值给edx的低2个字节,完成了中断
 描述符高4个字节的组装。
 	第四行 将eax内组装好的描述符低4个字节存放到描述符内
 	第五行 将edx内组装好的描述符高4个字节存放到描述符内
 3 输出
 	无
 至此,完成了将处理函数的地址放入0x80描述符表项的功能。
 ps:上述嵌入汇编描述简单,深入了解请查阅相关资料

如何调用系统调用

系统调用有两百多个,那么是怎么区分呢,其实很简单,给每个系统调用分配一个id区分,根据id去得到函数的入口地址即可。
这个id就存放include/unistd.h中,如#define __NR_whoami 73 就是给系统调用分配了一个id。怎么根据id去得到函数的入口地址呢,其实也很简单,内核维护了一张全局的表(sys_call_table),表项中即是对应处理函数的入口地址。这个表维护在include/linux/sys.h中。
其实系统调用的过程就是根据_syscall0或者_syscall1等宏定义将其展开为一段嵌入汇编代码。_syscall0如下:

#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
        : "=a" (__res) \
        : "0" (__NR_##name)); \
if (__res >= 0) \
        return (type) __res; \
errno = -__res; \
return -1; \
}

主要就是将系统调用号给到eax寄存器,然后进入0x80中断,中断程序根据中断描述符表项,进入到gdt表,得到内核代码段的基址,然后结合中断描述符表项的偏移得到处理函数的入口地址system_call,system_call代码如下:

.align 2
system_call:
        cmpl $nr_system_calls-1,%eax
        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
                                # 0x10代表了内核数据段
        mov %dx,%ds
        mov %dx,%es
        movl $0x17,%edx         # fs points to local data space
        mov %dx,%fs
        call sys_call_table(,%eax,4)
        pushl %eax
        movl current,%eax
        cmpl $0,state(%eax)             # state
        jne reschedule
        cmpl $0,counter(%eax)           # counter
        je reschedule

主要就是将参数给到ebx ecx edx这几个寄存器,然后将fs指向用户数据,再进入sys_call_table中查找对应的处理函数,这个eax就是我们的系统调用号,在这里是72,根据72找到对应的函数,开始执行。
执行完毕后还有从系统调用返回(ret_from_sys_call),从中断返回等过程,这里我也没有了解,不再详述

总结

总结一下,就是将用户程序宏展开成一段汇编,然后进入0x80中断查找idt表,拿到对应处理函数的地址,开始执行,完毕后返回。只是需要的前置知识比较多,本人又是跨专业,在这方面0基础,所以讲的有点啰嗦。
自己的whoami并不完善,没有完成内核和用户的数据交换,有待改进。
还有比较坑的一点是,本来在下午三点多就把主体写完了,然而不知道怎么测试自己的功能,Linux0.11中又没有syscall函数。折腾到了晚上,最后还是在网上搜到了资料,原来还需要进入新的内核中,在/usr/include/unistd.h中再加一次#define __NR_whoami 72 才行,不然编译不过
为了避免下次踩坑和有需要的朋友参考,附上自己的测试代码

#define __LIBRARY__
#include <unistd.h>
_syscall0(int,whoami);
int main(){
	whoami();
}

参考资料

李治军老师讲解视频:https://www.bilibili.com/video/BV1xW41127xC?p=5
《Linux内核完全剖析-基于0.12版本》–赵炯
https://www.cnblogs.com/Ccluck-tian/p/11861975.html(这位讲的比我更有条理一些)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值