第三章内容概念讲的其实相对好理解,主要有如下几点:
系统调用是可控的内核入口,进程可以请求内核以自己的名义去执行某些动作,这就用到了系统调用,讲处理器从用户态切换到内核态。
在书中作者用到一个例子X86-32为例,按事件发生顺序:
1.应用程序通过外壳(wapper)函数,发起系统调用
2.参数入栈,传入外壳函数。
3.外壳函数将参数置入特定寄存器(包括系统调用编号)
4执行中断机器指令(int 0x80)。
5.内核响应中断指令,调用system_call()里程处理中断。
如何处理中断呢?
在内核栈保存寄存器的值
审核系统调用编号的有效性
通过编号找到相应的系统调用服务例程,调用时会先检查参数的有效性,然后执行任务。结果状态返回给system_call()例程
从内核栈中恢复寄存器的值,将系统调用返回值置于栈中
返回至外壳函数,切换回用户态
6.若系统调用服务例程的返回值表明调用有误,外壳函数会设置errno为对应的错误码.同时返回一个整型值表明系统调用是否成功。
本章的重点是围绕第6步,作者来处理来自函数库的错误,在阅读这些代码前,必须要学习C语言可变参数相关。ANSI C为了提高可移植性,通过头文件stdarg.h提供了一组方便使用可变长参数的宏。
/*
* stdarg.h
*
* Provides facilities for stepping through a list of function arguments of
* an unknown number and type.
*
* NOTE: Gcc should provide stdarg.h,and I believe their version will work
* with crtdll. If necessary I think you can replace this with the GCC
* stdarg.h.
*
* Note that the type used in va_arg is supposed to match the actual type
* *after default promotions*. Thus,va_arg (...,short) is not valid.
*
* This file is part of the Mingw32 package.
*
* Contributors:
* Created by Colin Peters <colin@bird.fu.is.saga-u.ac.jp>
*
* THIS SOFTWARE IS NOT COPYRIGHTED
*
* This source code is offered for use in the public domain. You may
* use,modify or distribute it freely.
*
* This code is distributed in the hope that it will be useful but
* WITHOUT ANY WARRANTY. ALL WARRANTIES,EXPRESS OR IMPLIED ARE HEREBY
* DISCLAMED. This includes but is not limited to warranties of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* $Revision: 1.2 $
* $Author: noer $
* $Date: 1998/10/10 00:51:16 $
*
*/
#ifndef _STDARG_H_
#define _STDARG_H_
/*
* Don't do any of this stuff for the resource compiler.
*/
#ifndef RC_INVOKED
/*
* I was told that Win NT likes this.
*/
#ifndef _VA_LIST_DEFINED
#define _VA_LIST_DEFINED
#endif
#ifndef _VA_LIST
#define _VA_LIST
typedef char* va_list;
#endif
/*
* Amount of space required in an argument list (ie. the stack) for an
* argument of type t.
*/
#define __va_argsiz(t) \
(((sizeof(t) + sizeof(int) - 1) / sizeof(int)) * sizeof(int))
/*
* Start variable argument list processing by setting AP to point to the
* argument after pN.
*/
#ifdef __GNUC__
/*
* In GNU the stack is not necessarily arranged very neatly in order to
* pack shorts and such into a smaller argument list. Fortunately a
* neatly arranged version is available through the use of __builtin_next_arg.
*/
#define va_start(ap,pN) \
((ap) = ((va_list) __builtin_next_arg(pN)))
#else
/*
* For a simple minded compiler this should work (it works in GNU too for
* vararg lists that don't follow shorts and such).
*/
#define va_start(ap,pN) \
((ap) = ((va_list) (&pN) + __va_argsiz(pN)))
#endif
/*
* End processing of variable argument list. In this case we do nothing.
*/
#define va_end(ap) ((void)0)
/*
* Increment ap to the next argument in the list while returing a
* pointer to what ap pointed to first,which is of type t.
*
* We cast to void* and then to t* because this avoids a warning about
* increasing the alignment requirement.
*/
#define va_arg(ap,t) \
(((ap) = (ap) + __va_argsiz(t)),\
*((t*) (void*) ((ap) - __va_argsiz(t))))
#endif /* Not RC_INVOKED */
#endif /* not _STDARG_H_ */
下面我们分别解析每个具体的函数:
看到源文件中typedef char* va_list; 我们可以知道va_list就是char *类型,揭开神秘面纱第一步.
va函数的优势表现在使用的方便性和易用性上,可以使代码更简洁。C编译器为了统一在不同的硬件架构、硬件平台上的实现,和增加代码的可移植性,提供了一系列宏来屏蔽硬件环境不同带来的差异。
ANSI C标准下,va的宏定义在stdarg.h中,它们有:
va_list,va_start(),va_arg(),va_end()。
这里,移动指针使其指向下一个参数,那么移动指针时的偏移量是多少呢,没有具体答案,因为这里涉及到内存对齐(alignment)问题,内存对齐跟具体使用的硬件平台有密切关系,比如大家熟知的32位x86平台规定所有的变量地址必须是4的倍数(sizeof(int) = 4)。va机制中用宏__va_argsiz(_INTSIZEOF(n))来解决这个问题,没有这些宏,va的可移植性无从谈起。注:依据不同glibc版本,对应的宏名字不同。
/* * Amount of space required in an argument list (ie. the stack) for an * argument of type t. */
#define __va_argsiz(t) \
(((sizeof(t) + sizeof(int) - 1) / sizeof(int)) * sizeof(int))
#define va_start(ap,pN) \
((ap) = ((va_list) (&pN) + __va_argsiz(pN)))
#endif//第一个可选参数地址
/*
* Increment ap to the next argument in the list while returing a
* pointer to what ap pointed to first,t) \
(((ap) = (ap) + __va_argsiz(t)),\
*((t*) (void*) ((ap) - __va_argsiz(t))))
同样注释部分也说明了,我们指向list中的下一个参数,返回list开始指向的参数.
参考如下例子:
/*
Name: 可变参数
Copyright: 52coder.net
Author: 52coder
Date: 04/09/17 23:44
Description: 可变参数
*/
#include <stdio.h>
#include <stdarg.h>
void print_args(int count,...);
int main(int argc,char* argv[])
{
print_args(5,1,2,3,4,5);
return 0;
}
void print_args(int count,...)
{
int i,value;
va_list arg_ptr;
va_start(arg_ptr,count);
for(i=0; i<count; i++) {
value = va_arg(arg_ptr,int);
printf("position %d = %d\n",i+1,value);
}
va_end(arg_ptr);
}
参考例子:
/* Name: 可变参数 Copyright: 52coder.net Author: 52coder Date: 04/09/17 23:44 Description: 可变参数 */
#include <stdio.h>
#include <stdarg.h>
double average(int num,...)
{
va_list valist;
double sum = 0.0;
int i;
/* 为 num 个参数初始化 valist */
va_start(valist,num);
/* 访问所有赋给 valist 的参数 */
for (i = 0; i < num; i++)
{
sum += va_arg(valist,int);
}
/* 清理为 valist 保留的内存 */
va_end(valist);
return sum/num;
}
int main()
{
printf("Average of 2,3,4,5 = %f\n",average(4,5));
printf("Average of 5,10,15 = %f\n",average(3,5,10,15));
}
下面这个例子是征服C指针中的一个例子,我个人认为这个例子非常非常的好。代码如下:
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
void tiny_printf(char * format,...)
{
int i;
va_list ap;
va_start(ap,format);
for(i = 0;format[i]!='\0';i++)
{
switch(format[i])
{
case 's':
printf("%s ",va_arg(ap,char*));
break;
case 'd':
printf("%d ",int));
break;
default:
assert(0);
}
}
va_end(ap);
putchar('\n');
}
int main()
{
tiny_printf("sdd","result..",5);
return 0;
}
首先书中从printf入手讲解可变长参数,例如 printf(“%d,%s\n”,100,str); 参数压入栈中,不论有多少个参数,第一个参数(指向”%d,%s\n”的指针)一定存在于距离固定的场所,如果参数不存入栈,按照从左往右的顺序的话,就不能找到第一个参数。 在代码中利用了assert(0),只要程序经过这里就hi报错,因为我们设计的tiny_printf只能处理s和d类型,用户输入不能输入其它类型。
原文链接:https://www.f2er.com/bash/390484.html