声明:本文章转载自:http://blog.sina.com.cn/s/blog_967e337b01011nix.html
(一)
Linux环境:
Linux localhost.localdomain 2.6.18-238.el5 #1 SMP Sun Dec 19 14:24:47 EST 2010 i686 i686 i386 GNU/Linux
确保你已经安装了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/include
sudo cp lib/libapue.a /usr/lib
好了,测试一下,记得要用-lapue命令让编译器链接apue库
gcc main.c -lapue
(二)(网上另一种方法)
这里要谈到的一个问题就是该书中的源代码编译的问题。此书中差不多每个历程中,都会有这样一行源码:
#include "ourhdr.h"
在第二版中改为:
#include "apue.h"
这个头文件是作者把把每个例程中常用的标准头文件,一些常用的出错处理函数(err_**()之类的函数)和一些常用的宏定义给整理在一个头文件中。这个 可以省去在每个例程中录入较多的重复代码,这样可以减少每个例程的长度。但是,这样就给读者带来了不少麻烦。因为我们还要去搞明白如和把这个头文件编译, 然后做成库文件,添加到我们的系统中。特别读于初学者,本来满怀信心的,结果在编译第一个程序的时候就出现了问题。我也没有搞明白如何把 "ourhdr.h"静态的编译到系统中。
不过,不明白如何使用"ourhdr.h"这个头文件,并不会影响我们学习APUE,也不会影响我们编译和运行每一个例程。其实,简单的想一下,如果一个 C程序要能顺利的编译和运行,除了我们要语法正确等方面外,最根本的是要保证我们程序中所调用的函数以及宏等等都要有完整的来源,也就是必须包含所有调用 函数和宏所在的头文件。对于一个具体的源程序,如果我们正确的包含了头文件,那么剩下的就是程序本生语法方面应该注意的事项。
如何确定系统调用函数包含在那个头文件中呢?这在Unix/Linux系统下并非一件难事。Unix/Linux下命令man可以帮助我们找到。man命 令不仅可以帮助我们查找一般命令的用法,同时提供不同层次的帮助诸如系统调用或者管理员级别的命令等等(譬如FreeBSD6.1中,man 1是用户专用手册,man 2是系统调用,man 3是库函数查询等等)。
下面我们就以APUE书中程序1-1 (实现ls命令部分功能)为例,来说明如何将书中的程序改编成全部使用标准头文件的程序。其中,操作系统用的是FreeBSD6.1,经过相应的修改可以 在书中所说的几个Unix系统及Linux系统中运行,我也曾在Debian Linux下成功编译和运行该程序。书中1-1.c的原始代码如下:
#include <sys/types.h>
#include <dirent.h>
#include "ourhdr.h"
int
main(int argc, char *argv[])
{
DIR *dp;
struct dirent *dirp;
if (argc != 2)
err_quit("usage: ls directory_name");
if ((dp = opendir(argv[1])) == NULL)
err_sys("can't open %s", argv[1]);
while ((dirp = readdir(dp)) != NULL)
printf("%s\n", dirp->d_name);
closedir(dp);
exit(0);
}
从书后面的附录中可以看到"ourhdr.h"的内容比较多,包含了比较多的常用头文件,一些宏定义和一些常用函数和出错函数的定义。其实,对于每一个具体的程序,我们只需要找到该程序中用到的头文件即可。
该1-1.c中所用到的系统函数调用有:opnedir(),readdir(),printf(),closedir()和exit()。
其中,对于常用的函数prinft()和exit(),它们所在的头文件一般都知道,分别是<stdio.h>和<stdlib.h>。而对于opnedir (),readdir()和closedir(),我们可以通过man opendir,man readdir,man closedir得到这三个关于目录操作的函数所在的头文件都是:<sys/types.h>和<dirent.h>。这两个头 文件在源程序中也已经列出。
其次,1-1.c中还用到了作者自定义的两个函数:err_quit()和err_sys()。这两个函数主要使用来进行出错处理的。当然,使用这两个函 数对错误信息的处理是比较完善的。但是,作为我们学习来讲,了解程序的核心功能是首要的,我们可以将出错处理简化一点,即当遇到错误的时候,我们只简单的 使用printf()函数来提示一下有错误发生。当然,用printf()来进行出错处理并不是一种很合理的方法,而且往往我们看不到更关键的错误信息, 但对于我们仅仅作为学习来用还是可以接受的。毕竟我们要理解的核心部分是程序的功能实现,出错处理在于其次。
通过以上的说明,我们可以将1-1.c修改为如下内容:
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
DIR *dp;
struct dirent *dirp;
if(argc != 2)
{
printf("You need input the directory name.\n");
exit(1);
}
if((dp = opendir(argv[1])) == NULL)
{
printf("cannot open %s\n", argv[1]);
exit(1);
}
while ((dirp = readdir(dp)) != NULL)
printf("%s\n", dirp->d_name);
closedir(dp);
exit(0);
}
这样修改后的程序已经与作者的头文件"ourhdr.h"没有关系,可以单独的进行编译。我使用的是root用户,执行命令:
# gcc 1-1.c //生成目标文件a.out
或者
# gcc -o 1-1 1-1.c //生成目标文件1-1
没有任何错误和警告,说明编译成功。这时我们执行生成的目标文件:
# ./a.out /home
或者
# ./1-1 /home
则会列出/home路径下的所有文件,包括目录(.)和(..)。
通过这样的方法,基本上我们可以将该书中所有的例程修改成不包含"ourhdr.h"的程序。这样,我们就可以单独的编译每一个例程,而不用顾及作者所给 的杂凑的头文件。同时这种比较笨的方法,反而有利于帮助我们了解不同系统调用所对应的头文件,对于学习来说,这应该是一件好事。