免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
123下一页
最近访问板块 发新帖
查看: 41744 | 回复: 26
打印 上一主题 下一主题

原 C/C++ 论坛 FAQ,尚未整理。  关闭 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2003-02-27 09:12 |只看该作者 |倒序浏览
原帖由 "版主模式" 发表:
本贴原题:
C/C++论坛FAQ(UNIX编程问答)

                
建议大家把自己认为好的贴子连接跟到后面
以后版主们会统一整理的

不然贴子数过多
想找找哪个有用还是很难,很多好贴子就会没有加进去
希望得到支持
              

本帖收集论坛内的FAQ
在论坛的回答中有许多好的回答
但是
如果每一个都要写到精华的话
那么精华又未免太多了
所以建议大家把自己认为好的回答写到这个帖子的回复中
并写上讨论的帖子连接


FAQ已整理
看看下面

一篇主要是本论坛FAQ
另一篇是其它人整理的,在这转载

论坛徽章:
0
2 [报告]
发表于 2003-03-01 14:15 |只看该作者

原 C/C++ 论坛 FAQ,尚未整理。

也欢迎大家添加
把帖子的回答中好的整理出来
会把整理者名字与回答者名字保存的
不要太在意是不是正确

因为我们不可能所有方面都知道
就是我也只是能尽量在我的知识之内保证准确
如果发现有不准确的请指出

论坛徽章:
0
3 [报告]
发表于 2003-03-02 09:29 |只看该作者

原 C/C++ 论坛 FAQ,尚未整理。

本帖收集论坛内的FAQ
在论坛的回答中有许多好的回答
但是
如果每一个都要写到精华的话
那么精华又未免太多了
所以建议大家把自己认为好的回答写到这个帖子的回复中
并写上讨论的帖子连接


    1.C/C++编程
    1.1: 我有问题,怎样发问,怎样查找答案
    1.2:STL string 怎么转换大小写
    1.3: 怎样用gcc生成动态库,用什么编译选项
    1.4:怎样指定程序链接某个库的动态库或静态库 版本
    1.5: 请问各UNIX平台下和编译器名
    1.6: solaris下有没有C函数可以获得进程的相关信息
    1.7: c++下使用<iostream>;的问题(namespace[名字空间]关键字问题)
    1.8: :volatile的作用是什么
    1.9: 请问,用 gcc 或 ld 连接程序时,如何设置段的属性?


    2.数据库编程
    2.1:如何在pro*c中调用存储过程
    2.2: 请问在pro*c中知道错误代码,怎么查这个代码的错误信息
    2.3: AIX下编译的嵌入式DB2 C程序,删除数据库后再创建时连接不成功
    2.4: 请问SOLARIS下使用OCI编程序要连什么库
    2.5:请问怎样用ct-librery编程

    3    图形界面编程
    3.1:论坛中有人说 GTK+ 并不支持中文,是这样吗?
    3.2:为什么在控件上的汉字无法显示,并伴有如下错误警告?
    3.3:为什么在屏幕上输出的汉字变成了乱码?
    3.4:在修改控件字体的第一步就发生了错误,为什么 gdk_font_load() 总是失败?
    3.5:我不想使用宋体,怎样获得其它字体的代号?
    3.6:你为什么没有说到 gettext() 及相关国际化标准?

    4    UNIX系统编程
    4.1  我建立共享内存之后,忘记了删除掉。请问:1)如果我不管它,它是由系统自己释放掉么
    4.2  请问IPCS显示的各权限位意义
    4.3  如何在unix下实现kbhit的功能?谢谢!!

    8. Solaris内核编程相关问题 [本篇为转载]
    8.1 Solaris内核模块中如何getcwd
    8.2
    8.3 如何避免一个套接字进入TIME_WAIT状态
    8.4 结构在优化编译中的对齐问题
    8.5
    8.6 如何得到非局部变量列表
    8.7
    8.8 如何单独获得Solaris编译环境
    8.9 如何获取Solaris内核可调参数列表
    8.10
    8.11 如何页边界对齐式分配内存
    8.12
    8.13 compile()和step()怎么用




1.1: 我有问题,怎样发问,怎样查找答案
A  许多问题可以通过GOOGLE查找答案,GOOGLE网址是www.google.com,上面的资料都很具体。另外在论坛的精华区也有不少讨论,许多可以直接解决你的问题,建议问前先在GOOGLE查找,并看看精华区。   如果已找了GOOGLE并看了精华区,但是对某些方面不理解,那么就在论坛上发问。发问时标题要清楚,并把你的问题描述清楚。许多人不会看你帖子的内容,所以在标题中写清楚问题也是很重要的。
   如果要粘贴代码,那么使用发言功能中的CODE功能,这样可以保证代码不会乱,能有更多人看懂你代码。
    当别人准确回答了你的问题时,应该说声谢谢,表示这个问题已回答,并表示对帮助你的人的感谢。这样会让更多的人觉得你的一个好学有礼貌的人,从而会更注意你的问题。
    如果你按上面这些方法做了,并不能保证你的发言一定能回答,因为不是每个人什么问题都知道,但可以保证会有更多人关注你的问题,回答的可能性会更高。



1.2:STL string 怎么转换大小写
A:使用STL算法中的transform 函数

  1.         string str22 = "This IS a MiXed CaSE stRINg";
  2.         transform (str22.begin(),str22.end(), str22.begin(), tolower);
复制代码






1.3: 怎样生成动态库
A:创建步骤:
    首先创建object文件,这个文件将加入通过gcc –fPIC 参数命令加入到共享函数库里面。PIC的意思是“位置无关代码”(Position Independent Code)。下面是一个标准的格式:

  1. gcc -shared -Wl,-soname,your_soname -o library_name file_list library_list
复制代码

下面再给一个例子,它创建两个object文件(a.o和b.o),然后创建一个包含a.o和b.o的共享函数库。例子中”-g”和“-Wall”参数不是必须的。

  1. gcc -fPIC -g -c -Wall a.c
  2. gcc -fPIC -g -c -Wall b.c
  3. gcc -shared -Wl,-soname,liblusterstuff.so.1 -o liblusterstuff.so.1.0.1 a.o b.o -lc
复制代码

下面是一些需要注意的地方:

· 不用使用-fomit-frame-pointer这个编译参数除非你不得不这样。虽然使用了这个参数获得的函数库仍然可以使用,但是这使得调试程序几乎没有用,无法跟踪调试。

· 使用-fPIC来产生代码。 使用shared 说明生成动态库,使用soname说明生成的库名

· 某些情况下,使用gcc 来生成object文件,需要使用“-Wl,-export-dynamic”这个选项参数。通常,动态函数库的符号表里面包含了这些动态的对象的符号。这个选项在创建ELF格式的文件时候,会将所有的符号加入到动态符号表中。可以参考ld的帮助获得更详细的说明。

star3s补充:
对于C++程序要使用EXTERN “C”说明输出接口

  1.         extern "C"
  2.         {
  3.           int soTest(int a,int b) ;
  4.         }

  5.         int soTest(int a,int b)
  6.         {
  7.           return a+b;
  8.         }
复制代码




1.4:怎样指定程序链接某个库的动态库或静态库版本
A:使用Bdynamic和-Bstatic选项。默认情况下,假如共享库可用,连接器会使用共享库。但是-Bdynamic和-Bstatic提供了很好控制库的方法。它们可以决定用共享库还是用静态库。

传-Bdynamic和-Bstatic选项给连接器,如下操作:
# gcc -o main main.o -Wl,-Bstatic \
-lfoo -Wl,-Bdynamic -lbar

# gcc -o main main.o -Wl,-Bstatic
告诉连接器所有的库(象libc等等)都使用静态的版本。



1.5: 请问各UNIX平台下和编译器名
kaisakaisa :
  sun CC (c++) cc(c)
  alpha cxx(c++) cc(c)
  HP aCC(c++)
  IBM xlC (c++)

1.6: solaris下有没有C函数可以获得进程的相关信息
比如:进程名、进程pid、进程所占CPU/Memory、开始时间、运行状态等等。谢谢。
liupch:2003-03-19 10:03
读取/proc/进程号/psinfo这个文件。
就用我告诉你的那个函数
ioctl(fd, PIOCPSINFO, &procinfo);
在看一下procinfo这个结构就知道了。


1.7: c++下使用<iostream>;的问题
在c++下使用include <iostream>;后为什么编译器会报错呢
1.检查你是不是使用g++编译器编译。gcc编c++程序会有问题
2.检查在include 头文件后有没有使用using namespace std;
新的标准C++加了几个关键字,其中最常用的就是namespace(名字空间)
加入这个关键字是为了避免在大程序中符号名重定义问题
同时原来的标准c++类都包含在std 名字空间中(如果使用c++ 的标准函数库,如<cstdio>; 那么它们也在std名字空间中)
新的标准c++头文件没有 .h,所以当你们include它们时要注意使用std名字空间.
以下一个例子

  1. #nclude <iostream>;
  2. #include <string>;
  3. using namespace std;  //在include 所有标准c++头文件后
  4. main()
  5. {
  6.     cout<<"hello world"<<endl;
  7. }
复制代码




1.8 :volatile的作用是什么
volatile的本意是“易变的”
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
简单点:
就是该变量会以编译器无法预知的方式发生变化,请编译器不要做优化(所有的编译器的优化均假设编译器知道变量的变化规律)


1.9: 请问,用 gcc 或 ld 连接程序时,如何设置段的属性?
幽灵兵:
为了实现共享数据,这个方法要比其它 进程/线程 的数据同步方法更
快,更方便,更灵活,而且是所有方法中最简单的。
比如我在 Win 下这样做:
1. 创建个 DLL 专门用于数据交换
2. 在 DLL 中声明一个全局变量 Var1
3. 在连接时声明 .bss 段为共享段
( Vc Linker: /section:.bss,S | Gcc: [type] Var1 __attrbiute__ ((section("shared", shared)); )
4. 在需要这个变量的进程中调用这个 DLL
... ...
其实就是将这个DLL作为一种数据池... 难道你们没用过吗?

我想将 .bss 段设置为 共享,gcc 在 NT 下可以用:
int Var __attrbiute__ ((section("shared", shared));
在 Linux/UNIX 下怎么办呢?

JohnBull :2003-04-27
Linux系统并不强调“段”的使用,因为Linux的设计者考虑到这个OS将移植到包括RISC等多种平台上,而事实上只有Intel的CPU才强调“段”的概念,其他(包括大多数64位)系统都是基于“页”进行设计的。
在Linux中,请用共享内存。




2.1:如何在pro*c中调用存储过程
ilmare :
在嵌入式SQL中使用CALL 语句调用存储过程的格式如下:

  1. EXEC SQL CALL [schema.] [package.] stored_proc[@db_link] (arg1, ...)
  2. [INTO :ret_var [[INDICATOR] :ret_ind]];
复制代码

例子:
创建一个PL/SQL函数fact,该函数存储于mathpkg包中

  1. EXEC SQL CREATE OR REPLACE PACKAGE BODY mathpkg as
  2. function fact(n IN INTEGER) RETURN INTEGER AS
  3. BEGIN
  4. IF (n <= 0) then return 1;
  5. ELSE return n * fact(n - 1);
  6. END IF;
  7. END fact;
  8. END mathpkg;
  9. END-EXEC.
复制代码

使用该函数
...
int num, fact;
...
EXEC SQL CALL mathpkg.fact(:num) INTO :fact;
...



2.2: 请问在pro*c中知道错误代码,怎么查这个代码的错误信息?
mengwg :
在sql*plus中使用>;oerr ora 1013

CHUJUN_98:
sqlca中含有中文含义 sqlca.sqlerrm.sqlerrmc代表的是sqlCZHI行的结果描述




2.3: AIX下编译的嵌入式DB2 C程序,删除数据库后再创建时连接不成功
预编译:embprep proc 得到proc.bnd和proc.c文件,编译没有错误发生。
编译:xlc -o proc proc.c -ldb2 编译正确,生成执行文件proc。
执行proc一切正常。

因为涉及到可能的移植问题,于是测试将数据库Emp Drop掉(db2 drop database Emp),再重新建原来相同的数据库Emp和表info,建成后执行proc,程序可正常连接数据库,可在对表info操作时(如select)返回SQLCODE错误=-805,也就是说无法对表进行操作。重新编译该程序后,运行正常!

AIX 下的DB2数据库不太好用,它在编译时会产生一个PKG存放于数据库中,
并且会对它打上时间戳。如果从新建库获建表的话都会破坏时间戳,必须从新编译或rebind,以产生新的PKG。

hasjing:2003-03-17 12:55
AIX 下的DB2数据库不太好用,它在编译时会产生一个PKG存放于数据库中,
并且会对它打上时间戳。如果从新建库获建表的话都会破坏时间戳,必须从新编译或rebind,以产生新的PKG。

时间戳的目的是为了保证db2数据库优化策略的一致性,而且PKG的内部名称
是SQC文件的前八个字母,所以在开发时要当心每个模块的名字前八个字母不能相同!否则会产生PKG覆盖,会产生818的错误代码!
所以IBM的东西虽好,但是规矩太多!

在db2环境下
? SQL0805
可以查看错误代码!


2.4: 请问SOLARIS下使用OCI编程序要连什么库
在Solaris8下安装了oracle8.1.6,并编译用OCI编写的程序,其中使用了很多的oci函数,包括Direct Path API,但联接时,提示OCIDirPathColArrayReset'等符号找不到
编译时已加入了-L/export/home/opt/oracle/8.1.6/lib -lclntsh
如果将Direct Path的调用去掉,则连接成功,请各位高手不吝赐教

wangz :2003-03-20
加上-lclient8就可以了!!试试看,我这里可以

2.5:请问怎样用ct-librery编程
有哪位熟悉ct-library编程,我在写有关socket通讯的程序,需要和sybase数据库交互信息。希望和高手切磋一下!!!

minsanyuan :2003-04-21 22:55
        调CT-Library就可以了,那来的socket呀,
        如果用socket就不要用ct-Library了,
        如果你很牛,可以试试两都都用
        1. 初始化ct-library
        ct_init( ....)
        2. 分配连接结构
        ct_con_alloc(...)
        3. 设置用户名及口令
        ct_con_props(...)
        4. 建立连接
        ct_connect(..)
        5. 断开连接
        ct_exit( ... )
        6. 释放
        ct_ctx_drop(...)
        /Sybase/sample下有例子





3 GTK+ 编程
以下由付强提供,QQ:775341 ,感谢他把自己的学习成果写出来与大家分享
原作者声明如下
        >; 这些文章其实是我的学习笔记,以 FAQ方式代问自答,希
        >; 望能帮初学者解决一点实际问题。
        >; 您可以随意修改或转载本文,但请保证内容的正确性,以
        >; 免误人子弟 --- 谢谢 :)

  
3.1问:论坛中有人说 GTK+ 并不支持中文,是这样吗?
-----------------------------------------------------------
答:
        GTK+ 采用 UTF-8 编码就是为了支持多字节文字,所以GTK+
        肯定支持中文,而且非常出色,你可以看到网上使用 GTK+ 编写
        的中文软件数不胜数,所以没必要理会那种说法。
        ( 我真的看过这类的帖子,误人子弟...呵呵... )

>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;

3.2问:为什么在控件上的汉字无法显示,并伴有如下错误警告:
"** (:1864): WARNING **: Invalid UTF8 string passed to
pango_layout_set_text()"
-----------------------------------------------------------
答:
        GTK+ 中的字符串均采用 UTF-8 格式编码,这个提示就是告
        诉您,程序中的字符串编码格式不是 UTF-8,通常这是由编辑器
        造成的,例如在我的 VC7 中,默认的存档编码格式为 GB2312。
        但是,除了将文件以 UTF-8编码格式存档外,你还可以使用
        glib 的转换函数在程序的运行过程中将字符串转换为 UTF-8。
        例如: 将
        button = gtk_button_new_with_label( "确定" );
        改成
        button = gtk_button_new_with_label(
        g_locale_to_utf8("确定",4,NULL,NULL,NULL));

>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;

3.3问:为什么在屏幕上输出的汉字变成了乱码?
-----------------------------------------------------------
答:
        既然可以显示乱码,就表明与编码格式无关。
        您可能已经想到了 --- 对,问题就出在字体上。
        一般情况下,系统会用"Sans"作为默认字体,但这种字体中
        只包含了 ASCII 码,所以汉字是无法显示的。

>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;

3.4问: 在修改控件字体的第一步就发生了错误,gdk_font_load()
总是失败,为什么?

-----------------------------------------------------------
答:
首先向您提供一些建议:
1.不要用 GDK 的一些函数更改字体,因为那是 GTK+的不稳定
因素之一(至少在2.0以下版本中是这样的)。
2.为了获得更多的字体,应该避免使用 GdkFont类型,及相关
函数,取而代之的是 Pango 库,这是 GTK+ 的一部分。
3.GdkFont 只能使用 XFont 字体,而且使用方法相对复杂,
PangoFontDescription 可以使用 XFont,以及一些本地字体,
包括 Win Font, XRender Font, TrueType Font,所以您没有必
要再留恋 GdkFont 了。
4.您不需要修改所有控件,更简便的方法是在主窗口创建后立
即修改它的字体,将来创建的控件会继承它的这项属性。
例如:

  1. window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
  2. {
  3. GtkStyle *style = gtk_rc_get_style( window );
  4. pango_font_description_set_family(
  5. style->;font_desc, "Stsong" );
  6. gtk_widget_set_style( window, style );
  7. }
复制代码

( 这三句代码是将程序的字体设置为 "华文宋体" )

>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;

3.5问:我不想使用宋体,怎样获得其它字体的代号?
-----------------------------------------------------------
答:
        部分字体的代号和它的文件名是相同的,例如黑体的代码为
        "Simhei",华文宋体为"Stsong"。
        您还可以通过一些 Linux/Unix 下的编辑软件的字体选择框
        来查找字体对应的代码。
        但标准的方法是调用 Pango库中的相应函数来查找机器上已
        安装的字体。
        如果想了解更多关于 Pango的高级使用方法,请参考它自带
        的 API 开发手册。
>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;>;

3.6问:你为什么没有说到 gettext() 及相关国际化标准?
答:
        目前国内多数程序员采用这个办法汉化程序,所以我没有必
        要再多说。但它不一定对每个人都合适,我通常采用自己管理语
        言模块的方法实现国际化,这在很多情况下要比 gettext()更方
        便。


4. 系统编程
4.1  我建立共享内存之后,忘记了删除掉。请问:1)如果我不管它,它是由系统自己释放掉么
一颗流星:
        我建立共享内存之后,忘记了删除掉。请问:1)如果我不管它,它是由系统自己释放掉么?那么系统管理共享内存的机制是怎样的? 2)能否手动释放?如果能,用什么语句呢?
gadfly :2003-04-11
            不会自动释放,可以用ipcrm删掉
蓝色键盘:2003-04-18
            用shmctl或者ipcrm删除后,只有如下情况出现才会被彻底清除。
        ****直到最后一个连接进程释放它*****
        或者
        ****没有一个进程在attach它******


4.2  请问IPCS显示的各权限位意义
一颗流星:
        -C---------表示具有什么存取权?
蓝色键盘:2003-04-18
        ipcs显示的结果中
        标志位是共有11位。其中前两位表示如下:
        R 表示进程等待msgrcv
        S 表示进程等待msgsnd
        D 表明该段IPC资源已经被删除,直到最后一个连接进程释放它(请注意这一点,很多情况下往往无法用ipcrm正常删除IPC资源,原因就在于此)
        C 在进程attach时,共享内存段已经被清除
        - 相应的权限没有被设置
       
        其它的9位表明该段IPC资源的权限信息。这个大家都明白,但是注意
        - 表明指定权限没有被设置。


4.3  如何在unix下实现kbhit的功能?谢谢!!
clilye:如何在unix下实现kbhit的功能?谢谢!!
fieryfox: 2003-04-29
        下面的文字摘自Unix Programming FAQ:
       
        3.2 How can I read single characters from the terminal?
        =======================================================
       
        How can I read single characters from the terminal? My program is
        always waiting for the user to press `<RETURN>;'.
       
        Terminals are usually in canonical mode, where input is read in lines after
        it is edited. You may set this into non-canonical mode, where you set how
        many characters should be read before input is given to your program. You
        also may set the timer in non-canonical mode terminals to 0, this timer
        flushs your buffer at set intervals. By doing this, you can use `getc()' to
        grab the key pressed immediately by the user. We use `tcgetattr()' and
        `tcsetattr()' both of which are defined by POSIX to manipulate the
        `termios' structure.
       
        #include <stdlib.h>;
        #include <stdio.h>;
       
        #include <termios.h>;
        #include <string.h>;
       
        static struct termios stored;
       
        void set_keypress(void)
        {
        struct termios new;
       
        tcgetattr(0,&stored);
       
        memcpy(&new,&stored,sizeof(struct termios));
       
        /* Disable canonical mode, and set buffer size to 1 byte */
        new.c_lflag &= (~ICANON);
        new.c_cc[VTIME] = 0;
        new.c_cc[VMIN] = 1;
       
        tcsetattr(0,TCSANOW,&new);
        return;
        }
       
        void reset_keypress(void)
        {
        tcsetattr(0,TCSANOW,&stored);
        return;
        }
       
        3.3 How can I check and see if a key was pressed?
        =================================================
       
        How can I check and see if a key was pressed? On DOS I use the
        `kbhit()' function, but there doesn't seem to be an equivalent?
       
        If you set the terminal to single-character mode (see previous answer),
        then (on most systems) you can use `select()' or `poll()' to test for
        readability.








8. Solaris内核编程相关问题

8.1 Solaris内核模块中如何getcwd
Q: 在Solaris 7 64-bit内核模块中如何获知一个进程的当前工作目录(cwd),getcwd
并不是一个系统调用

A: Rich Teer <rich@rite-group.com>;
最好通过u->;u_cdir获取当前工作目录(cwd)的vnode(v节点)。但这依赖于内核当前上
下文,curproc可能并不对应你期望的进程。

  1. usr/include/sys/user.h

  2. typedef struct user
  3. {
  4. ... ...
  5. /*
  6. * protected by p_lock
  7. */
  8. struct vnode * u_cdir; /* current directory */
  9. struct vnode * u_rdir; /* root directory */
复制代码


8.3 如何避免一个套接字进入TIME_WAIT状态
Q: 我正在写一个unix server程序,不是daemon,经常需要在命令行上重启它,绝大
多数时候工作正常,但是某些时候会报告"bind: address in use",于是重启失
败。

A: Andrew Gierth <andrew@erlenstar.demon.co.uk>;
server程序总是应该在调用bind()之前设置SO_REUSEADDR套接字选项。至于
TIME_WAIT状态,你无法避免,那是TCP协议的一部分。

Q: 如何避免等待60秒之后才能重启服务

A: Erik Max Francis <max@alcyone.com>;

使用setsockopt,比如

  1. --------------------------------------------------------------------------  
  2. int option = 1;  

  3. if ( setsockopt ( masterSocket, SOL_SOCKET, SO_REUSEADDR, &option,  
  4.                   sizeof( option ) ) < 0 )  
  5. {  
  6.     die( "setsockopt" );  
  7. }  
  8. --------------------------------------------------------------------------  
复制代码


Q: 编写 TCP/SOCK_STREAM 服务程序时,SO_REUSEADDR到底什么意思?
A: 这个套接字选项通知内核,如果端口忙,但TCP状态位于 TIME_WAIT ,可以重用
端口。如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息,
指明"地址已经使用中"。如果你的服务程序停止后想立即重启,而新套接字依旧
使用同一端口,此时 SO_REUSEADDR 选项非常有用。必须意识到,此时任何非期
望数据到达,都可能导致服务程序反应混乱,不过这只是一种可能,事实上很不
可能。

一个套接字由相关五元组构成,协议、本地地址、本地端口、远程地址、远程端
口。SO_REUSEADDR 仅仅表示可以重用本地本地地址、本地端口,整个相关五元组
还是唯一确定的。所以,重启后的服务程序有可能收到非期望数据。必须慎重使
用 SO_REUSEADDR 选项。

Q: 在客户机/服务器编程中(TCP/SOCK_STREAM),如何理解TCP自动机 TIME_WAIT 状
态?


A: W. Richard Stevens <1999年逝世,享年49岁>;

下面我来解释一下 TIME_WAIT 状态,这些在<<Unix Network Programming Vol I>;>;
中2.6节解释很清楚了。

MSL(最大分段生存期)指明TCP报文在Internet上最长生存时间,每个具体的TCP实现
都必须选择一个确定的MSL值。RFC 1122建议是2分钟,但BSD传统实现采用了30秒。

TIME_WAIT 状态最大保持时间是2 * MSL,也就是1-4分钟。

IP头部有一个TTL,最大值255。尽管TTL的单位不是秒(根本和时间无关),我们仍需
假设,TTL为255的TCP报文在Internet上生存时间不能超过MSL。

TCP报文在传送过程中可能因为路由故障被迫缓冲延迟、选择非最优路径等等,结果
发送方TCP机制开始超时重传。前一个TCP报文可以称为"漫游TCP重复报文",后一个
TCP报文可以称为"超时重传TCP重复报文",作为面向连接的可靠协议,TCP实现必须
正确处理这种重复报文,因为二者可能最终都到达。

一个通常的TCP连接终止可以用图描述如下:


  1. client                     server  
  2.            FIN M  
  3. close  ----------------->;  (被动关闭)  
  4.            ACK M+1  
  5.        <-----------------  
  6.            FIN N  
  7.        <-----------------  close  
  8.            ACK N+1  
  9.        ----------------->;  
复制代码

为什么需要 TIME_WAIT 状态?

假设最终的ACK丢失,server将重发FIN,client必须维护TCP状态信息以便可以重发
最终的ACK,否则会发送RST,结果server认为发生错误。TCP实现必须可靠地终止连
接的两个方向(全双工关闭),client必须进入 TIME_WAIT 状态,因为client可能面
临重发最终ACK的情形。


  1. {  
  2. scz 2001-08-31 13:28  

  3. 先调用close()的一方会进入TIME_WAIT状态  
  4. }  
复制代码


此外,考虑一种情况,TCP实现可能面临先后两个同样的相关五元组。如果前一个连
接处在 TIME_WAIT 状态,而允许另一个拥有相同相关五元组的连接出现,可能处理
TCP报文时,两个连接互相干扰。使用 SO_REUSEADDR 选项就需要考虑这种情况。

为什么 TIME_WAIT 状态需要保持 2MSL 这么长的时间?

如果 TIME_WAIT 状态保持时间不足够长(比如小于2MSL),第一个连接就正常终止了。
第二个拥有相同相关五元组的连接出现,而第一个连接的重复报文到达,干扰了第二
个连接。TCP实现必须防止某个连接的重复报文在连接终止后出现,所以让TIME_WAIT
状态保持时间足够长(2MSL),连接相应方向上的TCP报文要么完全响应完毕,要么被
丢弃。建立第二个连接的时候,不会混淆。

A: 小四 <scz@nsfocus.com>;

在Solaris 7下有内核参数对应 TIME_WAIT 状态保持时间

# ndd -get /dev/tcp tcp_time_wait_interval
240000
# ndd -set /dev/tcp tcp_time_wait_interval 1000

缺省设置是240000ms,也就是4分钟。如果用ndd修改这个值,最小只能设置到1000ms,
也就是1秒。显然内核做了限制,需要Kernel Hacking。

# echo "tcp_param_arr/W 0t0" | adb -kw /dev/ksyms /dev/mem
physmem 3b72
tcp_param_arr: 0x3e8 = 0x0
# ndd -set /dev/tcp tcp_time_wait_interval 0

我不知道这样做有什么灾难性后果,参看<<Unix编程/应用问答中文版>;>;的声明。




Q: TIME_WAIT 状态保持时间为0会有什么灾难性后果?在普遍的现实应用中,好象也 就是服务器不稳定点,不见得有什么灾难性后果吧?  

D: rain@bbs.whnet.edu.cn

Linux 内核源码 /usr/src/linux/include/net/tcp.h 中

#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to successfully
* close the socket, about 60 seconds */

最好不要改为0,改成1。端口分配是从上一次分配的端口号+1开始分配的,所以一般
不会有什么问题。端口分配算法在tcp_ipv4.c中tcp_v4_get_port中。



8.4 结构在优化编译中的对齐问题  

Q: 我正在写一个流模块,其中用到了#pragma pack(),当使用

gcc -D_KERNEL -c abc.c
ld -r -o abc abc.o

编译链接时,一切正常。为了获得64-bit模块,我必须使用Sun Workshop 5.0,
结果导致系统崩溃。访问

http://docs.sun.com/htmlcoll/coll.32.8/iso-8859-1/CPPPG/Pragmas.html#15434

上面说必须在编译链接应用程序的时候指定"-misalign",所以我用了如下命令编译

/opt/SUNWspro/bin/cc -D_KERNEL -misalign -c abc.c
/usr/ccs/bin/ld -r -o abc abc.o

但是我不知道该如何在链接时指定"-misalign"。使用的是"/usr/ccs/bin/ld"。

A: Casper H.S. Dik - Network Security Engineer <Casper.Dik@Holland.Sun.Com>;

"-misalign"仅仅用于应用程序,无法应用到内核编程中。"-misalign"使得编译
获得的代码增加了一些runtime glue,它们将指示内核模拟unaligned load(慢)。
作为内核编程,没有等效技术。

Q: 使用#pragma pack()是因为需要读取来自Windows客户端的报文,对端使用
#pragma pack(1)压缩了所使用的数据结构


  1.    #pragma pack(1)  
  2.    typedef struct pkt_hdr_struct  
  3.    {  
  4.        uint8_t  pkt_ver;  
  5.        uint32_t pkt_type;  
  6.        uint32_t pkt_len;  
  7.    } pkt_hdr_t;  
  8.    #pragma pack()  
复制代码

为了采用这个结构读取网络数据,Solaris端的服务程序需要强制转换匹配该结构,
但是一旦企图读取紧接在pkt_ver成员之后的pkt_type成员,崩溃了。尝试过其他
办法,首先用一个字符指针读取第一个字节,然后指针增一,把该指针强制类型
转换成( uint32_t * ),然后读取数据,依然崩溃。

此外,是否意味着无法在内核模块编程中使用#pragma pack()

A: Ed L Cashin <ecashin@coe.uga.edu>;

我想你可以单独写一个pkt_header_read()函数,单字节读取然后拼装成相应的数
据类型。如果你想避免函数调用,可以使用"inline"关键字。

A: Casper H.S. Dik - Network Security Engineer <Casper.Dik@Holland.Sun.Com>;

你是否意识到pkt_hdr_t结构使得你必须自己转换字节序(对端是x86平台)
我不认为#pragma pack()是最好的解决办法,考虑定义如下结构


  1.   struct phs  
  2.    {  
  3.        char ver;  
  4.        char type[4];  
  5.        char len[4];  
  6.    }  
复制代码


采用memcpy()读取数据

memcpy( &phs.type[0], &pkt.pkt_type, 4 );

A: Andrew Gabriel <andrew@cucumber.demon.co.uk>;

采用字符指针是正确的,但是你犯了个错误,编写如下函数


  1. int read_misaligned_int ( int * iptr )  
  2.    {  
  3.        int    i;  
  4.        int    value;  
  5.        char * ptr  = ( char * )iptr;  
  6.        char * vptr = ( char * )&  

  7.        for ( i = 0; i < sizeof( int ); i++ )  
  8.        {  
  9.            *vptr++ = *ptr++;  
  10.        }  

  11.        return( value );  
  12.    }  
复制代码


此外,既然你提到对端是x86平台,可能还需要考虑字节序转换的问题

A: W. Richard Stevens <1999年逝世,享年49岁>;

  1. /*  
  2. * return value:  
  3. *     1 big-endian  
  4. *     2 little-endian  
  5. *     3 unknow  
  6. *     4 sizeof( short ) != 2  
  7. */  
  8. static int byte_order ( void )  
  9. {  
  10.     union  
  11.     {  
  12.         short s;  
  13.         char  c[ sizeof( short ) ];  
  14.     } un;  

  15.     un.s = 0x0201;  
  16.     if ( 2 == sizeof( short ) )  
  17.     {  
  18.         if ( ( 2 == un.c[0] ) && ( 1 == un.c[1] ) )  
  19.         {  
  20.             puts( "big-endian" );  
  21.             return( 1 );  
  22.         }  
  23.         else if ( ( 1 == un.c[0] ) && ( 2 == un.c[1] ) )  
  24.         {  
  25.             puts( "little-endian" );  
  26.             return( 2 );  
  27.         }  
  28.         else  
  29.         {  
  30.             puts( "unknow" );  
  31.             return( 3 );  
  32.         }  
  33.     }  
  34.     else  
  35.     {  
  36.         puts( "sizeof( short ) = %d", sizeof( short ) );  
  37.         return( 4 );  
  38.     }  
  39.     return( 3 );  
  40. }  /* end of byte_order */  
复制代码


D: CERNET 华中地区网络中心 程序设计版 集体讨论汇总

为了解决Unix自定义结构在GCC优化编译中对齐问题,一般解决办法是用如下宏封装
自定义结构

  1. #pragma pack(1)

  2. struct my_arphdr
  3. {
  4. };

  5. #pragma pack()
复制代码


如果是SPARC/Solaris,还可以这样

  1. struct my_arphdr
  2. {
  3. } __attribute__ ((packed));
复制代码


两种办法其实都可以用在Unix系统/GCC编译器中。

D: mbuf@smth

关于结构中字节对齐问题,相应编译器选项为

GCC/G++ : -fpack-struct
Sun Workshop cc/CC: -misalign

最好不这样做,会大大降低程序效率,特别在某些架构中。应该尝试用位操作来处理。

D: Unknown@smth

GCC可以这么解决

  1. #ifdef __GCC__
  2. #define PACKED __attribute__((__packed__))
  3. #else
  4. #define PACKED
  5. #endif

  6. struct msg
  7. {
  8.         u_int16_t PACKED first;
  9.         ...
  10. };
复制代码


还是 VC 简单,#include <pshpack1.h>; 就搞定了

A: gfh_nuaa

DEC : #pragma pack(1)
SUN : #pragma pack(1)
AIX : 编译时 -q align=packed
HP-UX : #pragma pack 1

D: Joe Durusau

在 Visual C++ 中,使用 "-ZP1" 就可以让编译器对自定义结构进行单字节对齐,实
际就是取消了对齐优化。

A: law@apue.dhs.org 2001-12-20 13:09

1) 结构内部成员的pack

  1. struct foo  
  2. {  
  3.     char a;  
  4.     int  b __attribute__ ((packed));  
  5. };  
复制代码


2) 整个结构的pack


  1. struct foo  
  2. {  
  3.     char a;  
  4.     int  b;  
  5. }__attribute__ ((packed));  
复制代码


3) 文件范围的pack


  1. struct foo  
  2. {  
  3.     char a;  
  4.     int  b;  
  5. };  

  6. ... ...
复制代码


4) 编译选项的pack

-fpack-struct

但这是最危险的做法,因为这样做可能会使库函数和你的程序对结构内成员的偏移理
解不一致。

Q: 小四 <scz@nsfocus.com>;

#pragma pack(push)
#pragma pack(n)
... ...
#pragma pack(pop)

push/pop这个用法都谁支持啊

A: law@apue.dhs.org

这个写法我没见过,VC和GCC都是这样写的

#pragma (push, N) // 把原来align设置压栈,并设新的pack为N
#pragma (pop) // align设置弹栈



8.6 如何得到非局部变量列表

Q: 什么工具可以从目标文件中提取非局部变量列表

A: Donald McLachlan <don@mars.dgrc.crc.ca>;

最简单的就是nm,假设你有一个目标文件(或者已链接过的可执行文件),nm -g将显
示所有"全局"变量。下面是一个Solaris的例子:


  1. --------------------------------------------------------------------------  
  2. /* gcc -o junk junk.c */  

  3. int        var1;  
  4. static int var2;  

  5. int main ( void )  
  6. {  
  7.     int var3;  

  8.     return( 0 );  
  9. }  /* end of main */  
  10. --------------------------------------------------------------------------  

  11. $ nm -g junk  

  12. junk:  

  13. [Index]   Value      Size    Type  Bind  Other Shndx   Name  

  14. [66]    |    133640|       0|OBJT |GLOB |0    |15     |_DYNAMIC  
  15. [61]    |    133496|       0|OBJT |GLOB |0    |13     |_GLOBAL_OFFSET_TABLE_  
  16. [71]    |    133528|       0|OBJT |GLOB |0    |14     |_PROCEDURE_LINKAGE_TABLE_  
  17. [69]    |         0|       0|NOTY |WEAK |0    |UNDEF  |__deregister_frame_info  
  18. [60]    |         0|       0|NOTY |WEAK |0    |UNDEF  |__register_frame_info  
  19. [70]    |    133836|       0|OBJT |GLOB |0    |19     |_edata  
  20. [59]    |    133872|       0|OBJT |GLOB |0    |20     |_end  
  21. [58]    |    133864|       4|OBJT |GLOB |0    |20     |_environ  
  22. [72]    |     67960|       0|OBJT |GLOB |0    |12     |_etext  
  23. [67]    |    133600|       0|FUNC |GLOB |0    |UNDEF  |_exit  
  24. [75]    |     67936|      20|FUNC |GLOB |0    |11     |_fini  
  25. [64]    |     67908|      28|FUNC |GLOB |0    |10     |_init  
  26. [73]    |     67956|       4|OBJT |GLOB |0    |12     |_lib_version  
  27. [57]    |     67380|     116|FUNC |GLOB |0    |9      |_start  
  28. [62]    |    133576|       0|FUNC |GLOB |0    |UNDEF  |atexit  
  29. [68]    |    133864|       4|OBJT |WEAK |0    |20     |environ  
  30. [63]    |    133588|       0|FUNC |GLOB |0    |UNDEF  |exit  
  31. [74]    |     67784|      24|FUNC |GLOB |0    |9      |main  
  32. [65]    |    133868|       4|OBJT |GLOB |0    |20     |var1  
  33. $  
复制代码


注意到var2这样的"静态全局变量",由于仅仅在单个源文件中有效,nm -g并未显示
它。如果不指定-g选项,将显示var2(当然会显示更多垃圾信息)。


  1. $ nm junk  

  2. junk:  

  3. [Index]   Value      Size    Type  Bind  Other Shndx   Name  

  4. ... ...  
  5. [65]    |    133868|       4|OBJT |GLOB |0    |20     |var1  
  6. [46]    |    133860|       4|OBJT |LOCL |0    |20     |var2  
  7. $
复制代码


8.8 如何单独获得Solaris编译环境

Q: 我需要安装哪些包

A: Seán Boran <sean@boran.com>;

需要下列Solaris安装包:
SUNWbtool、SUNWsprot、SUNWtoo、SUNWhea、SUNWarc、SUNWlibm、SUNWlibms

可以用pkginfo [-l]检查是否安装了这些包

$ pkginfo SUNWbtool SUNWsprot SUNWtoo SUNWhea SUNWarc SUNWlibm SUNWlibms
system SUNWarc Archive Libraries
system SUNWbtool CCS tools bundled with SunOS
system SUNWhea SunOS Header Files
system SUNWlibm Sun WorkShop Bundled libm
system SUNWlibms Sun WorkShop Bundled shared libm
system SUNWsprot Solaris Bundled tools
system SUNWtoo Programming Tools
$

可以从Solaris CD中单独安装缺少的包(pkgadd)

象make这样的工具安装在/usr/ccs/bin,增加到$PATH环境变量中。但是这个make和
某些工具相冲突,比如BIND,此时应该安装GNU make,确认GNU make的搜索路径位于
/usr/ccs/bin/make之前。另外,$PATH环境变量中/usr/ccs/bin应该位于/usr/ucb之
前。

8.9 如何获取Solaris内核可调参数列表

Q: 谁有Solaris内核可调参数列表

A: Andrew Garman <andrew_garman@ins.com>;

执行

/usr/xpg4/bin/nm /platform/sun4u/kernel/unix | egrep 'OBJT \|GLOB' | more

显示结果中部分为Solaris内核可调参数,另外一些非可调内核参数。可以用ndd获取、
设置网络相关参数。

D: scz <cloudksy@263.net>;

可以考虑

/usr/ccs/bin/nm -nx /dev/ksyms | egrep 'OBJT \|GLOB' | more

不知道二者区别何在?第二个报告内容应该包含了后来动态加载内核模块输出的符号,
第一个才对应基本内核输出的符号。

8.11 如何页边界对齐式分配内存  
Q: 我希望在页边界上分配大块内存,要求普通用户、非特权进程亦能使用此技术。
在mmap(2)手册页中没有明确表明返回地址边界对齐。它提到可以指定起始地址以
保证页边界对齐,但没有说明如果由系统选定起始地址时是否也是页边界对齐的。
MAP_ANON并非所有系统都支持,我需要在Solaris 2.x上运行。

A: Andrew Gierth <andrew@erlenstar.demon.co.uk>;

mmap(2)即可满足要求。某些系统提供了valloc或者memalign,但它们的实现机制是,
分配超过请求大小的内存,然后调整之,这相当浪费。

mmap(2)应该始终是页边界对齐的。

在那些不支持 MAP_ANON 的系统上,打开/dev/zero获取句柄,传递给mmap(2),效果
是一样的。

mmap(2)的可移植性足够好,不过"分配超过请求大小的内存并调整之"可能更具有可
移植性。
8.13 compile()和step()怎么用
Q: 我知道这两个函数是Solaris对正则表达式的支持函数,可到底怎么用呢?

  1. A: microcat <rotm@263.net>;  

  2. --------------------------------------------------------------------------  
  3. /* gcc -Wall -O3 -o reg reg.c -lgen */  
  4. #include <stdio.h>;  
  5. #include <stdlib.h>;  
  6. #include <regexpr.h>;  

  7. int main ( int argc, char * argv[] )  
  8. {  
  9.     char * expbuf = NULL;  

  10.     if ( ( expbuf = compile( argv[1], NULL, NULL ) ) == NULL )  
  11.     {  
  12.         exit( EXIT_FAILURE );  
  13.     }  
  14.     if ( step( argv[2], expbuf ) )  
  15.     {  
  16.         printf( "Match at: %s\n", loc1 );  
  17.     }  
  18.     else  
  19.     {  
  20.         printf( "No match.\n" );  
  21.     }  
  22.     free( expbuf );  
  23.     exit( EXIT_SUCCESS );  
  24. }  /* end of main */  
  25. --------------------------------------------------------------------------  

  26. $ ./reg '^.*inetd$' '/usr/sbin/inetd'  
  27. Match at: /usr/sbin/inetd  
  28. $  
复制代码

论坛徽章:
0
4 [报告]
发表于 2003-03-09 10:29 |只看该作者

原 C/C++ 论坛 FAQ,尚未整理。

从网上找的,没有整理,,建议大家使用google查找原文

[code]
发信人: Sinbad <MicroBin@263.net>;
标  题: Unix编程/应用问答0.03外发版
发信站: 辛巴达 (Wed Mar 13 11:26:49 2002)

发信人: scz (小四★跨越封锁线⊙), 信区: Security
标  题: Unix编程/应用问答中文版 0.03 2002-03-06 外发版
发信站: UNIX编程 (Wed Mar  6 13:22:08 2002) , 转信

--------------------------------------------------------------------------
名称 -- Unix编程/应用问答中文版
版本 -- 0.03 ( 2002-03-06 外发版 )
维护 -- 小四 <cloudsky@263.net>; or <scz@nsfocus.com>;
主页 -- http://www.nsfocus.com
创建 -- 2001-02-05 13:49
更新 -- 2002-03-03 17:42

感谢 --

    感谢C语言的发明者、Unix操作系统的发明者、感谢全世界C程序员创造的Unix共
    享传统文化圈,她是如此强大、充满禁忌、而又魅力四射。

    感谢我的朋友,deepin <deepin@nsfocus.com>;,在整个维护过程中的支持、帮
    助和鼓励。感谢我所有的NSFocus安全研究小组的朋友(tt、yuange、security@
    nsfocus.com ... ...),是他们容忍我在这个非正业上花费时间。

主要支持人员(字母顺序) --

    Andrew Gierth <andrew@erlenstar.demon.co.uk>;
    backend       <backend@nsfocus.com>;
    Casper Dik    <Casper.Dik@Holland.Sun.COM>;
    deepin        <deepin@nsfocus.com>;
    scz           <scz@nsfocus.com>;
    suxm          <suxm@gnuchina.org>;
    tt            <warning3@nsfocus.com>;

简介 --

    这份文档不是FAQ(Frequently Answered Question),不少问题属于FUQ(Freque-
ntly Unanswered Question)。换句话说,不一定是最常见的编程、应用问答,很可
能其中的答案尚是一个构思,还没有成为现实,又或者根本是个错误的思想火花。但
是,她的确在试图回答一些很有意义的问题,让更多的Unix/C程序员、系统管理员共
享彼此的智慧,那是三十年前无数前辈精英做到过的,也是我们正试图做到的。

    Q -- Question
    A -- Answer
    D -- Discuss

声明 --

    永久拒绝任何商业性质的转载、摘录、引用。在不对所有文字做任何修正的前提
下,允许一切教育性质的转载、摘录、引用,无须提前知会维护者(就是me,faint)。
一旦出现需要修正文字的情况,只能通过维护者修正。维护者会在下一次版本升级过
程中正式增加这种修正,保留提供修正者应有信息。同时意味着提供修正者永久自愿
放弃商业性质的所有权益。不接受这种条件的提供修正者,务必提前知会维护者,此
类修正将不出现在下一次版本升级中。

    文中所附各种源代码,在严格意义上可能存在版权问题,所以事实上这份文档带
有"半地下"性质,使用者务必自己小心卷入此类纠纷。

    文中技术可能涉及未公开的、未文档化的、非规范的编程、应用接口,文档提供
的重在思想,而不保证是正确、高效、唯一的解答。

    维护者不对文中任何技术引起的任何灾难性后果负任何法律上的、道义上的责任。

    Ok, Let's go.

辅助说明 --

    2002-03-06 12:14

    辅助说明只在"外发版"中存在,稍微解释一下。

    一直没有单独出一份完整的,原因很多。如果搁在1995/1996/1997时的CERNET,
    这些原因都不成为原因,现在成为原因。不想多说为什么,明白的自然明白,不
    明白的当我白痴好了,反正别问我。

    出于"声明"中的某些理由,不能在单份完整文档中附带可能会带来麻烦的文字、
    代码,比如Solaris libproc编程接口。但是,在散篇中你能找到它们。如果你
    愿意,可以自己将散篇收回到该文档中,这将与我无关。一切索要残缺部分的邮
    件概不回复。

    本份文档的绝大多数内容在"中国教育科研网华南地区网络中心BBS"(bbs.gznet.
    edu.cn)的Solaris版发布过了,包括下面处理掉的目录列表。是该版前版主CPU
    师兄当年的风范促使我开始整理这份文档的,当还昔日指教之情谊。

    该份文档"允许一切教育性质的自由转载、摘录、引用,无须提前知会维护者"。
    我也只是义务维护一下,不对本文档拥有任何权益。如果不幸潜在拥有而践踏了
    某种信念,在你看到该辅助说明的同时,我将自动放弃这种潜在可能拥有的权益。
    同时意味着一切因本文档带来的麻烦,将由你个人承担。

    既然来自Unix共享传统文化圈,就让它彻底回到Unix共享传统文化圈中去吧。

    欢迎一切建设性的、非索要性质的Email交流。

--------------------------------------------------------------------------

目录

0.    Unix/C传奇问题
0.1   Dennis Ritchie 和 Ken Thompson
0.2   W. Richard Stevens 之死

1.    系统管理配置问题
1.1   如何给SUN工作站增加eeprom硬件口令保护
1.2   如何增加交换空间
1.3   为什么我不能在/home目录下创建子目录
1.4   如何改变一台主机的locale
1.5   Solaris 7自动注销
1.6   一个目录拥有setgid设置,怎么理解
1.7   非Sun Console上有无等价Stop-A的按键
1.8   如何让一个用户只能ftp而无法telnet
1.9
1.10  为什么Sun工作站非要输入boot命令才能启动
1.11  如何让Solaris识别新增加的硬件
1.12

2.    堆栈相关问题
2.1   如何理解pstack的输出信息
2.2   
2.3   Solaris中如何获取一个C程序的调用栈回溯
2.4   如何编程获取栈底地址
2.5   如何得到一个运行中进程的内存映像
2.6   调试器如何工作的
2.7   x86/Linux上如何处理SIGFPE信号

3.    -lelf、-lkvm、-lkstat相关问题
3.1   如何判断可执行文件是否携带了调试信息
3.2   mprotect如何用
3.3   mmap如何用
3.4   getrusage如何用
3.5   setitimer如何用

4.    系统资源相关问题
4.1   主流Unix操作系统上如何编程获取进程的内存、CPU利用状况
4.2   Solaris下如何获知CPU速率
4.3   如何编程获取Solaris系统当前内存大小

5.    块设备相关问题
5.1   CDROM设备究竟在哪里
5.2   如何弹出光驱
5.3   如何利用超级块进行恢复工作
5.4   Solaris Root口令忘记了
5.5   如何使用fmthard
5.6   如何从光盘恢复Solaris 7的引导扇区
5.7   Solaris支持类似微软autorun.inf文件的功能吗
5.8   如何修改/dev/null的属性
5.9   
5.10  如何自己制作Solaris启动软盘
5.11  x86/Solaris如何访问FAT32分区

6.    /etc/system可调资源限制
6.1   Solaris下如何限制每个用户可拥有的最大进程数
6.2   如何配置系统使之支持更多的伪终端
6.3   如何增加每个进程可打开文件句柄数
6.4
6.5   做了setuid()这类调用的程序如何产生core dump
6.6   消息队列调整

7.    DNS相关问题
7.1   如何进行DNS区传输
7.2   如何获知权威名字服务器
7.3   如何配置DNS的委托解析
7.4   如何获知BIND的版本号
7.5   Solaris/FreeBSD/Linux如何指定域名解析的顺序

8.    Solaris内核编程相关问题
8.1   Solaris内核模块中如何getcwd
8.2   
8.3   如何避免一个套接字进入TIME_WAIT状态
8.4   结构在优化编译中的对齐问题
8.5   
8.6   如何得到非局部变量列表
8.7   
8.8   如何单独获得Solaris编译环境
8.9   如何获取Solaris内核可调参数列表
8.10   
8.11  如何页边界对齐式分配内存
8.12   
8.13  compile()和step()怎么用
8.14   
8.15   
8.16   
8.17   

9.    图形界面相关问题
9.1   如何避免进入Solaris的图形界面
9.2   Solaris 7的锁屏
9.3   如何调整键盘重复率
9.4   如何拔掉键盘继续运行Solaris
9.5   Solaris下如何设置显卡分辨率
9.6   Solaris下如何设置显示刷新率

10.   网卡相关问题
10.1  如何在程序中获取本机MAC地址
10.2  如何在Sun工作站上安装3块网卡
10.3  如何在Solaris x86上安装网卡驱动
10.4  Solaris 单网卡多IP(以太网卡别名)
10.5  如何修改主机名(hostname)
10.6  SPARC/Solaris 2.5/2.6/7/8下如何设置网卡100Mb全双工
10.7  Unix/Linux/BSD如何对抗ARP欺骗攻击
10.8   
10.9   
10.10
10.11 x86/Solaris如何强制设定网卡速率
10.12 Solaris/FreeBSD/Linux如何确定网卡Capability/Speed
10.13
10.14 traceroute是怎么实现的

11.   package相关问题
11.1  Solaris下如何将二进制软件包安装到指定目标路径下
11.2  Solaris下如何自己定制二进制安装包
11.3  如何恢复/usr/bin/su的缺省安装属性
11.4  如何获知指定包与其他包之间的依赖关系
11.5  Linux中如何知道ifconfig属于哪个包
11.6  Solaris下如何知道某包中有哪些文件

12.   日志相关问题
12.1   
12.2   
12.3  如何关闭cron的日志
12.4   

13.   进程相关问题
13.1  如何根据进程名获得PID
13.2   
13.3   
13.4  Solaris 7/8下ps输出中的问号
13.5   
13.6   
13.7  给定一个PID,如何知道它对应一个运行中的进程
13.8  Unix/Linux编程中所谓"僵尸进程"指什么
13.9  x86/FreeBSD 4.3-RELEASE的ptrace(2)手册页
13.10 Solaris下如何知道哪个进程使用了哪个端口
13.11 x86/FreeBSD如何快速获取指定用户拥有的进程数

14.   一些小工具的使用
14.1   
14.2   
14.3  只在本地文件系统上查找
14.4   

15.   32-bit/64-bit相关问题
15.1  Solaris下如何识别当前内核版本
15.2  如何启动Solaris 32-bit/64-bit内核
15.3  gcc支持64-bit编译吗
15.4  Solaris启动时内核文件找不到了
15.5  64-bit驱动程序无法在8下关联,但在7下工作正常

16.   库相关问题
16.1  在Solaris 7下编写网络程序需要链接哪些库
16.2   
16.3  链接过程中库的顺序
16.4   
16.5   
16.6  /usr/lib/ld.so.1损坏或丢失
16.7   
16.8   
16.9  Solaris 8下如何配置运行时链接环境

17.   文件查看问题
17.1  如何直接查看man文件
17.2  .tex文件怎么读
17.3  Solaris下怎么看.ps文件

18.   补丁相关问题
18.1  如何根据补丁号从Sun主站下载补丁
18.2   
18.3   
18.4  给Solaris 2.6安装推荐补丁集
18.5  已知补丁号,如何最快判断系统中是否已经安装该补丁
18.6  如何安装补丁

19.   终端相关问题
19.1  如何使Backspace键做删除操作,而不是显示^H
19.2   
19.3  如何清空stdin的缓冲
19.4  Linux Console下一按错键就叫,怎么关

20.   shell script问题
20.1  如何获取一个字符串的长度
20.2  读超时自动使用缺省值
20.3   
20.4  BASH中如何得到一个字符串的子串
20.5   
20.6   
20.7   
20.8  使用tr命令加密文件
20.9  有哪些命令用于查找定位
20.10
20.11 如何将大写文件名转换为小写文件名

21.   FreeBSD相关问题
21.1   
21.2  如何将一个512字节的文件写入主引导扇区
21.3   
21.4   
21.5   
21.6  x86/FreeBSD 4.x下不能cp覆盖/kernel
21.7  x86/FreeBSD下如何设置路由
21.8   
21.9  什么是locale
21.10 用cvsup安装vim
21.11 FreeBSD下vi输入中文会显示\x??\x??
21.12
21.13
21.14
21.15 UDMA ICRC error是什么意思
21.16 Limiting closed port RST response什么意思
21.17
21.18
21.19
21.20

22.   Linux Kernel Programming
22.1  直接访问内存[显存]地址
22.2   

23.   Linux相关问题
23.1   

--------------------------------------------------------------------------

0. Unix/C传奇问题

0.1 Dennis Ritchie 和 Ken Thompson

Q: 我想知道他们,为什么大家不断提到这两个名字?

A: All of Unix Programmers

我们也想知道,

1969年Dennis Ritchie 和 Ken Thompson在贝尔实验室创造性地发明了Unix操作系统,
为此1983年他们获得了图灵奖。

尽管Ritchie是C程序设计语言的发明者,但是他最喜欢的编程语言是Alef。而
Thompson是一名业余飞行员,曾到莫斯科驾驶过米格-29。

欢迎访问

http://cm.bell-labs.com/who/dmr/
http://cm.bell-labs.com/who/ken/

0.2 W. Richard Stevens 之死

Q: David Johns <odin@gte.net>;

我是他的崇拜者,用www.google.com搜索他的讣告,但这份讣告没有提及死因,有人
知道吗?

真地仅仅是英年早逝吗?

A: Nithyanandham <m.nithyanandham@blr.spcnl.co.in>;

他死于1999/09/01,家人不想让别人知道死因。讣告位于

http://www.azstarnet.com/clips/richard_stevens.html

A: joe broz <jbroz@transarc.ibm.com>;

似乎是一场攀岩事故,或者滑雪事故,我不确认。

1. 系统管理配置问题

1.1 如何给SUN工作站增加eeprom硬件口令保护

A: scz <scz@nsfocus.com>;

man -s 1M eeprom了解细节,要求当前是root身份

# /usr/sbin/eeprom (显示当前eeprom配置)

# /usr/sbin/eeprom security-mode=full ( 可选的有command, full, none)

此时进入交互式设置口令过程,总共输入两次,如果两次口令输入不一致,则本次设
置作废。成功设置之后除了go命令之外的其他ok状态下命令均需要口令,包括boot命
令。

设置成command时,同样进入交互式口令输入过程。此时,除了boot和go命令之外的
其他ok状态下命令均需要口令。注意,如果仅仅输入boot命令,不需要口令,一旦
boot命令后面带了参数,比如boot cdrom -s,同样需要输入口令。

如果设置成none(缺省设置),表示去掉这种口令保护。

# /usr/sbin/eeprom security-password= (等号后面无其他字符,直接回车)

如果想改变前面设置的口令,用这条命令,同样是交互式输入过程。

# /usr/sbin/eeprom security-#badlogins=3 (缺省是0)

设置口令输入尝试次数。

警告:如果设置了eeprom硬件保护口令而又忘记,会带来很多麻烦,务必小心。

一个可行的设置办法是,安全模式设置到command而不是full,这样至少可以正常启
动系统。于是只要记得root口令或者还有其他机会获得root权限(缓冲区溢出?),就
可以通过设置安全模式为none而挽救回来。

但是如果设置成full模式却忘记了eeprom口令,我想首先应该打电话给SUN的技术支
持。如果出于某种理由你不想这样做,我不确认eeprom是否可以热插拔,先用一个无
口令保护的eeprom启动系统,然后热插拔换上那个有口令保护的eeprom,然后用root
权限抹去eeprom口令。

1.2 如何增加交换空间

A: WT <wt@server.domain.top>;

你无法改变分区大小,但是可以增加/删除交换文件,效果类似交换分区。下列命令
在根目录下创建一个500MB的交换文件,名为swapfile

# mkfile 500m /swapfile

下列命令将使之生效

# swap -a /swapfile

现在你有了额外的500MB交换空间,为了每次重启后依旧有效,编辑/etc/vfstab文件
增加如下行

/swapfile - - swap - no -

# swap -l

这里"-l"意味着"list",显示所有交换空间。仔细阅读"swap"和"mkfile"的手册页。

1.3 为什么我不能在/home目录下创建子目录

Q: Solaris 7下,root身份,当我试图在/home目录下创建子目录时,系统拒绝,为
   什么?

A: mohansundarraj

如果/etc/rc2.d/S74autofs脚本中automount(1M)守护进程已经mount了/home,就是
这种现象,而这还是缺省安装后的情形。可以

# /etc/init.d/autofs stop
# umount /home

然后你就可以用root身份在/home下创建子目录,增加文件了。为了永久取消autofs
特性,可以将/etc/rc2.d/S74autofs脚本改名,并注释掉/etc/auto_home、
/etc/auto_master两个文件中的入口点。

SPARC/Solaris的缺省用户主目录是/export/home,而不是/home。

1.4 如何改变一台主机的locale

Q: 一台SPARC/Solaris 8运行在US locale环境中,现在我们想让它运行在
   IE(Ireland) locale环境中,以便可以使用欧洲日期格式,怎么办?

A: Sharad Ramachandran <estancio@hotmail.com>;

运行sys-unconfig,在此之前请man -s 1M sys-unconfig,

A: chad schrock <chad@radix.net>;

天啊,为了拍死一只苍蝇,你要引爆原子弹吗?

只需要做如下操作,在你的.cshrc/.profile/.bashrc等启动脚本中设置$LANG环境变
量的值为en_UK,注销,重新登录即可。为了使这个设置全局有效,修改
/etc/default/init文件,LANG=en_UK,重启动。

--------------------------------------------------------------------------
# @(#)init.dfl 1.2 92/11/26
#
# This file is /etc/default/init.  /etc/TIMEZONE is a symlink to this file.
# This file looks like a shell script, but it is not.  To maintain
# compatibility with old versions of /etc/TIMEZONE, some shell constructs
# (i.e., export commands) are allowed in this file, but are ignored.
#
# Lines of this file should be of the form VAR=value, where VAR is one of
# TZ, LANG, or any of the LC_* environment variables.
#
TZ=GMT+8
LANG=zh.GBK
--------------------------------------------------------------------------

参看locale(1)和locale(5),了解更多关于locale的信息。运行"locale -a",查看
当前系统所支持的所有locale。

A: Sun Microsystems 2001-06-12

有三种方式改变locale。首先用"locale -a"命令确认系统中已安装的locale

1) 从CDE登录屏幕上修改locale

选择 options ->; languages ->; choose the new locale

注意,如果登录用户的初始化文件中有不同的locale设置,将优先于系统全局locale
设置。

2) 临时设置locale(shell相关的)

ksh : LANG=<locale>;
sh  : LANG=<locale>;
      export LANG
csh : setenv LANG <locale>;
bash: export LANG=en_US(zh.GBK)

3) vi /etc/default/init

增加如下内容

LANG=<locale>;
LC_ALL=<locale>;

重启系统。

运行"locale"命令确认改变生效。

如果你希望使用的locale并未安装,参看如下文档安装locale

Solaris 8  : <<International Language Environments Guide>;>;

Solaris 7  : <<Solaris Internationalization Guide For Developers>;>;

Solaris 2.6: <<Solaris Internationalization Guide for Developers>;>;

D: scz <scz@nsfocus.com>; 1998-08

SPARC/Solaris 2.5下,为了在vi中正确看到中文需要设置环境变量

sh

LANG=C;export LANG
LC_CTYPE=iso_8859_1;export LC_CTYPE

csh

setenv LANG zh

关于设置LANG这个环境变量涉及到/usr/lib/locale下的目录权限。

1.5 Solaris 7自动注销

Q: 怎样设置才能30秒后自动注销

A: shridhara

不幸的是,Solaris对此没有什么好的支持。如果正在使用telnet会话,或许可以考
虑"logout"变量,参看telnet的手册页。一个变通的办法,使用K-Shell,它支持
TMOUT变量,用于指定非活动时限(以秒为单位)。比如,如果一个shell会话3分钟内
不活动,则终止这个shell会话

$ TMOUT=180;export TMOUT

可以在用户的.profile文件中放置该行。缺点是你只能使用ksh。

D: scz <scz@nsfocus.com>;

vi /etc/default/login

# TIMEOUT sets the number of seconds (between 0 and 900) to wait before
# abandoning a login session.
#
TIMEOUT=180

这里的超时设置针对登录过程,而不是登录成功后的shell会话超时设置。

1.6 一个目录拥有setgid设置,怎么理解

Q: 对一个目录做了setgid设置,可我并没有发现这和正常情况有什么区别

A: John Riddoch <jr@scms.rgu.ac.uk>;

在这种目录下创建新文件时将采用setgid设置对应的属组,比如

$ ls -ld b
drwxrws---   2 jr       group     512 Mar 14 17:13 b/
$ touch b/a
$ ls -l b/a
-rw-------   1 jr       group       0 Mar 14 17:13 b/a
$ id
uid=178(jr) gid=10(staff)

jr的缺省组是staff,而现在b/a文件属组是group。

D: 小四 <scz@nsfocus.com>;

SPARC/Solaris 7下测试

如果目录拥有SGID设置,那么该目录下新创建的文件将继承该目录的属组,而不是创
建者所对应的GID。

[root@ /export/home/scz]>; id   
uid=0(root) gid=1(other)  <-- 注意当前用户的属组
[root@ /export/home/scz]>; mkdir groupsgid
[root@ /export/home/scz]>; ls -ld groupsgid
drwxr-xr-x root other groupsgid/
[root@ /export/home/scz]>; chown scz:users groupsgid
[root@ /export/home/scz]>; chmod g+s groupsgid
[root@ /export/home/scz]>; ls -ld groupsgid
drwxr-sr-x scz users groupsgid/  <-- 目录拥有SGID设置
[root@ /export/home/scz]>; cd groupsgid/
[root@ /export/home/scz/groupsgid]>; touch scz_0
[root@ /export/home/scz/groupsgid]>; ls -l scz_0
-rw-r--r-- root users scz_0  <-- 注意属组变化
[root@ /export/home/scz/groupsgid]>; chmod g-s ../groupsgid/
[root@ /export/home/scz/groupsgid]>; ls -ld ../groupsgid/
drwxr-xr-x scz users ../groupsgid/
[root@ /export/home/scz/groupsgid]>; touch scz_1
[root@ /export/home/scz/groupsgid]>; ls -l scz_1
-rw-r--r-- root other scz_1  <-- 注意属组变化
[root@ /export/home/scz/groupsgid]>;

1.7 非Sun Console上有无等价Stop-A的按键

A: neomilev

如果是便携机,尝试alt/break 或者 ctrl/break。如果是vt100终端,尝试F11 或者
break

1.8 如何让一个用户只能ftp而无法telnet

A: 小四 <cloudsky@263.net>;

修改该用户在/etc/passwd中的shell为/bin/false,在/etc/shells文件中增加
/bin/false,此时,该用户只能ftp,telnet失败。

1.10 为什么Sun工作站非要输入boot命令才能启动

Q: 我有台Sun工作站,每次开机后停在ok状态下,需要手工输入boot命令才能启动,
   现在想避免这种效果,怎么办

A: /usr/sbin/eeprom auto-boot?=true
   /usr/sbin/eeprom auto-boot?  <-- 查询

A: dengdai@SMTH

进入OBP状态

ok setenv auto-boot? true
ok setenv boot-device disk

反之

ok setenv auto-boot? false

1.11 如何让Solaris识别新增加的硬件

Q: 比如新增加了网卡、硬盘、光驱什么的,如何让Solaris意识到这种增加

A: spp(低音炮) & suxm <suxm@gnuchina.org>;

有三种办法

a. Stop-A进入OBP状态,输入boot -r
b. sync(重复);reboot -- -r
c. touch /reconfigure;sync(重复);reboot

参看reboot(1M)、boot(1M)、eeprom(1M)、kernel(1M)、cfgadm(1M)、psradm(1M)手
册页

Q: 我新增加了一块硬盘,不想boot -r而立即生效,怎么办

A: 老大 <willxu@public.cs.hn.cn>; 2001-12-04 16:51

直接将第二块硬盘接上去,然后顺序执行如下命令,不用重新启动机器

modunload -i 0
drvconfig(1M)
devlinks(1M)
disks(1M)

如果需要重新格式化、分区、创建文件系统,就继续执行

format(1M)
newfs(1M)

2. 堆栈相关问题

2.1 如何理解pstack的输出信息

Q: 080603a7 main    (1, 80479b8, 80479c0)  + d53
   结尾的d53是什么

A: Roger A. Faulkner <raf@sunraf.Sun.COM>;

在代码段绝对地址0x080603a7处,main()调用了一个函数,0x080603a7正是
main + 0xd53,换句话说,从main()函数开始的0xd53偏移处。

2.3 Solaris中如何获取一个C程序的调用栈回溯

Q: 我想在Solaris 2.6极其后续版本上获取一个C程序的调用栈回溯,类似如下输出

   (10)  0x00045e08  integ + 0x408    [./two_brn.e]
   (11)  0x0006468c  trajcem + 0x128  [./two_brn.e]
   (12)  0x00055490  fly_traj + 0xf58 [./two_brn.e]
   (13)  0x0004052c  top_level + 0x14 [./two_brn.e]
   (14)  0x000567e4  _start + 0x34    [./two_brn.e]

   这样我就可以知道当程序崩溃、死锁的时候代码执行到了何处。在HP-UX和IRIX上
   可以利用U_STACK_TRACE()和trace_back_stack_and_print(),Solaris上呢?

Q: 有没有办法显示当前堆栈中的数据(GNU/Linux系统)?我希望自己的异常处理程序
   在进程结束前dump整个栈区(stack),以便观察到栈顶是什么函数。对于调试意想
   不到的运行时错误而言,这很重要。

A: Bjorn Reese <breese@mail1.stofanet.dk>;

   用/usr/proc/bin/pstack [-F] <pid ...>;

   参看这个例子代码,http://home1.stofanet.dk/breese/debug/debug.tar.gz

Q: is there a way to access call stack information at run time from within
   a program?  i've been maintaining my own crude stack using __FUNCTION__
   and linked lists but can't help but think there's gotta be a better
   way...

A: Nate Eldredge <neldredge@hmc.edu>;

   这依赖于你的系统,如果使用glibc 2.1或更新版本,可以使用backtrace()函数,
   参看<execinfo.h>;,其他系统可能有不同的技术支持。

   注意,你所使用的办法可能是唯一能够保证跨平台使用的

A: Andrew Gabriel <andrew@cucumber.demon.co.uk>; Consultant Software Engineer

   下面是一个backtrace()的应用举例,如果你使用Solaris 2.4及其后续版本,那
   么这个例子可以很好的工作。很可能无法工作在64-bit模式下,我没有尝试过,
   好像Solaris 7已经提供了一个类似的演示程序。还可以增加某些功能,我没有时
   间了。

/*
* Produce a stack trace for Solaris systems.
*
* Copyright (C) 1995-1998 Andrew Gabriel <andrew@cucumber.demon.co.uk>;
* Parts derived from Usenet postings of Bart Smaalders and Casper Dik.
*
*/

/* ......................................................................... */

#include <setjmp.h>;
#include <sys/types.h>;
#include <sys/reg.h>;
#include <sys/frame.h>;
#include <dlfcn.h>;
#include <errno.h>;
#include <unistd.h>;
#include <stdio.h>;

#if defined(sparc) || defined(__sparc)
#define FLUSHWIN() asm("ta 3";
#define FRAME_PTR_INDEX 1
#define SKIP_FRAMES 0
#endif

#if defined(i386) || defined(__i386)
#define FLUSHWIN()
#define FRAME_PTR_INDEX 3
#define SKIP_FRAMES 1
#endif

#if defined(ppc) || defined(__ppc)
#define FLUSHWIN()
#define FRAME_PTR_INDEX 0
#define SKIP_FRAMES 2
#endif

/* ......................................................................... */

static void print_address ( void * pc )
{
    Dl_info info;

    if ( dladdr( pc, &info ) == 0 )
    {
        /* not found */
        fprintf( stderr, "***  %s:0x%x\n", "??", ( unsigned int )pc );
    }
    else
    {
        /* found */
        fprintf( stderr, "***  %s:%s+0x%x\n", info.dli_fname, info.dli_sname,
                 ( unsigned int )pc - ( unsigned int )info.dli_saddr );
    }
    return;
}  /* end of print_address */

/* ......................................................................... */

static int validaddr ( void * addr )
{
    static long pagemask = -1;
    char        c;

    if ( pagemask == -1 )
    {
        pagemask = ~( sysconf( _SC_PAGESIZE ) - 1 );
    }
    addr = ( void * )( ( long )addr & pagemask );
    if ( mincore( ( char * )addr, 1, &c ) == -1 && errno == ENOMEM )
    {
        return 0;  /* invalid */
    }
    else
    {
        return 1;  /* valid */
    }
}  /* end of validaddr */

/* ......................................................................... */

/*
* this function walks up call stack, calling print_addess
* once for each stack frame, passing the pc as the argument.
*/

static void print_stack ( void )
{
    struct frame * sp;
    jmp_buf        env;
    int            i;
    int *          iptr;

    FLUSHWIN();

    setjmp( env );
    iptr = ( int * )env;

    sp = ( struct frame * )iptr[ FRAME_PTR_INDEX ];

    for ( i = 0; i < SKIP_FRAMES && sp; i++ )
    {
        if ( !validaddr( sp ) || !validaddr( &sp->;fr_savpc ) )
        {
            fprintf( stderr, "***[stack pointer corrupt]\n" );
            return;
        }
        sp = ( struct frame * )sp->;fr_savfp;
    }

    i = 100;  /* looping check */

    while ( validaddr( sp ) && validaddr( &sp->;fr_savpc ) && sp->;fr_savpc && --i
)
    {
         print_address( ( void * )sp->;fr_savpc );
         sp = ( struct frame * )sp->;fr_savfp;
    }
}  /* end of print_stack */

/* ......................................................................... */

void backtrace( void )
{
    fprintf( stderr, "***backtrace...\n" );
    print_stack();
    fprintf( stderr, "***backtrace ends\n" );
}

/* ......................................................................... */

2.4 如何编程获取栈底地址

Q: 虽然很多操作系统的用户进程栈底地址固定,但是我需要写一个可广泛移植C程序
   获取这个栈底地址。

A: tt <warning3@nsfocus.com>; 2001-06-02 19:40

假设堆栈(stack)向低地址方向增长,则所谓栈底指堆栈(stack)最高地址

x86/Linux         栈底是0xc0000000( 栈底往低地址的4个字节总是零 )
SPARC/Solaris 7/8 栈底是0xffbf0000( 栈底往低地址的4个字节总是零 )
SPARC/Solaris 2.6 栈底是0xf0000000( 栈底往低地址的4个字节总是零 )
x86/FreeBSD       栈底是0xbfc00000( 栈底往低地址的4个字节总是零 )
x86/NetBSD 1.5    栈底是0xbfbfe000
x86/OpenBSD 2.8   栈底是0xdfbfe000

D: jonah

对于NetBSD 1.5,栈底是0xbfc00000。根据源码,最高用户地址是0xbfbfe000,因为
最后4MB(2^22)的最后两页(0x2000字节,一页4096字节)保留用做U区,但是目前不再
使用这块内存。因此,0xbfbfe000才是真正的栈底。

tt在OpenBSD 2.8上测试结果,栈底是0xdfbfe000,注意和NetBSD 1.5相差很大。

A: tt <warning3@nsfocus.com>;

--------------------------------------------------------------------------
/*
* gcc -Wall -O3 -o gstack gstack.c
*
* A simple example to get the current stack bottom address
* warning3 <warning3@nsfocus.com>;
* 2001-06-01
*
* Modified by scz <scz@nsfocus.com>;
* 2001-06-02
*/

#include <stdio.h>;
#include <stdlib.h>;
#include <signal.h>;
#include <unistd.h>;
#include <setjmp.h>;

typedef void Sigfunc ( int );  /* for signal handlers */

       Sigfunc * signal           ( int signo, Sigfunc * func );
static Sigfunc * Signal           ( int signo, Sigfunc * func );
static char    * get_stack_bottom ( void );
static void      segfault         ( int signo );

static sigjmp_buf             jmpbuf;
static volatile sig_atomic_t  canjump = 0;
static Sigfunc               *seg_handler;
static Sigfunc               *bus_handler;  /* for xxxBSD */

Sigfunc * signal ( int signo, Sigfunc * func )
{
    struct sigaction act, oact;

    act.sa_handler = func;
    sigemptyset( &act.sa_mask );
    act.sa_flags   = 0;
    if ( sigaction( signo, &act, &oact ) < 0 )
    {
        return( SIG_ERR );
    }
    return( oact.sa_handler );
}  /* end of signal */

static Sigfunc * Signal ( int signo, Sigfunc * func )  /* for our signal() funct
ion */
{
    Sigfunc * sigfunc;

    if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )
    {
        exit( EXIT_FAILURE );
    }
    return( sigfunc );
}  /* end of Signal */

static char * get_stack_bottom ( void )
{
    volatile char *c;  /* for autovar, must be volatile */

    seg_handler = Signal( SIGSEGV, segfault );
    bus_handler = Signal( SIGBUS, segfault );
    c           = ( char * )&c;

    if ( sigsetjmp( jmpbuf, 1 ) != 0 )
    {
        Signal( SIGSEGV, seg_handler );
        Signal( SIGBUS, bus_handler );
        return( ( char * )c );
    }
    canjump = 1;  /* now sigsetjump() is OK */
    while ( 1 )
    {
        *c = *c;
        c++;
    }
    return( NULL );
}  /* end of get_stack_bottom */

static void segfault ( int signo )
{
    if ( canjump == 0 )
    {
        return;  /* unexpected signal, ignore */
    }
    canjump = 0;
    siglongjmp( jmpbuf, signo );  /* jump back to main, don't return */
}  /* end of segfault */

int main ( int argc, char * argv[] )
{
    fprintf( stderr, "Current stack bottom is at 0x%p\n", get_stack_bottom() );
    return( EXIT_SUCCESS );
}  /* end of main */
--------------------------------------------------------------------------

D: scz <scz@nsfocus.com>; 2001-06-03 00:38

W. Richard Stevens在<<Advanced Programming in the UNIX Environment>;>;中详细
介绍了setjmp/longjmp以及sigsetjmp/siglongjmp函数。

这个程序的原理很简单,不断向栈底方向取值,越过栈底的地址访问会导致SIGSEGV
信号,然后利用长跳转回到主流程报告当前c值,自然对应栈底。

tt测试表明,在x86/FreeBSD中导致SIGBUS信号。据jonah报告,不仅仅是FreeBSD,
NetBSD 以及 OpenBSD 系统中上述程序越界访问也导致SIGBUS信号,而不是SIGSEGV
信号。

非局部转移,比如函数间转移的时候考虑使用setjmp/longjmp。但是如果涉及到信号
句柄与主流程之间的转移,就不能使用longjmp了。当捕捉到信号进入信号句柄,此
时当前信号被自动加入进程的信号屏蔽字中,阻止后来产生的这种信号干扰此信号句
柄。如果用longjmp跳出信号句柄,此时进程的信号屏蔽字状态未知,有些系统做了
保存恢复,有些系统没有做。根据POSIX.1,此时应该使用sigsetjmp/siglongjmp函
数。下面来自SPARC/Solaris 7的setjmp(3C)

--------------------------------------------------------------------------
#include <setjmp.h>;

int  setjmp     ( jmp_buf env );
int  sigsetjmp  ( sigjmp_buf env, int savemask );
void longjmp    ( jmp_buf env, int val );
void siglongjmp ( sigjmp_buf env, int val );
--------------------------------------------------------------------------

如果savemask非0,sigsetjmp在env中保存进程当前信号屏蔽字,相应siglongjmp回
来的时候从env中恢复信号屏蔽字。

数据类型sig_atomic_t由ANSI C定义,在写时不会被中断。它意味着这种变量在具有
虚存的系统上不会跨越页边界,可以用一条机器指令对其存取。这种类型的变量总是
与ANSI类型修饰符volatile一并出现,防止编译器优化带来的不确定状态。

在longjmp/siglongjmp中,全局、静态变量保持不变,声明为volatile的自动变量也
保持不变。

无论是否使用了编译优化开关,为了保证广泛兼容性,都应该在get_stack_bottom()
中声明c为volatile变量。

注意这里,必须使用长跳转,而不能从信号句柄中直接返回。因为导致信号SIGSEGV、
SIGBUS分发的语句始终存在,直接从信号句柄中返回主流程,将回到引发信号的原指
令处,而不是下一条指令(把这种情况理解成异常,而不是中断),于是立即导致下一
次信号分发,出现广义上的死循环,所谓程序僵住。可以简单修改上述程序,不利用
长跳转,简单对一个全局变量做判断决定是否继续循环递增c,程序最终僵住;如果
在信号句柄中输出调试信息,很容易发现这个广义上的无限循环。

D: scz <scz@nsfocus.com>; 2001-06-03 00:40

在x86/Linux系统中用如下命令可以确定栈区所在

# cat /proc/1/maps  <-- 观察1号进程init
... ...
bfffe000-c0000000 rwxp fffff000 00:00 0
#

在SPARC/Solaris 7中用/usr/proc/bin/pmap命令确定栈区所在

# /usr/proc/bin/pmap 1  <-- 观察1号进程init
... ...
FFBEC000     16K read/write/exec     [ stack ]
#

16KB == 0x4000,0xFFBEC000 + 0x4000 == 0xFFBF0000

与前面tt介绍的

SPARC/Solaris 7/8 栈底是0xffbf0000( 栈底往低地址的4个字节总是零 )

相符合。

此外,在SPARC/Solaris 7下,可以这样验证之

# /usr/ccs/bin/nm -nx /dev/ksyms | grep "|_userlimit"
[7015]  |0x0000100546f8|0x000000000008|OBJT |GLOB |0    |ABS    |_userlimit
[8051]  |0x000010054700|0x000000000008|OBJT |GLOB |0    |ABS    |_userlimit32
# echo "_userlimit /J" | adb -k /dev/ksyms /dev/mem
physmem 3b72
_userlimit:
_userlimit:     ffffffff80000000
# skd64 0x000010054700 8
byteArray [ 8 bytes ] ---->;
0000000000000000  00 00 00 00 FF BF 00 00
#                             ~~~~~~~~~~~ 对于32-bit应用程序来说,这是用户
                                          空间上限

如果编译64-bit应用程序,用户空间上限是_userlimit,也就是0xffffffff80000000

# /opt/SUNWspro/SC5.0/bin/cc -xarch=v9 -O -o gstack gstack.c
# ./gstack
Current stack bottom is at 0xffffffff80000000
#

对于SPARC/Solaris 2.6 32-bit kernel mode

# echo "_userlimit /X" | adb -k /dev/ksyms /dev/mem
physmem 3d24
_userlimit:
_userlimit:     f0000000
#

2.5 如何得到一个运行中进程的内存映像

A: Sun Microsystems 1998-03-30

有些时候必须得到一个运行中进程的内存映像而不能停止该进程,Solaris系统了这
样的工具,gcore为运行中进程创建一个core文件。假设我的bash进程号是5347

# gcore 5347
gcore: core.5347 dumped
# file core.5347
core.5347:      ELF 32-位 MSB core文件 SPARC 版本 1,来自'bash'
#

注意,只能获取属主是你自己的进程的内存映像,除非你是root。

2.6 调试器如何工作的

Q: 我想在一个自己编写的程序中单步运行另外一个程序,换句话说,那是一个调试
   器,该如何做?

A: Erik de Castro Lopo <nospam@mega-nerd.com>;

   这是一个操作系统相关的问题。最一般的回答是使用ptrace()系统调用,尽管我
   不确认究竟这有多么普遍。Linux man手册上说SVr4、SVID EXT、AT&T、X/OPEN
   和BSD 4.3都支持它。

   为了使用ptrace(),你的程序应该调用fork(),然后在子进程中做如下调用:

   ptrace( PTRACE_TRACEME, 0, 0, 0 );

   接下来调用exec()家族的函数执行你最终企图跟踪的程序。

   为了单步进入子进程,在父进程中调用:

   ptrace( PTRACE_SINGLESTEP, 0, 0, 0 );

   还有一些其他函数做恢复/设置寄存器、内存变量一类的工作。

   GDB的源代码足以回答这个问题。

2.7 x86/Linux上如何处理SIGFPE信号

Q: 参看如下程序

--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -o sigfpe_test_0 sigfpe_test_0.c
*
* 注意与下面的编译效果进行对比,去掉优化开关-O3
*
* gcc -Wall -pipe -o sigfpe_test_0 sigfpe_test_0.c
*/

#include <stdio.h>;
#include <stdlib.h>;
#include <string.h>;
#include <signal.h>;
#include <unistd.h>;
#include <setjmp.h>;

/*
* for signal handlers
*/
typedef void Sigfunc ( int );

       Sigfunc * signal ( int signo, Sigfunc *func );
static Sigfunc * Signal ( int signo, Sigfunc *func );
static void      on_fpe ( int signo );

Sigfunc * signal ( int signo, Sigfunc *func )
{
    struct sigaction act, oact;

    act.sa_handler = func;
    sigemptyset( &act.sa_mask );
    act.sa_flags   = 0;
    if ( signo == SIGALRM )
    {
#ifdef  SA_INTERRUPT
        act.sa_flags |= SA_INTERRUPT;  /* SunOS 4.x */
#endif
    }
    else
    {
#ifdef  SA_RESTART
        act.sa_flags |= SA_RESTART;  /* SVR4, 44BSD */
#endif
    }
    if ( sigaction( signo, &act, &oact ) < 0 )
    {
        return( SIG_ERR );
    }
    return( oact.sa_handler );
}  /* end of signal */

static Sigfunc * Signal ( int signo, Sigfunc *func )
{
    Sigfunc *sigfunc;

    if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )
    {
        perror( "signal" );
        exit( EXIT_FAILURE );
    }
    return( sigfunc );
}  /* end of Signal */

static void on_fpe ( int signo )
{
    fprintf( stderr, "here is on_fpe\n" );
    return;
}  /* end of on_fpe */

int main ( int argc, char * argv[] )
{
    unsigned int i;

    Signal( SIGFPE, on_fpe );
    i = 51211314 / 0;
    /*
     * 另外,增加这行后,再次对比有-O3和无-O3的效果
     *
     * fprintf( stderr, "i = %#X\n", i );
     */
    return( EXIT_SUCCESS );
}  /* end of main */
--------------------------------------------------------------------------

有-O3、无-O3,以及有无最后那条fprintf()语句,效果上有差别,自行对比。如果
输出"here is on_fpe",则会发现永不停止。

D: 小四 <scz@nsfocus.com>; 2001-12-14 18:25

为了便于讨论,约定两个名词,中断和异常。这里中断指最常规的中断,比如int指
令带来的软中断。异常的典型代表有除0错。区别在于,发生异常时,x86架构上CPU
将当前EIP(指向引发异常的指令)压栈,发生中断时,x86架构上CPU将当前EIP的后一
个地址(指向引发中断的指令的后一条指令)压栈。在异常处理代码中,如果认为能够
从灾难中恢复,可以不修改被压栈的EIP,从而返回到引发异常的指令处。更多细节
请查看Intel手册。

这些是从前DOS下残留的汇编知识,不过也快忘光了,刚才又找元宝宝确认了一下。

在上述代码中,on_fpe()直接返回了,导致再次触发异常,所以无休止输出。事实上
在所有的计算器处理程序中,都会对SIGFPE信号做相应处理,前些日子看yacc/lex的
时候又碰上过。正确的做法是,利用远跳转转移,让开引发异常的指令。

代码修改如下

--------------------------------------------------------------------------
/*
* gcc -Wall -pipe -O3 -o sigfpe_test_1 sigfpe_test_1.c
*
* 注意与下面的编译效果进行对比,去掉优化开关-O3
*
* gcc -Wall -pipe -o sigfpe_test_1 sigfpe_test_1.c
*/

#include <stdio.h>;
#include <stdlib.h>;
#include <string.h>;
#include <signal.h>;
#include <unistd.h>;
#include <setjmp.h>;

/*
* for signal handlers
*/
typedef void Sigfunc ( int );

       Sigfunc * signal ( int signo, Sigfunc *func );
static Sigfunc * Signal ( int signo, Sigfunc *func );
static void      on_fpe ( int signo );

static sigjmp_buf             jmpbuf;
static volatile sig_atomic_t  canjump = 0;

Sigfunc * signal ( int signo, Sigfunc *func )
{
    struct sigaction act, oact;

    act.sa_handler = func;
    sigemptyset( &act.sa_mask );
    act.sa_flags   = 0;
    if ( signo == SIGALRM )
    {
#ifdef  SA_INTERRUPT
        act.sa_flags |= SA_INTERRUPT;  /* SunOS 4.x */
#endif
    }
    else
    {
#ifdef  SA_RESTART
        act.sa_flags |= SA_RESTART;  /* SVR4, 44BSD */
#endif
    }
    if ( sigaction( signo, &act, &oact ) < 0 )
    {
        return( SIG_ERR );
    }
    return( oact.sa_handler );
}  /* end of signal */

static Sigfunc * Signal ( int signo, Sigfunc *func )
{
    Sigfunc *sigfunc;

    if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )
    {
        perror( "signal" );
        exit( EXIT_FAILURE );
    }
    return( sigfunc );
}  /* end of Signal */

static void on_fpe ( int signo )
{
    if ( canjump == 0 )
    {
        return;  /* unexpected signal, ignore */
    }
    canjump = 0;
    fprintf( stderr, "here is on_fpe\n" );
    siglongjmp( jmpbuf, signo );  /* jump back to main, don't return */
    return;
}  /* end of on_fpe */

int main ( int argc, char * argv[] )
{
    unsigned int i;

    if ( sigsetjmp( jmpbuf, 1 ) != 0 )
    {
        fprintf( stderr, "c u later\n" );
        return( EXIT_SUCCESS );
    }
    /*
     * now sigsetjump() is OK
     */
    canjump = 1;
    Signal( SIGFPE, on_fpe );
    i = 51211314 / 0;
    /*
     * 另外,增加这行后,再次对比有-O3和无-O3的效果
     *
     * fprintf( stderr, "i = %#X\n", i );
     */
    return( EXIT_SUCCESS );
}  /* end of main */
--------------------------------------------------------------------------

关于-O3的讨论,对gcc编译器熟悉的朋友请继续,呵,我对Linux下的这此东西,实
在缺乏兴趣。

3. -lelf、-lkvm、-lkstat相关问题

3.1 如何判断可执行文件是否携带了调试信息

Q: 某些时候需要知道编译可执行文件时是否携带了调试信息(比如是否指定了-g编译
   选项)。检查可执行文件中是否包含".stab" elf section,".stab" section用于
   保存相关调试信息。

A: Sun Microsystems 2000-05-15

下面这个脚本演示如何判断可执行文件是否携带调试信息

--------------------------------------------------------------------------
#! /bin/sh
#
# Script that test whether or not a given file has been built for
# debug (-g option specified in the compilation)

if [ $# -le 0 ]
then
    echo "Usage: $1 filename"
    exit 1
fi

if [ ! -f $1 ]
then
    echo "File $1 does not exist"
    exit 1
fi

/usr/ccs/bin/dump -hv $1 | /bin/egrep -s '.stab$'
if [ $? -eq 0 ]
then
    echo "File '$1' has been built for debug"
    exit 0
else
    echo "File '$1' has not been built for debug"
    exit 1
fi
--------------------------------------------------------------------------

如果对ELF文件格式不熟悉,理解上述代码可能有点困难,参看
http://www.digibel.org/~tompy/hacking/elf.txt,这是1.1版的ELF文件格式规范。

3.2 mprotect如何用

A: 小四 <cloudsky@263.net>;

# truss prtconf 2>;&1 | grep sysconf
sysconfig(_CONFIG_PAGESIZE)                     = 8192
sysconfig(_CONFIG_PHYS_PAGES)                   = 16384
#

由此可知当前系统页尺寸是8192字节。

--------------------------------------------------------------------------
/*
* gcc -Wall -g -ggdb -static -o mtest mtest.c
*/
#include <stdio.h>;
#include <stdlib.h>;
#include <errno.h>;
#include <sys/mman.h>;

int main ( int argc, char * argv[] )
{
    char *buf;
    char  c;

    /*
     * 分配一块内存,拥有缺省的rw-保护
     */
    buf = ( char * )malloc( 1024 + 8191 );
    if ( !buf )
    {
        perror( "malloc" );
        exit( errno );
    }
    /*
     * Align to a multiple of PAGESIZE, assumed to be a power of two
     */
    buf     = ( char * )( ( ( unsigned int )buf + 8191 ) & ~8191 );
    c       = buf[77];
    buf[77] = c;
    printf( "ok\n" );
    /*
     * Mark the buffer read-only.
     *
     * 必须保证这里buf位于页边界上,否则mprotect()失败,报告无效参数
     */
    if ( mprotect( buf, 1024, PROT_READ ) )
    {
        perror( "\nmprotect" );
        exit( errno );
    }
    c       = buf[77];
    /*
     * Write error, program dies on SIGSEGV
     */
    buf[77] = c;

    exit( 0 );
}  /* end of main */
--------------------------------------------------------------------------

$ ./mtest
ok
段错误 (core dumped)  <-- 内存保护起作用了
$

3.3 mmap如何用

A: 小四 <cloudsky@263.net>;

下面写一个完成文件复制功能的小程序,利用mmap(2),而不是标准文件I/O接口。

--------------------------------------------------------------------------
/*
* gcc -Wall -O3 -o copy_mmap copy_mmap.c
*/
#include <stdio.h>;
#include <stdlib.h>;
#include <string.h>;  /* for memcpy */
#include <strings.h>;
#include <sys/mman.h>;
#include <sys/types.h>;
#include <sys/stat.h>;
#include <fcntl.h>;
#include <unistd.h>;

#define PERMS 0600

int main ( int argc, char * argv[] )
{
    int          src, dst;
    void        *sm, *dm;
    struct stat  statbuf;

    if ( argc != 3 )
    {
        fprintf( stderr, " Usage: %s <source>; <target>;\n", argv[0] );
        exit( EXIT_FAILURE );
    }
    if ( ( src = open( argv[1], O_RDONLY ) ) < 0 )
    {
        perror( "open source" );
        exit( EXIT_FAILURE );
    }
    /* 为了完成复制,必须包含读打开,否则mmap()失败 */
    if ( ( dst = open( argv[2], O_RDWR | O_CREAT | O_TRUNC, PERMS ) ) < 0 )
    {
        perror( "open target" );
        exit( EXIT_FAILURE );
    }
    if ( fstat( src, &statbuf ) < 0 )
    {
        perror( "fstat source" );
        exit( EXIT_FAILURE );
    }
    /*
     * 参看前面man手册中的说明,mmap()不能用于扩展文件长度。所以这里必须事
     * 先扩大目标文件长度,准备一个空架子等待复制。
     */
    if ( lseek( dst, statbuf.st_size - 1, SEEK_SET ) < 0 )
    {
        perror( "lseek target" );
        exit( EXIT_FAILURE );
    }
    if ( write( dst, &statbuf, 1 ) != 1 )
    {
        perror( "write target" );
        exit( EXIT_FAILURE );
    }
    /* 读的时候指定 MAP_PRIVATE 即可 */
    sm = mmap( 0, ( size_t )statbuf.st_size, PROT_READ,
               MAP_PRIVATE | MAP_NORESERVE, src, 0 );
    if ( MAP_FAILED == sm )
    {
        perror( "mmap source" );
        exit( EXIT_FAILURE );
    }
    /* 这里必须指定 MAP_SHARED 才可能真正改变静态文件 */
    dm = mmap( 0, ( size_t )statbuf.st_size, PROT_WRITE,
               MAP_SHARED, dst, 0 );
    if ( MAP_FAILED == dm )
    {
        perror( "mmap target" );
        exit( EXIT_FAILURE );
    }
    memcpy( dm, sm, ( size_t )statbuf.st_size );
    /*
     * 可以不要这行代码
     *
     * msync( dm, ( size_t )statbuf.st_size, MS_SYNC );
     */
    return( EXIT_SUCCESS );
}  /* end of main */
--------------------------------------------------------------------------

mmap()好处是处理大文件时速度明显快于标准文件I/O,无论读写,都少了一次用户
空间与内核空间之间的复制过程。操作内存还便于设计、优化算法。

文件I/O操作/proc/self/mem不存在页边界对齐的问题。至少Linux的mmap()的最后一
个形参offset并未强制要求页边界对齐,如果提供的值未对齐,系统自动向上舍入到
页边界上。

malloc()分配得到的地址不见得对齐在页边界上

/proc/self/mem和/dev/kmem不同。root用户打开/dev/kmem就可以在用户空间访问到
内核空间的数据,包括偏移0处的数据,系统提供了这样的支持。

显然代码段经过/proc/self/mem可写映射后已经可写,无须mprotect()介入。

D: scz <scz@nsfocus.com>;

Solaris 2.6下参看getpagesize(3C)手册页,关于如何获取页大小,一般是8192。
Linux下参看getpagesize(2)手册页,一般是4096。

3.4 getrusage如何用

A: 小四 <cloudsky@263.net>;

在SPARC/Solaris 2.6/7下结论一致,只支持了ru_utime和ru_stime成员,其他成员
被设置成0。修改头文件后在FreeBSD 4.3-RELEASE上测试,则不只支持ru_utime和
ru_stime成员。从FreeBSD的getrusage(2)手册页可以看到,这个函数源自4.2 BSD。

如此来说,至少对于SPARC/Solaris 2.6/7,getrusage(3C)并无多大意义。

3.5 setitimer如何用

D: scz <scz@nsfocus.com>;

为什么要学习使用setitimer(2),因为alarm(3)属于被淘汰的定时器技术。

A: 小四 <cloudsky@263.net>;

下面是个x86/FreeBSD 4.3-RELEASE下的例子

--------------------------------------------------------------------------
/*
* File     : timer_sample.c
* Author   : Unknown (Don't ask me anything about this program)
* Complie  : gcc -Wall -pipe -O3 -o timer_sample timer_sample.c
* Platform : x86/FreeBSD 4.3-RELEASE
* Date     : 2001-09-18 15:18
*/

/************************************************************************
*                                                                      *
*                               Head File                              *
*                                                                      *
************************************************************************/

#include <stdio.h>;
#include <stdlib.h>;
#include <sys/time.h>;
#include <signal.h>;

/************************************************************************
*                                                                      *
*                               Macro                                  *
*                                                                      *
************************************************************************/

typedef void Sigfunc ( int );  /* for signal handlers */

/************************************************************************
*                                                                      *
*                            Function Prototype                        *
*                                                                      *
************************************************************************/

static void      Atexit       ( void ( *func ) ( void ) );
static void      init_signal  ( void );
static void      init_timer   ( void );
static void      on_alarm     ( int signo );
static void      on_terminate ( int signo );
static int       Setitimer    ( int which, const struct itimerval *value,
                                struct itimerval *ovalue );
       Sigfunc * signal       ( int signo, Sigfunc *func );
static Sigfunc * Signal       ( int signo, Sigfunc *func );
static void      terminate    ( void );

/************************************************************************
*                                                                      *
*                            Static Global Var                         *
*                                                                      *
************************************************************************/

/************************************************************************/

static void Atexit ( void ( *func ) ( void ) )
{
    if ( atexit( func ) != 0 )
    {
        perror( "atexit" );
        exit( EXIT_FAILURE );
    }
    return;
}  /* end of Atexit */

/*
* 初始化信号句柄
*/
static void init_signal ( void )
{
    int i;

    Atexit( terminate );
    for ( i = 1; i < 9; i++ )
    {
        Signal( i, on_terminate );
    }
    Signal( SIGTERM, on_terminate );
    Signal( SIGALRM, on_alarm );
    return;
}  /* end of init_signal */

static void init_timer ( void )
{
    struct itimerval value;

    value.it_value.tv_sec  = 1;
    value.it_value.tv_usec = 0;
    value.it_interval      = value.it_value;
    Setitimer( ITIMER_REAL, &value, NULL );
}  /* end of init_timer */

static void on_alarm ( int signo )
{
    static int count = 0;

    /*
     * 演示用,这很危险
     */
    fprintf( stderr, "count = %u\n", count++ );
    return;
}

static void on_terminate ( int signo )
{
    /*
     * 这次我们使用atexit()函数
     */
    exit( EXIT_SUCCESS );
}  /* end of on_terminate */

static int Setitimer ( int which, const struct itimerval *value,
                       struct itimerval *ovalue )
{
    int ret;

    if ( ( ret = setitimer( which, value, ovalue ) ) < 0 )
    {
        perror( "setitimer" );
        exit( EXIT_FAILURE );
    }
    return( ret );
}  /* end of Setitimer */

Sigfunc * signal ( int signo, Sigfunc *func )
{
    struct sigaction act, oact;

    act.sa_handler = func;
    sigemptyset( &act.sa_mask );
    act.sa_flags   = 0;
    if ( signo == SIGALRM )
    {
#ifdef  SA_INTERRUPT
        act.sa_flags |= SA_INTERRUPT;  /* SunOS 4.x */
#endif
    }
    else
    {
#ifdef  SA_RESTART
        act.sa_flags |= SA_RESTART;  /* SVR4, 44BSD */
#endif
    }
    if ( sigaction( signo, &act, &oact ) < 0 )
    {
        return( SIG_ERR );
    }
    return( oact.sa_handler );
}  /* end of signal */

static Sigfunc * Signal ( int signo, Sigfunc *func )
{
    Sigfunc *sigfunc;

    if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )
    {
        perror( "signal" );
        exit( EXIT_FAILURE );
    }
    return( sigfunc );
}  /* end of Signal */

static void terminate ( void )
{
    fprintf( stderr, "\n" );
    return;
}  /* end of terminate */

int main ( int arg, char * argv[] )
{
    init_signal();
    init_timer();
    while ( 1 )
    {
        /*
         * 形成阻塞,降低CPU占用率
         */
        getchar();
    }
    return( EXIT_SUCCESS );
}  /* end of main */

/************************************************************************/

--------------------------------------------------------------------------

D: scz <scz@nsfocus.com>;

讨论一个问题。getchar()的作用是降低CPU占用率,可用top命令查看。

timer_sample.c中换用ITIMER_PROF/SIGPROF后,你会发现上述程序无输出,我据此
认为getchar()形成的阻塞不计算在进程虚拟时钟中,也不认为系统正在为进程利益
而运行。

如果进一步将getchar()去掉,直接一个while()无限循环,即使换用
ITIMER_PROF/SIGPROF,程序还是有输出。不过top命令查看的结果让你吐血,CPU几
乎无空闲。

D: scz <scz@nsfocus.com>;

setitimer( ITIMER_REAL, &value, NULL )导致分发SIGALRM信号,如果同时使用
alarm(),势毕造成冲突。此外注意sleep()、pause()等函数带来的冲突。

4. 系统资源相关问题

4.1 主流Unix操作系统上如何编程获取进程的内存、CPU利用状况

Q: Solaris下如何编程获知CPU占用率和内存占用信息呢,可移植吗?

Q: 我想写个程序遍历当前运行中的活动进程,Solaris提供相应系统调用了吗

A: Nicholas Dronen <ndronen@io.frii.com>;

   不可移植。man -s 4 proc,man -s 3k kstat

   如果不是编程,可以用top、mpstat、vmstat、sar(1)等等,还有
   /usr/ucb/ps -aux,对于Solaris来说,后者更直接精炼,top不是标准配置。

   # /usr/bin/prstat (Solaris 8 prstat(1M)手册页)
   # /usr/ucb/ps -aux | head (Solaris 2.x)

Q: 主流Unix操作系统上如何编程获取进程的内存、CPU利用状况,AIX、HP、SUN
   process memory usage
   process cpu time usage

A: Nate Eldredge <neldredge@hmc.edu>;
   man -s 3C getrusage

D: 小四 <cloudsky@263.net>;

在SPARC/Solaris 2.6/7下结论一致,只支持了ru_utime和ru_stime成员,其他成员
被设置成0。FreeBSD 4.3-RELEASE上测试,则不只支持ru_utime和ru_stime成员。从
FreeBSD的getrusage(2)手册页可以看到,这个函数源自4.2 BSD。

至少对于SPARC/Solaris 2.6/7,getrusage(3C)并无多大意义。

A: Robert Owen Thomas <robt@cymru.com>;

对于Solaris,可以利用procfs接口,下面的例子获取指定进程的内存占用情况

--------------------------------------------------------------------------
/*
* @(#)memlook.c 1.0 10 Nov 1997
* Robert Owen Thomas robt@cymru.com
* memlook.c -- A process memory utilization reporting tool.
*
* gcc -Wall -O3 -o memlook memlook.c
*/
#pragma ident "@(#)memlook.c 1.0 10 Nov 1997 Robert Owen Thomas robt@cymru.com"

#include <stdio.h>;
#include <stdlib.h>;
#include <sys/types.h>;
#include <sys/stat.h>;
#include <sys/signal.h>;
#include <sys/syscall.h>;
#include <sys/procfs.h>;
#include <sys/param.h>;
#include <unistd.h>;
#include <fcntl.h>;

int counter = 10;

int  showUsage ( const char * );
void getInfo   ( int, int );

int main ( int argc, char * argv[] )
{
    int  fd, pid, timeloop = 0;
    char pidpath[BUFSIZ];  /* /usr/include/stdio.h: #define BUFSIZ 1024 */

    switch ( argc )
    {
    case 2:
        break;
    case 3:
        timeloop = atoi( argv[2] );
        break;
    default:
        showUsage( argv[0] );
        break;
    }  /* end of switch */
    pid = atoi( argv[1] );
    sprintf( pidpath, "/proc/%-d", pid );  /* -表示向左靠 */
    /*
     * /proc/1/是目录,但在这种用法中,就是直接打开目录,不是打开文件
     */
    if ( ( fd = open( pidpath, O_RDONLY ) ) < 0 )
    {
        perror( pidpath );
        exit( 1 );
    }
    if ( 0 < timeloop )
    {
        for ( ; ; )
        {
            getInfo( fd, pid );
            sleep( timeloop );
        }
    }
    getInfo( fd, pid );
    close( fd );
    exit( 0 );
}  /* end of main */

int showUsage ( const char * progname )
{
    fprintf( stderr, "%s: usage: %s < PID >; [time delay]\n", progname, progname
);
    exit( 3 );
}  /* end of showUsage */

void getInfo ( int fd, int pid )
{
    prpsinfo_t prp;
    prstatus_t prs;

    if ( ioctl( fd, PIOCPSINFO, &prp ) < 0 )
    {
        perror( "ioctl" );
        exit( 5 );
    }
    if ( ioctl( fd, PIOCSTATUS, &prs ) < 0 )
    {
        perror( "ioctl" );
        exit( 7 );
    }
    if ( counter >; 9 )
    {
        fprintf( stdout, "ID\tIMAGE\t\tRSS\t\tHEAP\t\tSTACK\n" );
        counter = 0;
    }
    fprintf( stdout, "%u\t%-9u\t%-9u\t%-15u\t%-15u\n", pid,
             ( unsigned int )prp.pr_bysize, ( unsigned int )prp.pr_byrssize,
             ( unsigned int )prs.pr_brksize, ( unsigned int )prs.pr_stksize );
    counter++;
}  /* end of getInfo */
--------------------------------------------------------------------------

4.2 Solaris下如何获知CPU速率

A: Philip Brown <phil+s3@bolthole.no-bots.com>;

   psrinfo -v

   psrinfo | grep on-line | wc -l 简单给出CPU数目

A: scz <scz@nsfocus.com>;

# /usr/platform/`uname -i`/sbin/prtdiag -v
# /usr/platform/`uname -m`/sbin/prtdiag -v
# /usr/bin/netstat -k cpu_info0

A: Tony Walton <tony.walton@uk.sun.com>;

如果你装了Sun Workshop,还可以尝试fpversion命令

# /opt/SUNWspro/bin/fpversion
A SPARC-based CPU is available.
CPU's clock rate appears to be approximately 266.1 MHz.
Kernel says CPU's clock rate is 270.0 MHz.
Kernel says main memory's clock rate is 90.0 MHz.

Sun-4 floating-point controller version 0 found.
An UltraSPARC chip is available.
FPU's frequency appears to be approximately 277.1 MHz.

Use "-xtarget=ultra2i -xcache=16/32/1:256/64/1" code-generation option.

Hostid = 0x80BC3CB3.
#

4.3 如何编程获取Solaris系统当前内存大小

Q: 如何编程(或者有什么现成命令)获取Solaris系统当前内存大小?

A: Nithyanandham <m.nithyanandham@blr.spcnl.co.in>;

几个现成命令

/usr/platform/`uname -m`/sbin/prtdiag -v | grep Memory

prtconf -v | grep Memory

如果装了GNU top,也可以直接用top命令看到。

D: scz <scz@nsfocus.com>;

truss prtconf的输出中有如下内容

sysconfig(_CONFIG_PAGESIZE)                     = 8192
sysconfig(_CONFIG_PHYS_PAGES)                   = 16384
Memory size: 128 Megabytes

# /usr/ccs/bin/nm -nx /dev/ksyms | grep "|sysconfig$"
10626] |0x0000100ec110|0x0000000001bc|FUNC |GLOB |0    |ABS    |sysconfig
# find /usr/include -type f -name "*.h" | xargs grep -l _CONFIG_PAGESIZE
/usr/include/sys/sysconfig.h
# vi -R /usr/include/sys/sysconfig.h

/*
* cmd values for _sysconfig system call.
* WARNING: This is an undocumented system call,
* therefore future compatibility can not
* guaranteed.
*/

#define _CONFIG_PAGESIZE   6  /* system page size */
#define _CONFIG_PHYS_PAGES 26 /* phys mem installed in pages */

参看sysconf(3C)手册页。

_SC_PAGESIZE
_SC_PAGE_SIZE
_SC_PHYS_PAGES

A: Casper Dik <Casper.Dik@Holland.Sun.COM>;

--------------------------------------------------------------------------
/*
* Program to determine the size installed physical memory on Suns.
*
* Casper Dik.
*/

#define MEGABYTE 0x00100000
#define MAXMEM   0x7ff00000
#define THEMEM   "/dev/mem"

#include <stdio.h>;
#include <fcntl.h>;
#include <sys/types.h>;
#include <unistd.h>;

int main ( int argc, char * argv[] )
{
    int           fd = open( THEMEM, O_RDONLY );
    char          c;
    unsigned long pos, mapstart = 0;
    int           totmb = 0;

    if ( fd == -1 )
    {
        perror( THEMEM );
        exit( 1 );
    }
    for ( pos = 0; pos < MAXMEM; pos += MEGABYTE )
    {
        if (lseek( fd, pos, 0 ) == -1 )
        {
            perror( "lseek" );
            exit( 1 );
        }
        if ( read( fd, &c, 1 ) == -1 )
        {
            int size = ( pos - mapstart ) / MEGABYTE;

            if ( size != 0 )
            {
                printf( "found %3d MB starting at 0x%p\n", size, ( void * )mapst
art );
                totmb += size;
            }
            mapstart = pos + MEGABYTE;  /* start of next possible mapping */
        }
    }
    printf( "Total memory size: %d MB\n", totmb );
    exit( 0 );
}
--------------------------------------------------------------------------

由于需要读访问/dev/mem,普通用户用户无法使用该程序。

5. 块设备相关问题

5.1 CDROM设备究竟在哪里

Q: 为了mount光驱,需要哪些包

A: SUNWvolr SUNWcstl SUNWcstlx

D: Dennis Clarke <dclarke@blastwave.com>;

1) su - root
2) /etc/init.d/volmgt stop
3) ls -1 /dev/dsk/c*s2
4) mount -F hsfs -o ro /dev/dsk/c0t6d0s2 /cdrom

或者

1) /etc/init.d/volmgt stop
2) /etc/init.d/volmgt start
3)

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
5 [报告]
发表于 2003-05-27 20:34 |只看该作者

原 C/C++ 论坛 FAQ,尚未整理。

申明:


【整理了一下最近的帖子,主要是精彩回帖。
希望需要查找相关问题的到这里。精华区中的精彩回帖将被置为普通。】


1、关于指针的问题

http://www.chinaunix.net/forum/viewtopic.php?t=52887

2、关于行缓存

http://www.chinaunix.net/forum/viewtopic.php?t=51199

3、exit、_exit()、atexit以及return和main函数隐式返回的区别

http://www.chinaunix.net/forum/viewtopic.php?t=62916

4、ESQL(內嵌式SQL)简述

http://www.chinaunix.net/forum/viewtopic.php?t=59492

5、fork、exec系列与system、popen区别

http://www.chinaunix.net/forum/viewtopic.php?t=62446

6、如何将“字符串”转换为“浮点数”

http://www.chinaunix.net/forum/viewtopic.php?t=62074

7、Socket问题汇总
socket连接问问题
http://www.chinaunix.net/forum/viewtopic.php?t=60921

接受和发送报文
http://www.chinaunix.net/forum/viewtopic.php?t=59264

短连接的问题TIME_WAIT状态问题
http://www.chinaunix.net/forum/viewtopic.php?t=59456

bind的问题
http://www.chinaunix.net/forum/viewtopic.php?t=54290


UDP编程相关问题

http://www.chinaunix.net/forum/viewtopic.php?t=51499

网络编程中字符打印的问题

http://www.chinaunix.net/forum/viewtopic.php?t=51369

TCP SOCKET编程的读写函数使用规则

http://www.chinaunix.net/forum/viewtopic.php?t=51362

SOCKET编程中字节转化的问题

http://www.chinaunix.net/forum/viewtopic.php?t=49814

TCP socket 编程中客户和服务器是怎么连接的。

http://www.chinaunix.net/forum/viewtopic.php?t=47847

*******网络编程基础( 讨论版V2 )********
http://www.chinaunix.net/forum/viewtopic.php?t=44645

讨论bind()的一个问题

http://www.chinaunix.net/forum/viewtopic.php?t=46779

select的用法

http://www.chinaunix.net/forum/viewtopic.php?t=58606

8、标准c怎么实现数字到字符转换

http://www.chinaunix.net/forum/viewtopic.php?t=58206

9、用getenv()/putenv()实现setenv()

http://www.chinaunix.net/forum/viewtopic.php?t=58382

10、dbx源码调试工具快速上手Z

http://www.chinaunix.net/forum/viewtopic.php?t=59496

11、c和c++的初学者的入门程序

http://www.chinaunix.net/forum/viewtopic.php?t=55594

12、深度探索C++对象模型

http://www.chinaunix.net/forum/viewtopic.php?t=59107

13、phread 解读(五)----系列

http://www.chinaunix.net/forum/viewtopic.php?t=58765
http://www.chinaunix.net/forum/viewtopic.php?t=58762
http://www.chinaunix.net/forum/viewtopic.php?t=58762
http://www.chinaunix.net/forum/viewtopic.php?t=58759
http://www.chinaunix.net/forum/viewtopic.php?t=58758

14、关于VI编辑器的TAB设置

http://www.chinaunix.net/forum/viewtopic.php?t=54691


15、如何实现,输入一个字符串,然后退出循环

http://www.chinaunix.net/forum/viewtopic.php?t=53577

16、数组地址的问题

http://www.chinaunix.net/forum/viewtopic.php?t=52347

17、怎么查看共享内存的ipcs输出信息

http://www.chinaunix.net/forum/viewtopic.php?t=51861

18、使用共享内存shmat()的权限问题

http://www.chinaunix.net/forum/viewtopic.php?t=51658


19、signal函数可放在程序的什么地方?

http://www.chinaunix.net/forum/viewtopic.php?t=23866&postdays=0&postorder=asc&start=15

20、IPC的用处

http://www.chinaunix.net/forum/viewtopic.php?t=49960

21、IEEE标准对于fork和pthread_create的描述

http://www.chinaunix.net/forum/viewtopic.php?t=50171

22、有关libftp的移植问题

http://www.chinaunix.net/forum/viewtopic.php?t=47306


23、malloc分配的内存的生命周期

http://www.chinaunix.net/forum/viewtopic.php?t=47544

24、如何判断键盘输入为F1,F2,F3,上下键,左右键等

http://www.chinaunix.net/forum/viewtopic.php?t=45859

25、core dump系列分析之1

http://www.chinaunix.net/forum/viewtopic.php?t=68536

论坛徽章:
0
6 [报告]
发表于 2003-06-24 00:48 |只看该作者

原 C/C++ 论坛 FAQ,尚未整理。

整理至6/24 0。00
有一些标题写不清楚的里面内容没有细看
但总的来说这些贴子还是很少的

另外一些回复少的贴子也没有跟进去
会有渥掉的地方
大家发现后补充一下

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
7 [报告]
发表于 2003-06-29 21:37 |只看该作者

原 C/C++ 论坛 FAQ,尚未整理。

我从网上看到这篇faq,对网友经常问的一些系统调用(fork, select等)和近期本版经常问的问题(setitimer,如何获取内存,为什么进程的大小不缩减,如何读写串口等)做了比较详细的答复。我就抄过来了。


  1.      Unix编程常见问题解答(FAQ / Frequently Asked Questions)(v1.37)(中文版 v0.1.0)


  2. 关于这篇“常见问题解答”
  3. ************************

  4. 这篇“常见问题解答” 由Patrick Horgan自一九九六年五月开始起草;因其历经
  5. 数月未复更新,我从而接手编辑。我已经将其内容稍做重新安排并加入一些新的内
  6. 容;我仍然认为它仍处于“有待开发建设”中。

  7. 请将批评,建议,增补,更正或其它意见发给维护者,电子邮件地址:
  8. andrew@erlenstar.demon.co.uk

  9. 这篇文档的超文本版(英文)在WEB上可以获得。主站点设在
  10. “http://www.erlenstar.demon.co.uk/unix/faq_toc.html”。
  11. 美国镜像站点设在“http://www.whitefang.com/unix/faq_toc.html”。

  12. 这篇文档可以用FTP的方式自主机rtfm.mit.edu和其众多镜像站点的news.answers 归
  13. 档中找到(译者注:URL是ftp://rtfm.mit.edu/pub/faqs/unix-faq/programmer/faq)。
  14. 它的官方归档名是“unix-faq/programmer/faq”。其他将网络新闻组*.answers归档的
  15. 服务器也会在目录“comp.unix.programmer”下存放这篇文档。

  16. 其他信息资源未于此一一列出。读者可在新闻组comp.unix.programmer每周定期发
  17. 布的[READ ME FIRST]中找到其他“常见问题”,书籍,原代码等资源的的连接。
  18. 关于管理新闻组的小问题等等也能在其中找到;而我只想在将这篇文档中特别讨
  19. 论问题和回答。

  20. 所有提供的资料已经经过维护者编辑,所有错误或疏忽是我的责任,跟提供者无
  21. 关。

  22. 这篇“常见问题解答”现在以Texinfo资源格式维护;我使用“makeinfo”程序将其
  23. 转换成供新闻组阅读的原始字符文件格式,并使用“texi2html”程序将其转换成
  24. HTML格式。

  25. 版权所有:1997,1998, 1999, 2000 Andrew Gierth. 这篇文档允许通过新闻组或
  26. 电子邮件方式的分发,也允许在news.answers 归档的镜像FTP或WWW站点归档存
  27. 放,并保证提供所有维持该文档更新应付出的努力。(本许可能够以个人为单位取
  28. 消)未经维护者许可,不允许将该文档以其他任何方式发表,无论是书面,WWW,
  29. 光盘,或在其他任何媒体。

  30. 内容提供者名单,无先后次序:
  31. 问题目录
  32. ********

  33. (译者:这里我有意保留原文以便于查询)

  34. 1. Process Control 进程控制
  35.   1.1 Creating new processes: fork() 创建新进程:fork函数
  36.     1.1.1 What does fork() do? fork函数干什么?
  37.     1.1.2 What's the difference between fork() and vfork()? fork函数 与 vfork函数的区别在哪里?
  38.     1.1.3 Why use _exit rather than exit in the child branch of a fork? 为何在一个fork的子进程分支中使用_exit函数而不使用exit函数?
  39.   1.2 Environment variables 环境变量
  40.     1.2.1 How can I get/set an environment variable from a program? 我怎样在程序中获得/设置环境变量?
  41.     1.2.2 How can I read the whole environment? 我怎样读取整个环境变量表?
  42.   1.3 How can I sleep for less than a second? 我怎样睡眠小于一秒?
  43.   1.4 How can I get a finer-grained version of alarm()? 我怎样得到一个更细分时间单位的alarm函数版本(译者注:希望alarm的时间小于一秒)?
  44.   1.5 How can a parent and child process communicate? 父子进程如何通信?
  45.   1.6 How do I get rid of zombie processes? 我怎样去除僵死进程?
  46.     1.6.1 What is a zombie? 何为僵死进程?
  47.     1.6.2 How do I prevent them from occuring? 我怎样避免它们的出现?
  48.   1.7 How do I get my program to act like a daemon? 我怎样使我的程序作为守护程序运行?
  49.   1.8 How can I look at process in the system like ps does? 我怎样象ps程序一样审视系统的进程?
  50.   1.9 Given a pid, how can I tell if it's a running program? 给定一个进程号(译者注:pid: process ID),我怎样知道它是个正在运行的程序?
  51.   1.10 What's the return value of system/pclose/waitpid? system函数,pclose函数,waitpid函数 的返回值是什么?
  52.   1.11 How do I find out about a process' memory usage? 我怎样找出一个进程的存储器使用情况?
  53.   1.12 Why do processes never decrease in size? 为什么进程的大小不缩减?
  54.   1.13 How do I change the name of my program (as seen by `ps')? 我怎样改变我程序的名字(即“ps”看到的名字)?
  55.   1.14 How can I find a process' executable file? 我怎样找到进程的相应可执行文件?
  56.     1.14.1 So where do I put my configuration files then? 那么,我把配置文件放在哪里呢?
  57.   1.15 Why doesn't my process get SIGHUP when its parent dies? 为何父进程死时,我的进程未得到SIGHUP信号?
  58.   1.16 How can I kill all descendents of a process? 我怎样杀死一个进程的所有派生进程?

  59. 2. General File handling (including pipes and sockets) 一般文件操作(包括管道和套接字)
  60.   2.1 How to manage multiple connections? 怎样管理多个连接?
  61.     2.1.1 How do I use select()? 我怎样使用select()?
  62.     2.1.2 How do I use poll()? 我怎样使用poll() ?
  63.     2.1.3 Can I use SysV IPC at the same time as select or poll? 我是否可以将SysV 进程间通信 (译者注:IPC: Interprocess Communications) 与select或poll同
  64. 时使用?
  65.   2.2 How can I tell when the other end of a connection shuts down? 我怎么知道连接的另一端已关闭?
  66.   2.3 Best way to read directories? 读目录的最好方法?
  67.   2.4 How can I find out if someone else has a file open? 我怎么知道其他人已经打开一个文件?
  68.   2.5 How do I `lock' a file? 我怎样锁定一个文件?
  69.   2.6 How do I find out if a file has been updated by another process? 我怎么知道一个文件是否已被其他进程更新?
  70.   2.7 How does the `du' utility work? “du”工具程序是怎么工作的?
  71.   2.8 How do I find the size of a file? 我怎么知道一个文件的大小?
  72.   2.9 How do I expand `~' in a filename like the shell does? 我怎样象shell程序一样将一个文件名中含有的“~”展开?
  73.   2.10 What can I do with named pipes (FIFOs)? 我能用有名管道(FIFOs)(译者注:FIFO: First In First Oout)干什么?
  74.     2.10.1 What is a named pipe? 什么是有名管道?
  75.     2.10.2 How do I create a named pipe? 我怎样创建一个有名管道?
  76.     2.10.3 How do I use a named pipe? 我怎样使用一个有名管道?
  77.     2.10.4 Can I use a named pipe across NFS? 我能基于网络文件系统(译者注:NFS:Network File System)使用有名管道吗?
  78.     2.10.5 Can multiple processes write to the pipe simultaneously? 多个进程能否同时向这个管道写执行写操作?
  79.     2.10.6 Using named pipes in applications 在应用程序中使用有名管道。

  80. 3. Terminal I/O 终端输入/输出(I/O:input/output)
  81.   3.1 How can I make my program not echo input? 我怎样使我的程序不回射输入?
  82.   3.2 How can I read single characters from the terminal? 我怎样从终端读取单个字符?
  83.   3.3 How can I check and see if a key was pressed? 我怎样检查是否一个键被摁下?
  84.   3.4 How can I move the cursor around the screen? 我怎样将光标在屏幕里移动?
  85.   3.5 What are pttys? pttys(pttys:Pseudo-teletypes)是什么?
  86.   3.6 How to handle a serial port or modem? 怎样控制一个串行口和调制解调器(译者注:modem: modulate-demodulate)
  87.     3.6.1 Serial device names and types 串行设备和类型
  88.     3.6.2 Setting up termios flags 设置termios的标志位
  89.       3.6.2.1 c_iflag
  90.       3.6.2.2 c_oflag
  91.       3.6.2.3 c_cflag
  92.       3.6.2.4 c_lflag
  93.       3.6.2.5 c_cc

  94. 4. System Information 系统信息
  95.   4.1 How can I tell how much memory my system has? 我怎样知道我的系统有多少存储器容量?
  96.   4.2 How do I check a user's password? 我怎样检查一个用户的口令?
  97.     4.2.1 How do I get a user's password? 我怎样得到一个用户的口令?
  98.     4.2.2 How do I get shadow passwords by uid? 我怎样通过用户号(译者注:uid: User ID)得到阴影口令文件中的口令?
  99.     4.2.3 How do I verify a user's password? 我怎样核对一个用户的口令?

  100. 5. Miscellaneous programming 编程杂技
  101.   5.1 How do I compare strings using wildcards? 我怎样使用通配字符比较字符串?
  102.     5.1.1 How do I compare strings using filename patterns? 我怎样使用文件名通配模式比较字符串?
  103.     5.1.2 How do I compare strings using regular expressions? 我怎样使用正则表达式比较字符串?
  104.   5.2 What's the best way to send mail from a program? 什么是在程序中发送电子邮件的最好方法?
  105.     5.2.1 The simple method: /bin/mail 简单方法:/bin/mail
  106.     5.2.2 Invoking the MTA directly: /usr/lib/sendmail 直接启动邮件传输代理(译者注:MTA: mail transfer agent):/usr/bin/sendmail
  107.       5.2.2.1 Supplying the envelope explicitly 显式提供收件人信息
  108.       5.2.2.2 Allowing sendmail to deduce the recipients 允许sendmail程序根据邮件内容分析出收件人

  109. 6. Use of tools 工具的使用
  110.   6.1 How can I debug the children after a fork? 我怎样调试fork函数产生的子进程?
  111.   6.2 How to build library from other libraries? 怎样通过其他库文件建立新的库文件?
  112.   6.3 How to create shared libraries / dlls? 怎样创建动态连接库/dlls?
  113.   6.4 Can I replace objects in a shared library? 我能更改一个动态连接库里的目标吗?
  114.   6.5 How can I generate a stack dump from within a running program? 我能在一个运行着的程序中生成堆栈映象吗?


  115. 1. 进程控制
  116. ***********

  117. 1.1 创建新进程:fork函数
  118. ========================

  119. 1.1.1 fork函数干什么?
  120. ----------------------

  121.      #include <sys/types.h>;
  122.      #include <unistd.h>;

  123.      pid_t fork(void);

  124. ‘fork()’函数用于从已存在进程中创建一个新进程。新进程称为子进程,而原进程称为
  125. 父进程。你可以通过检查‘fork()’函数的返回值知道哪个是父进程,哪个是子进程。父
  126. 进程得到的返回值是子进程的进程号,而子进程则返回0。以下这个范例程序说明它的基本
  127. 功能:

  128.      pid_t pid;

  129.      switch (pid = fork())
  130.      {
  131.      case -1:
  132.          /* 这里pid为-1,fork函数失败 */
  133.          /* 一些可能的原因是 */
  134.          /* 进程数或虚拟内存用尽 */
  135.          perror("The fork failed!");
  136.          break;

  137.      case 0:
  138.          /* pid为0,子进程 */
  139.          /* 这里,我们是孩子,要做什么? */
  140.          /* ... */
  141.          /* 但是做完后, 我们需要做类似下面: */
  142.          _exit(0);

  143.      default:
  144.          /* pid大于0,为父进程得到的子进程号 */
  145.          printf("Child's pid is %d\n",pid);
  146.      }

  147. 当然,有人可以用‘if() ... else ...’语句取代‘switch()’语句,但是上面的形式是
  148. 一个有用的惯用方法。

  149. 知道子进程自父进程继承什么或未继承什么将有助于我们。下面这个名单会因为
  150. 不同Unix的实现而发生变化,所以或许准确性有了水份。请注意子进程得到的是
  151. 这些东西的 *拷贝*,不是它们本身。

  152. 由子进程自父进程继承到:

  153.    * 进程的资格(真实(real)/有效(effective)/已保存(saved) 用户号(UIDs)和组号(GIDs))

  154.    * 环境(environment)

  155.    * 堆栈

  156.    * 内存

  157.    * 打开文件的描述符(注意对应的文件的位置由父子进程共享,这会引起含糊情况)

  158.    * 执行时关闭(close-on-exec) 标志 (译者注:close-on-exec标志可通过fnctl()对文件描
  159.      述符设置,POSIX.1要求所有目录流都必须在exec函数调用时关闭。更详细说明,
  160.      参见<<UNIX环境高级编程>;>; W. R. Stevens, 1993, 尤晋元等译(以下简称<<高级编
  161.      程>;>;), 3.13节和8.9节)

  162.    * 信号(signal)控制设定

  163.    * nice值 (译者注:nice值由nice函数设定,该值表示进程的优先级,数值越小,优
  164.      先级越高)

  165.    * 进程调度类别(scheduler class) (译者注:进程调度类别指进程在系统中被调度时所
  166.      属的类别,不同类别有不同优先级,根据进程调度类别和nice值,进程调度程序可计
  167.      算出每个进程的全局优先级(Global process prority),优先级高的进程优先执行)

  168.    * 进程组号

  169.    * 对话期ID(Session ID) (译者注:译文取自<<高级编程>;>;,指:进程所属的对话期
  170.      (session)ID, 一个对话期包括一个或多个进程组, 更详细说明参见<<高级编程>;>;
  171.      9.5节)

  172.    * 当前工作目录

  173.    * 根目录 (译者注:根目录不一定是“/”,它可由chroot函数改变)

  174.    * 文件方式创建屏蔽字(file mode creation mask (umask)) (译者注:译文取自<<高级编
  175.      程>;>;,指:创建新文件的缺省屏蔽字)

  176.    * 资源限制

  177.    * 控制终端

  178. 子进程所独有:

  179.    * 进程号

  180.    * 不同的父进程号(译者注:即子进程的父进程号与父进程的父进程号不同,父进
  181.      程号可由getppid函数得到)

  182.    * 自己的文件描述符和目录流的拷贝(译者注:目录流由opendir函数创建,因其为
  183.      顺序读取,顾称“目录流”)

  184.    * 子进程不继承父进程的进程,正文(text),数据和其它锁定内存(memory locks)
  185.      (译者注:锁定内存指被锁定的虚拟内存页,锁定后,不允许内核将其在必要时
  186.      换出(page out),详细说明参见<<The GNU C Library Reference Manual>;>; 2.2版,
  187.      1999, 3.4.2节)

  188.    * 在tms结构中的系统时间(译者注:tms结构可由times函数获得,它保存四个数据
  189.      用于记录进程使用中央处理器(CPU:Central Processing Unit)的时间,包括:用户时
  190.      间,系统时间,用户各子进程合计时间,系统各子进程合计时间)

  191.    * 资源使用(resource utilizations)设定为0

  192.    * 阻塞信号集初始化为空集(译者注:原文此处不明确,译文根据fork函数手册页
  193.      稍做修改)

  194.    * 不继承由timer_create函数创建的计时器

  195.    * 不继承异步输入和输出

  196. 1.1.2 fork函数 与 vfork函数的区别在哪里里?
  197. -------------------------------------------

  198. 有些系统有一个系统调用‘vfork()’,它最初被设计成‘fork()’的较少额外支出
  199. (lower-overhead)版本。因为‘fork()’包括拷贝整个进程的地址空间,所以非常
  200. “昂贵”,这个‘vfork()’函数因此被引入。(在3.0BSD中)(译者注:BSD:
  201. Berkeley Software Distribution)

  202. *但是*,自从‘vfork()’被引入,‘fork()’的实现方法得到了很大改善,最值得
  203. 注意的是“写操作时拷贝”(copy-on-write)的引入,它是通过允许父子进程可访问
  204. 相同物理内存从而伪装(fake)了对进程地址空间的真实拷贝,直到有进程改变内
  205. 存中数据时才拷贝。这个提高很大程度上抹杀了需要‘vfork()’的理由;事实上,
  206. 一大部份系统完全丧失了‘vfork()’的原始功能。但为了兼容,它们仍然提供
  207. ‘vfork()’函数调用,但它只是简单地调用‘fork()’,而不试图模拟所有‘vfork()’
  208. 的语义(semantics, 译文取自<<高级编程>;>;,指定义的内容和做法)。

  209. 结论是,试图使用任何‘fork()’和‘vfork()’的不同点是*很*不明智的。事实上,
  210. 可能使用‘vfork()’根本就是不明智的,除非你确切知道你想*干什么*。

  211. 两者的基本区别在于当使用‘vfork()’创建新进程时,父进程将被暂时阻塞,而
  212. 子进程则可以借用父进程的地址空间。这个奇特状态将持续直到子进程要么退
  213. 出,要么调用‘execve()’,至此父进程才继续执行。

  214. 这意味着一个由‘vfork()’创建的子进程必须小心以免出乎意料地改变父进程的
  215. 变量。特别的,子进程必须不从包含‘vfork()’调用的函数返回,而且必须不调
  216. 用‘exit()’(如果它需要退出,它需要使用‘_exit()’;事实上,对于使用正常
  217. ‘fork()’创建的子进程这也是正确的)(译者注:参见1.1.3)

  218. 1.1.3 为何在一个fork的子进程分支中使用_exit函数而不使用exit函数?
  219. -----------------------------------------------------------------

  220. ‘exit()’与‘_exit()’有不少区别在使用‘fork()’,特别是‘vfork()’时变得很
  221. 突出。

  222. ‘exit()’与‘_exit()’的基本区别在于前一个调用实施与调用库里用户状态结构
  223. (user-mode constructs)有关的清除工作(clean-up),而且调用用户自定义的清除程序
  224. (译者注:自定义清除程序由atexit函数定义,可定义多次,并以倒序执行),相对
  225. 应,后一个函数只为进程实施内核清除工作。

  226. 在由‘fork()’创建的子进程分支里,正常情况下使用‘exit()’是不正确的,这是
  227. 因为使用它会导致标准输入输出(译者注:stdio: Standard Input Output)的缓冲区被
  228. 清空两次,而且临时文件被出乎意料的删除(译者注:临时文件由tmpfile函数创建
  229. 在系统临时目录下,文件名由系统随机生成)。在C++程序中情况会更糟,因为静
  230. 态目标(static objects)的析构函数(destructors)可以被错误地执行。(还有一些特殊情
  231. 况,比如守护程序,它们的*父进程*需要调用‘_exit()’而不是子进程;适用于绝
  232. 大多数情况的基本规则是,‘exit()’在每一次进入‘main’函数后只调用一次。)

  233. 在由‘vfork()’创建的子进程分支里,‘exit()’的使用将更加危险,因为它将影响
  234. *父*进程的状态。

  235. 1.2 环境变量
  236. ============

  237. 1.2.1  如何从程序中获得/设置环境变量?
  238. --------------------------------------
  239. 获得一个环境变量可以通过调用‘getenv()’函数完成。

  240.      #include <stdlib.h>;

  241.      char *getenv(const char *name);

  242. 设置一个环境变量可以通过调用‘putenv()’函数完成。

  243.      #include <stdlib.h>;

  244.      int putenv(char *string);

  245. 变量string应该遵守"name=value"的格式。已经传递给putenv函数的字符串*不*能够被
  246. 释放或变成无效,因为一个指向它的指针将由‘putenv()’保存。这意味着它必须是
  247. 在静态数据区中或是从堆(heap)分配的。如果这个环境变量被另一个‘putenv()’的
  248. 调用重新定义或删除,上述字符串可以被释放。

  249. /* 译者增加:

  250. 因为putenv()有这样的局限,在使用中经常会导致一些错
  251. 误,GNU libc 中还包括了两个BSD风格的函数:
  252. #include <stdlib.h>;
  253. int setenv(const char *name, const char *value, int replace);
  254. void unsetenv(const char *name);

  255. setenv()/unsetenv()函数可以完成所有putenv()能做的事。setenv() 可以不受指针
  256. 限制地向环境变量中添加新值,但传入参数不能为空(NULL)。当replace为0时,如
  257. 果环境变量中已经有了name项,函数什么也不做(保留原项),否则原项被覆盖。
  258. unsetenv()是用来把name项从环境变量中删除。注意:这两个函数只存在在BSD和GNU
  259. 库中,其他如SunOS系统中不包括它们,因此将会带来一些兼容问题。我们可以用
  260. getenv()/putenv()来实现:

  261. int setenv(const char *name,  const char *value, int replace)
  262. {
  263.    char *envstr;

  264.    if (name == NULL || value == NULL)
  265.       return 1;
  266.    if (getenv(name) !=NULL)
  267.      {
  268.         envstr = (char *) malloc(strlen(name) + strlen(value) + 2);
  269.         sprintf (envstr, "%s=%s", name, value);
  270.         if (putenv(envstr));
  271.            return 1;
  272.      }
  273.    return 0;
  274. }
  275. */

  276. 记住环境变量是被继承的;每一个进程有一个不同的环境变量表拷贝(译者注:
  277. 从core文件中我们可以看出这一点)。结果是,你不能从一个其他进程改变当前
  278. 进程的环境变量,比如shell进程。

  279. 假设你想得到环境变量‘TERM’的值,你需要使用下面的程序:

  280.      char *envvar;

  281.      envvar=getenv("TERM");

  282.      printf("The value for the environment variable TERM is ");
  283.      if(envvar)
  284.      {
  285.          printf("%s\n",envvar);
  286.      }
  287.      else
  288.      {
  289.          printf("not set.\n");
  290.      }

  291. 现在假设你想创建一个新的环境变量,变量名为‘MYVAR’,值为‘MYVAL’。
  292. 以下是你将怎样做:

  293.      static char envbuf[256];

  294.      sprintf(envbuf,"MYVAR=%s","MYVAL");

  295.      if(putenv(envbuf))
  296.      {
  297.          printf("Sorry, putenv() couldn't find the memory for %s\n",envbuf);
  298.          /* Might exit() or something here if you can't live without it */
  299.      }

  300. 1.2.2 我怎样读取整个环境变量表?
  301. --------------------------------

  302. 如果你不知道确切你想要的环境变量的名字,那么‘getenv()’函数不是很有用。
  303. 在这种情况下,你必须更深入了解环境变量表的存储方式。

  304. 全局变量,‘char **envrion’,包含指向环境字符串指针数组的指针,每一个字
  305. 符串的形式为‘“NAME=value”’(译者注:和putenv()中的“string”的格式相同)。
  306. 这个数组以一个‘空’(NULL)指针标记结束。这里是一个打印当前环境变量列表
  307. 的小程序(类似‘printenv’)。

  308.      #include <stdio.h>;

  309.      extern char **environ;

  310.      int main()
  311.      {
  312.          char **ep = environ;
  313.          char *p;
  314.          while ((p = *ep++))
  315.              printf("%s\n", p);
  316.          return 0;
  317.      }

  318. 一般情况下,‘envrion’变量作为可选的第三个参数传递给‘main()’;就是说,
  319. 上面的程序可以写成:

  320.      #include <stdio.h>;

  321.      int main(int argc, char **argv, char **envp)
  322.      {
  323.          char *p;
  324.          while ((p = *envp++))
  325.              printf("%s\n", p);
  326.          return 0;
  327.      }

  328. 虽然这种方法被广泛的操纵系统所支持(译者注:包括DOS),这种方法事实上并
  329. 没有被POSIX(译者注:POSIX: Portable Operating System Interace)标准所定义。(一
  330. 般的,它也比较没用)

  331. 1.3 我怎样睡眠小于一秒?
  332. ========================

  333. 在所有Unix中都有的‘sleep()’函数只允许以秒计算的时间间隔。如果你想要更
  334. 细化,那么你需要寻找替换方法:

  335.    * 许多系统有一个‘usleep()’函数

  336.    * 你可以使用‘select()’或‘poll()’,并设置成无文件描述符并试验;一个普
  337.      遍技巧是基于其中一个函数写一个‘usleep()’函数。(参见comp.unix.questions
  338.      FAQ 的一些例子)

  339.    * 如果你的系统有itimers(很多是有的)(译者注:setitimer和getitimer是两个操作
  340.      itimers的函数,使用“man setitimer”确认你的系统支持),你可以用它们自己撺一
  341.      个‘usleep()’。(参见BSD源程序的‘usleep()’以便知道怎样做)

  342.    * 如果你有POSIX实时(realtime)支持,那会有一个‘nanosleep()’函数。

  343. 众观以上方法,‘select()’可能是移植性最好的(直截了当说,它经常比
  344. ‘usleep()’或基于itimer的方法更有效)。但是,在睡眠中捕获信号的做法会有
  345. 所不同;基于不同应用,这可以成为或不成为一个问题。

  346. 无论你选择哪条路,意识到你将受到系统计时器分辨率的限制是很重要的(一
  347. 些系统允许设置非常短的时间间隔,而其他的系统有一个分辨率,比如说10毫
  348. 秒,而且总是将所有设置时间取整到那个值)。而且,关于‘sleep()’,你设置
  349. 的延迟只是最小值(译者注:实际延迟的最小值);经过这段时间的延迟,会有
  350. 一个中间时间间隔直到你的进程重新被调度到。

  351. 1.4  我怎样得到一个更细分时间单位的alarm函数版本?
  352. ==================================================

  353. 当今Unix系统倾向于使用‘setitimer()’函数实现闹钟,它比简单的‘alarm()’函
  354. 数具有更高的分辨率和更多的选择项。一个使用者一般需要首先假设‘alarm()’
  355. 和‘setitimer(ITIMER_REAL)’可能是相同的底层计时器,而且假设同时使用两
  356. 种方法会造成混乱。

  357. Itimers可被用于实现一次性或重复信号;而且一般有3种不同的计时器可以用:

  358. `ITIMER_REAL'
  359.       计数真实(挂钟)时间,然后发送‘SIGALRM’信号

  360. `ITIMER_VIRTUAL'
  361.       计数进程虚拟(用户中央处理器)时间,然后发送‘SIGVTALRM’信号

  362. `ITIMER_PROF'
  363.      计数用户和系统中央处理器时间,然后发送‘SIGPROF’信号;它供解释器
  364.      用来进行梗概处理(profiling)

  365. 然而itimers不是许多标准的一部份,尽管它自从4.2BSD就被提供。POSIX实时标
  366. 准的扩充定义了类似但不同的函数。

  367. 1.5 父子进程如何通信?
  368. ======================

  369. 一对父子进程可以通过正常的进程间通信的办法(管道,套接字,消息队列,共
  370. 享内存)进行通信,但也可以通过利用它们作为父子进程的相互关系而具有的一
  371. 些特殊方法。

  372. 一个最显然的方法是父进程可以得到子进程的退出状态。

  373. 因为子进程从它的父进程继承文件描述符,所以父进程可以打开一个管道的两端,
  374. 然后fork,然后父进程关闭管道这一端,子进程关闭管道另一端。这正是你从你的
  375. 进程调用‘popen()’函数运行另一个程序所发生的情况,也就是说你可以向
  376. ‘popen()’返回的文件描述符进行写操作而子进程将其当作自己的标准输入,或
  377. 者你可以读取这个文件描述符来看子进程向标准输出写了什么。(‘popen()’函数
  378. 的mode参数定义你的意图(译者注:mode=“r”为读,mode=“w”为写);如果你
  379. 想读写都做,那么你可以并不困难地用管道自己做到)

  380. 而且,子进程继承由父进程用mmap函数映射的匿名共享内存段(或者通过映射特
  381. 殊文件‘/dev/zero’);这些共享内存段不能从无关的进程访问。

  382. 1.6 我怎样去除僵死进程?
  383. ========================

  384. 1.6.1 何为僵死进程?
  385. --------------------

  386. 当一个程序创建的子进程比父进程提前结束,内核仍然保存一些它的信息以便父
  387. 进程会需要它 - 比如,父进程可能需要检查子进程的退出状态。为了得到这些信
  388. 息,父进程调用‘wait()’;当这个调用发生,内核可以丢弃这些信息。

  389. 在子进程终止后到父进程调用‘wait()’前的时间里,子进程被称为‘僵死进程’
  390. (‘zombie’)。(如果你用‘ps’,这个子进程会有一个‘Z’出现在它的状态区
  391. 里指出这点。)即使它没有在执行,它仍然占据进程表里一个位置。(它不消耗其
  392. 它资源,但是有些工具程序会显示错误的数字,比如中央处理器的使用;这是
  393. 因为为节约空间进程表的某些部份与会计数据(accounting info)是共用(overlaid)的。)

  394. 这并不好,因为进程表对于进程数有固定的上限,系统会用光它们。即使系统没
  395. 有用光 ,每一个用户可以同时执行的进程数有限制,它总是小于系统的限制。
  396. 顺便说一下,这也正是你需要总是 检查‘fork()’是否失败的一个原因。

  397. 如果父进程未调用wait函数而终止,子进程将被‘init’进程收管,它将控制子进
  398. 程退出后必须的清除工作。(‘init’是一个特殊的系统程序,进程号为1 - 它实际
  399. 上是系统启动后运行的第一个程序),

  400. 1.6.2 我怎样避免它们的出现?
  401. ----------------------------

  402. 你需要却认父进程为每个子进程的终止调用‘wait()’(或者‘waitpid()’,
  403. ‘wait3()’,等等); 或者,在某些系统上,你可以指令系统你对子进程的退出状
  404. 态没有兴趣。(译者注:在SysV系统上,可以调用signal函数,设置SIGCLD信号为
  405. SIG_IGN,系统将不产生僵死进程, 详细说明参见<<高级编程>;>;10.7节)

  406. 另一种方法是*两次*‘fork()’,而且使紧跟的子进程直接退出,这样造成孙子进
  407. 程变成孤儿进程(orphaned),从而init进程将负责清除它。欲获得做这个的程序,参
  408. 看范例章节的函数‘fork2()’。

  409. 为了忽略子进程状态,你需要做下面的步骤(查询你的系统手册页以知道这是否正
  410. 常工作):

  411.          struct sigaction sa;
  412.          sa.sa_handler = SIG_IGN;
  413.      #ifdef SA_NOCLDWAIT
  414.          sa.sa_flags = SA_NOCLDWAIT;
  415.      #else
  416.          sa.sa_flags = 0;
  417.      #endif
  418.          sigemptyset(&sa.sa_mask);
  419.          sigaction(SIGCHLD, &sa, NULL);

  420. 如果这是成功的,那么‘wait()’函数集将不再正常工作;如果它们中任何一个被
  421. 调用,它们将等待直到*所有*子进程已经退出,然后返回失败,并且
  422. ‘errno==ECHILD’。

  423. 另一个技巧是捕获SIGCHLD信号,然后使信号处理程序调用‘waitpid()’或
  424. ‘wait3()’。参见范例章节的完整程序。

  425. 1.7 我怎样使我的程序作为守护程序运行?
  426. ======================================

  427. 一个“守护程序”进程通常被定义为一个后台进程,而且它不属于任何一个终端
  428. 会话,(terminal session)。许多系统服务由守护程序实施;如网络服务,打印等。

  429. 简单地在后台启动一个程序并非足够是这些长时间运行的程序;那种方法没有正
  430. 确地将进程从启动它的终端脱离(detach)。而且,启动守护程序的普遍接受的的方
  431. 法是简单地手工执行或从rc脚本程序执行(译者注:rc:runcom);并希望这个守护
  432. 程序将其*自身*安置到后台。

  433. 这里是成为守护程序的步骤:

  434.   1. 调用‘fork()’以便父进程可以退出,这样就将控制权归还给运行你程序的
  435.      命令行或shell程序。需要这一步以便保证新进程不是一个进程组头领进程(process
  436.      group leader)。下一步,‘setsid()’,会因为你是进程组头领进程而失败。

  437.   2. 调用‘setsid()’ 以便成为一个进程组和会话组的头领进程。由于一个控制终端
  438.      与一个会话相关联,而且这个新会话还没有获得一个控制终端,我们的进程没
  439.      有控制终端,这对于守护程序来说是一件好事。

  440.   3. 再次调用‘fork()’所以父进程(会话组头领进程)可以退出。这意味着我们,一
  441.      个非会话组头领进程永远不能重新获得控制终端。

  442.   4. 调用‘chdir("/")’确认我们的进程不保持任何目录于使用状态。不做这个会导
  443.      致系统管理员不能卸装(umount)一个文件系统,因为它是我们的当前工作目录。

  444.      [类似的,我们可以改变当前目录至对于守护程序运行重要的文件所在目录]

  445.   5. 调用‘umask(0)’以便我们拥有对于我们写的任何东西的完全控制。我们不知
  446.      道我们继承了什么样的umask。

  447.      [这一步是可选的](译者注:这里指步骤5,因为守护程序不一定需要写文件)

  448.   6. 调用‘close()’关闭文件描述符0,1和2。这样我们释放了从父进程继承的标
  449.      准输入,标准输出,和标准错误输出。我们没办法知道这些文描述符符可能
  450.      已经被重定向去哪里。注意到许多守护程序使用‘sysconf()’来确认
  451.      ‘_SC_OPEN_MAX’的限制。‘_SC_OPEN_MAX’告诉你每个进程能够打
  452.      开的最多文件数。然后使用一个循环,守护程序可以关闭所有可能的文件描
  453.      述符。你必须决定你需要做这个或不做。如果你认为有可能有打开的文件描
  454.      述符,你需要关闭它们,因为系统有一个同时打开文件数的限制。

  455.   7. 为标准输入,标准输出和标准错误输出建立新的文件描述符。即使你不打算
  456.      使用它们,打开着它们不失为一个好主意。准确操作这些描述符是基于各自
  457.      爱好;比如说,如果你有一个日志文件,你可能希望把它作为标准输出和标
  458.      准错误输出打开,而把‘/dev/null’作为标准输入打开;作为替代方法,你可
  459.      以将‘/dev/console’作为标准错误输出和/或标准输出打开,而‘/dev/null’作
  460.      为标准输入,或者任何其它对你的守护程序有意义的结合方法。(译者注:一
  461.      般使用dup2函数原子化关闭和复制文件描述符,参见<<高级编程>;>;3.12节)

  462. 如果你的守护程序是被‘inetd’启动的,几乎所有这些步骤都不需要(或不建议
  463. 采用)。在那种情况下,标准输入,标准输出和标准错误输出都为你指定为网络
  464. 连接,而且‘fork()’的调用和会话的操纵不应做(以免使‘inetd’造成混乱)。只
  465. 有‘chdir()’和‘umask()’这两步保持有用。

  466. 1.8  我怎样象ps程序一样审视系统的进程?
  467. =======================================

  468. 你真的不该想做这个。

  469. 到目前为止,移植性最好的是调用‘popen(pscmd,"r")’并处理它的输出。(pscmd
  470. 应当是类似SysV系统上的‘“ps -ef”’,BSD系统有很多可能的显示选项:选
  471. 择一个。)

  472. 在范例章节有这个问题的两个完整解决方法;一个适用于SunOS 4,它需要root权
  473. 限执行并使用‘kvm_*’例程从内核数据结果读取信息;另一种适用于SVR4系统
  474. (包括Sun OS 5),它使用‘/proc’文件系统。

  475. 在具有SVR4.2风格‘/proc’的系统上更简单;只要对于每一个感兴趣的进程号从
  476. 文件‘/proc/进程号/psinfo’读取一个psinfo_t结构。但是,这种可能是最清晰的方
  477. 法也许又是最不得到很好支持的方法。(在FreeBSD的‘/proc’上,你从
  478. ‘/proc/进程号/status’读取一个半未提供文档说明(semi-undocumented)的可打印字
  479. 符串;Linux有一些与其类似的东西)

  480. 1.9  给定一个进程号,我怎样知道它是个正在运行的程序?
  481. =====================================================

  482. 使用‘kill()’函数,而已0作为信号代码(signal number)。

  483. 从这个函数返回有四种可能的结果:

  484.    * ‘kill()’返回0

  485.        - 这意味着一个给定此进程号的进程退出,系统允许你向它发送信号。该进
  486.          程是否可以是僵死进程与不同系统有关。

  487.    * ‘kill()’返回-1,‘errno == ESRCH’

  488.        - 要么不存在给定进程号的进程,要么增强的安全机制导致系统否认它的存
  489.          在。(在一些系统上,这个进程有可能是僵死进程。)

  490.    * ‘kill()’返回-1,‘errno == EPERM’

  491.        - 系统不允许你杀死(kill)这个特定进程。这意味着要么进程存在(它又可能是
  492.          僵死进程),要么严格的增强安全机制起作用(比如你的进程不允许发送信号
  493.          给*任何人*)。

  494.     * ‘kill()’返回-1,伴以其它‘errno’值

  495.        - 你有麻烦了!

  496. 用的最多的技巧是认为调用“成功”或伴以‘EPERM’的“失败”意味着进程存
  497. 在,而其它错误意味着它不存在。

  498. 如果你特别为提供‘/proc’文件系统的系统(或所有类似系统)写程序,一个替换
  499. 方法存在:检查‘proc/进程号’是否存在是可行的。

  500. 1.10  system函数,pclose函数,waitpid函数 的返回值是什么?
  501. ==========================================================

  502.      ‘system()’,‘pclose()’或者‘waitpid()’的返回值不象是我进程的退出值(exit
  503.         value)(译者注:退出值指调用exit() 或_exit()时给的参数)... 或者退出值左移了8
  504.        位...这是怎么搞的?

  505. 手册页是对的,你也是对的! 如果查阅手册页的‘waitpid()’你会发现进程的返回
  506. 值被编码了。正常情况下,进程的返回值在高16位,而余下的位用来作其它事。
  507. 如果你希望可移植,你就不能凭借这个,而建议是你该使用提供的宏。这些宏总
  508. 是在‘wait()’或‘wstat’的文档中说明了。

  509. 为了不同目的定义的宏(在‘<sys/wait.h>;’)包括(stat是‘waitpid()’返回的值):

  510. `WIFEXITED(stat)'
  511.      如果子进程正常退出则返回非0

  512. `WEXITSTATUS(stat)'
  513.      子进程返回的退出码

  514. `WIFSIGNALED(stat)'
  515.      如果子进程由与信号而 终止则返回非0

  516. `WTERMSIG(stat)'
  517.      终止子进程的信号代码

  518. `WIFSTOPPED(stat)'
  519.      如果子进程暂停(stopped)则返回非0

  520. `WSTOPSIG(stat)'
  521.      使子进程暂停的信号代码

  522. `WIFCONTINUED(stat)'
  523.      如果状态是表示子进程继续执行则返回非0

  524. `WCOREDUMP(stat)'
  525.      如果‘WIFSIGNALED(stat)’为非0,而如果这个进程产生一个内存映射文件
  526.      (core dump)则返回非0

  527. 1.11 我怎样找出一个进程的存储器使用情况?
  528. =========================================

  529. 如果提供的话,参看‘getrusage()’手册页

  530. 1.12 为什么进程的大小不缩减?
  531. =============================

  532. 当你使用‘free()’函数释放内存给堆时,几乎所有的系统都*不*减少你程序的
  533. 对内存的使用。被‘free()’释放的内存仍然属于进程地址空间的一部份,并将
  534. 被将来的‘malloc()’请求所重复使用。

  535. 如果你真的需要释放内存给系统,参看使用‘mmap()’分配私有匿名内存映射
  536. (private anonymous mappings)。当这些内存映射被取消映射时,内存真的将其释放给
  537. 系统。某些‘malloc()’的实现方法(比如在GNU C库中)在允许时自动使用‘mmap()’
  538. 实施大容量分配;这些内存块(blocks)随着‘free()’被释放回系统。

  539. 当然,如果你的程序的大小增加而你认为它不应该这样,你可能有一个‘内存泄
  540. 露’(‘memory leak’)- 即在你的的程序中有缺陷(bug)导致未用的内存没释放。

  541. 1.13 我怎样改变我程序的名字(即“ps”看到的名字)?
  542. =================================================

  543. 在BSD风格的系统中,‘ps’程序实际上审视运行进程的地址空间从而找到当前
  544. 的‘argv[]’,并显示它。这使得程序可以通过简单的修改‘argv[]’以改变它的
  545. 名字。

  546. 在SysV风格的系统中,命令的名字和参数的一般头80字节是存放在进程的u-区(
  547. u-area), 所以不能被直接修改。可能有一个系统调用用来修改它(不象是这样),
  548. 但是其它的话,只有一个方法就是实施一个‘exec()’,或者些内核内存(危险,
  549. 而且只有root才有可能)。

  550. 一些系统(值得注意的是Solaris)可以有‘ps’的两种不同版本,一种是在
  551. ‘/usr/bin/ps’拥有SysV的行为,而另一种在‘/usr/ucb/ps’拥有BSD的行为。在
  552. 这些系统中,如果你改变‘argv[]’,那么BSD版的‘ps’将反映这个变化,而
  553. SysV版将不会。

  554. 检查你的系统是否有一个函数‘setproctitle()’。

  555. 1.14 我怎样找到进程的相应可执行文件?
  556. =====================================

  557. 这个问题可以作为‘常见未回答问题’(‘Frequently Unanswered Questions’)的一
  558. 个好候选,因为事实上提出这个问题经常意味着程序的设计有缺陷。:)

  559. 你能作的‘最佳猜测’(‘best guess’)是通过审视‘argv[0]’的值而获得。如果
  560. 它包括一个‘/’,那么它可能是可执行程序的绝对或相对(对于在程序开始时的
  561. 当前目录而言)路径。如果不包括,那么你可以仿效shell对于‘PATH’变量的查
  562. 询来查找这个程序。但是,不能保证成功,因为有可能执行程序时‘argv[0]’是
  563. 一些任意值,也不排除这个可执行文件在执行后可能已经被更名或删除的情况。

  564. 如果所有你想做的只是能打印一个和错误消息一起出现的合适的名字,那么最好
  565. 的方法在‘main()’函数中将‘argv[0]’的值保存在全局变量中以供整个程序使
  566. 用。虽然没有保证说‘argv[0]’的值总是有意义,但在大多数情况下它是最好的
  567. 选择。

  568. 人们询问这个问题的最普通原因是意图定位他们程序的配置文件。这被认为是
  569. 不好的形式;包含可执行文件的目录应当*只*包含可执行文件,而且基于管理的
  570. 要求经常试图将配置文件放置在和可执行文件不同的文件系统。

  571. 试图做这个的一个比较不普通但更正规的理由是允许程序调用‘exec()’执行它
  572. 自己;这是一种用来完全重新初始化进程(比如被用于一些‘sendmail’的版本)的
  573. 办法(比如当一个守护程序捕获一个‘SIGHUP’信号)。

  574. 1.14.1 So where do I put my configuration files then?
  575. -----------------------------------------------------
  576. 1.14.1 那么,我把配置文件放在哪里里呢?

  577. 为配置文件安排正确的目录总是取决于你使用的Unix系统的特点;
  578. ‘/var/opt/PACKAGE’,‘/usr/local/lib’,‘/usr/local/etc’,或者任何其它一
  579. 些可能的地方。用户自定义的配置文件通常是在‘$HOME’下的以“.”开始的隐藏文件(
  580. 比如‘$HOME/.exrc’)。

  581. 从一个在不同系统上都能使用的软件包(package)的角度看,它通常意味着任何站
  582. 点范围(sitewide)的配置文件的位置有个已设定的缺省值,可能情况是使用一个在
  583. 配置脚本程序里的‘--prefix’选项(Autoconf 脚本程序集做这个工作)。你会希望允
  584. 许这个缺省值在程序执行时被一个环境变量重载。(如果你没使用配置脚本程序,
  585. 那么在编译时,将这个位置缺省值作为‘-D’选项放入项目文件(Makefile),或者
  586. 将其放入一个‘config.h’头文件,或做其它类似的工作)

  587. --

  588. 用户自定义配置需要放置于一个在‘$HOME’下的文件名“.”打头的文件,或者
  589. 在需要多个配置文件时,建立文件名“.”打头的子目录。(在列目录时,文件名以
  590. “.”打头的文件或目录缺省情况下被忽略。)避免在‘$HOME’建立多个文件,因
  591. 为这会造成非常杂乱的情况。当然,你也应该允许用户通过一个环境变量重载这个
  592. 位置。即使不能找到某个用户的配置文件,程序仍应当以适宜的方式执行。

  593. 1.15 为何父进程死时,我的进程未得到SIGHUP信号?
  594. ===============================================

  595. 因为本来就没有设想是这样做的。

  596. ‘SIGHUP’是一个信号,它按照惯例意味着“终端线路被挂断”。它与父进程
  597. 无关,而且通常由tty驱动程序产生(并传递给前台的进程组)。

  598. 但是,作为会话管理系统(session management system)的一部份,确切说有两种情况
  599. 下‘SIGHUP’会在一个进程死时发送出:

  600.    * 当一个终端设备与一个会话相关联,而这个会话的会话首领进程死时,
  601.      ‘SIGHUP’被发送至这个终端设备的所有前台进程组。

  602.    * 当一个进程死去导致一个进程组变成孤儿,而且该进程组里一个或多个进程
  603.      处于*暂停*状态时,那么‘SIGHUP’和‘SIGCONT’被发送至这个孤儿进程
  604.      组的所有成员进程。(一个孤儿进程组是指在该进程组中没有一个成员进程的
  605.      父进程属于和该进程组相同的会话的其它进程组。)

  606. 1.16 我怎样杀死一个进程的所有派生进程?
  607. =======================================

  608. 没有一个完全普遍的方法来做这个。虽然你可以通过处理‘ps’的输出确定进
  609. 程间的相互关系,但因为它只表示系统的一瞬间的状态(snapshot)所以并不可靠。

  610. 但是,如果你启动一个子进程,而它可能生成它自己的子进程,而你意图一次杀
  611. 死整个生成的事务(job),解决方法是将最先启动的子进程置于一个新的进程组,
  612. 当你需要时杀死整个进程组。

  613. 建议为创建进程组而使用的函数是‘setpgid()’。在可能情况下,使用这个函数
  614. 而不使用‘setpgrp()’,因为后一个在不同系统中有所不同(在一些系统上‘setgrp();’
  615. 等同于‘setpgid(0,0);’,在其它系统上,‘setpgrp()’和‘setpgid()’相同)。

  616. 参见范例章节的事务-控制范例程序。

  617. 放置一个子进程于其自身的进程组有一些影响。特别的,除非你显式地将该进程
  618. 组放置于前台,它将被认为是一个后台事务并具有以下结果:

  619.    * 试图从终端读取的进程将被‘SIGTTIN’信号暂停。

  620.    * 如果设置终端模式‘tostop’,那么试图向终端写的进程将被‘SIGTTOU’
  621.      信号暂停。(试图改变终端模式也导致这个结果,且不管当前‘tostop’是否
  622.      设置)

  623.    * 子进程将不会收到从终端发出的键盘信号(比如‘SIGINT’或‘SIGQUIT’)

  624. 在很多应用程序中输入和输出总会被重定向,所以最显著的影响将是丧失键盘
  625. 信号。父进程需要安排程序起码捕获‘SIGINIT’和‘SIGQUIT’(可能情况下,
  626. 还有‘SIGTERM’),并在需要情况下清除后台事务。


  627. 2. 一般文件操作(包括管道和套接字)
  628. *********************************

  629. 请同时参考套接字FAQ,在
  630. http://www.lcg.org/sock-faq/

  631. 2.1 如何管理多个连接?
  632. ======================
  633.    “我想同时监控一个以上的文件描述符(fd)/连接(connection)/流(stream),
  634. 应该怎么办?”

  635. 使用 select() 或 poll() 函数。

  636. 注意:select() 在BSD中被引入,而poll()是SysV STREAM流控制的产物。因此,
  637. 这里就有了平台移植上的考虑:纯粹的BSD系统可能仍然缺少poll(),而早一些
  638. 的SVR3系统中可能没有select(),尽管在SVR4中将其加入。目前两者都是POSIX.
  639. 1g标准,(译者注:因此在Linux上两者都存在)

  640. select()和poll()本质上来讲做的是同一件事,只是完成的方法不一样。两者都
  641. 通过检验一组文件描述符来检测是否有特定的时间将在上面发生并在一定的时间
  642. 内等待其发生。

  643. [重要事项:无论select()还是poll()都不对普通文件起很大效用,它们着重用
  644. 于套接口(socket)、管道(pipe)、伪终端(pty)、终端设备(tty)和其他一些字符
  645. 设备,但是这些操作都是系统相关(system-dependent)的。]

  646. 2.2.1 我如何使用select()函数?
  647. ------------------------------
  648. select()函数的接口主要是建立在一种叫'fd_set'类型的基础上。它('fd_set')
  649. 是一组文件描述符(fd)的集合。由于fd_set类型的长度在不同平台上不同,因此
  650. 应该用一组标准的宏定义来处理此类变量:

  651.     fd_set set;
  652.     FD_ZERO(&set);       /* 将set清零 */
  653.     FD_SET(fd, &set);    /* 将fd加入set */
  654.     FD_CLR(fd, &set);    /* 将fd从set中清除 */
  655.     FD_ISSET(fd, &set);  /* 如果fd在set中则真 */

  656. 在过去,一个fd_set通常只能包含少于等于32个文件描述符,因为fd_set其实只
  657. 用了一个int的比特矢量来实现,在大多数情况下,检查fd_set能包括任意值的
  658. 文件描述符是系统的责任,但确定你的fd_set到底能放多少有时你应该检查/修
  659. 改宏FD_SETSIZE的值。*这个值是系统相关的*,同时检查你的系统中的select()
  660. 的man手册。有一些系统对多于1024个文件描述符的支持有问题。[译者注:
  661. Linux就是这样的系统!你会发现sizeof(fd_set)的结果是128(*8 =
  662. FD_SETSIZE=1024) 尽管很少你会遇到这种情况。]

  663. select的基本接口十分简单:

  664.     int select(int nfds, fd_set *readset, fd_set *writeset,
  665.                fd_set *exceptset, struct timeval *timeout);
  666. 其中:
  667. nfds :      需要检查的文件描述符个数,数值应该比是三组fd_set中最大数
  668.             更大,而不是实际文件描述符的总数。
  669. readset:    用来检查可读性的一组文件描述符。
  670. writeset:   用来检查可写性的一组文件描述符。
  671. exceptset:  用来检查意外状态的文件描述符。(注:错误并不是意外状态)
  672. timeout:    NULL指针代表无限等待,否则是指向timeval结构的指针,代表最
  673.             长等待时间。(如果其中tv_sec和tv_usec都等于0, 则文件描述符
  674.             的状态不被影响,但函数并不挂起)

  675. 函数将返回响应操作的对应操作文件描述符的总数,且三组数据均在恰当位置被
  676. 修改,只有响应操作的那一些没有修改。接着应该用FD_ISSET宏来查找返回的文
  677. 件描述符组。

  678. 这里是一个简单的测试单个文件描述符可读性的例子:

  679.      int isready(int fd)
  680.      {
  681.          int rc;
  682.          fd_set fds;
  683.          struct timeval tv;
  684.    
  685.          FD_ZERO(&fds);
  686.          FD_SET(fd,&fds);
  687.          tv.tv_sec = tv.tv_usec = 0;
  688.    
  689.          rc = select(fd+1, &fds, NULL, NULL, &tv);
  690.          if (rc < 0)
  691.            return -1;
  692.    
  693.          return FD_ISSET(fd,&fds) ? 1 : 0;
  694.      }

  695. 当然如果我们把NULL指针作为fd_set传入的话,这就表示我们对这种操作的发生
  696. 不感兴趣,但select() 还是会等待直到其发生或者超过等待时间。

  697. [译者注:在Linux中,timeout指的是程序在非sleep状态中度过的时间,而不是
  698. 实际上过去的时间,这就会引起和非Linux平台移植上的时间不等问题。移植问
  699. 题还包括在System V风格中select()在函数退出前会把timeout设为未定义的
  700. NULL状态,而在BSD中则不是这样,Linux在这点上遵从System V,因此在重复利
  701. 用timeout指针问题上也应该注意。]

  702. 2.1.2 我如何使用poll()?
  703. ------------------------
  704. poll()接受一个指向结构'struct pollfd'列表的指针,其中包括了你想测试的
  705. 文件描述符和事件。事件由一个在结构中事件域的比特掩码确定。当前的结构在
  706. 调用后将被填写并在事件发生后返回。在SVR4(可能更早的一些版本)中的
  707. "poll.h"文件中包含了用于确定事件的一些宏定义。事件的等待时间精确到毫秒
  708. (但令人困惑的是等待时间的类型却是int),当等待时间为0时,poll()函数立即
  709. 返回,-1则使poll()一直挂起直到一个指定事件发生。下面是pollfd的结构。

  710.      struct pollfd {
  711.          int fd;        /* 文件描述符 */
  712.          short events;  /* 等待的事件 */
  713.          short revents; /* 实际发生了的事件 */
  714.      };

  715. 于select()十分相似,当返回正值时,代表满足响应事件的文件描述符的个数,
  716. 如果返回0则代表在规定事件内没有事件发生。如发现返回为负则应该立即查看
  717. errno,因为这代表有错误发生。

  718. 如果没有事件发生,revents会被清空,所以你不必多此一举。

  719. 这里是一个例子:

  720.    /* 检测两个文件描述符,分别为一般数据和高优先数据。如果事件发生
  721.       则用相关描述符和优先度调用函数handler(),无时间限制等待,直到
  722.       错误发生或描述符挂起。*/
  723.    
  724.    #include <stdlib.h>;
  725.    #include <stdio.h>;
  726.   
  727.    #include <sys/types.h>;
  728.    #include <stropts.h>;
  729.    #include <poll.h>;
  730.   
  731.    #include <unistd.h>;
  732.    #include <errno.h>;
  733.    #include <string.h>;
  734.   
  735.    #define NORMAL_DATA 1
  736.    #define HIPRI_DATA 2
  737.   
  738.    int poll_two_normal(int fd1,int fd2)
  739.    {
  740.        struct pollfd poll_list[2];
  741.        int retval;
  742.   
  743.        poll_list[0].fd = fd1;
  744.        poll_list[1].fd = fd2;
  745.        poll_list[0].events = POLLIN|POLLPRI;
  746.        poll_list[1].events = POLLIN|POLLPRI;
  747.   
  748.        while(1)
  749.        {
  750.            retval = poll(poll_list,(unsigned long)2,-1);
  751.            /* retval 总是大于0或为-1,因为我们在阻塞中工作 */
  752.   
  753.            if(retval < 0)
  754.            {
  755.                fprintf(stderr,"poll错误: %s\n",strerror(errno));
  756.                return -1;
  757.            }
  758.    
  759.            if(((poll_list[0].revents&POLLHUP) == POLLHUP) ||
  760.               ((poll_list[0].revents&POLLERR) == POLLERR) ||
  761.               ((poll_list[0].revents&POLLNVAL) == POLLNVAL) ||
  762.               ((poll_list[1].revents&POLLHUP) == POLLHUP) ||
  763.               ((poll_list[1].revents&POLLERR) == POLLERR) ||
  764.               ((poll_list[1].revents&POLLNVAL) == POLLNVAL))
  765.              return 0;
  766.   
  767.            if((poll_list[0].revents&POLLIN) == POLLIN)
  768.              handle(poll_list[0].fd,NORMAL_DATA);
  769.            if((poll_list[0].revents&POLLPRI) == POLLPRI)
  770.              handle(poll_list[0].fd,HIPRI_DATA);
  771.            if((poll_list[1].revents&POLLIN) == POLLIN)
  772.              handle(poll_list[1].fd,NORMAL_DATA);
  773.            if((poll_list[1].revents&POLLPRI) == POLLPRI)
  774.              handle(poll_list[1].fd,HIPRI_DATA);
  775.        }
  776.    }

  777. 2.1.3 我是否可以同时使用SysV IPC和select()/poll()?
  778. ---------------------------------------------------
  779. *不能。* (除非在AIX上,因为它用一个无比奇怪的方法来实现这种组合)

  780. 一般来说,同时使用select()或poll()和SysV 消息队列会带来许多麻烦。SysV
  781. IPC的对象并不是用文件描述符来处理的,所以它们不能被传递给select()和
  782. poll()。这里有几种解决方法,其粗暴程度各不相同:
  783.    - 完全放弃使用SysV IPC。 :-)
  784.    - 用fork(),然后让子进程来处理SysV IPC,然后用管道或套接口和父进程
  785.      说话。父进程则使用select()。
  786.    - 同上,但让子进程用select(),然后和父亲用消息队列交流。
  787.    - 安排进程发送消息给你,在发送消息后再发送一个信号。*警告*:要做好
  788.      这个并不简单,非常容易写出会丢失消息或引起死锁的程序。
  789. ……另外还有其他方法。

  790. 2.2 我如何才能知道和对方的连接被终止?
  791. ======================================
  792. 如果你在读取一个管道、套接口、FIFO等设备时,当写入端关闭连接时,你将会
  793. 得到一个文件结束符(EOF)(read()返回零字节读取)。如果你试图向一个管道或
  794. 套接口写入,当读取方关闭连接,你将得到一个SIGPIPE的信号,它会使进程终
  795. 止除非指定处理方法。(如果你选择屏蔽或忽略信号,write()会以EPIPE错误退
  796. 出。)

  797. 2.3 什么是读取目录的最好方法?
  798. ==============================
  799. 历史上曾有过许多不同的目录读取方法,但目前你应该使用POSIX.1标准的
  800. <dirent.h>;接口。

  801. opendir()函数打开一个指定的目录;readdir()将目录以一种标准的格式读入;
  802. closedir()关闭描述符。还有一些其他如rewinddir()、telldir()和seekdir()
  803. 等函数,相信不难理解。

  804. 如果你想用文件匹配符('*','?'),那么你可以使用大多数系统中都存在的glob()
  805. 函数,或者可以查看fnmatch()函数来得到匹配的文件名,或者用ftw()来遍历整
  806. 个目录树。

  807. 2.4 我如何才能知道一个文件被另外进程打开?
  808. ==========================================
  809. 这又是一个“经常不被回答的问题”,因为一般来说你的程序不会关心文件是否
  810. 正被别人打开。如果你需要处理文件的并发操作,那你应该使用咨询性文件锁。

  811. 一般来说要做到这点很难,像fuser或lsof这样可以告诉你文件使用情况的工具
  812. 通过解析内核数据来达到目的,但这种方法十分不健康!而且你不能从你的程序
  813. 中调用它们来获取信息,因为也许当它们执行完成之后,文件的使用状况在瞬间
  814. 又发生了变化,你无法保证这些信息的正确。

  815. 2.5 我如何锁住一个文件?
  816. ========================
  817. 有三种不同的文件锁,这三种都是“咨询性”的,也就是说它们依靠程序之间的
  818. 合作,所以一个项目中的所有程序封锁政策的一致是非常重要的,当你的程序需
  819. 要和第三方软件共享文件时应该格外地小心。

  820. 有些程序利用诸如 FIlENAME.lock 的文件锁文件,然后简单地测试此类文件是否
  821. 存在。这种方法显然不太好,因为当产生文件的进程被杀后,锁文件依然存在,
  822. 这样文件也许会被永久锁住。UUCP中把产生文件的进程号PID存入文件,但这样做
  823. 仍然不保险,因为PID的利用是回收型的。

  824. 这里是三个文件锁函数:
  825.      flock();
  826.      lockf();
  827.      fcntl();

  828. flock()是从BSD中衍生出来的,但目前在大多数UNIX系统上都能找到,在单个主
  829. 机上flock()简单有效,但它不能在NFS上工作。Perl中也有一个有点让人迷惑的
  830. flock()函数,但却是在perl内部实现的。

  831. fcntl()是唯一的符合POSIX标准的文件锁实现,所以也是唯一可移植的。它也同
  832. 时是最强大的文件锁——也是最难用的。在NFS文件系统上,fcntl()请求会被递
  833. 交给叫rpc.lockd的守护进程,然后由它负责和主机端的lockd对话,和flock()
  834. 不同,fcntl()可以实现记录层上的封锁。

  835. lockf()只是一个简化了的fcntl()文件锁接口。

  836. 无论你使用哪一种文件锁,请一定记住在锁生效之前用sync来更新你所有的文件
  837. 输入/输出。

  838.       lock(fd);
  839.       write_to(some_function_of(fd));
  840.       flush_output_to(fd); /* 在去锁之前一定要冲洗输出 */
  841.       unlock(fd);
  842.       do_something_else;   /* 也许另外一个进程会更新它 */
  843.       lock(fd);
  844.       seek(fd, somewhere); /* 因为原来的文件指针已不安全 */
  845.       do_something_with(fd);
  846.       ...

  847. 一些有用的fcntl()封锁方法(为了简洁略去错误处理):


  848.      #include <fcntl.h>;
  849.      #include <unistd.h>;
  850.    
  851.      read_lock(int fd)   /* 整个文件上的一个共享的文件锁 */
  852.      {
  853.          fcntl(fd, F_SETLKW, file_lock(F_RDLCK, SEEK_SET));
  854.      }
  855.    
  856.      write_lock(int fd)  /* 整个文件上的一个排外文件锁 */
  857.      {
  858.          fcntl(fd, F_SETLKW, file_lock(F_WRLCK, SEEK_SET));
  859.      }
  860.    
  861.      append_lock(int fd) /* 一个封锁文件结尾的锁,
  862.                             其他进程可以访问现有内容 */
  863.      {
  864.          fcntl(fd, F_SETLKW, file_lock(F_WRLCK, SEEK_END));
  865.      }

  866. 前面所用的file_lock函数如下:

  867.      struct flock* file_lock(short type, short whence)
  868.      {
  869.          static struct flock ret ;
  870.          ret.l_type = type ;
  871.          ret.l_start = 0 ;
  872.          ret.l_whence = whence ;
  873.          ret.l_len = 0 ;
  874.          ret.l_pid = getpid() ;
  875.          return &ret ;
  876.      }

  877. 2.6 我如何能发现一个文件已由另外一个进程更新?
  878. ==============================================
  879. 这又几乎是一个经常不被回答的问题,因为问这个问题的人通常期待能有一个系
  880. 统级的告示来反映当前目录或文件被修改,但没有什么保证移植性的实现方法,
  881. IRIX有一个非标准的功能用来监测文件操作,但从未听说在其他平台上也有相类
  882. 似的功能。

  883. 一般来说,你能尽的最大努力就是用fstat()函数,通过监视文件的mtime和ctime
  884. 你能得知文件什么时候被修改了,或者被删除/连接/改名了,听起来很复杂,所
  885. 以你应该反思一下为什么你要做这些。

  886. 2.7 请问du是怎样工作的?
  887. ========================
  888. du只简单地用stat()(更准确地说是用lstat()函数)遍历目录结构中的每个文件
  889. 和目录,并将它们所占用的磁盘块加在一起。

  890. 如果你想知道其中细节,总是这么一句话:“读下源代码吧,老兄!”

  891. BSD(FreeBSD、NetBSD和OpenBSD)的源代码在这些发行的FTP网站的源码目录里,
  892. GNU版本的源码当然可以在任何一个GNU镜像站点中找到——前提是你自己懂得如
  893. 何解包。

  894. 2.8 我如何得到一个文件的长度?
  895. ==============================
  896. 用stat()或在文件打开后用fstat()。

  897. 这两个调用会将文件信息填入一个结构中, 其中你能找到诸如文件主人、属性、
  898. 大小、最后访问时间、最后修改时间等所有关于此文件的东西。

  899. 下面的程序大体示范如何用stat()得到文件大小。

  900.      #include <stdlib.h>;
  901.      #include <stdio.h>;
  902.    
  903.      #include <sys/types.h>;
  904.      #include <sys/stat.h>;
  905.    
  906.      int get_file_size(char *path,off_t *size)
  907.      {
  908.        struct stat file_stats;
  909.    
  910.        if(stat(path,&file_stats))
  911.          return -1;
  912.    
  913.        *size = file_stats.st_size;
  914.        return 0;
  915.      }

  916. 2.9 我如何像shell里一样扩展在文件名里的'~'?
  917. ============================================
  918. '~'的标准用法如下:如果单独使用或者后面跟一个'/',那么'~'就被当作当前
  919. 用户的home目录,[译者注:事实上'~'就被替换为$HOME环境变量],如果'~'后
  920. 直接跟一个用户名,则被替换的就是那个用户的home目录。如果没有合适的匹
  921. 配,则shell不会做任何改动。

  922. 请注意,有可能一些文件的确是以'~'打头的,不分青红皂白地将'~'替换会使你
  923. 的程序无法打开这些文件。一般来说,从shell通过命令行或环境变量传递入程
  924. 序的文件名不须要进行替换,因为shell已经替你做好,而程序自己生成的、用
  925. 户输入的,或从配置文件中读取的却应该进行替换。

  926. 这里是一段用标准string类的C++实现:

  927.    string expand_path(const string& path)
  928.    {
  929.        if (path.length() == 0 || path[0] != '~')
  930.          return path;
  931.   
  932.        const char *pfx = NULL;
  933.        string::size_type pos = path.find_first_of('/');
  934.   
  935.        if (path.length() == 1 || pos == 1)
  936.        {
  937.            pfx = getenv("HOME");
  938.            if (!pfx)
  939.            {
  940.                // 我们想替换"~/",但$HOME却没有设置
  941.                struct passwd *pw = getpwuid(getuid());
  942.                if (pw)
  943.                  pfx = pw->;pw_dir;
  944.            }
  945.        }
  946.        else
  947.        {
  948.            string user(path,1,(pos==string::npos) ? string::npos : pos-1);
  949.            struct passwd *pw = getpwnam(user.c_str());
  950.            if (pw)
  951.              pfx = pw->;pw_dir;
  952.        }
  953.        // 如果我们不能找到能替换的选择,则将path返回
  954.   
  955.        if (!pfx)
  956.          return path;
  957.   
  958.        string result(pfx);
  959.   
  960.        if (pos == string::npos)
  961.          return result;
  962.   
  963.        if (result.length() == 0 || result[result.length()-1] != '/')
  964.          result += '/';
  965.   
  966.        result += path.substr(pos+1);
  967.   
  968.        return result;
  969.    }

  970. 2.10 有名管道(FIFO)能做什么?
  971. =============================

  972. 2.10.1 什么是有名管道?
  973. -----------------------
  974. 有名管道是一个能在互不相关进程之间传送数据的特殊文件。一个或多个进程向
  975. 内写入数据,在另一端由一个进程负责读出。有名管道是在文件系统中可见的,
  976. 也就是说ls可以直接看到。(有名管道又称FIFO,也就是先入先出。)

  977. 有名管道可以将无关的进程联系起来,而无名的普通管道一般只能将父子进程联
  978. 系起来——除非你很努力地去尝试——当然也能联系两个无关进程。有名管道是
  979. 严格单向的,尽管在一些系统中无名管道是双向的。

  980. 2.10.2 我如何建立一个有名管道?
  981. -------------------------------
  982. 在shell下交互地建立一个有名管道,你可以用mknod或mkfifo命令。在有些系统
  983. 中,mknod产生的文件可能在/etc目录下,也就是说,可能不在你的目录下出现,
  984. 所以请查看你系统中的man手册。[译者注:在Linux下,可以看一下fifo(4)]

  985. 要在程序中建立一个有名管道:

  986.    /* 明确设置umask,因为你不知道谁会读写管道 */
  987.    umask(0);
  988.    if (mkfifo("test_fifo", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP))
  989.    {
  990.        perror("mkfifo");
  991.        exit(1);
  992.    }

  993. 也可以使用mknod。[译者注:在Linux下不推荐使用mknod,因为其中有许多臭虫
  994. 在NFS下工作更要小心,能使用mkfifo就不要用mknod,因为mkfifo()是POSIX.1
  995. 标准。]

  996.    /* 明确设置umask,因为你不知道谁会读写管道 */
  997.    umask(0);
  998.    if (mknod("test_fifo",
  999.               S_IFIFO | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP,
  1000.               0))
  1001.    {
  1002.        perror("mknod");
  1003.        exit(1);
  1004.    }

  1005. 2.10.3 我如何使用一个有名管道?
  1006. -------------------------------
  1007. 使用有名管道十分简单:你如同使用一个普通文件一样打开它,用read()和
  1008. write()进行操作。但对管道使用open()时可能引起阻塞,下面一些常用规律可
  1009. 以参考。

  1010.    * 如果你同时用读写方式(O_RDWR)方式打开,则不会引起阻塞。
  1011.    * 如果你用只读方式(O_RDONLY)方式打开,则open()会阻塞一直到有写方打
  1012.      开管道,除非你指定了O_NONBLOCK,来保证打开成功。
  1013.    * 同样以写方式(O_WRONLY)打开也会阻塞到有读方打开管道,不同的是如果
  1014.      O_NONBLOCK被指定open()会以失败告终。

  1015. 当对有名管道进行读写的时,注意点和操作普通管道和套接字时一样:当写入方
  1016. 关闭连接时read()返回EOF,如果没有听众write()会得到一个SIGPIPE的信号,
  1017. 对此信号进行屏蔽或忽略会引发一个EPIPE错误退出。

  1018. 2.10.4 能否在NFS上使用有名管道?
  1019. --------------------------------
  1020. 不能,在NFS协议中没有相关功能。(你可能可以在一个NFS文件系统中用有名管
  1021. 道联系两个同在客户端的进程。)

  1022. 2.10.5 能否让多个进程同时向有名管道内写入数据?
  1023. -----------------------------------------------
  1024. 如果每次写入的数据少于PIPE_BUF的大小,那么就不会出现数据交叉的情况。但
  1025. 由于对写入的多少没有限制,而read()操作会读取尽可能多的数据,因此你不能
  1026. 知道数据到底是谁写入的。

  1027. PIPE_BUF的大小根据POSIX标准不能小于512,一些系统里在<limits.h>;中被定
  1028. 义,[译者注:Linux中不是,其值是4096。]这可以通过pathconf()或fpathconf()
  1029. 对单独管道进行咨询得到。

  1030. 2.10.6 有名管道的应用
  1031. ---------------------
  1032.     “我如何时间服务器和多个客户端的双向交流?”

  1033. 一对多的形式经常出现,只要每次客户端向服务器发出的指令小于PIPE_BUF,它
  1034. 们就可以通过一个有名管道向服务器发送数据。客户端可以很容易地知道服务器
  1035. 传发数据的管道名。

  1036. 但问题在于,服务器不能用一个管道来和所有客户打交道。如果不止一个客户在
  1037. 读一个管道,是无法确保每个客户都得到自己对应的回复。

  1038. 一个办法就是每个客户在向服务器发送信息前都建立自己的读入管道,或让服务
  1039. 器在得到数据后建立管道。使用客户的进程号(pid)作为管道名是一种常用的方
  1040. 法。客户可以先把自己的进程号告诉服务器,然后到那个以自己进程号命名的管
  1041. 道中读取回复。


  1042. 3. 终端输入/输出
  1043. ****************

  1044. 3.1 我怎样使我的程序不回射输入?
  1045. ================================

  1046.      我怎样能使我的程序不回射输入,就象登录时询问我的口令时那样?

  1047. 有一个简单方法,也有一个稍微复杂点的方法:

  1048. 简单方法是使用‘getpass()’函数,它几乎能在所有Unix系统上找到。它以一个
  1049. 给定的字符串参数作为提示符(prompt)。它读取输入直到读到一个‘EOF’或换
  1050. 行符(译者注:‘EOF’用‘^d’输入,而换行符为‘^m’或回车)然后返回一个
  1051. 指向位于静态内存区包含键入字符的字符串指针。(译者注:字符串不包含换行
  1052. 符)

  1053. 复杂一点的方法是使用‘tcgetattr()’函数和‘tcsetattr()’函数,两个函数都使用
  1054. 一个‘struct termios’结构来操纵终端。下面这两段程序应当能设置回射状态和
  1055. 不回射状态。

  1056.      #include <stdlib.h>;
  1057.      #include <stdio.h>;

  1058.      #include <termios.h>;
  1059.      #include <string.h>;

  1060.      static struct termios stored_settings;

  1061.      void echo_off(void)
  1062.      {
  1063.          struct termios new_settings;
  1064.          tcgetattr(0,&stored_settings);
  1065.          new_settings = stored_settings;
  1066.          new_settings.c_lflag &= (~ECHO);
  1067.          tcsetattr(0,TCSANOW,&new_settings);
  1068.          return;
  1069.      }

  1070.      void echo_on(void)
  1071.      {
  1072.          tcsetattr(0,TCSANOW,&stored_settings);
  1073.          return;
  1074.      }

  1075. 两段程序使用到的都是在POSIX标准定义的。

  1076. 3.2 我怎样从终端读取单个字符?
  1077. ==============================

  1078.      我怎样从终端读取单个字符?我的程序总是要等着用户按回车。

  1079. 终端通常在标准(canonical)模式,在此模式输入总是经编辑后以行读入。你可以
  1080. 设置终端为非标准(non-canonical)模式,而在此模式下你可以设置在输入传递给
  1081. 你的程序前读入多少字符。你也可以设定非标准模式的计时器为0,这个计时器
  1082. 根据设定的时间间隔清空你的缓冲区。这样做使你可以使用‘getc()’函数立即
  1083. 获得用户的按键输入。我们使用的‘tcgetattr()’函数和‘tcsetattr()’函数都
  1084. 是在POSIX中定义用来操纵‘termios’结构的。

  1085.      #include <stdlib.h>;
  1086.      #include <stdio.h>;

  1087.      #include <termios.h>;
  1088.      #include <string.h>;

  1089.      static struct termios stored_settings;

  1090.      void set_keypress(void)
  1091.      {
  1092.          struct termios new_settings;

  1093.          tcgetattr(0,&stored_settings);

  1094.          new_settings = stored_settings;

  1095.          /* Disable canonical mode, and set buffer size to 1 byte */
  1096.          new_settings.c_lflag &= (~ICANON);
  1097.          new_settings.c_cc[VTIME] = 0;
  1098.          new_settings.c_cc[VMIN] = 1;

  1099.          tcsetattr(0,TCSANOW,&new_settings);
  1100.          return;
  1101.      }

  1102.      void reset_keypress(void)
  1103.      {
  1104.          tcsetattr(0,TCSANOW,&stored_settings);
  1105.          return;
  1106.      }

  1107. 3.3 我怎样检查是否一个键被摁下?
  1108. ================================

  1109.     我怎样检查是否一个键被摁下?在DOS上我用‘kbhit()’函数,但是在UNIX
  1110.     上看来没有相同作用的函数?

  1111. 如果你设定了终端为单一字符模式(参见上一个问题解答),那么(在大多数系统)
  1112. 上你可以使用‘select()’函数或‘poll()’函数测试输入是否可读。

  1113. 3.4 我怎样将光标在屏幕里移动?
  1114. ==============================

  1115.      我怎样将光标在屏幕里移动?我想不用curses而做全屏编辑。(译者注:curses
  1116.      是一个C/C++编程工具库,它提供编程者许多函数调用,在不用关心终端类型
  1117.      的情况下操纵终端的显示)。

  1118. 不开玩笑,你也许不应该想去做这个。Curses工具库知道怎样控制不同终端类型
  1119. 所表现出的奇特的东西(oddities);当然termcap/terminfo数据会告诉你任何终端类型
  1120. 具有的这些奇特东西,但你可能会发现正确把握所有这些奇特组合是一件艰巨的
  1121. 工作。(译者注:在Linux系统上,termcap数据位于/etc/termcap,而terminfo数据位于
  1122. /usr/share/terminfo下按不同终端类型首字母存放的不同文件,目前终端类型数已逾
  1123. 两千种)

  1124. 但是,你坚决要把你自己搞的手忙脚乱(getting your hands dirty),那么去研究一下
  1125. ‘termcap’的函数集,特别是‘tputs()’,‘tparm()’和‘tgoto()’函数。

  1126. 3.5 pttys是什么?
  1127. =================

  1128. Pseudo-teletypes(pttys, ptys,或其它不同的缩写)是具有两部份的伪设备(pseudo-devices):
  1129. 一部份为“主人”一边,你可以认为是一个‘用户’,另一部份是“仆人”一边,
  1130. 它象一个标准的tty设备一样工作。

  1131. 它们之所以存在是为了提供在程序控制下的一种模拟串行终端行为的方法。比
  1132. 如,‘telnet’在远端系统使用一个伪终端;服务器的远端登录shell程序只是从“仆
  1133. 人”一边的tty设备期待着得到操作行为,而在“主人”一边的伪终端由一个守护程
  1134. 序控制,同时守护程序将所有数据通过网络转发。pttys也被其它程序使用,比如
  1135. ‘xterm’,‘expect’,‘script’,‘screen’,‘emacs’和其它很多程序。

  1136. 3.6 怎样控制一个串行口和调制解调器?
  1137. ====================================

  1138. Unix系统下对于串行口的控制很大程度上受串行终端传统的使用影响。以往,
  1139. 需要不同ioctls函数调用的组合和其它黑客行为才能控制一个串行设备的正确操
  1140. 作,不过值得庆幸的是,POSIX在这个方面的标准化作了一些努力。

  1141. 如果你使用的系统不支持‘<termios.h>;’头文件,‘tcsetattr()’和其它相关函数,
  1142. 那么你只能到其它地方去找点资料(或将你的老古董系统升级一下)。

  1143. 但是不同的系统仍然有显著的区别,主要是在设备名,硬件流控制的操作,和
  1144. 调制解调器的信号方面。(只要可能,尽量让设备驱动程序去做握手(handshaking)
  1145. 工作,而不要试图直接操纵握手信号。)

  1146. 打开和初始华串行设备的基本步骤是:

  1147.     * 调用‘open()’函数打开设备;而且可能需要使用特定标志作为参数:

  1148.     `O_NONBLOCK'
  1149.           除非使用这个标志,否则打开一个供拨入(dial-in)或由调制解调器控制的设
  1150.           备会造成‘open()’调用被阻塞直到线路接通(carrier is present)。一个非
  1151.           阻塞的打开操作给你在需要时令调制解调器控制失效的机会。(参见下面的
  1152.           CLOCAL)

  1153.     `O_NOCTTY'
  1154.           在自4.4BSD演化的系统上这个标志是多余的,但在其它系统上它控制串行
  1155.           设备是否成为会话的控制终端。在大多数情况下你可能不想获得一个控制
  1156.           终端,所以就要设置这个标志,但是也有例外情况。

  1157.    * 调用‘tcgetattr()’函数获得当前设备模式。虽然有人会经常取消(ignore)得到的
  1158.       大多数或全部初始设定,但它仍然不失为一个初始化‘struct termios’结构的
  1159.       便利方法。

  1160.    * 设置termios 结构里‘c_iflag’,‘c_oflag’,‘c_flag’,‘c_lfag’和‘c_cc’
  1161.       为合适的值。(参见下面部分。)

  1162.    * 调用‘cfsetispeed()’和‘cfsetospeed()’设定设想的波特率。很少系统允许你
  1163.      设置不同的输入和输出速度,所以普通规律是你需要设成同一个设想的值。

  1164.    * 调用‘tcsetattr()’设定设备模式。

  1165.    * 如果你是用‘O_NONBLOCK’标志打开的端口,你可能希望调用‘fcntl()’
  1166.      函数将‘O_NONBLOCK’标志重新设置成关闭。因为不同系统看来对是否
  1167.      由非阻塞打开的端口对今后的‘read()’调用造成影响有不同的处理;所以
  1168.      最好显式地设置好。

  1169. 一旦你打开并设置了端口,你可以正常地调用‘read()’函数和‘write()’函数。
  1170. 注意到‘read()’函数的行为将受到你调用‘tcsetattr()’函数时的标志参数设定
  1171. 的控制。

  1172. ‘tcflush()’,‘tcdrain()’,‘tcsendbreak()’和‘tcflow()’是其它一些你应当
  1173. 注意的函数。

  1174. 当你使用完端口想要关闭时,要注意一些系统上特别恶心的小危险;如果有任何
  1175. 输出数据等待被写到设备(比如输出流被硬件或软件握手而暂停),你的进程将因
  1176. 为‘close()’函数调用而被挂起(hang)直到输出数据排空,而且这时的进程是*不
  1177. 可杀的*(unkillably)。所以调用‘tcflush()’函数丢弃待发数据可能是个明智之举。

  1178. (在我的经验中,在tty设备上被阻塞的输出数据是造成不可杀进程最普通的原因。)
复制代码

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
8 [报告]
发表于 2003-06-29 21:42 |只看该作者

原 C/C++ 论坛 FAQ,尚未整理。

//faint,忒长了。



  1. 3.6.1 串行设备和类型
  2. --------------------

  3. 不同系统用于串行端口设备的设备名大相径庭。以下是不同系统的一些例子:

  4.    * ‘/dev/tty[0-9][a-z]’作为直接访问设备,而
  5.      ‘/dev/tty[0-9][A-Z]’ 作为调制解调器控制设备(比如SCO Unix)

  6.    * ‘/dev/cua[0-9]p[0-9]’作为直接访问设备,‘/dev/cul[0-9]p[0-9]’作为拨出设
  7.       备,而‘/dev/ttyd[0-9]p[0-9]’作为拨入设备(比如HP-UX)

  8.    * ‘/dev/cua[a-z][0-9]’作为拨出设备而‘/dev/tty[a-z][0-9]’作为拨入设备(比如
  9.       FreeBSD)

  10. 是否正确地同所用设备名交互,并在任何硬件握手信号线上产生相应的效果是
  11. 与系统,配置和硬件有关的,但是差不多总是遵循下面这些规则(假设硬件是
  12. RS-232 DTE):

  13.    - 对于任何设备的一个成功打开操作应当设置DTR和RTS

  14.    - 一个对于由调制解调器控制或供拨入的设备的阻塞打开操作将等待DCD(并且
  15.      可能DSR和/或CTS也需要)被设置,通常在设置DTR/RTS之后。

  16.    - 如果一个对于拨出设备的打开操作正巧赶上一个对于相应拨入设备的打开操作
  17.      因为等待线路接通而阻塞,那么打开拨出的操作*也许*造成打开拨入的操作完
  18.      成,但*也许也不*造成。一些系统为拨入和拨出端口实现一个简单的共享方案,
  19.      当拨出端口在使用时,拨入端口被有效地设置成睡眠状态(“put to sleep”);其
  20.      它系统不这样做,在这种系统上为避免竞争(contention)问题,需要外部的协助才
  21.      能使拨入和拨出共享端口(比如UUCP锁定文件的使用)。

  22. 3.6.2 设置termios的标志位
  23. -------------------------

  24. 这里是对你在使用你自己打开的串行设备时设置termios标志的一些提示(即与你使
  25. 用已存在的控制tty相反)

  26. 3.6.2.1 c_iflag
  27. ...............

  28. 你也许希望将*所有*‘c_iflag’的标志位设成0,除非你希望使用软件流控制(ick),
  29. 在这种情况下你设置‘IXON’和‘IXOFF’。(译者注:有三个标志控制流控制:
  30. IXON,IXOFF ,和IXANY,如果IXON被设置,那么tty输入队列的软件流控制
  31. 被设置。当程序无法跟上输入队列的速度,tty传输一个STOP字符,而当输入队
  32. 列差不多空时发送START字符。如果IXON被设置,那么tty输出队列的软件流控
  33. 制被设置。当tty所连接的设备跟不上输出速度,tty将阻塞程序对tty的写操作。如果
  34. IXANY被设置,那么一旦tty从设备收到任何字符,被暂定的输出将继续 - 译自SCO
  35. Unix 网上文档http://uw7doc.sco.com/SDK_sysprog/CTOC-TermDevCntl.html,“TTY flow
  36. control”章节,第五,六段)

  37. 3.6.2.2 c_oflag
  38. ...............

  39. 大部分‘c_oflag’的标志位是为了能使对于慢终端的输出可以正常工作所做的这
  40. 样或那样的黑客行为,由此,一些较新的系统认为几乎所有这些标志位已经过
  41. 时从而摈弃了它们(特别是所有血淋淋(gory)的输出排列对齐(output-padding)选项)。
  42. 如同‘c_iflag’,将它设置成全0对于大部分应用程序来说是合理的。

  43. 3.6.2.3 c_cflag
  44. ...............

  45. 当设置字符的大小时,记住首先使用‘CSIZE’屏蔽,比如设置8位字符,需要:

  46.          attr.c_cflag &= ~CSIZE;
  47.          attr.c_cflag |= CS8;

  48. 在‘c_cflag’里的其它你有可能需要设置为*真*的标志包括‘CREAD’和
  49. ‘HUPCL’。

  50. 如果你需要产生偶校验,那么设置‘PARENB’并清除‘PARODD’;如果你
  51. 需要产生奇校验,那么同时设置‘PARENB’和‘PARODD’。如果你根本不
  52. 想设置校验,那么确认清除‘PARENB’。

  53. 清除‘CSTOPB’ ,除非你真需要产生两个停止位。

  54. 设置硬件流控制的标志可能也能在‘c_cflag’中找到,但它们不是被标准化的(
  55. 这是一个遗憾)

  56. 3.6.2.4 c_lflag
  57. ...............

  58. 大部分应用程序可能需要关闭‘ICANON’(标准状态,即基于行的,并进行输
  59. 入处理),‘ECHO’和‘ISIG’。

  60. ‘IEXTEN’是个更复杂的问题。如果你不把它关闭,具体实现允许你作一些非
  61. 标准的事情(比如在‘c_cc’中定义增加的控制字符)从而可能导致不可预料的接
  62. 果,但是在一些系统上,你可能需要保持‘IEXTEN’标志为真以得到一些有用
  63. 的特征,比如硬件流控制。

  64. 3.6.2.5 c_cc
  65. ............

  66. 这是一个包括输入中带有特殊含义字符的数组。这些字符被命名为‘VINTR’,
  67. ‘VSTOP’等等;这些名字是这个数组的索引。

  68. (这些字符中的两个其实根本不是字符,而是当‘ICANON’被关闭时对于
  69. ‘read()’函数行为的控制;它们是‘VMIN’和‘VTIME’。)

  70. 这些索引名字经常被提及的方式会让人以为它们是实在的变量,比如“设置
  71. VMIN为1” 其实意味着“设置c_cc[VMIN]为1”。这种简写是有用的并且只是
  72. 偶尔引起误会。

  73. ‘c_cc’的很多变量位置只有当其它标志被设定时才会用到。

  74. 只有‘ICANON’被设置,才用到以下变量:
  75.      ‘VEOF’,‘VEOL’,‘VERASE’,‘VKILL’(如果定义了而且
  76.      ‘IEXTEN’被设定,那么‘VEOL2’,‘VSTATUS’和‘VWERASE’
  77.         也用到)

  78. 只有‘ISIG’被设置,才用到以下变量:
  79.      ‘VINTR’,‘VQUIT’,‘VSUSP’(如果定义了而且‘IEXTEN’被设定,
  80.        那么‘VDSUSP’也用到)

  81. 只有‘IXON’或‘IXOFF’被设置,才用到以下变量:
  82.      ‘VSTOP’,‘VSTART’

  83. 只有‘ICANON’被取消,才用到以下变量:
  84.      ‘VMIN’,‘VTIME’

  85. 不同系统实现会定义增加的‘c_cc’变量。谨慎的做法是在设定你希望使用的值
  86. 以前,使用‘_POSIX_VDISABLE’初始化这些变量(常量‘NCCS’提供这个数
  87. 组的大小)

  88. ‘VMIN’和‘VTIME’(根据不同的实现方法,它们有可能和‘VEOF’和‘VEOL’
  89. 分享相同两个变量)具有以下含义。‘VTIME’的值(如果不为0)总是被解释为以十
  90. 分之一秒为单位的计时器)(译者注:VTIME变量是一个字节长,所以1表示0.1秒,
  91. 最大为255,表示25.5秒)

  92. `c_cc[VMIN] >; 0, c_cc[VTIME] >; 0'
  93.      只要输入已经有VMIN字节长,或者输入至少有一个字符而在读取最后一个字
  94.      符之前VTIME已经过期,或者被信号中断,‘read()’将返回。

  95. `c_cc[VMIN] >; 0, c_cc[VTIME] == 0'
  96.      只要输入已经有VMIN字节长,或者被信号中断,‘read()’将返回。否则,将
  97.      无限等待下去。

  98. `c_cc[VMIN] == 0, c_cc[VTIME] >; 0'
  99.      只要有输入‘read()’就返回;如果VTIME过期却没有数据,它会返回没有读
  100.      到字符。(这和调制解调器挂断时的文件结束标志有一点冲突;使用1作为VMIN,
  101.      调用‘alarm()’或‘select()’函数并给定超时参数可以避免这个问题。)

  102. `c_cc[VMIN] == 0, c_cc[VTIME] == 0'
  103.       ‘read()’总是立刻返回;如果没有数据则返回没有读到字符。(与上面的问题
  104.         相同)

  105. 4. 系统信息
  106. ***********

  107. 4.1 怎样知道我的系统有多少存储器容量?
  108. =====================================

  109. 这是另一个‘经常未回答的问题’。在多数情况下,你不该试图去找到答案.

  110. 如果你必需得到答案,问题的答案通常是有的,但非常依赖于不同的操作系统。
  111. 例如,在Solaris中,可以用 `sysconf(_SC_PHYS_PAGES)' 和 `sysconf(_SC_PAGESIZE)';
  112. 在FreeBSD中,可以用`sysctl()'; 在Linux中可以通过读取并处理`/proc/meminfo'得到
  113. (使用该文件时需小心你的程序,它要接受历史上任何不同合法格式). 其它的操作
  114. 系统有各自的方式,我也没有意识到更多可移植的方法。

  115. 在HP-UX(9版和10版)中,可以使用如下的代码:

  116.      struct pst_static pst;

  117.      if (pstat_getstatic(&pst, sizeof(pst), (size_t) 1, 0) != -1)
  118.      {
  119.          printf(" Page Size: %lu\n", pst.page_size);
  120.          printf("Phys Pages: %lu\n", pst.physical_memory);
  121.      }

  122. 4.2 我怎样检查一个用户的口令?
  123. =============================

  124. 4.2.1 我怎样得到一个用户的口令?
  125. -------------------------------

  126. 在多数的UNIX系统中, 用户口令通常存放在`/etc/passwd'文件中.  该文件一般是
  127. 这种格式:

  128. 用户名:口令:用户号:用户组号:注释:用户目录:登录shell
  129. (username:password:uid:gid:gecos field:home directory:login shell)

  130. 但是这些标准随着时间而不断改变, 现在的用户信息可能存放在其它机器上, 或
  131. 者说并不存放在 `/etc/passwd' 文件中。 当今系统实现也使用 `shadow' 文件保存用
  132. 户口令以及一些敏感信息. 该文件只允许有特定权限的用户读取.

  133. 为安全考虑,用户口令一般是加密的,而不是用明文表示的。

  134. POSIX 定义了一组访问用户信息的函数. 取得一个用户信息的最快方式是使用`getpwnam()'
  135. 和`getpwuid()' 函数. 这两个函数都返回一个结构指针, 该结构包含了用户的所有信
  136. 息. `getpwnam()' 接收用户名字符串(username), `getpwuid()' 接收用户号(uid),
  137. (`uid_t'类型在POSIX中有定义). 若调用失败则返回NULL.

  138. 但是, 正如前面所讲, 当今的操作系统都有一个shadow文件存放敏感信息,即用户口令。
  139. 有些系统当调用者用户号是超级用户时返回用户口令, 其它用户要求你使用其它方式存取
  140. shadow文件. 这时你可以使用`getspnam()', 通过输入用户名得到一个有关用户信息的结
  141. 构. 再者, 为了能够成功的完成这些, 你需要有一定的权限. (在一些系统中, 如HP-UX和
  142. SCO, 你可以用`getprpwnam()'代替。)

  143. 4.2.2 我怎样通过用户号得到阴影口令文件中的口令?
  144. -----------------------------------------------

  145.       我的系统使用一组getsp*函数获得重要用户信息的. 然而, 我没有`getspuid()',
  146.       只有`getspnam()'. 我怎样做才能通过用户号获得用户信息呢?

  147. 变通方法是相对非常容易的。下面的函数可以直接放入你个人的应用函数库:

  148.      #include <stdlib.h>;
  149.      #include <stdio.h>;

  150.      #include <pwd.h>;
  151.      #include <shadow.h>;

  152.      struct spwd *getspuid(uid_t pw_uid)
  153.      {
  154.        struct spwd *shadow;
  155.        struct passwd *ppasswd;

  156.        if( ((ppasswd = getpwuid(pw_uid)) == NULL)
  157.            || ((shadow = getspnam(ppasswd->;pw_name)) == NULL))
  158.          return NULL;

  159.        return shadow;
  160.      }

  161. 现在的问题是, 有些系统在阴影文件中并不保存用户号(uid)以及其它的信息。

  162. 4.2.3 我怎样核对一个用户的口令?
  163. -------------------------------

  164. 一个基本的问题是, 存在各种各样的认证系统, 所以口令也就并不总是象它们看上去
  165. 那样。 在传统的系统中, 使用UNIX风格的加密算法,加密算法是不同的,有些系统使
  166. 用DES(译者注:DES:Data Encryption Standard,为NIST[National Institute of
  167. Standard and Technology]确认的标准加密算法,最新消息表明,NIST将采用一种新
  168. 的加密标准Rijndael逐步取代DES)算法,其它的系统, 如FreeBSD国际版使用MD5(译者
  169. 注:MD5是当今最为广泛使用的单项散列算法,由Ron Rivest发明,详细资料参见RFC 1321
  170. http://www.faqs.org/rfcs/rfc1321.html)算法。

  171. 最常用的方法是使用一种单项加密算法(译者注:即单项散列[Hash]算法)。输入的
  172. 明文口令被加密,然后与文件中存放的加密口令比较。怎样加密的详细信息可以
  173. 查看`crypt()'的手册页, 这里有一个通常做法的版本:

  174.      /*  输入明文口令和加密口令, 检查是否匹配,
  175.       *  成功返回 1, 其它情况返回 0。
  176.      */

  177.      int check_pass(const char *plainpw, const char *cryptpw)
  178.      {
  179.          return strcmp(crypt(plainpw,cryptpw), cryptpw) == 0;
  180.      }

  181. 这个函数之所以能工作是因为加密函数使用的添加(salt)字串存放在加密口令字串的前部。

  182. *警告:* 在一些系统中, 口令加密是使用一种‘crypt()’的变体,即‘bigcrypt()’函数。


  183. 5. 编程杂技
  184. ***********

  185. 5.1 我怎样使用通配字符比较字符串?
  186. ==================================

  187. 对于它的回答依赖于你所谓‘通配字符’一词的确切含义。

  188. 有两种很不相同的概念被认定为‘通配字符’。它们是:

  189. *文件名通配模式*(filename patterns)
  190.      这是shell用来进行文件名匹配替换的(expansion)(或称为‘globbing’)

  191. *正则表达式*
  192.      这是供编辑器用的,比如‘grep’,等等。它是用来匹配正文,而它们正常
  193.      情况下不应用于文件名。

  194. 5.1.1 我怎样使用文件名通配模式比较字符串?
  195. ------------------------------------------

  196. 除非你不走运,你的系统应该有函数‘fnmatch()’供你进行文件名匹配。它一
  197. 般只允许Bourne Shell风格的模式。它识别‘*’,‘[...]’和‘?’,但可能不
  198. 支持在Korn和Bourne-Again shell程序下才有的更神秘(arcane)的模式。

  199. 如果你没有这个函数,那么比闭门造车更好的方法是你可以从BSD或GNU原程
  200. 序那里去抄(snarfing)一个过来。

  201. 而且,在普通的匹配实际文件名情况下,查阅‘glob()’函数,它将搜索到匹配
  202. 一个给定模式的所有存在文件。

  203. 5.1.2 我怎样使用正则表达式比较字符串?
  204. --------------------------------------

  205. 有很多稍有句法不同的正则表达式;大部分系统起码使用两种:一种是‘ed’
  206. 程序可以识别的,有时候被记作‘基本正则表达式’,另一种是‘egrep’程序
  207. 可以识别的,记作‘扩充正则表达式’。Perl(译者注:Perl: Practical Extract and
  208. Report Language,实用析取与报表语言)语言拥有它自己稍有不同的风格,Emacs
  209. 也是。

  210. 为了支持这么多格式,相应的有很多实现。系统一般有正则表达式匹配函数(通
  211. 常为‘regcomp()’函数和‘regexec()’函数)提供,但是要小心使用;有些系统
  212. 有超过一种实现可用,附之以不同的接口。另外,还有很多可用的软件库的实
  213. 现(顺便说一下,一般都是将一个正则表达式编译成内部形式然后再使用,因为
  214. 总是假设你有很多字符串要比较同一正则表达式。)

  215. 一个可用的软件库是‘rx’软件库,从GNU的镜像站点可以得到。它看来是正在
  216. 开发中,基于你不同的观点这是一件又好又不好的事情 :-)

  217. 5.2 什么是在程序中发送电子邮件的最好方法?
  218. ==========================================

  219. 有好几种从Unix程序发电子邮件的方法。根据不同情况最好的选择有所不同,
  220. 所以我将提供两个方法。还有第三种方法,这里没有说道,是连接本地主机的SMTP
  221. (译者注:SMTP:Simple Mail Transfer Protocol简单邮件传输协议)端口并直接使
  222. 用SMTP协议,参见RFC 821(译者注:RFC:Request For Comments)。

  223. 5.2.1 简单方法:/bin/mail
  224. -------------------------

  225. 对于简单应用,执行‘mail’程序已经是足够了(通常是‘/bin/mail’,但一些系统
  226. 上有可能是‘/usr/bin/mail’)。

  227. *警告:*UCB Mail程序的一些版本甚至在非交互模式下也会执行在消息体(message
  228. body)中以‘~!’或‘~|’打头的行所表示的命令。这可能有安全上的风险。

  229. 象这样执行:‘mail -s '标题' 收件人地址 ...’,程序将把标准输入作为消息体,
  230. 并提供缺省得消息头(其中包括已设定的标题),然后传递整个消息给‘sendmail’
  231. 进行投递。

  232. 这个范例程序在本地主机上发送一封测试消息给‘root’:

  233.      #include <stdio.h>;

  234.      #define MAILPROG "/bin/mail"

  235.      int main()
  236.      {
  237.          FILE *mail = popen(MAILPROG " -s 'Test Message' root", "w");
  238.          if (!mail)
  239.          {
  240.              perror("popen");
  241.              exit(1);
  242.          }

  243.          fprintf(mail, "This is a test.\n");

  244.          if (pclose(mail))
  245.          {
  246.              fprintf(stderr, "mail failed!\n");
  247.              exit(1);
  248.          }
  249.      }

  250. 如果要发送的正文已经保存在一个文件中,那么可以这样做:

  251.          system(MAILPROG " -s 'file contents' root </tmp/filename");

  252. 这个方法可以扩展到更复杂的情况,但是得当心很多潜在的危险(pitfalls):

  253.    * 如果使用system()或popen(),你必须非常当心将参数括起来从而保护它们不被
  254.      错误的进行文件名匹配替换或单词分割。

  255.    * 基于用户设置数据来构造命令行是缓冲区越界错误和其它安全漏洞的普遍原
  256.      因。

  257.    * 这种方法不允许设定CC:(译者注:CC:Carbon Copy 抄送)或 BCC:(译者注:
  258.      BCC:Blind Carbon Copy:盲送,指投递地址不在消息中出现的抄送)的收件人。
  259.      (一些/bin/mail程序的版本允许,其它则不允许)

  260. 5.2.2 直接启动邮件传输代理(译者注:MTA: mail transfer agent):/usr/bin/sendmail
  261. -------------------------------------------------------------------------------

  262. ‘mail’程序是“邮件用户代理”(Mail User Agent)的一个例子,它旨在供用户
  263. 执行以收发电子邮件,但它并不负责实际的传输。一个用来传输邮件的程序被
  264. 称为“邮件传输代理”(MTA),而在Unix系统普遍能找到的邮件传输代理被称为
  265. ‘sendmail’。也有其它在使用的邮件传输代理,比如‘MMDF’,但这些程序
  266. 通常包括一个程序来模拟‘sendmail’的普通做法。

  267. 历史上,‘sendmail’通常在‘/usr/lib’里找到,但现在的趋势是将应用库程序从
  268. ‘/usr/lib’挪出,并挪入比如‘/usr/sbin’或‘/usr/libexec’等目录。结果是,一般
  269. 总是以绝对路径启动‘sendmail’程序,而路径是由系统决定的。

  270. 为了了解‘sendmail’程序怎样工作,通常需要了解一下“信封”(envelope)的概
  271. 念。这非常类似书面信件;信封上定义这个消息投递给谁,并注明由谁发出(
  272. 为了报告错误的目的)。在信封中包含的是“消息头”和“消息体”,之间由一个
  273. 空行隔开。消息头的格式主要在RFC 822中提供;并且参见MIME 的RFC文档。(
  274. 译者注:MIME的文档包括:RFC1521,RFC1652)

  275. 有两种主要的方法使用‘sendmail’程序以生成一个消息:要么信封的收件人能
  276. 被显式的提供,要么‘sendmail’程序可被指示从消息头中推理出它们。两种方
  277. 法都有优缺点。

  278. 5.2.2.1 显式提供信封内容
  279. .........................

  280. 消息的信封内容可在命令行上简单的设定。它的缺点在于邮件地址可能包含的
  281. 字符会造成‘system()’和‘popen() ’程序可观的以外出错(grief),比如单引号,
  282. 被括起的字符串等等。传递这些指令给shell程序并成功解释可以预见潜在的危
  283. 险。(可以将命令中任何一个单引号替换成单引号、反斜杠、单引号、单引号的
  284. 顺序组合,然后再将整个地址括上单引号。可怕,呃?)

  285. 以上的一些不愉快可以通过避开使用‘system()’或‘popen()’函数并求助于‘
  286. fork()’和‘exec()’函数而避免。这有时不管怎样也是需要的;比如,用户
  287. 自定义的对于SIGCHLD信号的处理函数通常会打断‘pclose()’函数从而影响到
  288. 或大或小的范围。

  289. 这里是一个范例程序:

  290.      #include <sys/types.h>;
  291.      #include <sys/wait.h>;
  292.      #include <unistd.h>;
  293.      #include <stdlib.h>;
  294.      #include <fcntl.h>;
  295.      #include <sysexits.h>;
  296.      /* #include <paths.h>; 如果你有的话 */

  297.      #ifndef _PATH_SENDMAIL
  298.      #define _PATH_SENDMAIL "/usr/lib/sendmail"
  299.      #endif

  300.      /* -oi 意味着 "不要视‘ .’为消息的终止符"
  301.       * 删除这个选项 ,"--" 如果使用sendmail 8版以前的版本 (并希望没有人
  302.       * 曾经使用过一个以减号开头的收件人地址)
  303.       * 你也许希望加 -oem (report errors by mail,以邮件方式报告错误)
  304.       */

  305.      #define SENDMAIL_OPTS "-oi","--"

  306.      /* 下面是一个返回数组中的成员数的宏 */

  307.      #define countof(a) ((sizeof(a))/sizeof((a)[0]))

  308.      /* 发送由FD所包含以读操作打开的文件之内容至设定的收件人;前提是这
  309.       * 个文件中包含RFC822定义的消息头和消息体,收件人列表由NULL指针
  310.       * 标志结束;如果发现错误则返回-1,否则返回sendmail的返回值(它使用
  311.       * <sysexits.h>;中提供的有意义的返回代码)
  312.       */

  313.      int send_message(int fd, const char **recipients)
  314.      {
  315.          static const char *argv_init[] = { _PATH_SENDMAIL, SENDMAIL_OPTS };
  316.          const char **argvec = NULL;
  317.          int num_recip = 0;
  318.          pid_t pid;
  319.          int rc;
  320.          int status;

  321.          /* 计算收件人数目 */

  322.          while (recipients[num_recip])
  323.              ++num_recip;

  324.          if (!num_recip)
  325.              return 0;    /* 视无收件人为成功 */

  326.          /* 分配空间给参数矢量 */

  327.          argvec = malloc((sizeof char*) * (num_recip+countof(argv_init)+1));
  328.          if (!argvec)
  329.              return -1;

  330.          /* 初始化参数矢量 */

  331.          memcpy(argvec, argv_init, sizeof(argv_init));
  332.          memcpy(argvec+countof(argv_init),
  333.                 recipients, num_recip*sizeof(char*));
  334.          argvec[num_recip + countof(argv_init)] = NULL;

  335.          /* 需要在此增加一些信号阻塞 */

  336.          /* 产生子进程 */

  337.          switch (pid = fork())
  338.          {
  339.          case 0:   /* 子进程 */

  340.              /* 建立管道 */
  341.              if (fd != STDIN_FILENO)
  342.                  dup2(fd, STDIN_FILENO);

  343.              /* 其它地方已定义 -- 关闭所有>;=参数的文件描述符对应的参数 */
  344.              closeall(3);

  345.              /* 发送: */
  346.              execv(_PATH_SENDMAIL, argvec);
  347.              _exit(EX_OSFILE);

  348.          default:  /* 父进程 */

  349.              free(argvec);
  350.              rc = waitpid(pid, &status, 0);
  351.              if (rc < 0)
  352.                  return -1;
  353.              if (WIFEXITED(status))
  354.                  return WEXITSTATUS(status);
  355.              return -1;

  356.          case -1:  /* 错误 */
  357.              free(argvec);
  358.              return -1;
  359.          }
  360.      }

  361. 5.2.2.2 允许sendmail程序推理出收件人
  362. .....................................

  363. ‘sendmail’的‘-t’选项指令‘sendmail’程序处理消息的头信息,并使用所有
  364. 包含收件人(即:‘To:’,‘Cc:’和‘Bcc:’)的头信息建立收件人列表。它的优
  365. 点在于简化了‘sendmail’的命令行,但也使得设置在消息头信息中所列以外的
  366. 收件人成为不可能。(这通常不是一个问题)

  367. 作为一个范例,以下这个程序将标准输入作为一个文件以MIME附件方式发送给
  368. 设定的收件人。为简洁起见略去了一些错误检查。这个程序需要调用‘metamail’
  369. 分发程序包的‘mimecode’程序。

  370.      #include <stdio.h>;
  371.      #include <unistd.h>;
  372.      #include <fcntl.h>;
  373.      /* #include <paths.h>; 如果你有的话 */

  374.      #ifndef _PATH_SENDMAIL
  375.      #define _PATH_SENDMAIL "/usr/lib/sendmail"
  376.      #endif

  377.      #define SENDMAIL_OPTS "-oi"
  378.      #define countof(a) ((sizeof(a))/sizeof((a)[0]))

  379.      char tfilename[L_tmpnam];
  380.      char command[128+L_tmpnam];

  381.      void cleanup(void)
  382.      {
  383.          unlink(tfilename);
  384.      }

  385.      int main(int argc, char **argv)
  386.      {
  387.          FILE *msg;
  388.          int i;

  389.          if (argc < 2)
  390.          {
  391.              fprintf(stderr, "usage: %s recipients...\n", argv[0]);
  392.              exit(2);
  393.          }

  394.          if (tmpnam(tfilename) == NULL
  395.              || (msg = fopen(tfilename,"w")) == NULL)
  396.              exit(2);

  397.          atexit(cleanup);

  398.          fclose(msg);
  399.          msg = fopen(tfilename,"a");
  400.          if (!msg)
  401.              exit(2);

  402.          /* 建立收件人列表 */

  403.          fprintf(msg, "To: %s", argv[1]);
  404.          for (i = 2; i < argc; i++)
  405.              fprintf(msg, ",\n\t%s", argv[i]);
  406.          fputc('\n',msg);

  407.          /* 标题 */

  408.          fprintf(msg, "Subject: file sent by mail\n");

  409.          /* sendmail程序会自动添加 From:, Date:, Message-ID: 等消息头信息 */

  410.          /* MIME的处理 */

  411.          fprintf(msg, "MIME-Version: 1.0\n");
  412.          fprintf(msg, "Content-Type: application/octet-stream\n");
  413.          fprintf(msg, "Content-Transfer-Encoding: base64\n");

  414.          /* 消息头结束,加一个空行 */

  415.          fputc('\n',msg);
  416.          fclose(msg);

  417.          /* 执行编码程序 */

  418.          sprintf(command, "mimencode -b >;>;%s", tfilename);
  419.          if (system(command))
  420.              exit(1);

  421.          /* 执行信使程序 */

  422.          sprintf(command, "%s %s -t <%s",
  423.                  _PATH_SENDMAIL, SENDMAIL_OPTS, tfilename);
  424.          if (system(command))
  425.              exit(1);

  426.          return 0;
  427.      }


  428. 6. 工具的使用
  429. *************

  430. 6.1 我怎样调试fork函数产生的子进程?
  431. ====================================

  432. 根据可用的工具有两种不同的方法:

  433. 你的调试器(debugger)可能有允许你选择是否跟踪调用‘fork()’以后的父或子进程
  434. 的选项,对于某些目的来说那已经足够了。

  435. 替换方法是,你的调试器可能有一个选项允许你将它依附(attach)到一个正在执行
  436. 的程序。这样你可以依附调试器到一个已经开始执行的子进程。如果你不需要从
  437. 子进程一开始就开始测试,这通常已经足够。否则,你会希望在子进程的‘fork()’
  438. 调用后插入一个‘sleep()’调用,或者插入如下的循环:

  439.      {
  440.          volatile int f = 1;
  441.          while(f);
  442.      }

  443. 这样子进程将一直在此循环不往下执行直到你用调试器设定‘f’为0。

  444. 并且记住,使用调试器并非是找到你程序中错误的唯一方法;在很多Unix系统
  445. 上有一些工具程序可用来跟踪系统调用和信号,而且丰富的日志经常也是有
  446. 用的。

  447. 6.2 怎样通过其他库文件建立新的库文件?
  448. ======================================

  449. 前提是我们所说的是归档(archive)(静态)库,最简单的方法是将所有选择的库
  450. 文件使用‘ar x’命令在一个空目录里拆分成它们原始的单个目标文件(object),
  451. 然后再合并成一个。当然,文件名有可能会重复,但是如果这个库文件很大,
  452. 你也许一开始就不想将它们合并在一起。

  453. 6.3 怎样创建动态连接库(shared library)/dlls?
  454. =============================================

  455. 创建动态连接库(shared libraries)的方法根据不同的系统有所不同。这个过程主要
  456. 分两步;第一步要求包括在动态连接库中的目标必须首先是编译好的,通常需
  457. 要某个编译选项指示这串代码是位置无关的(position-indepenent);第二步,是将
  458. 这些目标连接在一起形成一个库文件。

  459. 这里是一个演示以上道理的小程序:

  460.      /* shrobj.c 文件 */

  461.      const char *myfunc()
  462.      {
  463.          return "Hello World";
  464.      }

  465.      /* shrobj.c 结束 */

  466.      /* hello.c 文件 */

  467.      #include <stdio.h>;

  468.      extern const char *myfunc();

  469.      main()
  470.      {
  471.          printf("%s\n", myfunc());
  472.          return 0;
  473.      }

  474.      /* hello.c 结束 */

  475.      $ gcc -fpic -c shrobj.c
  476.      $ gcc -shared -o libshared.so shrobj.o
  477.      $ gcc hello.c libshared.so
  478.      $ ./a.out
  479.      Hello World

  480. 到目前为止,如果你希望库文件和它的创建过程是都可以移植的话,那么最好
  481. 的办法是使用GNU Libtool程序包。它是个小型的工具程序套件,这些工具程序
  482. 知道建立动态连接库的平台无关性;你可以只发布你的程序必要的部分,从而
  483. 当一个安装者配置你的软件包时,他能决定生成什么库。Libtool程序包在不支持
  484. 动态连接库的系统上也能工作得很好。它而且知道与GNU Autoconf程序和GNU
  485. Automake程序挂钩(如果你使用这些工具来管理你程序的编译创建过程)。

  486. 如果你不想使用Libtool程序包,那么对于gcc以外的编译器,你需要按照下面所
  487. 列修改编译器参数:

  488. AIX 3.2 使用 xlc (未证实)
  489.      取消‘-fpic’选项,以‘-bM:SRE -bE:libshared.exp’取代‘-shared’。你并且
  490.      需要创建一个名为‘libshared.exp’的文件保存一个所有输出符号(symbols to export)
  491.      的列表,比如以上的范例程序,就需要输出‘myfunc’符号。另外,在连接库
  492.      时使用‘-e _nostart’参数(在较新的AIX版本上,我相信应该将其变成‘-bnoentry’)。

  493. SCO OpenServer 5 使用 SCO 开发系统(Development System) (未证实)
  494.      如果你使用ELF(译者注:ELF:执行与连接格式Executable and Linking Forrmat,
  495.      一种Unix可执行目标文件的格式)格式,那么共享库只能在OS5上可用,而它
  496.      需要‘-belf’选项。并以‘-Kpic’取代‘-fpic’,在连接时使用‘cc -belf -G’。

  497. Solaris 使用 SparcWorks 编译器
  498.      以‘-pic’取代‘-fpic’,并以‘ld -G’取代‘gcc -shared’。

  499. (鼓励大家提供更多的材料丰富上述列表)


  500. 其它要当心的问题:

  501.    * AIX和(我相信)Digital Unix不需要-fpic选项,因为所有代码都是位置无关的。

  502.    * AIX一般需要你创建一个‘输出文件’,即一个保存所有动态连接库中输出
  503.      符号的列表。一些连接器(linker)版本(可能只有SLHS连接器,是svld?)有一个
  504.      选项可以输出所有符号。

  505.    * 如果你对于连接器想使用普遍的‘-l’参数来引用你的动态连接库,你必须
  506.      理解你的系统在实际运行时是怎样寻找动态连接库的。最普通的方法是使用
  507.      ‘LD_LIBRARY_PATH’环境变量,但是通常在连接时有一种其它选项可以
  508.      设定。

  509.    * 大多数实现方法是在程序内部记录所希望的动态连接库在运行时的位置。这
  510.      样把一个动态连接库从一个目录移到另一个目录将导致程序无法工作。许多
  511.      系统对于连接器有一个选项用以设定希望运行时动态连接库的位置(比如在
  512.      Solaris系统上是‘-R’连接器选项,或者是‘LD_RUN_PATH’环境变量)。

  513.    * ELF和a.out的实现方法可能有一个连接器选项‘-Bsymbolic’,它导致在库本
  514.      身内部的引用被解释好。否则,在这些系统上,所有符号的解释将推迟到最
  515.      后连接时再进行,这样在main程序中的单一函数将重载库中的对应函数。

  516. 6.4 我能更改一个动态连接库里的目标吗?
  517. ======================================

  518. 一般不行。

  519. 在大多数系统上(除了AIX),当你连接目标并形成一个动态连接库时,它就象连
  520. 接一个可执行程序;目标并不保留它们单一的特征。结果是,一般不能从一个动
  521. 态连接库里析取出或更换一个单一的目标。

复制代码

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
9 [报告]
发表于 2003-06-29 21:47 |只看该作者

原 C/C++ 论坛 FAQ,尚未整理。


  1. 6.5 我能在一个运行着的程序中生成堆栈映象吗?
  2. ============================================

  3. 一些系统提供库函数可以提取(unwinding)出堆栈,从而(比如)你可以在一个错误
  4. 处理函数中生成一个堆栈映象,但是只有一小部分系统有这些函数。

  5. 一个可能的变通方法(workaround)是让你的程序执行一个调试器调试*它自己* -
  6. 详细方法仍然根据不同系统稍有不同,但一般的概念是这样:

  7.      void dump_stack(void)
  8.      {
  9.          char s[160];

  10.          sprintf(s, "/bin/echo 'where\ndetach' | dbx -a %d", getpid());
  11.          system(s);

  12.          return;
  13.      }

  14. 你需要根据你不同的系统对dbx的参数和命令进行加工,或者甚至换另一个调试
  15. 器,比如‘gdb’,但这仍然是我见过的对于这种问题最普遍的解决方法。为此,
  16. 荣誉授予Ralph Corderoy。

  17. 下面列表包含在一些系统上需要用到的命令行:

  18. 大多数使用dbx的系统
  19.     `"/bin/echo 'where\ndetach' | dbx /path/to/program %d"'

  20. AIX
  21.      `"/bin/echo 'where\ndetach' | dbx -a %d"'

  22. IRIX
  23.      `"/bin/echo 'where\ndetach' | dbx -p %d"'
  24.   ?
  25. 范例程序
  26. ********

  27. 捕获 SIGCHLD 信号
  28. =================

  29.      #include <sys/types.h>;  /* 在任何其它 sys 下的头文件之前引用这个头文件 */
  30.      #include <sys/wait.h>;   /* waitpid()和一些不同的宏所需的头文件 */
  31.      #include <signal.h>;     /* 信号函数的头文件 */
  32.      #include <stdio.h>;      /* fprintf函数的头文件 */
  33.      #include <unistd.h>;     /* fork函数的头文件 */   
  34.      void sig_chld(int);     /* 我们的 SIGCHLD 信号处理函数的原形(prototype) */
  35.    
  36.      int main()
  37.      {
  38.          struct sigaction act;
  39.          pid_t pid;
  40.    
  41.          /* 设定sig_chld函数作为我们SIGCHLD信号的处理函数 */
  42.          act.sa_handler = sig_chld;
  43.    
  44.          /* 在这个范例程序里,我们不想阻塞其它信号 */
  45.          sigemptyset(&act.sa_mask);
  46.    
  47.          /*
  48.           * 我们只关谋恢罩沟淖咏?蹋??皇潜恢卸?          * 的子进程 (比如用户在终端上按Control-Z)
  49.           */
  50.          act.sa_flags = SA_NOCLDSTOP;
  51.    
  52.          /*
  53.           * 使这些设定的值生效. 如果我们是写一个真实的应用程序,
  54.           * 我们也许应该保存这些原有值,而不是传递一个NULL。
  55.           */
  56.          if (sigaction(SIGCHLD, &act, NULL) < 0)
  57.          {
  58.              fprintf(stderr, "sigaction failed\n");
  59.              return 1;
  60.          }
  61.    
  62.          /* fork */
  63.          switch (pid = fork())
  64.          {
  65.          case -1:
  66.              fprintf(stderr, "fork failed\n");
  67.              return 1;
  68.    
  69.          case 0:                         /* 是子进程,直接结束 */
  70.              _exit(7);                   /* 退出状态 = 7 */
  71.    
  72.          default:                        /* 父进程 */
  73.              sleep(10);                  /* 给子进程完成的时间 */
  74.          }
  75.    
  76.          return 0;
  77.      }
  78.    
  79.      /*
  80.       * 信号处理函数 -- 只有当接收到一个SIGCHLD信号才被调用,
  81.       * 即有一个子进程终止
  82.       */
  83.      void sig_chld(int signo)
  84.      {
  85.          int status, child_val;
  86.    
  87.          /* 非阻塞地等待任何子进程结束 */
  88.          if (waitpid(-1, &status, WNOHANG) < 0)
  89.          {
  90.              /*
  91.               * 不建议在信号处理函数中调用标准输入/输出函数,
  92.               * 但在一个类似这个的玩具程序里或许没问题
  93.               */
  94.              fprintf(stderr, "waitpid failed\n");
  95.              return;
  96.          }
  97.    
  98.          /*
  99.           * 我们现在有保存在‘status’变量中的子进程退出信息并可以使用
  100.           * wait.h中定义的宏对其进行操作
  101.           */
  102.          if (WIFEXITED(status))                /* 子进程是正常退出吗? */
  103.          {
  104.              child_val = WEXITSTATUS(status); /* 获取子进程的退出状态 */
  105.              printf("child's exited normally with status %d\n", child_val);
  106.          }
  107.      }

  108. 读取进程表 - SUNOS 4 版
  109. =======================

  110.      #define _KMEMUSER
  111.      #include <sys/proc.h>;
  112.      #include <kvm.h>;
  113.      #include <fcntl.h>;
  114.    
  115.      char regexpstr[256];
  116.      #define INIT            register char *sp=regexpstr;
  117.      #define GETC()          (*sp++)
  118.      #define PEEKC()         (*sp)
  119.      #define UNGETC(c)       (--sp)
  120.      #define RETURN(pointer) return(pointer);
  121.      #define ERROR(val)
  122.      #include <regexp.h>;
  123.    
  124.      pid_t
  125.      getpidbyname(char *name,pid_t skipit)
  126.      {
  127.          kvm_t *kd;
  128.          char **arg;
  129.          int error;
  130.          char *p_name=NULL;
  131.          char expbuf[256];
  132.          char **freeme;
  133.          int curpid;
  134.          struct user * cur_user;
  135.          struct user myuser;
  136.          struct proc * cur_proc;
  137.    
  138.    
  139.          if((kd=kvm_open(NULL,NULL,NULL,O_RDONLY,NULL))==NULL){
  140.              return(-1);
  141.          }
  142.          sprintf(regexpstr,"^.*/%s$",name);
  143.          compile(NULL,expbuf,expbuf+256,'\0');
  144.    
  145.          while(cur_proc=kvm_nextproc(kd)){
  146.              curpid = cur_proc->;p_pid;
  147.              if((cur_user=kvm_getu(kd,cur_proc))!=NULL){
  148.                  error=kvm_getcmd(kd,cur_proc,cur_user,&arg,NULL);
  149.                  if(error==-1){
  150.                      if(cur_user->;u_comm[0]!='\0'){
  151.                          p_name=cur_user->;u_comm;
  152.                      }
  153.                  }
  154.                  else{
  155.                      p_name=arg[0];
  156.                  }
  157.              }
  158.              if(p_name){
  159.                  if(!strcmp(p_name,name)){
  160.                      if(error!=-1){
  161.                          free(arg);
  162.                      }
  163.                      if(skipit!=-1 && ourretval==skipit){
  164.                          ourretval=-1;
  165.                      }
  166.                      else{
  167.                          close(fd);
  168.                          break;
  169.                      }
  170.                      break;
  171.                  }
  172.                  else{
  173.                      if(step(p_name,expbuf)){
  174.                          if(error!=-1){
  175.                              free(arg);
  176.                          }
  177.                          break;
  178.                      }
  179.                  }
  180.              }
  181.              if(error!=-1){
  182.                  free(arg);
  183.              }
  184.              p_name=NULL;
  185.          }
  186.          kvm_close(kd);
  187.          if(p_name!=NULL){
  188.              return(curpid);
  189.          }
  190.          return (-1);
  191.      }

  192. 读取进程表 - SYSV 版
  193. ====================

  194.      pid_t
  195.      getpidbyname(char *name,pid_t skipit)
  196.      {
  197.          DIR  *dp;
  198.          struct dirent *dirp;
  199.          prpsinfo_t retval;
  200.          int fd;
  201.          pid_t ourretval=-1;
  202.    
  203.          if((dp=opendir("/proc"))==NULL){
  204.              return -1;
  205.          }
  206.          chdir("/proc");
  207.          while((dirp=readdir(dp))!=NULL){
  208.              if(dirp->;d_name[0]!='.'){
  209.                  if((fd=open(dirp->;d_name,O_RDONLY))!=-1){
  210.                      if(ioctl(fd,PIOCPSINFO,&retval)!=-1){
  211.                          if(!strcmp(retval.pr_fname,name)){
  212.                              ourretval=(pid_t)atoi(dirp->;d_name);
  213.                              if(skipit!=-1 && ourretval==skipit){
  214.                                  ourretval=-1;
  215.                              }
  216.                              else{
  217.                                  close(fd);
  218.                                  break;
  219.                              }
  220.                          }
  221.                      }
  222.                      close(fd);
  223.                  }
  224.              }
  225.          }
  226.          closedir(dp);
  227.          return ourretval;
  228.      }

  229. 读取进程表 - AIX 4.2 版
  230. =======================

  231.      #include <stdio.h>;
  232.      #include <procinfo.h>;
  233.    
  234.      int getprocs(struct procsinfo *, int, struct fdsinfo *,
  235.                   int, pid_t *, int);
  236.    
  237.      pid_t getpidbyname(char *name, pid_t *nextPid)
  238.      {
  239.        struct procsinfo  pi;
  240.        pid_t             retval = (pid_t) -1;
  241.        pid_t             pid;
  242.    
  243.        pid = *nextPid;
  244.    
  245.        while(1)
  246.        {
  247.          if(getprocs(&pi, sizeof pi, 0, 0, &pid, 1) != 1)
  248.            break;
  249.    
  250.          if(!strcmp(name, pi.pi_comm))
  251.          {
  252.            retval = pi.pi_pid;
  253.            *nextPid = pid;
  254.            break;
  255.          }
  256.        }
  257.    
  258.        return retval;
  259.      }
  260.    
  261.      int main(int argc, char *argv[])
  262.      {
  263.        int   curArg;
  264.        pid_t pid;
  265.        pid_t nextPid;
  266.    
  267.        if(argc == 1)
  268.        {
  269.          printf("syntax: %s <program>; [program ...]\n",argv[0]);
  270.          exit(1);
  271.        }
  272.    
  273.        for(curArg = 1; curArg < argc; curArg++)
  274.        {
  275.          printf("Process IDs for %s\n", argv[curArg]);
  276.    
  277.          for(nextPid = 0, pid = 0; pid != -1; )
  278.            if((pid = getpidbyname(argv[curArg], &nextPid)) != -1)
  279.              printf("\t%d\n", pid);
  280.        }
  281.      }

  282. 使用popen函数和ps命令读取进程表
  283. ===============================

  284.      #include <stdio.h>;      /* FILE, sprintf, fgets, puts */
  285.      #include <stdlib.h>;     /* atoi, exit, EXIT_SUCCESS */
  286.      #include <string.h>;     /* strtok, strcmp */
  287.      #include <sys/types.h>;  /* pid_t */
  288.      #include <sys/wait.h>;   /* WIFEXITED, WEXITSTATUS */
  289.    
  290.      char *procname(pid_t pid)
  291.      {
  292.         static char line[133], command[80], *linep, *token, *cmd;
  293.         FILE *fp;
  294.         int status;
  295.    
  296.         if (0 == pid) return (char *)0;
  297.    
  298.         sprintf(command, "ps -p %d 2>;/dev/null", pid);
  299.         fp = popen(command, "r");
  300.         if ((FILE *)0 == fp) return (char *)0;
  301.    
  302.         /* 读取标题行 */
  303.         if ((char *)0 == fgets(line, sizeof line, fp))
  304.         {
  305.            pclose(fp);
  306.            return (char *)0;
  307.         }
  308.    
  309.         /* 从标题栏分析出命令名所在列。
  310.          * (BSD风格的系统将指示命令的"COMMAND"字符串放在第5列,SysV好象将
  311.          * 指示命令的“CMD”或“COMMAND”字符串放在第4列)
  312.          */
  313.         for (linep = line; ; linep = (char *)0)
  314.         {
  315.            if ((char *)0 == (token = strtok(linep, " \t\n")))
  316.            {
  317.               pclose(fp);
  318.               return (char *)0;
  319.            }
  320.            if (0 == strcmp("COMMAND", token) || 0 == strcmp("CMD", token))
  321.            { /*  我们找到COMMAND所在列 */
  322.               cmd = token;
  323.               break;
  324.            }
  325.         }
  326.    
  327.         /* 读取 ps(1) 输出行 */
  328.         if ((char *)0 == fgets(line, sizeof line, fp))
  329.         {
  330.            pclose(fp);
  331.            return (char *)0;
  332.         }
  333.    
  334.         /* 抓COMMAND标题下面的词 ... */
  335.         if ((char *)0 == (token = strtok(cmd, " \t\n")))
  336.         {
  337.            pclose(fp);
  338.            return (char *)0;
  339.         }
  340.    
  341.         status = pclose(fp);
  342.         if (!WIFEXITED(status) || 0 != WEXITSTATUS(status))
  343.           return (char *)0;
  344.    
  345.         return token;
  346.      }
  347.    
  348.      int main(int argc, char *argv[])
  349.      {
  350.         puts(procname(atoi(argv[1])));
  351.         exit(EXIT_SUCCESS);
  352.      }

  353. 守护程序工具函数
  354. ================

  355.      #include <unistd.h>;
  356.      #include <stdlib.h>;
  357.      #include <fcntl.h>;
  358.      #include <signal.h>;
  359.      #include <sys/types.h>;
  360.      #include <sys/wait.h>;
  361.      #include <errno.h>;
  362.    
  363.      /* closeall() -- 关闭所有>;=给定值的文件描述符 */
  364.    
  365.      void closeall(int fd)
  366.      {
  367.          int fdlimit = sysconf(_SC_OPEN_MAX);
  368.    
  369.          while (fd < fdlimit)
  370.            close(fd++);
  371.      }
  372.    
  373.      /* daemon() - 将进程从用户端脱离并消失进入后台,若失败返回-1,
  374.       * 但是在那种情况下你只能退出,因为我们可能已经生成了子进程。
  375.       * 这是基于BSD的版本,所以调用方需负责类似umask等等其它的工作。
  376.       */
  377.    
  378.      /* 相信在所有Posix系统上都能工作 */
  379.    
  380.      int daemon(int nochdir, int noclose)
  381.      {
  382.          switch (fork())
  383.          {
  384.              case 0:  break;
  385.              case -1: return -1;
  386.              default: _exit(0);          /* 原进程退出 */
  387.          }
  388.    
  389.          if (setsid() < 0)               /* 不应该失败 */
  390.            return -1;
  391.    
  392.          /* 如果你希望将来获得一个控制tty,则排除(dyke)以下的switch语句 */
  393.          /* -- 正常情况不建议用于守护程序 */
  394.    
  395.          switch (fork())
  396.          {
  397.              case 0:  break;
  398.              case -1: return -1;
  399.              default: _exit(0);
  400.          }
  401.    
  402.          if (!nochdir)
  403.            chdir("/");
  404.    
  405.          if (!noclose)
  406.          {
  407.              closeall(0);
  408.              open("/dev/null",O_RDWR);
  409.              dup(0); dup(0);
  410.          }
  411.    
  412.          return 0;
  413.      }
  414.    
  415.      /* fork2() -- 类似fork函数,但子进程立刻变成孤儿进程
  416.       *            (当它退出时不产生僵死进程)
  417.       * 返回1给父进程,不是任何有意义的进程号.
  418.       * 父进程不能使用wait函数等待子进程结束 (它们是无关的).
  419.       */
  420.    
  421.      /* 这个版本假设你没有捕获和忽略SIGCHLD信号. */
  422.      /* 如果你有设定,则不管怎样应使用fork函数 */
  423.    
  424.      int fork2()
  425.      {
  426.          pid_t pid;
  427.          int rc;
  428.          int status;
  429.    
  430.          if (!(pid = fork()))
  431.          {
  432.              switch (fork())
  433.              {
  434.                case 0:  return 0;
  435.                case -1: _exit(errno);    /* 假设错误码都小于256 */
  436.                default: _exit(0);
  437.              }
  438.          }
  439.    
  440.          if (pid < 0 || waitpid(pid,&status,0) < 0)
  441.            return -1;
  442.    
  443.          if (WIFEXITED(status))
  444.            if (WEXITSTATUS(status) == 0)
  445.              return 1;
  446.            else
  447.              errno = WEXITSTATUS(status);
  448.          else
  449.            errno = EINTR;  /* 唉,类似这个 :-) */
  450.    
  451.          return -1;
  452.      }

  453. 一个使用以上函数的范例程序:

  454.      #include <sys/types.h>;
  455.      #include <sys/socket.h>;
  456.      #include <netinet/in.h>;
  457.      #include <stdio.h>;
  458.      #include <stdlib.h>;
  459.      #include <syslog.h>;
  460.      #include <errno.h>;
  461.    
  462.      int daemon(int,int);
  463.      int fork2(void);
  464.      void closeall(int);
  465.    
  466.      #define TCP_PORT 8888
  467.    
  468.      void errexit(const char *str)
  469.      {
  470.          syslog(LOG_INFO, "%s failed: %d (%m)", str, errno);
  471.          exit(1);
  472.      }
  473.    
  474.      void errreport(const char *str)
  475.      {
  476.          syslog(LOG_INFO, "%s failed: %d (%m)", str, errno);
  477.      }
  478.    
  479.      /* 实际的子进程在此. */
  480.    
  481.      void run_child(int sock)
  482.      {
  483.          FILE *in = fdopen(sock,"r");
  484.          FILE *out = fdopen(sock,"w");
  485.          int ch;
  486.    
  487.          setvbuf(in, NULL, _IOFBF, 1024);
  488.          setvbuf(out, NULL, _IOLBF, 1024);
  489.    
  490.          while ((ch = fgetc(in)) != EOF)
  491.            fputc(toupper(ch), out);
  492.    
  493.          fclose(out);
  494.      }
  495.    
  496.      /* 这是守护程序的主要工作 -- 侦听连接并生成子进程 */
  497.    
  498.      void process()
  499.      {
  500.          struct sockaddr_in addr;
  501.          int addrlen = sizeof(addr);
  502.          int sock = socket(AF_INET, SOCK_STREAM, 0);
  503.          int flag = 1;
  504.          int rc = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
  505.                              &flag, sizeof(flag));
  506.    
  507.          if (rc < 0)
  508.            errexit("setsockopt");
  509.    
  510.          addr.sin_family = AF_INET;
  511.          addr.sin_port = htons(TCP_PORT);
  512.          addr.sin_addr.s_addr = INADDR_ANY;
  513.    
  514.          rc = bind(sock, (struct sockaddr *) &addr, addrlen);
  515.          if (rc < 0)
  516.            errexit("bind");
  517.    
  518.          rc = listen(sock, 5);
  519.          if (rc < 0)
  520.            errexit("listen");
  521.    
  522.          for (;;)
  523.          {
  524.              rc = accept(sock, (struct sockaddr *) &addr, &addrlen);
  525.    
  526.              if (rc >;= 0)
  527.                switch (fork2())
  528.                {
  529.                  case 0:  close(sock); run_child(rc); _exit(0);
  530.                  case -1: errreport("fork2"); close(rc); break;
  531.                  default: close(rc);
  532.                }
  533.          }
  534.      }
  535.    
  536.      int main()
  537.      {
  538.          if (daemon(0,0) < 0)
  539.          {
  540.              perror("daemon");
  541.              exit(2);
  542.          }
  543.    
  544.          openlog("test", LOG_PID, LOG_DAEMON);
  545.    
  546.          process();
  547.    
  548.          return 0;
  549.      }

  550. 调制解调器控制范例程序
  551. ======================

  552.      /* 发出一些简单调制解调器命令
  553.       * 需要串行设备的设备名 (最好是拨出设备,
  554.       * 或者是非调制解调器控制设备) 作为它唯一的参数.
  555.       * 如果你没有可共使用的拨出设备, 那么以CFLAGS_TO_SET取代CLOCAL。
  556.       */
  557.    
  558.      #include <stdio.h>;
  559.      #include <stdlib.h>;
  560.      #include <fcntl.h>;
  561.      #include <unistd.h>;
  562.      #include <sys/types.h>;
  563.      #include <sys/time.h>;
  564.      #include <sys/ioctl.h>;   /* 也许需要;和系统有关 */
  565.      #include <termios.h>;
  566.      #include <errno.h>;
  567.      #include <string.h>;
  568.      #include <ctype.h>;
  569.    
  570.      #define CFLAGS_TO_SET (CREAD | HUPCL)
  571.      #define CFLAGS_TO_CLEAR (CSTOPB | PARENB | CLOCAL)
  572.    
  573.      enum flowmode { NoFlow, HardFlow, SoftFlow };
  574.    
  575.      /* 和系统有关 */
  576.      #define CFLAGS_HARDFLOW (CRTSCTS)
  577.    
  578.    
  579.      #define EXAMPLE_BAUD B19200
  580.      #define EXAMPLE_FLOW HardFlow
  581.    
  582.    
  583.      static void die(const char *msg)
  584.      {
  585.          fprintf(stderr, "%s\n", msg);
  586.          exit(1);
  587.      }
  588.    
  589.      static int close_and_complain(int fd, const char *msg, int err)
  590.      {
  591.          fprintf(stderr, "%s: %s\n", msg, strerror(err));
  592.          if (fd >;= 0)
  593.              close(fd);
  594.          errno = err;
  595.          return -1;
  596.      }
  597.    
  598.    
  599.      int open_port(const char *name, speed_t baud, enum flowmode flow)
  600.      {
  601.          int flags;
  602.          struct termios attr;
  603.    
  604.          int fd = open(name, O_RDWR | O_NONBLOCK | O_NOCTTY);
  605.    
  606.          if (fd < 0)
  607.              return close_and_complain(-1, "open", errno);
  608.    
  609.          /* 设定一些不明确是否敏感的值 */
  610.    
  611.          if (tcgetattr(fd, &attr) < 0)
  612.              return close_and_complain(fd, "tcgetattr", errno);
  613.    
  614.          /* 无特殊输入或输出处理 */
  615.    
  616.          attr.c_iflag = (flow == SoftFlow) ? (IXON | IXOFF) : 0;
  617.          attr.c_oflag = 0;
  618.    
  619.          /* 设定8位字符宽和一些杂项控制模式 */
  620.    
  621.          attr.c_cflag &= ~(CSIZE | CFLAGS_TO_CLEAR | CFLAGS_HARDFLOW);
  622.          attr.c_cflag |= (CS8 | CFLAGS_TO_SET);
  623.          if (flow == HardFlow)
  624.              attr.c_cflag |= CFLAGS_HARDFLOW;
  625.    
  626.          /* 本机模式 */
  627.    
  628.          attr.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ISIG);
  629.    
  630.          /* 特殊字符 -- 许多已被先前的设定取消 */
  631.    
  632.          {
  633.              int i;
  634.      #ifdef _POSIX_VDISABLE
  635.              attr.c_cc[0] = _POSIX_VDISABLE;
  636.      #else
  637.              attr.c_cc[0] = fpathconf(fd, _PC_VDISABLE);
  638.      #endif
  639.              for (i = 1; i < NCCS; i++)
  640.                  attr.c_cc[i] = attr.c_cc[0];
  641.          }
  642.    
  643.          attr.c_cc[VSTART] = 0x11;
  644.          attr.c_cc[VSTOP] = 0x13;
  645.    
  646.          /* 对read()函数的计时控制 */
  647.    
  648.          attr.c_cc[VMIN] = 1;
  649.          attr.c_cc[VTIME] = 0;
  650.    
  651.          /* 波特律 */
  652.    
  653.          cfsetispeed(&attr, baud);
  654.          cfsetospeed(&attr, baud);
  655.    
  656.          /* 写入设定 */
  657.    
  658.          if (tcsetattr(fd, TCSANOW, &attr) < 0)
  659.              return close_and_complain(fd, "tcsetattr", errno);
  660.    
  661.          /* 如果系统记住了先前的O_NONBLOCK设定,就取消它 */
  662.    
  663.          flags = fcntl(fd, F_GETFL, 0);
  664.          if (flags < 0)
  665.              return close_and_complain(fd, "fcntl(GETFL)", errno);
  666.          if (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0)
  667.              return close_and_complain(fd, "fcntl(SETFL)", errno);
  668.    
  669.          return fd;
  670.      }
  671.    
  672.      /* 一些简单的计时工具函数 */
  673.    
  674.      /* 向*TV加 SECS 和USECS */
  675.    
  676.      static void timeradd(struct timeval *tv, long secs, long usecs)
  677.      {
  678.          tv->;tv_sec += secs;
  679.          if ((tv->;tv_usec += usecs) >;= 1000000)
  680.          {
  681.              tv->;tv_sec += tv->;tv_usec / 1000000;
  682.              tv->;tv_usec %= 1000000;
  683.          }
  684.      }
  685.    
  686.      /* 设定 *RES = *A - *B, 返回结果的符号 */
  687.    
  688.      static int timersub(struct timeval *res,
  689.                          const struct timeval *a, const struct timeval *b)
  690.      {
  691.          long sec = a->;tv_sec - b->;tv_sec;
  692.          long usec = a->;tv_usec - b->;tv_usec;
  693.    
  694.          if (usec < 0)
  695.              usec += 1000000, --sec;
  696.    
  697.          res->;tv_sec = sec;
  698.          res->;tv_usec = usec;
  699.    
  700.          return (sec < 0) ? (-1) : ((sec == 0 && usec == 0) ? 0 : 1);
  701.      }
  702.    
  703.    
  704.      /* 这个函数不试图处理非正常的字符串 (比如 ababc)
  705.       * 超时以微妙计
  706.       * 一个更通常的做法是使用alarm()函数处理超时.
  707.       * 这个函数为简便起见不使用信号处理并试图提供一种替换方法
  708.       */
  709.    
  710.      int expect(int fd, const char *str, int timeo)
  711.      {
  712.          int matchlen = 0;
  713.          int len = strlen(str);
  714.          struct timeval now,end,left;
  715.          fd_set fds;
  716.          char c;
  717.    
  718.          gettimeofday(&end, NULL);
  719.          timeradd(&end, timeo/1000, timeo%1000);
  720.    
  721.          while (matchlen < len)
  722.          {
  723.              gettimeofday(&now, NULL);
  724.              if (timersub(&left, &end, &now) <= 0)
  725.                  return -1;
  726.    
  727.              FD_ZERO(&fds);
  728.              FD_SET(fd, &fds);
  729.              if (select(fd+1, &fds, NULL, NULL, &left) <= 0)
  730.                  return -1;
  731.    
  732.              if (read(fd, &c, 1) != 1)
  733.                  return -1;
  734.    
  735.              if (isprint((unsigned char)c) || c == '\n' || c == '\r')
  736.                  putchar(c);
  737.              else
  738.                  printf("\\x%02x", c);
  739.    
  740.              if (c == str[matchlen])
  741.                  ++matchlen;
  742.              else
  743.                  matchlen = 0;
  744.          }
  745.    
  746.          return 0;
  747.      }
  748.    
  749.    
  750.      int main(int argc, char **argv)
  751.      {
  752.          int fd;
  753.          unsigned char c;
  754.    
  755.          if (argc < 2)
  756.              die("no port specified");
  757.    
  758.          setvbuf(stdout, NULL, _IONBF, 0);
  759.    
  760.          fd = open_port(argv[1], EXAMPLE_BAUD, EXAMPLE_FLOW);
  761.          if (fd < 0)
  762.              die("cannot open port");
  763.    
  764.          write(fd, "AT\r", 3);
  765.          if (expect(fd, "OK", 5000) < 0)
  766.          {
  767.              write(fd, "AT\r", 3);
  768.              if (expect(fd, "OK", 5000) < 0)
  769.              {
  770.                  tcflush(fd, TCIOFLUSH);
  771.                  close(fd);
  772.                  die("no response to AT");
  773.              }
  774.          }
  775.    
  776.          write(fd, "ATI4\r", 5);
  777.          expect(fd, "OK", 10000);
  778.    
  779.          putchar('\n');
  780.    
  781.          tcflush(fd, TCIOFLUSH);
  782.          close(fd);
  783.    
  784.          return 0;
  785.      }

  786. 事务控制范例程序
  787. ================


  788.      /* 生成前台/后台事务的函数 */
  789.    
  790.      #include <stdio.h>;
  791.      #include <unistd.h>;
  792.      #include <stdlib.h>;
  793.      #include <fcntl.h>;
  794.      #include <signal.h>;
  795.      #include <sys/types.h>;
  796.      #include <sys/wait.h>;
  797.      #include <errno.h>;   
  798.    
  799.      /* 一些下面的函数会因为无法定位控制tty和调用方不在前台而失败。
  800.       * 第一种情况时,我们假设一个前台程序会有为标准输入,标准输出或标准错误输出打开的ctty,
  801.       * 而如果没有则返回ENOTTY。
  802.       * 第二种情况时,除foreground_self()函数的特殊情况以外,
  803.       * 若一个非前台程序打算输出一些东西到前台,我们返回EPERM。
  804.       * (也许想得太多了)
  805.       */
  806.    
  807.    
  808.      /* 为给定的pgrp安排一个终端 (打开一个ctty) .
  809.       * 这个tcsetpgrp()外壳程序只是因为POSIX中特别错误(bogusity)的地方而需要;
  810.       * 遵照标准的系统在一个非前台进程调用tcsetpgrp函数时传递SIGTTOU
  811.       * 信号(差不多总是这样)。这是虚假的一致性之于一般想法的胜利。
  812.       */
  813.    
  814.      int assign_terminal(int ctty, pid_t pgrp)
  815.      {
  816.          sigset_t sigs;
  817.          sigset_t oldsigs;
  818.          int rc;
  819.    
  820.          sigemptyset(&sigs);
  821.          sigaddset(&sigs,SIGTTOU);
  822.          sigprocmask(SIG_BLOCK, &sigs, &oldsigs);
  823.    
  824.          rc = tcsetpgrp(ctty, pgrp);
  825.    
  826.          sigprocmask(SIG_SETMASK, &oldsigs, NULL);
  827.    
  828.          return rc;
  829.      }
  830.    
  831.    
  832.      /* 类似fork函数,但做事务控制。如果新建立的进程放在前台则设fg为真。
  833.       * (这样隐式地将调用方进程放置到后台,所以做完这个后要当心tty的输入/输出)
  834.       * 设定pgrp为-1以创建一个新事务,在此情况下返回的进程号即是新事务的进程组号,
  835.       * 或者设定一个同一会话中存在的事务(一般只用来启动管道操作的第二个或第二个以后
  836.       * 的进程)。
  837.       */
  838.    
  839.      pid_t spawn_job(int fg, pid_t pgrp)
  840.      {
  841.          int ctty = -1;
  842.          pid_t pid;
  843.    
  844.          /* 如果生成一个*新*的前台事务,起码要求标准输入,标准输出或
  845.           * 标准错误输出的其中一个指向的是控制tty,并且当前进程在前台。
  846.           * 只有当在存在事务中开始一个新前台进程时才检查控制中的tty。
  847.           * 一个没有控制tty的会话只能有后台事务。
  848.           */
  849.    
  850.          if (fg)
  851.          {
  852.              pid_t curpgrp;
  853.    
  854.              if ((curpgrp = tcgetpgrp(ctty = 2)) < 0
  855.                  && (curpgrp = tcgetpgrp(ctty = 0)) < 0
  856.                  && (curpgrp = tcgetpgrp(ctty = 1)) < 0)
  857.                  return errno = ENOTTY, (pid_t)-1;
  858.    
  859.              if (pgrp < 0 && curpgrp != getpgrp())
  860.                  return errno = EPERM, (pid_t)-1;
  861.          }
  862.    
  863.          switch (pid = fork())
  864.          {
  865.              case -1: /* fork失败 */
  866.                  return pid;
  867.    
  868.              case 0: /* 子进程 */
  869.    
  870.                  /* 建立新进程组, 如果需要则将我们放到前台
  871.                   * 不知道如果setpgid函数调用失败该怎么办(“不会发生”)
  872.                   */
  873.    
  874.                  if (pgrp < 0)
  875.                      pgrp = getpid();
  876.    
  877.                  if (setpgid(0,pgrp) == 0 && fg)
  878.                      assign_terminal(ctty, pgrp);
  879.    
  880.                  return 0;
  881.    
  882.              default: /* 父进程 */
  883.    
  884.                  /* 这里也建立自进程组. */
  885.    
  886.                  if (pgrp < 0)
  887.                      pgrp = pid;
  888.    
  889.                  setpgid(pid, pgrp);
  890.    
  891.                  return pid;
  892.          }
  893.    
  894.          /*不会执行到这里*/
  895.      }
  896.    
  897.    
  898.      /* 用SIGNO表示的信号杀死PGRP表示的事务 */
  899.    
  900.      int kill_job(pid_t pgrp, int signo)
  901.      {
  902.          return kill(-pgrp, signo);
  903.      }
  904.    
  905.    
  906.      /* 中断PGRP表示的事务 */
  907.    
  908.      int suspend_job(pid_t pgrp)
  909.      {
  910.          return kill_job(pgrp, SIGSTOP);
  911.      }
  912.    
  913.    
  914.      /* 继续在后台执行PGRP表示的事务 */
  915.    
  916.      int resume_job_bg(pid_t pgrp)
  917.      {
  918.          return kill_job(pgrp, SIGCONT);
  919.      }
  920.    
  921.    
  922.      /* 继续在前台执行PGRP表示的事务 */
  923.    
  924.      int resume_job_fg(pid_t pgrp)
  925.      {
  926.          pid_t curpgrp;
  927.          int ctty;
  928.    
  929.          if ((curpgrp = tcgetpgrp(ctty = 2)) < 0
  930.              && (curpgrp = tcgetpgrp(ctty = 0)) < 0
  931.              && (curpgrp = tcgetpgrp(ctty = 1)) < 0)
  932.              return errno = ENOTTY, (pid_t)-1;
  933.    
  934.          if (curpgrp != getpgrp())
  935.              return errno = EPERM, (pid_t)-1;
  936.    
  937.          if (assign_terminal(ctty, pgrp) < 0)
  938.              return -1;
  939.    
  940.          return kill_job(pgrp, SIGCONT);
  941.      }
  942.    
  943.    
  944.      /* 将我们自己放置到前台,比如在中断一个前台事务之后调用
  945.       */
  946.    
  947.      int foreground_self()
  948.      {
  949.          pid_t curpgrp;
  950.          int ctty;
  951.    
  952.          if ((curpgrp = tcgetpgrp(ctty = 2)) < 0
  953.              && (curpgrp = tcgetpgrp(ctty = 0)) < 0
  954.              && (curpgrp = tcgetpgrp(ctty = 1)) < 0)
  955.              return errno = ENOTTY, (pid_t)-1;
  956.    
  957.          return assign_terminal(ctty, getpgrp());
  958.      }
  959.    
  960.    
  961.      /* closeall() - 关闭所有>;=给定FD的文件描述符 */
  962.    
  963.      void closeall(int fd)
  964.      {
  965.          int fdlimit = sysconf(_SC_OPEN_MAX);
  966.    
  967.          while (fd < fdlimit)
  968.              close(fd++);
  969.      }
  970.    
  971.    
  972.      /* 类似system()函数,但将给定的命令作为后台事务执行,返回shell进程
  973.       * 的进程号(并且也是这个事务的进程组号,适用于kill_job等等)。
  974.       * 如果参数INFD,OUTFD或ERRFD为非NULL,则打开一个管道和一个文件描述
  975.       * 符保存与该管道有关的父进程端,然后在子进程中将被从定向到/dev/null。
  976.       * 并且在子进程中关闭所有>;2的文件描述符(一个经常过份估计的工作)
  977.       */
  978.    
  979.      pid_t spawn_background_command(const char *cmd,
  980.                                     int *infd, int *outfd, int *errfd)
  981.      {
  982.          int nullfd = -1;
  983.          int pipefds[3][2];
  984.          int error = 0;
  985.    
  986.          if (!cmd)
  987.              return errno = EINVAL, -1;
  988.    
  989.          pipefds[0][0] = pipefds[0][1] = -1;
  990.          pipefds[1][0] = pipefds[1][1] = -1;
  991.          pipefds[2][0] = pipefds[2][1] = -1;
  992.    
  993.          if (infd && pipe(pipefds[0]) < 0)
  994.              error = errno;
  995.          else if (outfd && pipe(pipefds[1]) < 0)
  996.              error = errno;
  997.          else if (errfd && pipe(pipefds[2]) < 0)
  998.              error = errno;
  999.    
  1000.          if (!error && !(infd && outfd && errfd))
  1001.          {
  1002.              nullfd = open("/dev/null",O_RDWR);
  1003.              if (nullfd < 0)
  1004.                  error = errno;
  1005.          }
  1006.    
  1007.          if (!error)
  1008.          {
  1009.              pid_t pid = spawn_job(0, -1);
  1010.              switch (pid)
  1011.              {
  1012.                  case -1: /* fork失败 */
  1013.                      error = errno;
  1014.                      break;
  1015.    
  1016.                  case 0: /* 子进程 */
  1017.    
  1018.                      dup2(infd ? pipefds[0][0] : nullfd, 0);
  1019.                      dup2(outfd ? pipefds[1][1] : nullfd, 1);
  1020.                      dup2(errfd ? pipefds[2][1] : nullfd, 2);
  1021.                      closeall(3);
  1022.    
  1023.                      execl("/bin/sh","sh","-c",cmd,(char*)NULL);
  1024.    
  1025.                      _exit(127);
  1026.    
  1027.                  default: /* 父进程 */
  1028.    
  1029.                      close(nullfd);
  1030.                      if (infd)
  1031.                          close(pipefds[0][0]), *infd = pipefds[0][1];
  1032.                      if (outfd)
  1033.                          close(pipefds[1][1]), *outfd = pipefds[1][0];
  1034.                      if (errfd)
  1035.                          close(pipefds[2][1]), *errfd = pipefds[2][0];
  1036.    
  1037.                      return pid;
  1038.              }
  1039.          }
  1040.    
  1041.          /* 只在错误时执行到这里 */
  1042.    
  1043.          {
  1044.              int i,j;
  1045.              for (i = 0; i < 3; ++i)
  1046.                  for (j = 0; j < 2; ++j)
  1047.                      if (pipefds[i][j] >;= 0)
  1048.                          close(pipefds[i][j]);
  1049.          }
  1050.    
  1051.          if (nullfd >;= 0)
  1052.              close(nullfd);
  1053.    
  1054.          return errno = error, (pid_t) -1;
  1055.      }
  1056.    
  1057.    
  1058.      /*---------------------------------------*/
  1059.      /* 这里是使用上述函数一个小例子.         */
  1060.    
  1061.      pid_t bgjob = -1;
  1062.      volatile int signo = 0;
  1063.    
  1064.      #ifndef WCOREDUMP
  1065.       /* 如果没有 WCOREDUMP, 你也许会希望在你的平台上为它设置一个准确的定义
  1066.        * (这通常是(status & 0x80) 但也不总是这样),或者就赌没有core dumps(
  1067.        * 就象这个程序所做)
  1068.        */
  1069.      # define WCOREDUMP(status) (0)
  1070.      #endif
  1071.    
  1072.      int check_children()
  1073.      {
  1074.          pid_t pid;
  1075.          int status;
  1076.          int count = 0;
  1077.    
  1078.          while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) >; 0)
  1079.          {
  1080.              if (pid == bgjob && !WIFSTOPPED(status))
  1081.                  bgjob = -1;
  1082.    
  1083.              ++count;
  1084.    
  1085.              if (WIFEXITED(status))
  1086.                  fprintf(stderr,"Process %ld exited with return code %d\n",
  1087.                          (long)pid, WEXITSTATUS(status));
  1088.              else if (WIFSIGNALED(status))
  1089.                  fprintf(stderr,"Process %ld killed by signal %d%s\n",
  1090.                          (long)pid, WTERMSIG(status),
  1091.                          WCOREDUMP(status) ? " (core dumped)" : "");
  1092.              else if (WIFSTOPPED(status))
  1093.                  fprintf(stderr,"Process %ld stopped by signal %d\n",
  1094.                          (long)pid, WSTOPSIG(status));
  1095.              else
  1096.                  fprintf(stderr,"Unexpected status - pid=%ld, status=0x%x\n",
  1097.                          (long)pid, status);
  1098.          }
  1099.    
  1100.          return count;
  1101.      }
  1102.    
  1103.    
  1104.      void sighandler(int sig)
  1105.      {
  1106.          if (sig != SIGCHLD)
  1107.              signo = sig;
  1108.      }
  1109.    
  1110.    
  1111.      int main()
  1112.      {
  1113.          struct sigaction act;
  1114.          int sigcount = 0;
  1115.    
  1116.          act.sa_handler = sighandler;
  1117.          act.sa_flags = 0;
  1118.          sigemptyset(&act.sa_mask);
  1119.          sigaction(SIGINT,&act,NULL);
  1120.          sigaction(SIGQUIT,&act,NULL);
  1121.          sigaction(SIGTERM,&act,NULL);
  1122.          sigaction(SIGTSTP,&act,NULL);
  1123.          sigaction(SIGCHLD,&act,NULL);

  1124.    
  1125.          fprintf(stderr,"Starting background job 'sleep 60'\n");
  1126.          bgjob = spawn_background_command("sleep 60", NULL, NULL, NULL);
  1127.          if (bgjob < 0)
  1128.          {
  1129.              perror("spawn_background_command");
  1130.              exit(1);
  1131.          }
  1132.          fprintf(stderr,"Background job started with id %ld\n", (long)bgjob);
  1133.          while (bgjob >;= 0)
  1134.          {
  1135.              if (signo)
  1136.              {
  1137.                  fprintf(stderr,"Signal %d caught\n", signo);
  1138.                  if (sigcount++)
  1139.                      kill_job(bgjob, SIGKILL);
  1140.                  else
  1141.                  {
  1142.                      kill_job(bgjob, SIGTERM);
  1143.                      kill_job(bgjob, SIGCONT);
  1144.                  }
  1145.              }
  1146.    
  1147.              if (!check_children())
  1148.                  pause();
  1149.          }
  1150.    
  1151.          fprintf(stderr,"Done - exiting\n");
  1152.          return 0;
  1153.      }


  1154. =========================================================================
  1155. Andrew (译者注:Andrew为原英文版编辑者Andrew Gierth,中文版由Edward Jiang
  1156. <Edward.Jiang@oracle.com>; 发起并组织翻译和编辑)
复制代码

论坛徽章:
0
10 [报告]
发表于 2003-06-30 23:32 |只看该作者

原 C/C++ 论坛 FAQ,尚未整理。

没有过排好版
以后统一排


FAQ

输入密码回显*号代码 Zhutr: 2003-04-18        1
exit 与_exit atexit 用法        4
c\c++几本经典书籍下载连接        5
*ptr++ 和 *(ptr++)是一样的么        5
伪造一个HTTP包,然后把客户机访问的任何站点定向到一个页面        6
如果使用的库或是glibc没有安装到标准目录下什么办        9
comp.lang.c  FAQ        9
连接时报`__ctype_b'没有找到        9
为什么两次fork可以防止僵死进程        9
如何知道和共享内存关联的所有进程号        10
怎样通过系统函数查看swap状态        10
SOCKET出现大量FIN_WAIT_2和CLOSE_WAIT连接的问题        10
怎样删除大量小文件        11
信号灯使用SEM_UNDO的危机        11
在pro*c中怎样调用oracle的存储过程        12
关于proc和oci的下栽        12
请问如何在两个进程中传递socket描述符        12
我的socket程序编译有问题连拉出错        12
getppid源码        13
Pro*c 编译的Makefile 例子        15
shm 限制值为什么不准确        16
C函数可以获得进程的相关信息        18
X11开发资料        20


输入密码回显*号代码
Zhutr: 2003-04-18
#include <termios.h>;
#include <fcntl.h>;
#include <sys/types.h>;
#include <sys/stat.h>;
int getpassword(char *passwd);
main()
{
   char      passwd[64];
   if(getpassword(passwd)==-1)
   {
      printf("error";
      exit(1);
   }
   printf("%s\n",passwd);
   return(0);
}
int getpassword(char *passwd)
{
   int fd = -1, i=0, termpid;
   struct termios term, termsave;
   char str[200];
   char tmp;
   strcpy(str,"/dev/tty";
   if ((fd = open(str, O_RDWR | O_NOCTTY )) < 0) return -1;
   sprintf(str, "lease input the password:";
   write(fd, str, strlen(str));
   tcgetattr(fd, &term);
   tcgetattr(fd, &termsave);
   term.c_lflag &= ~(ICANON|ECHO|ISIG);
   tcsetattr(fd, TCSANOW, &term);
   read(fd, &tmp, 1);
   while(tmp!='\n')
   {
      passwd = tmp;
      i++;
      tcsetattr(fd, TCSANOW, &termsave);
      write(fd,"*",1);
      tcsetattr(fd, TCSANOW, &term);
      read(fd, &tmp, 1);
   }
   tcsetattr(fd, TCSANOW, &termsave);
   passwd='\0';
   write(fd,"\n",1);
   return 0;
}
superhoo: 2003-05-16
#include <stdio.h>;
#include <termios.h>;
#include <unistd.h>;
#include <sys/time.h>;

static struct termios inital_settings , new_settings;
static int peek_character=-1;

void init_keyboard(void);
void close_keyboard(void);
int kbhit(void);
int readch(void);
void delayusec(int usec);

int main()
{
   int ch=0;
   init_keyboard();
   while(ch != 'q' ){
      //printf("looping \n";
      delayusec(100);
      if(kbhit()){
         ch=readch();
         printf("You hit %x\n",ch);
      }
   }
   close_keyboard();
   exit(0);
}

void init_keyboard(void)
{
   tcgetattr(0,&inital_settings);
   new_settings = inital_settings;
   new_settings.c_lflag &= ~ICANON;
   new_settings.c_lflag &= ~ECHO;
   new_settings.c_lflag &= ~ISIG;
   new_settings.c_cc[VMIN]=1;
   new_settings.c_cc[VTIME]=0;
   tcsetattr(0,TCSANOW,&new_settings);
}

void close_keyboard(void)
{
   tcsetattr(0,TCSANOW,&inital_settings);
}

int kbhit(void)
{
   char ch;
   int nread;
   
   if(peek_character != -1)
      return 1;
   new_settings.c_cc[VMIN]=0;
   tcsetattr(0,TCSANOW,&new_settings);
   nread=read(0,&ch,1);
   new_settings.c_cc[VMIN]=1;
   tcsetattr(0,TCSANOW,&new_settings);
   
   if(nread==1)
   {
      peek_character=ch;
      return 1;
   }
   return 0;
}

int readch(void)
{
   char ch;
   
   if(peek_character != -1)
   {
      ch=peek_character;
      peek_character =  -1;
      return ch;
   }
   
   read(0,&ch,1);
   return ch;
}
void delayusec(int usec)
{
   struct timeval tv;
   
   tv.tv_sec = 0;
        tv.tv_usec = usec;
        select(0, NULL, NULL, NULL, &tv);
        
}


exit 与_exit atexit 用法
exit函数与_exit函数区别:这两个函数都是用于正常终止一个程序,_exit立即进入内核,exit做一些清除处理(包括调用执行各终止处理程序,关闭所有标准IO流等),然后进入内核
exit会对所有流调用fclose,所以会造成文件的刷新(多数情况下这是我们所需要的).
atexit用于登记终止处理程序.一个进程最多可以登记32个函数,这些函数由exit自动调用,调用顺序与登记顺序相反(LIFO),函数类型必须为void (void),同一函数可以被登记多次,同时也会被调用多次

c\c++几本经典书籍下载连接
提供者:rdd 2003-05-11
1、        C++ Primer中文版
ftp://210.25.133.20/newbook/c++primer.zip
介绍: 感谢 gaofeis@hotmail.com 网友
C++ Primer的第三版结合了Stanley Lippman的实践经验和Josée Lajoie对于ANSI/ISO标准C++的深入理解。这本指导书的第三版已经被重新改写过,以便更加精确地讲述标准C++的特性和用法。在本书中,对于C++初学者特别有价值的是一些来自真实世界中的程序例子,它们说明了泛型程序(generic program)的设计、面向对象程序的设计、模板的用法,以及使用标准C++进行程序设计的方方面面。而且,本书也在适当的地方讲到了一些习惯用法以及效率指导。
2、Effective C++ & More Effective C++ 英文版下载
http://www.fmdstudio.net/book/ec_mec.zip
中文下载
http://fengyy.vip.sina.com/images/C/effectiveCpp.rar
3、C++编程思想
http://www.vckbase.com/tools/downtools.asp?id=41
介绍: 本书作者根据自己学习C++的亲身体会及多年教学经验,用简单的例子和简练的叙述讲解C++编程,别具特色。 全书共分十八章,内容涉及对象的演化、数据抽象、隐藏实现、初始化与清除、函数重载与缺省参数、输入输出流介绍、常量、内联函数、命名控制、引用和拷贝构造函数、运算符重载、动态对象创建、继承和组合、多态和虚函数、模板和包容器类、多重继承、异常处理和运行时类型识别。 本书作为正式教材和自学用书均非常优秀,作为程序设计者的参考用书亦极为合适。
4、More Effective C++中文
http://palms.myrice.com/tutorial/MoreECpp/index.htm
5、STL Programmer's Guide
http://fengyy.vip.sina.com/images/C/stlguide.rar

*ptr++ 和 *(ptr++)是一样的么
陶深: 2003-05-10
其实* 与++的优先级是一样的
在*和++同时出现的情况下依据从右到左的原则
但ptr++又先做在加一 . 所以
*ptr++ 的意思是先取ptr的指针值,然后在指针值后移指向下个元素
但*ptr++的结果还是*ptr 只有到下个循环是*ptr的值才是后个元素

伪造一个HTTP包,然后把客户机访问的任何站点定向到一个页面
cbchen: 2003-06-22
源码和问题都由cbchen解决,源码中是有点小bug就是首问校验和问题
我想伪造一个HTTP包,然后把客户机访问的任何站点定向到一个页面,但是这个TCP包头的校验和怎么也不对,哪位能帮我看看?这个校验的算法当TCP数据区为空的时候是正确的,但是当填入数据的时候就不对了....
哪位大哥能帮我传一段TCP包头校验的代码?(TCP数据区不为空)


是我程序里面的一个计算长度的弄错了。。。校验算法没有问题的。
在此提醒各位的是,TCP伪头部的长度是12+TCPHDR+数据区的长度

void send_tcp_htm(int sockfd,struct sockaddr_in *addr)
{
    u_char htmlbuf[]="HTTP1.1 200 OK\r\nServer: IIS SERVER\r\nData:WED,18 JUN 2003 00:25:09 GMT\r\tContent-Type:text/html\r\nAccept-Range:bytes\r\nLast_Modified:Mon,16 Jun 2003 11:35:55 GMT\r\nContent_Length:187\r\n\r\n<html>;\r\nhello\r\n</html>;\r\n"; //伪造的html数据
    int bufsize=sizeof(htmlbuf);

    struct send_tcp
    {
        struct iphdr ip;
        struct tcphdr tcp;
    } send_tcp;

    struct pseudo_header //tcp伪头部
    {
        unsigned int source_address;
        unsigned int dest_address;
        unsigned char placeholder;
        unsigned char protocol;
        unsigned short tcp_length;
        struct tcphdr tcp;
        u_char html[bufsize];
    } pseudo_header;

    int tcp_socket;
    struct sockaddr_in sin;
    int sinlen;
    u_char *str;
    str=(u_char *)&send_tcp;

    /* form ip packet */
    send_tcp.ip.ihl = 5;
    send_tcp.ip.version = 4;
    send_tcp.ip.tos = 0;
    send_tcp.ip.tot_len = htons(40+sizeof(htmlbuf));
    send_tcp.ip.frag_off = 0;
    send_tcp.ip.ttl = 64;
    send_tcp.ip.protocol = IPPROTO_TCP;
    send_tcp.ip.check = 0;
    send_tcp.ip.saddr = iprecv->;ip_dst.s_addr;
    send_tcp.ip.daddr = addr->;sin_addr.s_addr;

    /* form tcp packet */
    send_tcp.tcp.dest = addr->;sin_port;
    send_tcp.tcp.source = tcprecv->;dest;
    send_tcp.tcp.ack_seq = htonl(ntohl(tcprecv->;seq)+len);
    send_tcp.tcp.res1 = 1;
    send_tcp.tcp.doff = 5;
    send_tcp.tcp.fin = 0;
    send_tcp.tcp.syn = 0;
    send_tcp.tcp.rst = 0;
    send_tcp.tcp.psh = 1;
    send_tcp.tcp.ack = 1;
    send_tcp.tcp.urg = 0;
    send_tcp.tcp.res2 = 0;
    send_tcp.tcp.window = htons(512);
    send_tcp.tcp.check = 0;
    send_tcp.tcp.urg_ptr = 0;
    send_tcp.tcp.seq = tcprecv->;ack_seq;


    /* set fields that need to be changed */

    send_tcp.ip.id = 0 ;


    send_tcp.tcp.check = 0;
    send_tcp.ip.check = 0;

    /* calculate the ip checksum */
    send_tcp.ip.check=in_cksum((unsigned short *)&send_tcp.ip, 20);

    /* set the pseudo header fields */
    pseudo_header.source_address = send_tcp.ip.saddr;
    pseudo_header.dest_address = send_tcp.ip.daddr;
    pseudo_header.placeholder = 0;
    pseudo_header.protocol = IPPROTO_TCP;
    pseudo_header.tcp_length = htons(20);
    bcopy((char *)&send_tcp.tcp, (char *)&pseudo_header.tcp, 20);//将send_tcp的数据拷如pseudo_header中的tcp
    bcopy(htmlbuf, (char *)&pseudo_header.html, sizeof(htmlbuf));
    send_tcp.tcp.check = in_cksum((unsigned short *)&pseudo_header, sizeof(pseudo_header));
    bcopy(htmlbuf,(u_char *)(str+40),sizeof(htmlbuf));

    printf("size:%d\n",sizeof(htmlbuf));
    if(sendto(sockfd, str, sizeof(htmlbuf)+40, 0, (struct sockaddr *)addr,sizeof(struct sockaddr))<0)printf("sendto error!\n";


}

in_cksum(unsigned short *addr, int len)
{
    register int nleft = len;
    register u_short *w = addr;
    register int sum = 0;
    u_short answer =0;

    while (nleft >; 1)
    {
        sum += *w++;
        nleft -= 2;
    }
    if (nleft == 1)
    {
        *(u_char *)(&answer) = *(u_char *)w;
        sum += answer;
    }
    sum = (sum >;>; 16) + (sum & 0xffff);
    sum += (sum >;>; 16);
    answer = ~sum;
    return(answer);
}

如果使用的库或是glibc没有安装到标准目录下什么办
蓝色键盘  : 2003-06-19
配置环境变量LD_LIBRARY_PATH指定库路径

comp.lang.c  FAQ
fieryfox: 2003-06-21
英文的,对c/c++语言本身有什么不懂的可以看看,或是对什么用法不熟的也可以看,推荐
http://www.plethora.net/~seebs/faqs/c-iaq.html
http://www.eskimo.com/~scs/C-faq/top.html

连接时报`__ctype_b'没有找到
viacocha: 2003-06-22
我用google查了,ctype_b是<ctype.h>;里定义的一个函数,我看到redhat里的bug报告里说gcc 2.3.X里有一个ctype_b的bug,说这个函数编译有错,没想到这么一个BUG让我遇上了,最后redhat说解决方案一是重做一个ctype_b函数,二是等待!!

注意符号__ctype_b对应c语言中的符号_ctype_b ,因为编译器会在全局变量前加个_号

为什么两次fork可以防止僵死进程
gadfly  : 2003-06-21
原帖由 "wangrujun " 发表:
无论是fork 1次还是fork2次,其结果都是子进程被init进程领养(假设父进程先退出的)。之后子进程调用exit,最终会被init进程释放它的资源,并真正死亡。


你这个假设并不是两次fork的与一次fork比较的前提。看看APUE的151页最后几行.
原帖由 "apue" 发表:

回忆一下8 . 5 节中有关僵死进程的讨论。如果一个进程要f o r k 一个子进程,但不要求它等待
子进程终止,也不希望子进程处于僵死状态直到父进程终止,实现这一要求的诀窍是调用f o r k
两次。程序8 - 5 实现了这一点。


fork两次在防止僵死方面来说,就是因为儿子进程先退出,孙子进程就被init接管了,实际上与最初的父进程脱离了关系,就不会僵死了。

两次fork的其它作用就是做daemon。原因就像无双说的用于daemon编写。     

如何知道和共享内存关联的所有进程号
无双: 2003-06-27
fuser命令可以
共享内存应该有文件映射吧

只要有文件映射的就可以通过fuser来查看
gadfly: 2003-06-28
至于共享内存的attach个数可以通过,shmctl(segId, IPC_STAT, &ds);取struct shmid_ds ds的成员
unsigned short int shm_nattch; /* number of current attaches */
来获得。
fuser恐怕只对文件的打开起作用
jobman: 2003-06-28
你说的问题我也遇到,共享内存有一个 nattch 字段,表示当前
有几个进程和该共享内存段连接,我想你是需要知道这是哪些进程,
可是看起来不太可能,恐怕得自己解决,因为在核心部分没有留下
相应得信息。至少我没有发现

怎样通过系统函数查看swap状态
gadfly :swap可以通过statfs来查看吧

哪里有c/c++视频讲座下载
原标题为” 哪里有c/c++视频讲座下载”,如果有问题可以查找那个标题
ecat: 2003-05-08
http://www.buct.edu.cn/itat/C++/

SOCKET出现大量FIN_WAIT_2和CLOSE_WAIT连接的问题
Fzchh:“请教SOCKET出现大量FIN_WAIT_2和CLOSE_WAIT连接的问题”
我的服务端在接收到大量的SOCKET的短时间的连接的时候,会发现我的服务器出现我的监听断口上出现许多FIN_WAIT_2和CLOSE_WAIT的连接,导致我新的客户端不能连接。各位大哥是否能够帮我想点办法,如何限制服务端的服务和怎么比较快的释放这些端口?!
无双:2003-06-10
会不会是因为对方还没有close造成的
使用close操作的话
那么会等到socket buffer中所有数据都发送完后才会调用close
会不会是这个原因
stevenyi:2003-06-11
主动close的一方的socket会进入time_wait状态,所以你在客户端主动close就没问题了

怎样删除大量小文件
gadfly: 2003-06-10
文件太多,有的系统rm *是会有问题的。
可以如楼上说的,
也可已如: rm [a-e]*
rm [f-h]*
.......
依次删除
JohnBull:
文件太多的话,shell会把*扩展成为超长的文件名列表,有可能超过shell命令行长度上限
chdonald:
这样太麻烦了,因为rm不能够删除太多的文件,用[a-e]也无法得知道是否会超过,应该用
ls | xargs rm
doni:
echo * |xargs rm –f

信号灯使用SEM_UNDO的危机
chdonald:
我使用了semop,用到了SEM_UNDO这个选项,目的正是想象书上所说的可以让它在进程退出时自动还原所做操作,可是问题出现了,当我调式程序运行了几把之后,出现"No space left on device"的错误,而且不是出在semget(已经可以正确获得SEMID),而是出在semop,查了下GOOGLE,大多是出在semget创建信号灯不够的情况下,man了一把,发现是:
ENOSPC
The limit on the number of individual processes
requesting an SEM_UNDO would be exceeded.
说明使用SEM_UNDO是有次数限制的,最后只能舍弃SEM_UNDO这个本以为是好东东的选项目.
sunlan: 2003-06-23
你不会在进程退出的时候自己又对信号灯做了把P/V操作吧?!

如果使用了SEM_UNDO,则在进程退出时不能显式的做P/V操作,而必须由系统为你做,否则就等于做了两遍!
在多进程通过信号灯同步资源、而单个进程可能运行较长时间的情况下,不建议使用SEM_UNDO,否则运行进程将长时间占用资源,仅当其退出后才会释放!

在pro*c中怎样调用oracle的存储过程
临风左岸   : 2003-06-25
EXEC SQL EXECUTE
BEGIN
raise_salary(:emp_id, :increase);
END;
END-EXEC;

关于proc和oci的下栽
ccrazy: 2003-06-13
资料不错,想使用的都可以下来看
http://www.vvsoft.net/vvbksd/index.asp?typename=oracle

请问如何在两个进程中传递socket描述符
fieryfox: 2003-05-26
用ioctl可以传递描述符。I_SENDFD和I_RECVFD

描述符是OS为每个进程维护的,其实是描述符表的入口,里边有复杂的数据结构以屏蔽文件、设备、socket等的不同。描述符是进程内唯一的,出了该进程就没有了意义。比如都是描述符5,对进程A和进程B其含义是完全不同的,因此需要ioctl即OS参与才能完成描述符传递。

蓝色键盘:
能够实现进程之间传输描述子的方式很多,例如socketpair,FIFO,socket域,shm等等。如果是父子进程之前传输的话,用管道(有名,流管道)或者socketpair会简单一些。

我的socket程序编译有问题连拉出错
蓝色键盘: 2003-05-12
对于solaris上面的socket程序的编译连接,需要做如下的补充:

1、相对于其它的unix系统,solaris上面gcc对于语法的检查要严格得多。
在编译的时候要求加入必要的库文件,但是在很多的系统中可能不需要,例如Linux。

2、对于socket编程来说,solaris上面至少需要用-lsocket(注意中间没有空格)来指明连接scoket库。

3、在加入-lsocket的情况下,编译仍然出错的话,需要加入-lnsl。例如:
$gcc -c -g test.c -lsocket –lnsl

getppid源码
pid_t getpppid(pid_t pid)
{
    psinfo_t psinf;
    int fd;
    char buf[256];

    sprintf(buf, "/proc/%d/psinfo", pid);
    fd=open(buf,0);
    if(fd!=-1) {
        read(fd, &psinf, sizeof(psinfo_t));
        close(fd);
    }

    return psinf.pr_ppid;
}

Q:有谁能把C中的restrict讲清楚???

A:
caohongxin:
我指的restrict 是C99标准中新增加的类型修饰符.
书中原话是这样的
/*********************************************************
restrict这种修饰符只适用于指针.
由restrict修饰的指针是最初唯一对指针所指向的对象进行存取的办法,
仅当第二个指针基于第一个时,才能对对象进行存取.
因此,对对象的存取都限定于基于有restrict修饰的指针表达式中.
由restrict修饰的指针主要被用做函数指针,或者指向由malloc()分配的内存变量.
restrict数据类型不改变程序的语义.


使用restrict限制一个地址只能通过一个指针访问(就像是代码1增加了restrict限制后编译时会报错)

另外要注意的是这个关键字只是c99功能
不是标准c++中的

'Restrict' Pointers
One of the new features in the recently approved C standard C99, is the restrict pointer qualifier. This qualifier can be applied to a data pointer to indicate that, during the scope of that pointer declaration, all data accessed through it will be accessed only through that pointer but not through any other pointer. The 'restrict' keyword thus enables the compiler to perform certain optimizations based on the premise that a given object cannot be changed through another pointer. Now you're probably asking yourself, "doesn't const already guarantee that?" No, it doesn't. The qualifier const ensures that a variable cannot be changed through a particular pointer. However, it's still possible to change the variable through a different pointer. For example:

代码:

  void f (const int* pci, int *pi; // is *pci immutable?
  {
    (*pi)+=1; // not necessarily: n is incremented by 1
     *pi = (*pci) + 2; // n is incremented by 2
  }
  int n;
  f( &n, &n);//增加restrict关键字是因为这里会出问题,
//如果对两个参数都使用了restrict关键字,那么这里编译时会报错,因为一
//个地址可以通过两个指针访问了


In this example, both pci and pi point to the same variable, n. You can't change n's value through pci but you can change it using pi. Therefore, the compiler isn't allowed to optimize memory access for *pci by preloading n's value. In this example, the compiler indeed shouldn't preload n because its value changes three times during the execution of f(). However, there are situations in which a variable is accessed only through a single pointer. For example:


代码:

  FILE *fopen(const char * filename, const char * mode);


The name of the file and its open mode are accessed through unique pointers in fopen(). Therefore, it's possible to preload the values to which the pointers are bound. Indeed, the C99 standard revised the prototype of the function fopen() to the following:


/* new declaration of fopen() in <stdio.h>; */
FILE *fopen(const char * restrict filename,
const char * restrict mode);

Similar changes were applied to the entire standard C library: printf(), strcpy() and many other functions now take restrict pointers:

代码:

  int printf(const char * restrict format, ...);
  char *strcpy(char * restrict s1, const char * restrict s2);
C++ doesn't support restrict yet. However, since many C++ compilers are also C compilers, it's likely that this feature will be added to most C++ compilers too.


Pro*c 编译的Makefile 例子
Wangz: 2003-05-14
SHELL=/bin/sh
CC= /opt/ansic/bin/cc
PC= proc

OUTPUT = $(HOME)/bin

CFLAGS= -g -I$(HOME)/include -I$(ORACLE_HOME)/precomp/public  
PCFLAGS= include=$(HOME)/include parse=no \
   CHAR_MAP=STRING SQLCHECK=SEMANTICS  \
   userid=user/pwd
ORALIBS= -DDEBUG \
   -I$(ORACLE_HOME)/rdbms/public \
   -I$(ORACLE_HOME)/precomp/public \
   -I$(ORACLE_HOME)/rdbms/demo \
   -I$(ORACLE_HOME)/plsql/public \
   -I$(ORACLE_HOME)/network/public \
   -L$(ORACLE_HOME)/lib/ \
   -L$(HOME)/lib
TFLAGS= \
        -lpubdb \
        -lclntsh \
        -lsql8 \
        -Y -Qy -lc -lm

.SUFFIXES:      .pc .c .o

all: \
   a.o

.pc.o:
   $(PC) $(PCFLAGS) $<
   $(CC) $(CFLAGS) -c $(<:.pc=.c)
   $(CC) $(CFLAGS) -o $(OUTPUT)/$* $*.o $(ORALIBS) ${TFLAGS}

clean:
   rm *.c
   rm *.o

shm 限制值为什么不准确
beggar:
我想测试我最大能得到多少个shm的id.结果发现一个进程中可用的信号灯最大是系统上限而不是进程上限,下ulimit显示不符
$ sysctl kern.ipc.shmmni kern.ipc.shmseg //查看系统限制值
kern.ipc.shmmni: 192
kern.ipc.shmseg: 128
$ ./a.out
Your Limits is:189  //程序的输出值 (4个是其它进程可用)
$
无双: 2003-05-15
有的系统的实现的话可能不会实现按进程限制
而而只是限制总数目

在许多系统中对POSIX标准的实现就是这样的
所以并不是说只要是符合POSIX的就一定会这样
很多系统对POSIX理解都有点偏差(个人理解,不一定准确,但现在仍然觉得可能是这个原因)

beggar:
这两天看了一下FreeBSD关于ipc的源代码,也查了一些资料。结果并不是你所说的posix理解偏差。“有的系统的实现的话可能不会实现按进程限制,而只是限制总数目”不知你说的是什么系统?

在FB源文件sysv_shm.c中找到关于shmseg和shmmni的变量:

TUNABLE_INT("kern.ipc.shmmni", &shminfo.shmmni);
TUNABLE_INT("kern.ipc.shmseg", &shminfo.shmseg);
发现在shmget函数中没有出现检查shminof.shmseg的地方.只是在开头检查了一下
shmmin,shmmax,shmmni,没有对shmseg进行检查。代码如下

if (uap->;size < shminfo.shmmin || uap->;size >; shminfo.shmmax)
                return EINVAL;
        if (shm_nused >;= shminfo.shmmni) /* any shmids left? */
                return ENOSPC;
但在shmat函数中却发现了检查shmseg的代码

if (i >;= shminfo.shmseg)
                return EMFILE;
编程测试

#include <sys/types.h>;
#include <unistd.h>;
#include <sys/ipc.h>;
#include <sys/shm.h>;
#define MAXLIST 1000
int shmlist[MAXLIST];
char *shmpt[MAXLIST];
int main(int argc,char **argv)
{
        int i,n;
        for(i=0;i<MAXLIST;i++){
                if((shmlist=shmget(IPC_PRIVATE,1,SHM_R|SHM_W))<0) break;
                if((shmpt=shmat(shmlist,0,0))==(void*) -1) { //增加shmat
                            warn("break at shmat\n";
                            break;
        }
        printf("Your Limits is:%d\n",i);
        for(n=0;n<=i;n++){
                shmdt(shmpt[n]);
                if(shmctl(shmlist[n],IPC_RMID,NULL)<0)
                        errx(1,"error when remove share memory id.\n";
        }
        exit(0);
}

编译运行后的结果果然是kern.ipc.shmseg设定的值128
$ ./a.out
a.out: break at shmat: Too many open files
Your Limits is:128
$

查找了很多资料,发现很多关于shmseg和shmmni之间区别的翻译讲得含糊不清,而且都不规范。大多如你所paste的那样
SHMSEG 每进程最大共享内存段数量 只需要 1 个段,不过缺省比这高得多.
SHMMNI 系统范围最大共享内存段数量 类似 SHMSEG + 用于其他应用的空间

其实shmseg真正限制的是进程可以用shmat连接的内存段的数量,而不是限制用shmget创建内存段的数量.在linux 及solaris上测试结果也是如此。

蓝色键盘:
对于楼主的这个问题,我当时在hp-11上做了测试,但是测试测结果无法回答楼主的问题(因为楼主的系统我没有接触过)。的确,在unix各个主流系统中,不管是shm这一块,很多地方实现的差异较大,我觉得楼主最后贴出了部分代码证明了对于shmat的限制(这个限制也是对于进程能够连接的共享内存段的限制),对于整个shm的大小应该(为了不误导人,还是用应该修饰)有限制的,这种限制或许不在那段代码中(例如可能在像核心申请shm的时候做了限制)。我觉得只要大家相互引导,相互探讨,问题还会逐步的明了的,如果大家都有兴趣,并且能够坚持的话,当然也要有时间。

在unix中的很多的东西,谁是真正的标准,谁是圣经!答案是没有。技术总在进步,每个厂商都是在遵循一定标准的前提下开发。如果我们能去对一些核心的东西做讨论,本身意味着论坛中的朋友在进步,既然是讨论,就不能保证所说的每一句话都必须天衣无缝,如果非要这样的话,我建议大家还是自己看书好了。毕竟一本好书能够出版是经过严格审批的!

C函数可以获得进程的相关信息
Cs: 2003-03-18
The getpid() function returns the process ID of the calling process.
for other information, you can try getrusage:

NAME
getrusage - get information about resource utilization

SYNOPSIS
#include <sys/resource.h>;

int getrusage(int who, struct rusage *r_usage);

refer to man page for detail usage of the func.

Liupch:
读取/proc/进程号/psinfo这个文件。
就用我告诉你的那个函数
ioctl(fd, PIOCPSINFO, &procinfo);
在看一下procinfo这个结构就知道了。

Chdonald: 2003-05-16
--------------------------------------------------------------------------------
写了个简单的程序,不是很完善
/* Usage: proc <pid>;
*/

#include <stdio.h>;
#include <unistd.h>;
#include <stropts.h>;
#include <sys/ioctl.h>;
#include <sys/old_procfs.h>;
#include <sys/types.h>;
#include <sys/stat.h>;
#include <fcntl.h>;
#include <errno.h>;

main(int argc, char **argv)
{
    int fd;
    char path[32];
    prpsinfo_t procinfo;

    snprintf(path,32,"/proc/%s",argv[1]);
    fd = open(path, O_RDONLY);
    printf("fd = %d\n", fd);
    if (fd < 0){
        fprintf(stderr, "open %s error:%s\n", path, strerror(errno));
        close(fd);
        exit(1);
    }
    ioctl(fd, PIOCPSINFO, &procinfo);
    printf("status=%c\n", procinfo.pr_sname);
    printf("zomb = %c\n", procinfo.pr_zomb); //if != 0, zomb
    printf("nice = %c\n", procinfo.pr_nice);
    printf("uid = %d\n", procinfo.pr_uid);
    printf("gid = %d\n", procinfo.pr_gid);
    printf("pid = %d\n", procinfo.pr_pid);
    printf("ppid = %d\n", procinfo.pr_ppid);
    printf("group leader = %d\n", procinfo.pr_pgrp);
    printf("sid = %d\n", procinfo.pr_sid);
    printf("start time = %d ns\n", procinfo.pr_start.tv_sec*10^9 + procinfo.pr_sta
            rt.tv_nsec);
    printf("user and system time = %d ns\n", procinfo.pr_time.tv_sec*10^9 + procin
            fo.pr_time.tv_nsec);
    printf("priority = %d\n", procinfo.pr_pri); //high value = high pri
    printf("tty = %d\n", procinfo.pr_ottydev);
    printf("used cpu = %d\%\n", procinfo.pr_pctcpu);
    printf("used mem = %d\%\n", procinfo.pr_pctmem);
    close(fd);
}

X11开发资料
Butterfly:2003-05-12
X11R5的。http://www.opengroup.org/products/publications/catalog/de.htm
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP