From:http://blog.csdn.net/mitesi/article/details/19015397
APUE说明及源码下载地址 :http://www.apuebook.com/
APUE 电子版 PDF 下载地址:http://download.csdn.net/download/freeking101/10012610
1. 介绍
总结:APUE是一本经典的书,对于Unix程序员如此,对于其它程序员也是相当有价值的。先给两张可以概括本书所讨论内容的两张图片。
第一部分:对本书总体结构的解析
本书共分21章。
1-2章为本书的入门简介与本书的特色所在,为何称作“特色所在”呢?因为本书始终都是在遵从那3个标准来进行编写的,即 ISO C、IEEE POSIX、The Single UNIX Specification 这三个标准,每下一行是上一行的超集,对于每一个Unix的c程序员来说都是最重要的编程标准。它决定了你代码的“高度”与“可用性”。第二章的一节还提到了“limits”即“限制”这个概念,这对于可移植性的代码很重要。
3-5章是关于“文件系统”和相关“I/O函数”的讨论,最重要的概念是“文件描述符”。这里面dup 和dup2 函数很值得”嚼味“,有兴趣可以看看我的另一篇文章”dup2(fd,0)和dup2(0,fd)一样吗?“
6章,讨论了一些“系统文件”。
79章,讨论了“进程”。个人认为下面这张图可以说是对程序的内存布局的典型情况作了很好图示。(见7.6节)
10章,讨论了“信号”,它是一种软中断。同时,它为“异步通信”的实现提供了可能。
11-12章,是相对于《apue》第一版新增的内容之一。对线程作了讨论。你可以认为“线程”就是一种特殊的“函数”,只不过它能够共享调用进程的资源,能够独立于调用进程(或其它线程)并行执行。当然线程也可以同步,也可以异步执行。还有很多其它有趣的线程特性,可以详细地阅读该书。
13章,讨论了“守护进程”的概念与编写方法。至此,本书的核心内容基本讨论完毕。
后面14~19章讨论了高级I/O,高级进程间通信,及终端和伪终端的概念。
14章的高级I/O,主要留意I/O的“阻塞”和“非阻塞”两种不同形式及各自的应用场合。要特别注意,所谓的阻塞或非阻塞是由“文件”的O_NONBLOCK 标志(注意文件描述符起的作用)决定的,而不是I/O 函数决定的。另外,这个章节中给出了“锁”的概念,这是进程或线程同步的重要技术。
15章讨论了进程间通信的种种方法,个人推荐“管道”和“FIFO”两种,至于“XSI IPC”则不建议使用,理由在书中也说得很清楚。
16章讨论了“网络套接字”编程。本想自己画个图来说明,没有时间就以后再说吧。
17章讨论了基于15章中所讨论的“流机制”和16章讨论的“套接字”的两种高级IPC,具体是哪两个,有兴趣自己去看书吧。前者没有什么可移植性,后者倒是很不错的概念。(后者很好体现了制定“套接字”的初衷:既可用于网络的通信,也可用于本机进程的通信)
18章讨论了终端I/O。(晕!看过才知道,终端的属性也太多了吧?)
19章讨论了“伪终端”,即“PTY”的概念和使用它的初衷。个人认为书中已经说得很透彻了。如果你明白了“主设备”和“从设备”的关系,及伪终端运行在从设备上的原理和用处,那么你的知识体系又扩大很多了。
20章讨论了如何在Unix中构建一个支持 并发访问的数据库。
21章通过构建一个打印机的CS模型(客户-服务器)来整合前面大部分章节的知识,做一个应用示例。
第二部分:本书重点内容讨论
1.“标准”的重要性。一句话,没有第一部分所提到的3个标准,就不存在Unix编程了。(个人对“标准”的理解与重视)
2.“内核”是什么?能为我们提供什么功能呢?再回过头来,看看一开始我给出的两张图片。第一张是内核在整个系统中所处的位置,我想说的是:内核是对系统所有硬件资源的“管理和组织者”,你可以说它是“管家”。它负责为上层的软件运行提供所需的硬件资源的分配和时间调度的分配等等。可以认为内核包含两大功能:一、驱动程序集合;二、调度系统。第二张是内核所包含的功能模块,也可以说是本书所讨论内容的概括。21个章节中后19个的内容都是对这5个模块的详细展开。
3.文件系统的核心概念:“文件描述符”。内核对文件系统的管理都是基于“文件描述符”的。不管是创建文件,移动或复制文件,修改文件,删除文件都使用“文件描述符”来对某个想操作的文件进行引用和表示。
4.区别一下信号,进程间通信,和进程间同步,及共享等概念的不同。信号,是一种软中断;它是异步通信的基础。所以它(异步通信)是进程间通信的一种方式(我们主要使用的signal调用,kill调用等等)。而进程间通信还有别的形式,如Pipes 即管道(典型的是传统的无名单工管道),FIFOs(又叫有名管道,典型的是双工管道),XSI IPC(包括三种:消息队列,信号量(不是信号的概念哈!),共享内存)。后面这些方式主要是同步通信,它们主要用于进程间同步。而共享,主要是指内存共享,一般会用到14章提到的“锁机制”来保证一致性,像前面的共享内存就是它的一种应用。但你要知道共享的概念主要有两方面,一是进程间共享,如“内存共享”,文件共享(dup文件描述符)等;二是线程共享,共享的概念对于线程似乎是“天经地义”的,但主要指的是内存中变量等的“小范围”共享,这点要与进程共享区别。
5.层次与组件。Unix是一个典型的层次型系统。由第一张图片可以看出设计者的这种思路。这样做可以简化系统的设计,提高系统的性能。另一方面,Unix也是以“组件”(也可叫做“构件”)来将系统的所需功能以模块的形式很好地组织起来。如图二。其实,Unix或Linux的内核也是这种思路。比如你可以定制自己所需的高效内核,删除其中一些不需要的驱动代码模块。如果不是这种“组件”的思想,那么为达到定制的目的,你就不得不重写整个内核了。
6.本书的特色:尽可能给出所有用到的函数原型,以及它的实现思想。作者力求读者不止能够全面了解到 Unix 中有哪些你可以用的 庞大的 c 语言库,更希望能明白我们为何需要某个函数,以及怎样实现这个函数。Unix中c 语言也是分门别类的,而不是没有组织的。这种“规范化”的“标准”,给了我很深的印象。(诚如第1条中我强调的那样)
7.这本书有着所有优秀计算机类书籍的良好组织编排,特别是给出了很详实的“附录”和“索引”。这些细节是需要很多时间和功夫才能做到尽善尽美的。这也是国内许多同类书籍缺少的。现在,我可以将它作为“字典”来随时查阅了。
第三部分:系统地看待编程
该书是一本结合底层的系统级编程参考书。看完之后,对编程有了更加系统级的认识。
早期计算机诞生的目的就是为了解决庞大的计算问题。那时的计算机主要是以“硬件驱动”。
这样说主要是我认为:用户把一个计算问题(可以认为是他想运行的程序),通过某种方式,比如打孔纸条输入给计算机,计算机读取并分析该程序代码,做计算,并输出,比如可以输出到显示器上。这种模式,简单也单调。它不能允许多任务,不能做到更高的硬件资源利用效率。
为了解决效率利用问题,人们希望做一个比较底层的“系统程序”,然后在这个“系统程序”的环境中来组织,管理和调度其他“用户程序”,从而实现多任务与时间上对硬件资源的更高效利用,即“软件驱动”硬件资源。从而“操作系统”这个底层软件诞生了,Unix就是其中一个,而Unix操作系统的核心部分就是“内核”。因为要调度其它多个用户进程,所以内核要有“调度模块”,因为这些程序都是要利用计算机的硬件资源来做事情,所以内核要给所有的上层程序提供“驱动模块”以便它们使用计算机的硬件资源。(见本文第二部分2)
我始终认为软件和硬件的发展总是紧密关联的。
现在我们有了“操作系统”,那么现在我们看看这种“软件驱动”到底给我们带来了什么好处,与早期的“硬件驱动”比较。
从电脑加电开始,一步步启动并初始化操作系统(即内核),然后用户登录,最后在其中运行自己的计算任务。(见我的另一篇文章:Linux启动过程,用户登录过程,及内核进程调度schedule简单介绍)schedule(pid=0)就是内核的调度模块,init(pid=1)就是初始化模块。在schedule的调度下程序交替利用cpu的时间片完成自己的任务,并受到schedule的调度,如开始,暂停,休眠,终止等等。因为Unix中是“进程树”的进程组织形式,所以每个程序总有它的父进程,最顶端的父进程就是schedule,所以,内核总是能够管理和控制其它进程。
用户将自己的计算任务(可以是某段程序代码)存储在内存中供内核调度运行。但是由于内存容量有限,并且易失,所以后来演变为,将程序存放在外存中(比如硬盘),要用时再读取到内存中供内核调度。怎么有组织的存储我们的程序还有其他数据文件呢?于是我们需要有一个“文件系统”来管理这些程序和数据文件。而我们想要使用这些文件时,需要一套完善的I/O函数来进行操作,如读取,修改,写入等。这些都是本书3~5章和14章所讨论内容。
好吧,现在程序已经读取到了内存中,内核也在某个时间片选中该程序,调度并开始运行它,并赋予它某个pid号和它所需要的种种运行时环境和相关的硬件资源(如内存空间,寄存器,一些文件描述符等等),现在假如它是进程A,它可能由于计算需要还会产生子进程,如进程B来,而系统中可能还有很多其它的进程C,进程D之类的与它可能发生关系。由于Unix是多任务系统。所以在下个时间片,该进程就会被其它进程(比如进程C)替换出cpu,转而进入暂停或休眠状态。又由于Unix内核有抢占机制,所以可能当前时间片未完,它也可能被高优先级的其它进程(比如进程D)中断,被强制替换出cpu。关于进程这一块就涉及到进程环境的初始化,进程的控制(内核对用户进程的控制,父进程对子进程的控制等等),由于是多进程环境,所以各个进程之间存在联系(如父子进程,进程组,会话等概念)。由于多进程之间存在联系,那么必然需要内核提供一些它们之间相互通信的机制,包括同步通信和异步通信(见本文第二部分4的讨论)。这些关于进程的内容都是本书7~9章讨论的。而“信号”的机制在10章作了讨论,它是异步通信的基础。15~17章讨论了多种Unix下的通信机制。
有人觉得多进程都还不能充分利用计算机的硬件资源,于是就提出了多线程。多线程解决I/O操作对多进程造成阻塞导致效率低的问题。当然多线程还有一些别的特性,一些别的内核线程映射技巧来提高程序的运行效率。这部分在书中11~12章进行了讨论。
最后Unix提供了一个其它类别的操作系统少有的东东,即“终端”和“伪终端”的概念和设计。这些“组件”丰富了Unix编程的内容,为程序提供了独特的终端I/O函数,设计出有用的终端或伪终端程序。本书中18~19章作了深入讨论。Linux图形用户界面的终端窗口就是一个“伪终端”的前端。
说到图形界面,这里就再多说几句,本书中没有讨论这一部分,因为那不是内核所关注的模块或组件,而是“额外”的组件。如果我们要编写具有交互功能的程序,那么系统提供图形界面的“函数库”就是必须的了。那部分的编程内容你就得另外学习。
一开头我就说了,这本书不局限于面向Unix程序员,对于其它程序员也会有帮助的。
这里说明一下,比如你是一位Java程序员,那么你能从中学到什么呢。
首先你要知道Java提供了一个“虚拟机”(jvm)的概念,你可以认为它就是一个操作系统,或者内核。
jvm如同内核一样,提供了自己的库函数(就像Unix下丰富的底层函数调用一样)。这些函数库也有文件I/O函数,进程控制函数,线程控制函数,网络套接字部分,当然还少不了“信号机制”等等。
知道这些后,你就明白了,在jvm中编程如同在Unix下编程一样,需要面对的问题也一样了。
首先你要知道jvm的调度模块,它提供了那些底层库函数(驱动模块),那么你就可以开始编写代码了吧。
*****************************************************************************************************
APUE 书中源码编译
1.编译源码
假设源码解压到/home/crazyboy/tmp/apue.2e.src目录下。首先修改与自己系统对应的Make.defines.*文件,我的是linux系统,就修改Make.defines.linux文件,将里面的WKDIR修改成源码所在的目录,即这一行改成"WKDIR=/home/crazyboy/tmp/apue.2e.src".然后make或者make linux。如果make的时候报"ARG_MAX"未定义错误,那么修改include目录下的apue.h文件,添加一行:#define ARG_MAX 4096 然后修改threadctl/getenv1.c和threadctl/getenv3.c这两个文件,添加#include "apue.h".然后再make
2.使用apue.h和libapue.a
源码的README中只说明如何编译,如果要编译自己敲的代码的话,需要将include/apue.h拷贝到/usr/include目录下,再将lib/libapue.a拷贝到/usr/lib/或者/usr/lib64/目录下.然后就可以在自己敲的代码中使用#include <apue.h>,编译的时候需要加上-lapue选项.比如要编译hello.c:
gcc hello.c -lapue -o hello
确保你已经安装了gcc和gawk。
步骤
1.到www.apuebook.com下载源码
2.tar解包,cd apue.2e
3.vi Make.defines.linux 修改变量WKDIR,指向你的apue源码的位置,我的是/home/huangz/code/apue.2e,所以 WKDIR=/home/huangz/code/apue.2e
4.vi include/apue.h 增加一个常量ARG_MAX,这是threadctl/getenv1.c和threadctl/getenv3.c要用到的;4096这个值是参考里给的,如果有问题,自己修改吧。#define ARG_MAX 4096
5.vi threadctl/getenv1.c 增加#include "apue.h"
6.vi threadctl/getenv3.c 增加#include "apue.h"
7.vi threads/badexit2.c 修改第31行,将pthread_self()的返回值转换为int类型。printf("thread 2: ID is %d\n", (int)pthread_self());
8.vi std/linux.mk 将两个nawk改为gawk
9.make
10.sudo cp include/apue.h /usr/includesudo cp lib/libapue.a /usr/lib
好了,测试一下,记得要用-lapue命令让编译器链接apue库
gcc main.c -lapue
《UNIX 环境高级编程》每个历程中,都会有这样一行:
#include "apue.h"
这个头文件是作者把把每个例程中常用的标准头文件,一些常用的出错处理函数(err_**()之类的函数)和一些常用的宏定义给整理在一个头文件中。这个可以省去在每个例程中录入较多的重复代码,这样可以减少每个例程的长度。给读者带来了不少麻烦。下面给出一种代码的编译方法。
2、修改相应平台的文件,我使用的是linux,所以修改Make.defines.linux
你修改的只需要这一行WKDIR=/home/your_dir/apue2e_src/apue.2e,改成自己的目录路径
3、cd到apue.2e目录执行make,之后你会在lib目录下面找到libapue.a 这个文件.
现在,你可以把它拷贝到你能寻找的地方,在编写例子的时候,你就可以
4、拷贝apue2e_src/apue.2e/include/apue.h和apue2e_src/apue.2e/lib/libapue.a
到你的代码目录。
5、使用gcc -o hello hello.c libapue.a来编译你的代码
——————————————————————————————
照着做,然后make一下,最后报错,大概原因是找不到nawk命令。貌似linux下没有这命令。查到这命令出现在apue.2e/std/linux.mk文件中,试着把nawk换成gawk,再make clean一下,重新make,编译通过了。
我使用下面命令编译源程序:
gcc -o myls ls1.c -I../include ../lib/libapue.a
——————————————————————————————
gawk命令:
gawk ( GNU awk )工具是一种模式扫描和处理语言,它搜索一个或多个文件,以查看这些文件中是否存在匹配指定模式的记录(通常是文本)。每次发现时,它通过执行动作的方式处理文本。使用 gawk 可以生成报告或者过滤文本, gawk 的很多结构来自 C 语言。
Gawk 工具是 UNIX awk 的 GNU 版。为了方便起见,很多 linux 系统将 /bin/awk 链接到/bin/gawk ,这样用户可以使用两者中的任何一个来运行程序。
/
1.在第二个步骤中要apue.2e中修改Make.defines.linux,但一开始找不到这个文件。原因很简单:找错代码了...重下个。
2.第四个步骤中拷贝 /apue.2e/include/apue.h和/apue.2e/lib/libapue.a到你的代码目录。就可以解决找不到源函数的问题。其中/apue.2e下的一大堆fig*就是书中的示例代码。fig1.3是第一个源码。
3.在执行#gcc -o test1 fig1.3 libapue.a 编译时会出现File format not recognized:文件类型显示不正确的错误。只要修改fig1.3名字就OK。
执行命令:#cp fig1.3 sample13.c
#gcc -o test1 sample13.c libapue.a 就编译成功了!
目录
(一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO
(二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO
(三) 一起学 Unix 环境高级编程 (APUE) 之 文件和目录
(四) 一起学 Unix 环境高级编程 (APUE) 之 系统数据文件和信息
(五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境
(六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制
(七) 一起学 Unix 环境高级编程 (APUE) 之 进程关系 和 守护进程
(八) 一起学 Unix 环境高级编程 (APUE) 之 信号
(九) 一起学 Unix 环境高级编程 (APUE) 之 线程
(十) 一起学 Unix 环境高级编程 (APUE) 之 线程控制
(十一) 一起学 Unix 环境高级编程 (APUE) 之 高级 IO
(十二) 一起学 Unix 环境高级编程 (APUE) 之 进程间通信(IPC)
(十三) [终篇] 一起学 Unix 环境高级编程 (APUE) 之 网络 IPC:套接字