unix编程常见问题

标题: Unix编程常见问题解答


1. 进程控制
***********

1.1 创建新进程:fork函数
========================

1.1.1 fork函数干什么?
----------------------

#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);

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

pid_t pid;

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

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

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

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

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

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

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

* 环境(environment)

* 堆栈

* 内存

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

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

* 信号(signal)控制设定

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

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

* 进程组号

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

* 当前工作目录

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

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

* 资源限制

* 控制终端

子进程所独有:

* 进程号

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

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

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

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

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

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

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

* 不继承异步输入和输出

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

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

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

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

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

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

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

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

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

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

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

1.2 环境变量
============

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

#include <stdlib.h>

char *getenv(const char *name);

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

#include <stdlib.h>

int putenv(char *string);

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

/* 译者增加:

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

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

int setenv(const char *name, const char *value, int replace)
{
char *envstr;

if (name == NULL || value == NULL)
return 1;
if (getenv(name) !=NULL)
{
envstr = (char *) malloc(strlen(name) + strlen(value) + 2);
sprintf (envstr, "%s=%s", name, value);
if (putenv(envstr));
return 1;
}
return 0;
}
*/

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

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

char *envvar;

envvar=getenv("TERM");

printf("The value for the environment variable TERM is ");
if(envvar)
{
printf("%s/n",envvar);
}
else
{
printf("not set./n");
}

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

static char envbuf[256];

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

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

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

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

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

#include <stdio.h>

extern char **environ;

int main()
{
char **ep = environ;
char *p;
while ((p = *ep++))
printf("%s/n", p);
return 0;
}

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

#include <stdio.h>

int main(int argc, char **argv, char **envp)
{
char *p;
while ((p = *envp++))
printf("%s/n", p);
return 0;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1.5 父子进程如何通信?
======================

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

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

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

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

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

1.6.1 何为僵死进程?
--------------------

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

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

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

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

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

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

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

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

struct sigaction sa;
sa.sa_handler = SIG_IGN;
#ifdef SA_NOCLDWAIT
sa.sa_flags = SA_NOCLDWAIT;
#else
sa.sa_flags = 0;
#endif
sigemptyset(&sa.sa_mask);
sigaction(SIGCHLD, &sa, NULL);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

你真的不该想做这个。

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

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

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

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

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

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

* ‘kill()’返回0

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

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

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

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

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

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

- 你有麻烦了!

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

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

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

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

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

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

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

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

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

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

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

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

`WIFCONTINUED(stat)'
如果状态是表示子进程继续执行则返回非0

`WCOREDUMP(stat)'
如果‘WIFSIGNALED(stat)’为非0,而如果这个进程产生一个内存映射文件
(core dump)则返回非0

1.11 我怎样找出一个进程的存储器使用情况?
=========================================

如果提供的话,参看‘getrusage()’手册页

1.12 为什么进程的大小不缩减?
=============================

当你使用‘free()’函数释放内存给堆时,几乎所有的系统都*不*减少你程序的
对内存的使用。被‘free()’释放的内存仍然属于进程地址空间的一部份,并将
被将来的‘malloc()’请求所重复使用。

如果你真的需要释放内存给系统,参看使用‘mmap()’分配私有匿名内存映射
(private anonymous mappings)。当这些内存映射被取消映射时,内存真的将其释放给
系统。某些‘malloc()’的实现方法(比如在GNU C库中)在允许时自动使用‘mmap()’
实施大容量分配;这些内存块(blocks)随着‘free()’被释放回系统。

当然,如果你的程序的大小增加而你认为它不应该这样,你可能有一个‘内存泄
露’(‘memory leak’)- 即在你的的程序中有缺陷(bug)导致未用的内存没释放。

1.13 我怎样改变我程序的名字(即“ps”看到的名字)?
=================================================

在BSD风格的系统中,‘ps’程序实际上审视运行进程的地址空间从而找到当前
的‘argv[]’,并显示它。这使得程序可以通过简单的修改‘argv[]’以改变它的
名字。

在SysV风格的系统中,命令的名字和参数的一般头80字节是存放在进程的u-区(
u-area), 所以不能被直接修改。可能有一个系统调用用来修改它(不象是这样),
但是其它的话,只有一个方法就是实施一个‘exec()’,或者些内核内存(危险,
而且只有root才有可能)。

一些系统(值得注意的是Solaris)可以有‘ps’的两种不同版本,一种是在
‘/usr/bin/ps’拥有SysV的行为,而另一种在‘/usr/ucb/ps’拥有BSD的行为。在
这些系统中,如果你改变‘argv[]’,那么BSD版的‘ps’将反映这个变化,而
SysV版将不会。

检查你的系统是否有一个函数‘setproctitle()’。

1.14 我怎样找到进程的相应可执行文件?
=====================================

这个问题可以作为‘常见未回答问题’(‘Frequently Unanswered Questions’)的一
个好候选,因为事实上提出这个问题经常意味着程序的设计有缺陷。:)

你能作的‘最佳猜测’(‘best guess’)是通过审视‘argv[0]’的值而获得。如果
它包括一个‘/’,那么它可能是可执行程序的绝对或相对(对于在程序开始时的
当前目录而言)路径。如果不包括,那么你可以仿效shell对于‘PATH’变量的查
询来查找这个程序。但是,不能保证成功,因为有可能执行程序时‘argv[0]’是
一些任意值,也不排除这个可执行文件在执行后可能已经被更名或删除的情况。

如果所有你想做的只是能打印一个和错误消息一起出现的合适的名字,那么最好
的方法在‘main()’函数中将‘argv[0]’的值保存在全局变量中以供整个程序使
用。虽然没有保证说‘argv[0]’的值总是有意义,但在大多数情况下它是最好的
选择。

人们询问这个问题的最普通原因是意图定位他们程序的配置文件。这被认为是
不好的形式;包含可执行文件的目录应当*只*包含可执行文件,而且基于管理的
要求经常试图将配置文件放置在和可执行文件不同的文件系统。

试图做这个的一个比较不普通但更正规的理由是允许程序调用‘exec()’执行它
自己;这是一种用来完全重新初始化进程(比如被用于一些‘sendmail’的版本)的
办法(比如当一个守护程序捕获一个‘SIGHUP’信号)。

1.14.1 So where do I put my configuration files then?
-----------------------------------------------------
1.14.1 那么,我把配置文件放在哪里里呢?

为配置文件安排正确的目录总是取决于你使用的Unix系统的特点;
‘/var/opt/PACKAGE’,‘/usr/local/lib’,‘/usr/local/etc’,或者任何其它一
些可能的地方。用户自定义的配置文件通常是在‘$HOME’下的以“.”开始的隐藏文件(
比如‘$HOME/.exrc’)。

从一个在不同系统上都能使用的软件包(package)的角度看,它通常意味着任何站
点范围(sitewide)的配置文件的位置有个已设定的缺省值,可能情况是使用一个在
配置脚本程序里的‘--prefix’选项(Autoconf 脚本程序集做这个工作)。你会希望允
许这个缺省值在程序执行时被一个环境变量重载。(如果你没使用配置脚本程序,
那么在编译时,将这个位置缺省值作为‘-D’选项放入项目文件(Makefile),或者
将其放入一个‘config.h’头文件,或做其它类似的工作)

--

用户自定义配置需要放置于一个在‘$HOME’下的文件名“.”打头的文件,或者
在需要多个配置文件时,建立文件名“.”打头的子目录。(在列目录时,文件名以
“.”打头的文件或目录缺省情况下被忽略。)避免在‘$HOME’建立多个文件,因
为这会造成非常杂乱的情况。当然,你也应该允许用户通过一个环境变量重载这个
位置。即使不能找到某个用户的配置文件,程序仍应当以适宜的方式执行。

1.15 为何父进程死时,我的进程未得到SIGHUP信号?
===============================================

因为本来就没有设想是这样做的。

‘SIGHUP’是一个信号,它按照惯例意味着“终端线路被挂断”。它与父进程
无关,而且通常由tty驱动程序产生(并传递给前台的进程组)。

但是,作为会话管理系统(session management system)的一部份,确切说有两种情况
下‘SIGHUP’会在一个进程死时发送出:

* 当一个终端设备与一个会话相关联,而这个会话的会话首领进程死时,
‘SIGHUP’被发送至这个终端设备的所有前台进程组。

* 当一个进程死去导致一个进程组变成孤儿,而且该进程组里一个或多个进程
处于*暂停*状态时,那么‘SIGHUP’和‘SIGCONT’被发送至这个孤儿进程
组的所有成员进程。(一个孤儿进程组是指在该进程组中没有一个成员进程的
父进程属于和该进程组相同的会话的其它进程组。)

1.16 我怎样杀死一个进程的所有派生进程?
=======================================

没有一个完全普遍的方法来做这个。虽然你可以通过处理‘ps’的输出确定进
程间的相互关系,但因为它只表示系统的一瞬间的状态(snapshot)所以并不可靠。

但是,如果你启动一个子进程,而它可能生成它自己的子进程,而你意图一次杀
死整个生成的事务(job),解决方法是将最先启动的子进程置于一个新的进程组,
当你需要时杀死整个进程组。

建议为创建进程组而使用的函数是‘setpgid()’。在可能情况下,使用这个函数
而不使用‘setpgrp()’,因为后一个在不同系统中有所不同(在一些系统上‘setgrp();’
等同于‘setpgid(0,0);’,在其它系统上,‘setpgrp()’和‘setpgid()’相同)。

参见范例章节的事务-控制范例程序。

放置一个子进程于其自身的进程组有一些影响。特别的,除非你显式地将该进程
组放置于前台,它将被认为是一个后台事务并具有以下结果:

* 试图从终端读取的进程将被‘SIGTTIN’信号暂停。

* 如果设置终端模式‘tostop’,那么试图向终端写的进程将被‘SIGTTOU’
信号暂停。(试图改变终端模式也导致这个结果,且不管当前‘tostop’是否
设置)

* 子进程将不会收到从终端发出的键盘信号(比如‘SIGINT’或‘SIGQUIT’)

在很多应用程序中输入和输出总会被重定向,所以最显著的影响将是丧失键盘
信号。父进程需要安排程序起码捕获‘SIGINIT’和‘SIGQUIT’(可能情况下,
还有‘SIGTERM’),并在需要情况下清除后台事务。







__________________
No Copyright 2005 UnWindows
All rights Shared and Open source

渴望和linux使用者交流
MSN:yummyzhao@msn.com
EMAIL:yummyzhao@163.com

此帖于 05-02-16 22:48 被 UnWindows 编辑.
  UnWindows 当前离线   回复时引用此帖
旧 05-02-16, 22:20 第 2 帖
 
 
 
注册会员  
  注册日期: Oct 2004
  我的住址: 有山有水有平原
  帖子: 56
   精华 : 1
 

标题: Unix编程常见问题解答《6 工具的使用》


6. 工具的使用
*************

6.1 我怎样调试fork函数产生的子进程?
====================================

根据可用的工具有两种不同的方法:

你的调试器(debugger)可能有允许你选择是否跟踪调用‘fork()’以后的父或子进程
的选项,对于某些目的来说那已经足够了。

替换方法是,你的调试器可能有一个选项允许你将它依附(attach)到一个正在执行
的程序。这样你可以依附调试器到一个已经开始执行的子进程。如果你不需要从
子进程一开始就开始测试,这通常已经足够。否则,你会希望在子进程的‘fork()’
调用后插入一个‘sleep()’调用,或者插入如下的循环:

{
volatile int f = 1;
while(f);
}

这样子进程将一直在此循环不往下执行直到你用调试器设定‘f’为0。

并且记住,使用调试器并非是找到你程序中错误的唯一方法;在很多Unix系统
上有一些工具程序可用来跟踪系统调用和信号,而且丰富的日志经常也是有
用的。

6.2 怎样通过其他库文件建立新的库文件?
======================================

前提是我们所说的是归档(archive)(静态)库,最简单的方法是将所有选择的库
文件使用‘ar x’命令在一个空目录里拆分成它们原始的单个目标文件(object),
然后再合并成一个。当然,文件名有可能会重复,但是如果这个库文件很大,
你也许一开始就不想将它们合并在一起。

6.3 怎样创建动态连接库(shared library)/dlls?
=============================================

创建动态连接库(shared libraries)的方法根据不同的系统有所不同。这个过程主要
分两步;第一步要求包括在动态连接库中的目标必须首先是编译好的,通常需
要某个编译选项指示这串代码是位置无关的(position-indepenent);第二步,是将
这些目标连接在一起形成一个库文件。

这里是一个演示以上道理的小程序:

/* shrobj.c 文件 */

const char *myfunc()
{
return "Hello World";
}

/* shrobj.c 结束 */

/* hello.c 文件 */

#include <stdio.h>

extern const char *myfunc();

main()
{
printf("%s/n", myfunc());
return 0;
}

/* hello.c 结束 */

$ gcc -fpic -c shrobj.c
$ gcc -shared -o libshared.so shrobj.o
$ gcc hello.c libshared.so
$ ./a.out
Hello World

到目前为止,如果你希望库文件和它的创建过程是都可以移植的话,那么最好
的办法是使用GNU Libtool程序包。它是个小型的工具程序套件,这些工具程序
知道建立动态连接库的平台无关性;你可以只发布你的程序必要的部分,从而
当一个安装者配置你的软件包时,他能决定生成什么库。Libtool程序包在不支持
动态连接库的系统上也能工作得很好。它而且知道与GNU Autoconf程序和GNU
Automake程序挂钩(如果你使用这些工具来管理你程序的编译创建过程)。

如果你不想使用Libtool程序包,那么对于gcc以外的编译器,你需要按照下面所
列修改编译器参数:

AIX 3.2 使用 xlc (未证实)
取消‘-fpic’选项,以‘-bM:SRE -bE:libshared.exp’取代‘-shared’。你并且
需要创建一个名为‘libshared.exp’的文件保存一个所有输出符号(symbols to export)
的列表,比如以上的范例程序,就需要输出‘myfunc’符号。另外,在连接库
时使用‘-e _nostart’参数(在较新的AIX版本上,我相信应该将其变成‘-bnoentry’)。

SCO OpenServer 5 使用 SCO 开发系统(Development System) (未证实)
如果你使用ELF(译者注:ELF:执行与连接格式Executable and Linking Forrmat,
一种Unix可执行目标文件的格式)格式,那么共享库只能在OS5上可用,而它
需要‘-belf’选项。并以‘-Kpic’取代‘-fpic’,在连接时使用‘cc -belf -G’。

Solaris 使用 SparcWorks 编译器
以‘-pic’取代‘-fpic’,并以‘ld -G’取代‘gcc -shared’。

(鼓励大家提供更多的材料丰富上述列表)


其它要当心的问题:

* AIX和(我相信)Digital Unix不需要-fpic选项,因为所有代码都是位置无关的。

* AIX一般需要你创建一个‘输出文件’,即一个保存所有动态连接库中输出
符号的列表。一些连接器(linker)版本(可能只有SLHS连接器,是svld?)有一个
选项可以输出所有符号。

* 如果你对于连接器想使用普遍的‘-l’参数来引用你的动态连接库,你必须
理解你的系统在实际运行时是怎样寻找动态连接库的。最普通的方法是使用
‘LD_LIBRARY_PATH’环境变量,但是通常在连接时有一种其它选项可以
设定。

* 大多数实现方法是在程序内部记录所希望的动态连接库在运行时的位置。这
样把一个动态连接库从一个目录移到另一个目录将导致程序无法工作。许多
系统对于连接器有一个选项用以设定希望运行时动态连接库的位置(比如在
Solaris系统上是‘-R’连接器选项,或者是‘LD_RUN_PATH’环境变量)。

* ELF和a.out的实现方法可能有一个连接器选项‘-Bsymbolic’,它导致在库本
身内部的引用被解释好。否则,在这些系统上,所有符号的解释将推迟到最
后连接时再进行,这样在main程序中的单一函数将重载库中的对应函数。

6.4 我能更改一个动态连接库里的目标吗?
======================================

一般不行。

在大多数系统上(除了AIX),当你连接目标并形成一个动态连接库时,它就象连
接一个可执行程序;目标并不保留它们单一的特征。结果是,一般不能从一个动
态连接库里析取出或更换一个单一的目标。

6.5 我能在一个运行着的程序中生成堆栈映象吗?
============================================

一些系统提供库函数可以提取(unwinding)出堆栈,从而(比如)你可以在一个错误
处理函数中生成一个堆栈映象,但是只有一小部分系统有这些函数。

一个可能的变通方法(workaround)是让你的程序执行一个调试器调试*它自己* -
详细方法仍然根据不同系统稍有不同,但一般的概念是这样:

void dump_stack(void)
{
char s[160];

sprintf(s, "/bin/echo 'where/ndetach' | dbx -a %d", getpid());
system(s);

return;
}

你需要根据你不同的系统对dbx的参数和命令进行加工,或者甚至换另一个调试
器,比如‘gdb’,但这仍然是我见过的对于这种问题最普遍的解决方法。为此,
荣誉授予Ralph Corderoy。

下面列表包含在一些系统上需要用到的命令行:

大多数使用dbx的系统
`"/bin/echo 'where/ndetach' | dbx /path/to/program %d"'

AIX
`"/bin/echo 'where/ndetach' | dbx -a %d"'

IRIX
`"/bin/echo 'where/ndetach' | dbx -p %d"'
?
范例程序
********

捕获 SIGCHLD 信号
=================

#include <sys/types.h> /* 在任何其它 sys 下的头文件之前引用这个头文件 */
#include <sys/wait.h> /* waitpid()和一些不同的宏所需的头文件 */
#include <signal.h> /* 信号函数的头文件 */
#include <stdio.h> /* fprintf函数的头文件 */
#include <unistd.h> /* fork函数的头文件 */
void sig_chld(int); /* 我们的 SIGCHLD 信号处理函数的原形(prototype) */

int main()
{
struct sigaction act;
pid_t pid;

/* 设定sig_chld函数作为我们SIGCHLD信号的处理函数 */
act.sa_handler = sig_chld;

/* 在这个范例程序里,我们不想阻塞其它信号 */
sigemptyset(&act.sa_mask);

/*
* 我们只关谋恢罩沟淖咏蹋皇潜恢卸? * 的子进程 (比如用户在终端上按Control-Z)
*/
act.sa_flags = SA_NOCLDSTOP;

/*
* 使这些设定的值生效. 如果我们是写一个真实的应用程序,
* 我们也许应该保存这些原有值,而不是传递一个NULL。
*/
if (sigaction(SIGCHLD, &act, NULL) < 0)
{
fprintf(stderr, "sigaction failed/n");
return 1;
}

/* fork */
switch (pid = fork())
{
case -1:
fprintf(stderr, "fork failed/n");
return 1;

case 0: /* 是子进程,直接结束 */
_exit(7); /* 退出状态 = 7 */

default: /* 父进程 */
sleep(10); /* 给子进程完成的时间 */
}

return 0;
}

/*
* 信号处理函数 -- 只有当接收到一个SIGCHLD信号才被调用,
* 即有一个子进程终止
*/
void sig_chld(int signo)
{
int status, child_val;

/* 非阻塞地等待任何子进程结束 */
if (waitpid(-1, &status, WNOHANG) < 0)
{
/*
* 不建议在信号处理函数中调用标准输入/输出函数,
* 但在一个类似这个的玩具程序里或许没问题
*/
fprintf(stderr, "waitpid failed/n");
return;
}

/*
* 我们现在有保存在‘status’变量中的子进程退出信息并可以使用
* wait.h中定义的宏对其进行操作
*/
if (WIFEXITED(status)) /* 子进程是正常退出吗? */
{
child_val = WEXITSTATUS(status); /* 获取子进程的退出状态 */
printf("child's exited normally with status %d/n", child_val);
}
}

读取进程表 - SUNOS 4 版
=======================

#define _KMEMUSER
#include <sys/proc.h>
#include <kvm.h>
#include <fcntl.h>

char regexpstr[256];
#define INIT register char *sp=regexpstr;
#define GETC() (*sp++)
#define PEEKC() (*sp)
#define UNGETC(c) (--sp)
#define RETURN(pointer) return(pointer);
#define ERROR(val)
#include <regexp.h>

pid_t
getpidbyname(char *name,pid_t skipit)
{
kvm_t *kd;
char **arg;
int error;
char *p_name=NULL;
char expbuf[256];
char **freeme;
int curpid;
struct user * cur_user;
struct user myuser;
struct proc * cur_proc;


if((kd=kvm_open(NULL,NULL,NULL,O_RDONLY,NULL))==NULL){
return(-1);
}
sprintf(regexpstr,"^.*/%s$",name);
compile(NULL,expbuf,expbuf+256,'/0');

while(cur_proc=kvm_nextproc(kd)){
curpid = cur_proc->p_pid;
if((cur_user=kvm_getu(kd,cur_proc))!=NULL){
error=kvm_getcmd(kd,cur_proc,cur_user,&arg,NULL);
if(error==-1){
if(cur_user->u_comm[0]!='/0'){
p_name=cur_user->u_comm;
}
}
else{
p_name=arg[0];
}
}
if(p_name){
if(!strcmp(p_name,name)){
if(error!=-1){
free(arg);
}
if(skipit!=-1 && ourretval==skipit){
ourretval=-1;
}
else{
close(fd);
break;
}
break;
}
else{
if(step(p_name,expbuf)){
if(error!=-1){
free(arg);
}
break;
}
}
}
if(error!=-1){
free(arg);
}
p_name=NULL;
}
kvm_close(kd);
if(p_name!=NULL){
return(curpid);
}
return (-1);
}

读取进程表 - SYSV 版
====================

pid_t
getpidbyname(char *name,pid_t skipit)
{
DIR *dp;
struct dirent *dirp;
prpsinfo_t retval;
int fd;
pid_t ourretval=-1;

if((dp=opendir("/proc"))==NULL){
return -1;
}
chdir("/proc");
while((dirp=readdir(dp))!=NULL){
if(dirp->d_name[0]!='.'){
if((fd=open(dirp->d_name,O_RDONLY))!=-1){
if(ioctl(fd,PIOCPSINFO,&retval)!=-1){
if(!strcmp(retval.pr_fname,name)){
ourretval=(pid_t)atoi(dirp->d_name);
if(skipit!=-1 && ourretval==skipit){
ourretval=-1;
}
else{
close(fd);
break;
}
}
}
close(fd);
}
}
}
closedir(dp);
return ourretval;
}

读取进程表 - AIX 4.2 版
=======================

#include <stdio.h>
#include <procinfo.h>

int getprocs(struct procsinfo *, int, struct fdsinfo *,
int, pid_t *, int);

pid_t getpidbyname(char *name, pid_t *nextPid)
{
struct procsinfo pi;
pid_t retval = (pid_t) -1;
pid_t pid;

pid = *nextPid;

while(1)
{
if(getprocs(&pi, sizeof pi, 0, 0, &pid, 1) != 1)
break;

if(!strcmp(name, pi.pi_comm))
{
retval = pi.pi_pid;
*nextPid = pid;
break;
}
}

return retval;
}

int main(int argc, char *argv[])
{
int curArg;
pid_t pid;
pid_t nextPid;

if(argc == 1)
{
printf("syntax: %s <program> [program ...]/n",argv[0]);
exit(1);
}

for(curArg = 1; curArg < argc; curArg++)
{
printf("Process IDs for %s/n", argv[curArg]);

for(nextPid = 0, pid = 0; pid != -1; )
if((pid = getpidbyname(argv[curArg], &nextPid)) != -1)
printf("/t%d/n", pid);
}
}

使用popen函数和ps命令读取进程表
===============================

#include <stdio.h> /* FILE, sprintf, fgets, puts */
#include <stdlib.h> /* atoi, exit, EXIT_SUCCESS */
#include <string.h> /* strtok, strcmp */
#include <sys/types.h> /* pid_t */
#include <sys/wait.h> /* WIFEXITED, WEXITSTATUS */

char *procname(pid_t pid)
{
static char line[133], command[80], *linep, *token, *cmd;
FILE *fp;
int status;

if (0 == pid) return (char *)0;

sprintf(command, "ps -p %d 2>/dev/null", pid);
fp = popen(command, "r");
if ((FILE *)0 == fp) return (char *)0;

/* 读取标题行 */
if ((char *)0 == fgets(line, sizeof line, fp))
{
pclose(fp);
return (char *)0;
}

/* 从标题栏分析出命令名所在列。
* (BSD风格的系统将指示命令的"COMMAND"字符串放在第5列,SysV好象将
* 指示命令的“CMD”或“COMMAND”字符串放在第4列)
*/
for (linep = line; ; linep = (char *)0)
{
if ((char *)0 == (token = strtok(linep, " /t/n")))
{
pclose(fp);
return (char *)0;
}
if (0 == strcmp("COMMAND", token) || 0 == strcmp("CMD", token))
{ /* 我们找到COMMAND所在列 */
cmd = token;
break;
}
}

/* 读取 ps(1) 输出行 */
if ((char *)0 == fgets(line, sizeof line, fp))
{
pclose(fp);
return (char *)0;
}

/* 抓COMMAND标题下面的词 ... */
if ((char *)0 == (token = strtok(cmd, " /t/n")))
{
pclose(fp);
return (char *)0;
}

status = pclose(fp);
if (!WIFEXITED(status) || 0 != WEXITSTATUS(status))
return (char *)0;

return token;
}

int main(int argc, char *argv[])
{
puts(procname(atoi(argv[1])));
exit(EXIT_SUCCESS);
}

守护程序工具函数
================

#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>

/* closeall() -- 关闭所有>=给定值的文件描述符 */

void closeall(int fd)
{
int fdlimit = sysconf(_SC_OPEN_MAX);

while (fd < fdlimit)
close(fd++);
}

/* daemon() - 将进程从用户端脱离并消失进入后台,若失败返回-1,
* 但是在那种情况下你只能退出,因为我们可能已经生成了子进程。
* 这是基于BSD的版本,所以调用方需负责类似umask等等其它的工作。
*/

/* 相信在所有Posix系统上都能工作 */

int daemon(int nochdir, int noclose)
{
switch (fork())
{
case 0: break;
case -1: return -1;
default: _exit(0); /* 原进程退出 */
}

if (setsid() < 0) /* 不应该失败 */
return -1;

/* 如果你希望将来获得一个控制tty,则排除(dyke)以下的switch语句 */
/* -- 正常情况不建议用于守护程序 */

switch (fork())
{
case 0: break;
case -1: return -1;
default: _exit(0);
}

if (!nochdir)
chdir("/");

if (!noclose)
{
closeall(0);
open("/dev/null",O_RDWR);
dup(0); dup(0);
}

return 0;
}

/* fork2() -- 类似fork函数,但子进程立刻变成孤儿进程
* (当它退出时不产生僵死进程)
* 返回1给父进程,不是任何有意义的进程号.
* 父进程不能使用wait函数等待子进程结束 (它们是无关的).
*/

/* 这个版本假设你没有捕获和忽略SIGCHLD信号. */
/* 如果你有设定,则不管怎样应使用fork函数 */

int fork2()
{
pid_t pid;
int rc;
int status;

if (!(pid = fork()))
{
switch (fork())
{
case 0: return 0;
case -1: _exit(errno); /* 假设错误码都小于256 */
default: _exit(0);
}
}

if (pid < 0 || waitpid(pid,&status,0) < 0)
return -1;

if (WIFEXITED(status))
if (WEXITSTATUS(status) == 0)
return 1;
else
errno = WEXITSTATUS(status);
else
errno = EINTR; /* 唉,类似这个 :-) */

return -1;
}

一个使用以上函数的范例程序:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <errno.h>

int daemon(int,int);
int fork2(void);
void closeall(int);

#define TCP_PORT 8888

void errexit(const char *str)
{
syslog(LOG_INFO, "%s failed: %d (%m)", str, errno);
exit(1);
}

void errreport(const char *str)
{
syslog(LOG_INFO, "%s failed: %d (%m)", str, errno);
}

/* 实际的子进程在此. */

void run_child(int sock)
{
FILE *in = fdopen(sock,"r");
FILE *out = fdopen(sock,"w");
int ch;

setvbuf(in, NULL, _IOFBF, 1024);
setvbuf(out, NULL, _IOLBF, 1024);

while ((ch = fgetc(in)) != EOF)
fputc(toupper(ch), out);

fclose(out);
}

/* 这是守护程序的主要工作 -- 侦听连接并生成子进程 */

void process()
{
struct sockaddr_in addr;
int addrlen = sizeof(addr);
int sock = socket(AF_INET, SOCK_STREAM, 0);
int flag = 1;
int rc = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
&flag, sizeof(flag));

if (rc < 0)
errexit("setsockopt");

addr.sin_family = AF_INET;
addr.sin_port = htons(TCP_PORT);
addr.sin_addr.s_addr = INADDR_ANY;

rc = bind(sock, (struct sockaddr *) &addr, addrlen);
if (rc < 0)
errexit("bind");

rc = listen(sock, 5);
if (rc < 0)
errexit("listen");

for (;;)
{
rc = accept(sock, (struct sockaddr *) &addr, &addrlen);

if (rc >= 0)
switch (fork2())
{
case 0: close(sock); run_child(rc); _exit(0);
case -1: errreport("fork2"); close(rc); break;
default: close(rc);
}
}
}

int main()
{
if (daemon(0,0) < 0)
{
perror("daemon");
exit(2);
}

openlog("test", LOG_PID, LOG_DAEMON);

process();

return 0;
}

调制解调器控制范例程序
======================

/* 发出一些简单调制解调器命令
* 需要串行设备的设备名 (最好是拨出设备,
* 或者是非调制解调器控制设备) 作为它唯一的参数.
* 如果你没有可共使用的拨出设备, 那么以CFLAGS_TO_SET取代CLOCAL。
*/

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h> /* 也许需要;和系统有关 */
#include <termios.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>

#define CFLAGS_TO_SET (CREAD | HUPCL)
#define CFLAGS_TO_CLEAR (CSTOPB | PARENB | CLOCAL)

enum flowmode { NoFlow, HardFlow, SoftFlow };

/* 和系统有关 */
#define CFLAGS_HARDFLOW (CRTSCTS)


#define EXAMPLE_BAUD B19200
#define EXAMPLE_FLOW HardFlow


static void die(const char *msg)
{
fprintf(stderr, "%s/n", msg);
exit(1);
}

static int close_and_complain(int fd, const char *msg, int err)
{
fprintf(stderr, "%s: %s/n", msg, strerror(err));
if (fd >= 0)
close(fd);
errno = err;
return -1;
}


int open_port(const char *name, speed_t baud, enum flowmode flow)
{
int flags;
struct termios attr;

int fd = open(name, O_RDWR | O_NONBLOCK | O_NOCTTY);

if (fd < 0)
return close_and_complain(-1, "open", errno);

/* 设定一些不明确是否敏感的值 */

if (tcgetattr(fd, &attr) < 0)
return close_and_complain(fd, "tcgetattr", errno);

/* 无特殊输入或输出处理 */

attr.c_iflag = (flow == SoftFlow) ? (IXON | IXOFF) : 0;
attr.c_oflag = 0;

/* 设定8位字符宽和一些杂项控制模式 */

attr.c_cflag &= ~(CSIZE | CFLAGS_TO_CLEAR | CFLAGS_HARDFLOW);
attr.c_cflag |= (CS8 | CFLAGS_TO_SET);
if (flow == HardFlow)
attr.c_cflag |= CFLAGS_HARDFLOW;

/* 本机模式 */

attr.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ISIG);

/* 特殊字符 -- 许多已被先前的设定取消 */

{
int i;
#ifdef _POSIX_VDISABLE
attr.c_cc[0] = _POSIX_VDISABLE;
#else
attr.c_cc[0] = fpathconf(fd, _PC_VDISABLE);
#endif
for (i = 1; i < NCCS; i++)
attr.c_cc[i] = attr.c_cc[0];
}

attr.c_cc[VSTART] = 0x11;
attr.c_cc[VSTOP] = 0x13;

/* 对read()函数的计时控制 */

attr.c_cc[VMIN] = 1;
attr.c_cc[VTIME] = 0;

/* 波特律 */

cfsetispeed(&attr, baud);
cfsetospeed(&attr, baud);

/* 写入设定 */

if (tcsetattr(fd, TCSANOW, &attr) < 0)
return close_and_complain(fd, "tcsetattr", errno);

/* 如果系统记住了先前的O_NONBLOCK设定,就取消它 */

flags = fcntl(fd, F_GETFL, 0);
if (flags < 0)
return close_and_complain(fd, "fcntl(GETFL)", errno);
if (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0)
return close_and_complain(fd, "fcntl(SETFL)", errno);

return fd;
}

/* 一些简单的计时工具函数 */

/* 向*TV加 SECS 和USECS */

static void timeradd(struct timeval *tv, long secs, long usecs)
{
tv->tv_sec += secs;
if ((tv->tv_usec += usecs) >= 1000000)
{
tv->tv_sec += tv->tv_usec / 1000000;
tv->tv_usec %= 1000000;
}
}

/* 设定 *RES = *A - *B, 返回结果的符号 */

static int timersub(struct timeval *res,
const struct timeval *a, const struct timeval *b)
{
long sec = a->tv_sec - b->tv_sec;
long usec = a->tv_usec - b->tv_usec;

if (usec < 0)
usec += 1000000, --sec;

res->tv_sec = sec;
res->tv_usec = usec;

return (sec < 0) ? (-1) : ((sec == 0 && usec == 0) ? 0 : 1);
}


/* 这个函数不试图处理非正常的字符串 (比如 ababc)
* 超时以微妙计
* 一个更通常的做法是使用alarm()函数处理超时.
* 这个函数为简便起见不使用信号处理并试图提供一种替换方法
*/

int expect(int fd, const char *str, int timeo)
{
int matchlen = 0;
int len = strlen(str);
struct timeval now,end,left;
fd_set fds;
char c;

gettimeofday(&end, NULL);
timeradd(&end, timeo/1000, timeo%1000);

while (matchlen < len)
{
gettimeofday(&now, NULL);
if (timersub(&left, &end, &now) <= 0)
return -1;

FD_ZERO(&fds);
FD_SET(fd, &fds);
if (select(fd+1, &fds, NULL, NULL, &left) <= 0)
return -1;

if (read(fd, &c, 1) != 1)
return -1;

if (isprint((unsigned char)c) || c == '/n' || c == '/r')
putchar(c);
else
printf("//x%02x", c);

if (c == str[matchlen])
++matchlen;
else
matchlen = 0;
}

return 0;
}


int main(int argc, char **argv)
{
int fd;
unsigned char c;

if (argc < 2)
die("no port specified");

setvbuf(stdout, NULL, _IONBF, 0);

fd = open_port(argv[1], EXAMPLE_BAUD, EXAMPLE_FLOW);
if (fd < 0)
die("cannot open port");

write(fd, "AT/r", 3);
if (expect(fd, "OK", 5000) < 0)
{
write(fd, "AT/r", 3);
if (expect(fd, "OK", 5000) < 0)
{
tcflush(fd, TCIOFLUSH);
close(fd);
die("no response to AT");
}
}

write(fd, "ATI4/r", 5);
expect(fd, "OK", 10000);

putchar('/n');

tcflush(fd, TCIOFLUSH);
close(fd);

return 0;
}

事务控制范例程序
================


/* 生成前台/后台事务的函数 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>

/* 一些下面的函数会因为无法定位控制tty和调用方不在前台而失败。
* 第一种情况时,我们假设一个前台程序会有为标准输入,标准输出或标准错误输出打开的ctty,
* 而如果没有则返回ENOTTY。
* 第二种情况时,除foreground_self()函数的特殊情况以外,
* 若一个非前台程序打算输出一些东西到前台,我们返回EPERM。
* (也许想得太多了)
*/


/* 为给定的pgrp安排一个终端 (打开一个ctty) .
* 这个tcsetpgrp()外壳程序只是因为POSIX中特别错误(bogusity)的地方而需要;
* 遵照标准的系统在一个非前台进程调用tcsetpgrp函数时传递SIGTTOU
* 信号(差不多总是这样)。这是虚假的一致性之于一般想法的胜利。
*/

int assign_terminal(int ctty, pid_t pgrp)
{
sigset_t sigs;
sigset_t oldsigs;
int rc;

sigemptyset(&sigs);
sigaddset(&sigs,SIGTTOU);
sigprocmask(SIG_BLOCK, &sigs, &oldsigs);

rc = tcsetpgrp(ctty, pgrp);

sigprocmask(SIG_SETMASK, &oldsigs, NULL);

return rc;
}


/* 类似fork函数,但做事务控制。如果新建立的进程放在前台则设fg为真。
* (这样隐式地将调用方进程放置到后台,所以做完这个后要当心tty的输入/输出)
* 设定pgrp为-1以创建一个新事务,在此情况下返回的进程号即是新事务的进程组号,
* 或者设定一个同一会话中存在的事务(一般只用来启动管道操作的第二个或第二个以后
* 的进程)。
*/

pid_t spawn_job(int fg, pid_t pgrp)
{
int ctty = -1;
pid_t pid;

/* 如果生成一个*新*的前台事务,起码要求标准输入,标准输出或
* 标准错误输出的其中一个指向的是控制tty,并且当前进程在前台。
* 只有当在存在事务中开始一个新前台进程时才检查控制中的tty。
* 一个没有控制tty的会话只能有后台事务。
*/

if (fg)
{
pid_t curpgrp;

if ((curpgrp = tcgetpgrp(ctty = 2)) < 0
&& (curpgrp = tcgetpgrp(ctty = 0)) < 0
&& (curpgrp = tcgetpgrp(ctty = 1)) < 0)
return errno = ENOTTY, (pid_t)-1;

if (pgrp < 0 && curpgrp != getpgrp())
return errno = EPERM, (pid_t)-1;
}

switch (pid = fork())
{
case -1: /* fork失败 */
return pid;

case 0: /* 子进程 */

/* 建立新进程组, 如果需要则将我们放到前台
* 不知道如果setpgid函数调用失败该怎么办(“不会发生”)
*/

if (pgrp < 0)
pgrp = getpid();

if (setpgid(0,pgrp) == 0 && fg)
assign_terminal(ctty, pgrp);

return 0;

default: /* 父进程 */

/* 这里也建立自进程组. */

if (pgrp < 0)
pgrp = pid;

setpgid(pid, pgrp);

return pid;
}

/*不会执行到这里*/
}


/* 用SIGNO表示的信号杀死PGRP表示的事务 */

int kill_job(pid_t pgrp, int signo)
{
return kill(-pgrp, signo);
}


/* 中断PGRP表示的事务 */

int suspend_job(pid_t pgrp)
{
return kill_job(pgrp, SIGSTOP);
}


/* 继续在后台执行PGRP表示的事务 */

int resume_job_bg(pid_t pgrp)
{
return kill_job(pgrp, SIGCONT);
}


/* 继续在前台执行PGRP表示的事务 */

int resume_job_fg(pid_t pgrp)
{
pid_t curpgrp;
int ctty;

if ((curpgrp = tcgetpgrp(ctty = 2)) < 0
&& (curpgrp = tcgetpgrp(ctty = 0)) < 0
&& (curpgrp = tcgetpgrp(ctty = 1)) < 0)
return errno = ENOTTY, (pid_t)-1;

if (curpgrp != getpgrp())
return errno = EPERM, (pid_t)-1;

if (assign_terminal(ctty, pgrp) < 0)
return -1;

return kill_job(pgrp, SIGCONT);
}


/* 将我们自己放置到前台,比如在中断一个前台事务之后调用
*/

int foreground_self()
{
pid_t curpgrp;
int ctty;

if ((curpgrp = tcgetpgrp(ctty = 2)) < 0
&& (curpgrp = tcgetpgrp(ctty = 0)) < 0
&& (curpgrp = tcgetpgrp(ctty = 1)) < 0)
return errno = ENOTTY, (pid_t)-1;

return assign_terminal(ctty, getpgrp());
}


/* closeall() - 关闭所有>=给定FD的文件描述符 */

void closeall(int fd)
{
int fdlimit = sysconf(_SC_OPEN_MAX);

while (fd < fdlimit)
close(fd++);
}


/* 类似system()函数,但将给定的命令作为后台事务执行,返回shell进程
* 的进程号(并且也是这个事务的进程组号,适用于kill_job等等)。
* 如果参数INFD,OUTFD或ERRFD为非NULL,则打开一个管道和一个文件描述
* 符保存与该管道有关的父进程端,然后在子进程中将被从定向到/dev/null。
* 并且在子进程中关闭所有>2的文件描述符(一个经常过份估计的工作)
*/

pid_t spawn_background_command(const char *cmd,
int *infd, int *outfd, int *errfd)
{
int nullfd = -1;
int pipefds[3][2];
int error = 0;

if (!cmd)
return errno = EINVAL, -1;

pipefds[0][0] = pipefds[0][1] = -1;
pipefds[1][0] = pipefds[1][1] = -1;
pipefds[2][0] = pipefds[2][1] = -1;

if (infd && pipe(pipefds[0]) < 0)
error = errno;
else if (outfd && pipe(pipefds[1]) < 0)
error = errno;
else if (errfd && pipe(pipefds[2]) < 0)
error = errno;

if (!error && !(infd && outfd && errfd))
{
nullfd = open("/dev/null",O_RDWR);
if (nullfd < 0)
error = errno;
}

if (!error)
{
pid_t pid = spawn_job(0, -1);
switch (pid)
{
case -1: /* fork失败 */
error = errno;
break;

case 0: /* 子进程 */

dup2(infd ? pipefds[0][0] : nullfd, 0);
dup2(outfd ? pipefds[1][1] : nullfd, 1);
dup2(errfd ? pipefds[2][1] : nullfd, 2);
closeall(3);

execl("/bin/sh","sh","-c",cmd,(char*)NULL);

_exit(127);

default: /* 父进程 */

close(nullfd);
if (infd)
close(pipefds[0][0]), *infd = pipefds[0][1];
if (outfd)
close(pipefds[1][1]), *outfd = pipefds[1][0];
if (errfd)
close(pipefds[2][1]), *errfd = pipefds[2][0];

return pid;
}
}

/* 只在错误时执行到这里 */

{
int i,j;
for (i = 0; i < 3; ++i)
for (j = 0; j < 2; ++j)
if (pipefds[i][j] >= 0)
close(pipefds[i][j]);
}

if (nullfd >= 0)
close(nullfd);

return errno = error, (pid_t) -1;
}


/*---------------------------------------*/
/* 这里是使用上述函数一个小例子. */

pid_t bgjob = -1;
volatile int signo = 0;

#ifndef WCOREDUMP
/* 如果没有 WCOREDUMP, 你也许会希望在你的平台上为它设置一个准确的定义
* (这通常是(status & 0x80) 但也不总是这样),或者就赌没有core dumps(
* 就象这个程序所做)
*/
# define WCOREDUMP(status) (0)
#endif

int check_children()
{
pid_t pid;
int status;
int count = 0;

while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0)
{
if (pid == bgjob && !WIFSTOPPED(status))
bgjob = -1;

++count;

if (WIFEXITED(status))
fprintf(stderr,"Process %ld exited with return code %d/n",
(long)pid, WEXITSTATUS(status));
else if (WIFSIGNALED(status))
fprintf(stderr,"Process %ld killed by signal %d%s/n",
(long)pid, WTERMSIG(status),
WCOREDUMP(status) ? " (core dumped)" : "");
else if (WIFSTOPPED(status))
fprintf(stderr,"Process %ld stopped by signal %d/n",
(long)pid, WSTOPSIG(status));
else
fprintf(stderr,"Unexpected status - pid=%ld, status=0x%x/n",
(long)pid, status);
}

return count;
}


void sighandler(int sig)
{
if (sig != SIGCHLD)
signo = sig;
}


int main()
{
struct sigaction act;
int sigcount = 0;

act.sa_handler = sighandler;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
sigaction(SIGQUIT,&act,NULL);
sigaction(SIGTERM,&act,NULL);
sigaction(SIGTSTP,&act,NULL);
sigaction(SIGCHLD,&act,NULL);


fprintf(stderr,"Starting background job 'sleep 60'/n");
bgjob = spawn_background_command("sleep 60", NULL, NULL, NULL);
if (bgjob < 0)
{
perror("spawn_background_command");
exit(1);
}
fprintf(stderr,"Background job started with id %ld/n", (long)bgjob);
while (bgjob >= 0)
{
if (signo)
{
fprintf(stderr,"Signal %d caught/n", signo);
if (sigcount++)
kill_job(bgjob, SIGKILL);
else
{
kill_job(bgjob, SIGTERM);
kill_job(bgjob, SIGCONT);
}
}

if (!check_children())
pause();
}

fprintf(stderr,"Done - exiting/n");
return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值