《Linux/UNIX系统编程手册》第3章读书笔记

来源:互联网 发布:mac office 2011 汉化 编辑:程序博客网 时间:2024/05/20 22:01
写在前面的话:
I was so much order then, I'm younger than that now.
昔日我曾如此苍老,如今才是风华正茂。
——Bob Dylan 《My Back Pages》
第3章 系统编程概念
本章主要讲系统调用和库函数的概念,以及对执行系统调用和库函数之后的状态进行检查和诊断,这里会介绍一组函数,而这些函数是本书作者自己编写,并给出了函数的声明和实现,本书中大多编程实例都会通过调用这些函数来诊断系统调用或库函数的错误。最后,探讨可移植性问题。
一、系统调用
1、系统调用:内核提供的接口层(由内核函数实现),用户程序通过这个系统调用接口来实现内核为我们写好的各种功能。
2、对系统调用步骤,根据图3-1作如下简化(当然,本书作者的更加细致,这里只是为了好理解):
封装函数----->系统调用接口----->系统调用例程------->系统调用服务例程(内核函数)

系统调用接口:通过软中断进入到内核空间,将系统调用的编号保存到寄存器中。
系统调用例程:通过编号找到对应的内核函数(就是从存放所有调用服务例程的列表找),执行某一个任务。
3、对书中“深入系统调用运作方式之前,关注一下几点”,作如下理解:
1)处理器状态的转换,由用户态(特权级3)转换为内核态(特权级0),提高处理器的执行级别,以便于CPU访问内核的内存
2)每一个系统调用对应一个编号
3)参数传递: 普通函数传参通过将参数放入堆栈区,系统调用参数先保存在cpu的寄存器中,在执行内核函数之前,放到内核堆栈区
二、库函数
许多库函数不会使用任何系统调用,还有些库函数构建于系统调用之上。也就是说,一些库函数是要通过系统调用实现的。
博主觉得这里应该将系统调用与库函数的区别指出,显然本书是写给对I/O进程有点基础,想深入了解的读者看的。博主水平有限,这里只根据对文件的读写方式的不同,来区别系统调用和库函数:
我们将采用库函数方式对文件进行操作,叫做标准IO;将采用系统调用的方式对文件进行操作文件IO。
当对文件进行操作的时候,文件IO直接通过系统调用对问进行读写;而标准I/O在用户空间空间创建缓冲区,读写文件时,先操作缓冲区,当满足条件时,进行实际读写操作。这样做,减少系统调用的次数,提高对文件的读写效率
文件IO的操作核心是文件描述符,而标准IO的操作核心是流指针。(关于文件IO,第四章和第五章将会介绍)
三、处理来自系统调用和库函数的错误
在进行错误检查时必须坚持首先检查函数的返回值是否表明调用出错,然后再检查erron(错误号)确定错误原因。常用的做法之一是根据erron值来打印错误消息,库函数perror()和strerror()可以实现这一目的。

作者在本节中一直在强调要检查错误,书中也是这么做的,如下面一段程序:

cnt = read(fd, buf, numbytes);    if(cnt == -1){        if(error == EINTR)            fprintf(stderr, "read was interrupted by a signal\n");        else{            /*Some other error occured*/        }    }

这段程序中,read是一个系统调用函数,用if条件语句检查函数的返回值是否为-1,如果为-1,说明调用出错,然后再用if条件语句检查erron确定错误原因。但是erron错误编号是有很多的,我们不可能将每个错误编号都判断一遍,于是作者为了简化程序中的错误处理,编写了错误诊断函数。

现在让我们当一回作者,考虑一下如何编写错误诊断函数?
首先,诊断错误要用到错误号,先弄一个数组存放这些erron对应的符号名,错误处理函数会使用该数组,去打印某个特定错误号相对应的符号名。作者这里用了一个数组ename[]来存放错误名。为什么要这么做?一是因为strerror()不会标识出与错误消息相对应的符号常量(如上例中的EINTR就是符号常量,它的错误号是4);另一方面,手册页在描述错误时,使用的是符号名称,打印出符号名称便于读者在手册页中查找错误原因。
接下来就要实现这些函数了,作者在这里定义了六个错误处理函数,errMsg()、errExit()、err_exit()、errExitEN()、fatal()、usageErr()、cmdLineErr()
这六个处理函数有各自的特点和使用范围,作者在书中已经有详细的说明,并在程序清单3-3中有它们的实现。我们不需要现在就记住这些函数是使用,只要在以后看到作者的程序清单用到了某个函数,考虑一下为什么在这里用,这样便能理解这些函数。

现在我们来研究一下程序清单3-3,看这几个函数是如何实现的。
从函数声明来看,这几个函数都是可变参数的,那么获取参数列表中的参数就是解决问题的关键。只要获取了可变参数,接下来按照p41~p43对每个函数的介绍,可以很容易理解。
<stdarg.h> 里面有几个清单中用到的函数:
typedef char* va_list;
void va_start ( va_list ap, prev_param );
type va_arg ( va_list ap, type ); 
void va_end ( va_list ap ); 
va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
<Step 1> 在调用参数表之前,定义一个 va_list 类型的变量,(假设va_list 类型变量被定义为ap);
<Step 2> 然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数;
<Step 3> 然后是获取参数,调用va_arg,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置;
<Step 4> 获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。说白了,就是让我们的程序具有健壮性。通常va_start和va_end是成对出现。
参考http://www.cnblogs.com/hanyonglu/archive/2011/05/07/2039916.html
但是,作者在获取参数时没有调用va_arg,有的用到的是vfprintf()直接打印错误信息;
参考http://baike.so.com/doc/175069-184966.html
而有的是调用自定义函数outputError(),在这个函数中打印错误信息。这个函数实现中调用了如下函数:
vsnprintf参考 http://blog.csdn.net/sunshine_okey/article/details/7386081
snprintf参考 http://baike.so.com/doc/6733871-6948230.html
getenv参考 http://baike.so.com/doc/6118960-6332105.html
还有些用到的函数这里没有提及,遇到不会的借鉴一下别人的经验,或者查看man手册,自己就能够解决。
四、解析数值型命令行参数的函数
我们知道,命令行可以传参,但数值型命令行参数传入的是字符串,要想把它转换成整型数,我们可以用atoi()、atol()、srttol(),而作者提供了两个函数getInt()、getLong(),这两个函数针对数值型参数提供了有效性检查,即如果arg未包含一个有效的整数字符串(即仅包含数字以及“+”和“-”),那么这两个函数会打印一条错误消息,并终止程序。
对于这两个函数的实现,程序清单3-6可以很容易看明白,唯一令博主不明白的是作者预定义了几个GN_*系列常量,与flag相或,是如何实现选择进制转换的?如果有研究过本段代码的朋友希望可以指点一二。
五、可移植性问题
这一小节中,作者向我们介绍了怎样编写自己的程序才能具有可移植性。看我这小节突然明白了之前一些不明白但却不影响学习的写法,比如说为啥非要把int重命名成pid_t。有些东西就是这样,初看不明白,可以先跳过去看后面的,等再次看的时候反而更加理解了。好了,如何使程序具有可移植性呢?博主把本小节归纳为以下几点:
1、特性测试宏:控制头文件显露对特定标准的定义;
2、使用系统数据类型声明变量;
3、正确初始化标准结构体;
4、使用预编译指令#ifdef(因为有的宏并不存在)。
六。课后练习
reboot()系统调用的参数magic2,没见过,不要紧,随便开个终端,man 2 reboot查看一下,第二个参数magic2的取值有4个:
LINUX_REBOOT_MAGIC2 (that is, 672274793)
LINUX_REBOOT_MAGIC2A (that is, 85072278)
LINUX_REBOOT_MAGIC2B (that is, 369367448)
LINUX_REBOOT_MAGIC2C (that is, 537993216)
这四个数字转换成十六进制数分别是:
672274793=0x28121969
85072278=0x05121996
369367448=0x16041998
537993216=0x20112000
看出什么来了吗?是不是很像日期呢?day,month,year,没错,就是生日。问了度娘,原来是Linux的作者Linus Torvalds自己和他三个女儿的生日。知道这个答案,我只想说一句:“原来可以这样!”

如果有读者看了已经发表过的这几篇读书笔记,会感觉到博主不是简单的知识点罗列,而是将书中的概念进行梳理不至于会被翻译弄的不知所云,博主在写每篇读书笔记时都会潜心研究书中内容,将已经具备的知识和书中内容进行融合,力求将此博文写成本书解析,最好可以冠以名为”Linux系统编程手册解析“。博主很乐意与别人分享知识和经验,这样做既可以帮助别人,也可以提高自己。





0 0