TLPI-Chapter 3系统编程概念

来源:互联网 发布:淘宝女式大衣 编辑:程序博客网 时间:2024/06/05 07:55

第三章内容概念讲的其实相对好理解,主要有如下几点:
系统调用是可控的内核入口,进程可以请求内核以自己的名义去执行某些动作,这就用到了系统调用,讲处理器从用户态切换到内核态。
在书中作者用到一个例子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_LISTtypedef 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, 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))))

同样注释部分也说明了,我们指向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, 2,3,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 ",va_arg(ap,int));                break;            default:                assert(0);        }    }    va_end(ap);    putchar('\n');}int main(){    tiny_printf("sdd","result..",3,5);    return 0;}

首先书中从printf入手讲解可变长参数,例如 printf(“%d,%s\n”,100,str);
参数压入栈中,不论有多少个参数,第一个参数(指向”%d,%s\n”的指针)一定存在于距离固定的场所,如果参数不存入栈,按照从左往右的顺序的话,就不能找到第一个参数。
在代码中利用了assert(0),只要程序经过这里就hi报错,因为我们设计的tiny_printf只能处理s和d类型,用户输入不能输入其它类型。

原创粉丝点击