第三章内容概念讲的其实相对好理解,主要有如下几点:
系统调用是可控的内核入口,进程可以请求内核以自己的名义去执行某些动作,这就用到了系统调用,讲处理器从用户态切换到内核态。
在书中作者用到一个例子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, 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),只要程序经过这里就会报错,因为我们设计的tiny_printf只能处理s和d类型,用户输入不能输入其它类型。