博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从JOS源码了解系统调用
阅读量:4166 次
发布时间:2019-05-26

本文共 5808 字,大约阅读时间需要 19 分钟。

        首先何为系统调用?根据维基百科的解释:a system call is how a program requests a service from an operating system's kernel. 简意就是用户程序对操作系统内核服务的请求。我们知道用户程序所能做的事情很有限,比如我们不能自己写代码直接操作硬盘,不能自己写代码控制控制台的输入输出等等,那怎么办呢?办法就是请求操作系统帮我们完成,毕竟操作系统掌控着所有的计算资源,因此对它来说,控制硬盘,显示器啦肯定都是不在话下的。

       其实我们平时写的很多用户程序都用到系统调用,只不过由于一般编程语言都会把系统调用封装在标准库里面,所以我们没有发觉。因此系统调用很多时候对我们来说就是简单的调用函数。比如c语言中,我们最常用的pritf格式化输出函数就需要用到系统调用,毕竟将一些东西显示到显示器上还不是那么容易的~

      下面以JOS操作系统的源码来了解一下系统调用,以及他是怎么封装在函数当中的。

     首先假如我们编写了以下用户程序

// hello, world#include 
voidmain(int argc, char **argv){ cprintf("hello, world!\n");}

首先需要调用打印函数,看一下打印函数的实现

intcprintf(const char *fmt, ...){	va_list ap;	int cnt;	va_start(ap, fmt);	cnt = vcprintf(fmt, ap);	va_end(ap);	return cnt;}
打印函数需要调用 vcprintf 函数,接着看vcprintf 函数的实现

intvcprintf(const char *fmt, va_list ap){	struct printbuf b;	b.idx = 0;	b.cnt = 0;	vprintfmt((void*)putch, &b, fmt, ap);	sys_cputs(b.buf, b.idx);	return b.cnt;}

可以看到vcprintf需要调用两个函数:vprintfmat 和 sys_cputs 

以下是vprintfmat 函数的代码

voidvprintfmt(void (*putch)(int, void*), void *putdat, const char *fmt, va_list ap)//caller : vprintfmt((void*)putch, &cnt, fmt, ap);{	register const char *p;	register int ch, err;	unsigned long long num;	int base, lflag, width, precision, altflag;	char padc;	while (1) {		while ((ch = *(unsigned char *) fmt++) != '%') {			if (ch == '\0')				return;			putch(ch, putdat);		       }		// Process a %-escape sequence		padc = ' ';		width = -1;		precision = -1;		lflag = 0;		altflag = 0;	reswitch:		switch (ch = *(unsigned char *) fmt++) {		// flag to pad on the right		case '-':			padc = '-';			goto reswitch;		// flag to pad with 0's instead of spaces		case '0':			padc = '0';			goto reswitch;		// width field		case '1':		case '2':		case '3':		case '4':		case '5':		case '6':		case '7':		case '8':		case '9':			for (precision = 0; ; ++fmt) {				precision = precision * 10 + ch - '0';				ch = *fmt;				if (ch < '0' || ch > '9')					break;			}			goto process_precision;		case '*':			precision = va_arg(ap, int);			goto process_precision;		case '.':			if (width < 0)				width = 0;			goto reswitch;		case '#':			altflag = 1;			goto reswitch;		process_precision:			if (width < 0)				width = precision, precision = -1;			goto reswitch;		// long flag (doubled for long long)		case 'l':			lflag++;			goto reswitch;		// character		case 'c':			putch(va_arg(ap, int), putdat);			break;		// error message		case 'e':			err = va_arg(ap, int);			if (err < 0)				err = -err;			if (err >= MAXERROR || (p = error_string[err]) == NULL)				printfmt(putch, putdat, "error %d", err);			else				printfmt(putch, putdat, "%s", p);			break;		// string		case 's':			if ((p = va_arg(ap, char *)) == NULL)				p = "(null)";			if (width > 0 && padc != '-')				for (width -= strnlen(p, precision); width > 0; width--)					putch(padc, putdat);			for (; (ch = *p++) != '\0' && (precision < 0 || --precision >= 0); width--)				if (altflag && (ch < ' ' || ch > '~'))					putch('?', putdat);				else					putch(ch, putdat);			for (; width > 0; width--)				putch(' ', putdat);			break;		// (signed) decimal		case 'd':			num = getint(&ap, lflag);			if ((long long) num < 0) {				putch('-', putdat);				num = -(long long) num;			}			base = 10;			goto number;		// unsigned decimal		case 'u':			num = getuint(&ap, lflag);			base = 10;			goto number;		// (unsigned) octal		case 'o':			num = getuint(&ap,lflag);                        base = 8;                        goto number;			break;		// pointer		case 'p':			putch('0', putdat);			putch('x', putdat);			num = (unsigned long long)				(uintptr_t) va_arg(ap, void *);			base = 16;			goto number;		// (unsigned) hexadecimal		case 'x':			num = getuint(&ap, lflag);			base = 16;		number:			printnum(putch, putdat, num, base, width, padc);			break;		// escaped '%' character		case '%':			putch(ch, putdat);			break;		// unrecognized escape sequence - just print it literally		default:			putch('%', putdat);			for (fmt--; fmt[-1] != '%'; fmt--)				/* do nothing */;			break;		}	}}

从vprintfmat函数的代码可以看出,他主要调用的是putch,所以下面再让我们看一下putch的代码

static voidputch(int ch, struct printbuf *b){	b->buf[b->idx++] = ch;	if (b->idx == 256-1) {		sys_cputs(b->buf, b->idx);		b->idx = 0;	}	b->cnt++;}

可以看到putch中主要掉用的是sys_cputs 函数,是不是似曾相识?回头看一下vcprintf函数,就会看到sys_cputs 函数!因此抛开细节,sys_cputs函数是关键!

好了让我们看看sys_cputs函数的真面目

voidsys_cputs(const char *s, size_t len){	syscall(SYS_cputs, 0, (uint32_t)s, len, 0, 0, 0);}

大吃一惊,我还以为他会是几百行呢。原来它也是调用了另一个函数 : syscall

不过看这个函数名称,好像离我们的系统调用很近了!

好,我们接着看下面的syscall函数源码

static inline int32_tsyscall(int num, int check, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5)// interface{	int32_t ret;	asm volatile("int %1\n"		: "=a" (ret)		: "i" (T_SYSCALL),		  "a" (num),		  "d" (a1),		  "c" (a2),		  "b" (a3),		  "D" (a4),		  "S" (a5)		: "cc", "memory");	if(check && ret > 0)		panic("syscall %d returned %d (> 0)", num, ret);	return ret;}
非常遗憾是看不懂的一段内联汇编,没事我们可以看看汇编后的汇编代码

800a18:	55                   	push   %ebp  800a19:	89 e5                	mov    %esp,%ebp  800a1b:	57                   	push   %edi  800a1c:	56                   	push   %esi  800a1d:	53                   	push   %ebx	asm volatile("int %1\n"  800a1e:	b8 00 00 00 00       	mov    $0x0,%eax  800a23:	8b 4d 0c             	mov    0xc(%ebp),%ecx  800a26:	8b 55 08             	mov    0x8(%ebp),%edx  800a29:	89 c3                	mov    %eax,%ebx  800a2b:	89 c7                	mov    %eax,%edi  800a2d:	89 c6                	mov    %eax,%esi  800a2f:	cd 30                	int    $0x30
简单看一下这段代码,前面部分是建立栈帧。asm volatile部分的代码从 0x800ale地址开始。前面都是寄存器操作,看后面,有一个int 30,软中断。通过JOS源码可以知道,JOS的IDT对应的系统调用号是0x30。 这样我们就可以理解int 30 了!

其实我之前一直不太理解的问题是,int 30之后 系统怎么知道程序请求的具体是哪一个系统调用呢?我也知道是通过eax寄存器中的参数确定,但我不知道用户进程是怎么设置eax中的参数的,如今读了JOS源码才算知道。原来,对于用户进程调用的库函数,所有的库函数都有一个公共的接口(这里就是syscall函数),于是就可以通过这个接口,设置eax寄存器中的参数,使得各个库函数分别对应到各自的系统调用中。而这一切对用户程序来说都是透明的,他根本不知道什么是eax。

PS:以上代码源自MIT JOS系统源码

转载地址:http://yiqxi.baihongyu.com/

你可能感兴趣的文章
在脚本中取table单元格的值(javascript)
查看>>
一些有用的js代码
查看>>
性能分析样例
查看>>
C#中out 及 ref 区别
查看>>
数字签名的原理
查看>>
.NET中RAS加解密和签名与验证的c#实现代码讲解,及实现程序提供
查看>>
根据控件ID获取控件
查看>>
ACE篇之四:通过ACE日志策略进行运行时配置
查看>>
ACE篇之五:ACE容器之一(双向链表)
查看>>
ACE篇之六:ACE容器之二(栈)
查看>>
ACE篇之七:ACE容器之三(队列)
查看>>
ACE篇之八:ACE容器之四(数组)
查看>>
ACE篇之九:ACE容器之五(集合)
查看>>
ACE篇之十:ACE容器之六(映射表管理器)
查看>>
ACE篇之十一:ACE容器之七(自调整的二叉树)
查看>>
基本的TCP/IP Socket用法(一)
查看>>
基本的TCP/IP Socket用法(二)
查看>>
处理事件及多个I/O流--ACE Reactor框架总览
查看>>
SIGINT and others
查看>>
vim编程常用命令(随时补充更新)
查看>>