荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: bstone (Back to real world!), 信区: Hacker
标  题: unix环境高级编程--第8章 进程控制 (下)
发信站: BBS 荔园晨风站 (Wed Mar 29 08:47:50 2000), 转信

发信人: scircle (yuanyuan), 信区: Security
标  题: unix环境高级编程--第8章 进程控制 (下)
发信站: BBS 水木清华站 (Mon Mar 27 15:59:57 2000)

P209
图85〓六个exec函数之间的区别
每个系统对参数表和环境表的总长度都有一个限制。在图27中,这种限制是ARG
-MAX。在P
OSIX1系统中,此值至少是4096字节。当使用shell的文件名扩充功能产生一个文
件名表时
,可能会受到此值的限制。例如,此命令
grep -POSIX 迹茫模*常病絊OURCE〓/usr/include/*/*h
在某些系统上可能产生下列形式的shell错:
arg〓〓list〓〓too〓〓long
由于历史原因,系统V中此限制是5120字节。43BSD和43+BSD在分发时此限制是
20,480
字节。作者所用的系统则允许多至一兆字节!(见程序21的输出)
前面曾提及在执行exec后,进程ID没有改变。除此之外,执行新程序的进程还保持
了原进程
的下列特征:
·进程ID和父进程ID
·实际用户ID和实际组ID
·添加组ID
·添加组ID
·进程组ID
·对话期ID
·控制终端
·闹钟尚余留的时间
·当前工作目录
·根目录
·文件方式创建屏蔽字
·文件锁
·进程信号 帘为?
·末决信号
·资源限制
·tms-utime,tms-stime,tms-cutime以及tms-ustime值
对打开文件的处理与每个描述符的进程中每个在exec时关闭标志值有关。回忆图3
2以及3
13节中对PD-CLOEXEC的说明,打开描述符都有一个在exec时关闭标志。若此标志
设置,则
在执行exec时关闭该描述符,否则该描述符的打开除非特地用fcntl设置了该标志
,否则系
统的默认操作是在exec后仍保持这种描述符打开。
POSIX1明确要求在exec时关闭打开目录流。(回忆42)节中所述的opendir函数
。)这通常
是由opendir函数实现的,它调用fcntl函数为对应于打开目录流的描述符设置在e
是由opendir函数实现的,它调用fcntl函数为对应于打开目录流的描述符设置在e
xec时关闭
标志。
注意,在exec前后实际用户ID和实际组ID保持不变,而有效ID是否改变则取决于所
执行程序
的文件的设置用户ID位和设置组ID位是否设置。如果新程序的设置用户ID位已设置
,则有效
用户ID变成程序文件属主的ID,否则有效用户ID不变。对组ID的处理方式与此相同
。
在很多Unix实现中,这六个函数中只有一个execve是系统核的系统调用。另外五个
只是库函
数,它们最终都要调用系统调用。这六个函数之间的关系示于图86中。在这种安
排中,库
函数execlp和execvp使用PATH环境变量查找第一个包含名为filename的可执行文件
的路径名
前缀。
P211
图86〓六个exec函数之间的关系
实际
程序88例示了exec函数。
P211
程序88〓exec函数的实例
在该程序中先调用execle,它要求一个路径名和一个特定的环境。下一个调用的是
在该程序中先调用execle,它要求一个路径名和一个特定的环境。下一个调用的是
execlp,
它用一个文件名,并将调用者的环境传送给新程序。execlp在这里能够工作的原因
是因为目
录/home/stevens/bin是当前路径前缀之一。注意,我们将第一个参数(在新程序中
的argv[
0])设置为路径名的文件名分量。某些shell将此参数设置为完全的路径名。
在程序88中要执行两次的程序echoall示于程序89中。这是一个普通程序,它
回送其所
有命令行参数及其全部环境表。

P212
程序89〓回送所有命令行参数和所有环境字符串
执行程序88时得到:
$ aout
argv[0]:echoall
argv[1]:myarg1
argv[2]:MY ARG2
USER=unknown
PATH=/tmp
argv[0]:echoall
$ argv[1]:only 1 arg
USer=stevens
USer=stevens
HOME=/home/stevens
LOGNAME=stevens
31 more lines that aren't shown其中31行没有显示
EDITOR=/usr/ucb/vi
注意,shell提示出现在第二个exec打印argv[0]和argv[1]之间。这是因为父
进程并不
等待该子进程结束。
810〓更改用户ID和组ID
可以用setuid函数设置实际用户ID和有效用户ID。与此类似,可以用setgid函数设
置实际组
ID和有效组ID。
#include <sys/typesh>
#include <unistdh>
int setuid(uid 迹茫模*常病絫 uid);
int setgid(gid 迹茫模*常病絫 gid);
Both return:0 if OK,-1 on error两个函数返回:若成功为0,出错为-1
关于谁能更改ID有若干规则。现在先考虑有关改变用户ID的规则(在这里关于用户
ID所说明
的一切都适用于组ID)。
1若进程具有超级用户特权,则setuid函数将实际用户ID,有效用户ID,以前保
存的设置
用户ID设置为uid。
用户ID设置为uid。
2若进程没有超级用户特权,但是uid等于实际用户ID或保存的设置用户ID,则s
etuid只将
有效用户ID设置为uid。不改变实际用户ID和保存的设置用户ID。
3如果上面两个条件都不满足,则errno设置为EPERM,并出错返回。
在这里假 摇迹茫模*常病絇OSIX 迹茫模*常病絊AVED 迹茫模*常病絀DS为真。
如果没有
提供这种功能,则上面所说的关于保存的设置用户ID的部分都无效。
FIPS 151-1要求此功能。
SVR4支持 迹茫模*常病絇OSIX 迹茫模*常病絊AVED 迹茫模*常病絀DS功能。
关于系统核所维护的三个用户ID,还要注意下列几点:
1只有超级用户进程可以更改实际用户ID。通常,实际用户ID是在用户登录时,
由login(1
)程序设置的,而且决不会改变它。因为login是一个超级用户进程,当它调用set
uid时,设
置所有三个用户ID。
2仅当对程序文件设置了设置-用户-ID位时,exec函数再设置有效用户ID。如果
设置-用户
-ID位没有设置,则exec函数不会改变有效用户ID,而将其维持为原先值。任何时
候都可以
调用setuid,将有效用户ID设置为或者实际用户ID,或者保存的设置-用户-ID。自
然,不能
将有效用户ID设置为任一随机值。
将有效用户ID设置为任一随机值。
3保存的设置-用户-ID是由exec从有效用户ID复制的。在exec按文件用户ID设置
了有效用
户ID后,即进行这种复制,并将此副本保存起来。
图87摘要列出了改变这三个用户ID的不同方法。
P214
图87〓改变三个用户ID的不同方法
注意,用82节中所述的getuid和geteuid函数只能获得实际用户ID和有效用户ID
的当前值
。我们不能获得所保存的设置-用户-ID的当前值。
实例
为了说明保存的设置-用户-ID特征的用法,让我们先观察一个使用该特征的程序。
我们所观
察的是贝克莱tip(1)程序(系统V的cu(1)程序与此类似。)这两个程序都连接到一个
远程系统
,或者是直接连接,或者是拨号一个调制解调器。当tip使用一个调制解调器时,
它必须通
过使用锁文件来独占使用它。此锁文件是与UUCP共享的,因为这两个程序可能在同
时要使用
同一调制解调器。将发生下列步骤:
1tip程序文件是由用户uucp拥有的,并且其设置-用户-ID位已设置。当exec此程
序时,则
关于用户ID得到下列结果:
关于用户ID得到下列结果:
实际用户ID=我们的用户ID
有效用户ID=UUCP
保存设置用户ID=UUCP
2tip存取所要求的锁文件。这些锁文件是由名为UUCP的用户所拥有的,因为有效
用户ID是
UUCP,所以tip可以存取这些锁文件。
3tip执行setuid(getuid())。因为tip不是超级用户进程,所以这仅仅改变有效
用户ID。
此时得到:
实际用户ID=我们的用户ID(未改变)
有效用户ID=我们的用户ID(未改变)
保存设置用户ID=UUCP(未改变)
现在,tip进程是以我们的用户ID作为其有效用户ID而运行的。这就意味着能存取
的只是我
们通常可以存取的,没有附加的存取权。
4当执行完我们所需的操作后,tip执行setuid(uucpuid),其中uucpuid是用户u
ucp的数值
用户ID(tip很可能在起动时调用geteuid,得到uucp的用户ID,然后将其保存起来
,我们并
不认为tip会搜索口令字文件以得到这一数值用户ID。)因为setuid的参数等于保存
的设置-
用户-ID,所以这种调用是许可的(这就是为什么需要保存的设置-用户-ID的原因。
用户-ID,所以这种调用是许可的(这就是为什么需要保存的设置-用户-ID的原因。
)现在得
到:
实际用户ID=我们的用户ID(末改变)
有效用户ID=uucp
保存设置用户ID=uucp(末改变)
5tip现在可对其锁文件进行操作以释放它们,因为tip的有效用户ID是。
以这种方法使用保存的设置-用户-ID,在进程的开始和结束部分就可以使用由于程
序文件的
设置用户ID而得到的额外的优先权。但是,进程在其运行的大部分时间只具有普通
的许可权
。如果进程不能在其结束部分切换回保存的设置-用户-ID,那么就不得不在全部运
行时间都
保持额外的许可权(这可能会造成麻烦)。
让我们来看一看如果在tip运行时为我们生成一个shell进程(先fork,然后exec)将
发生什么
。因为实际用户ID和有效用户ID都是我们的普通用户ID(上面的第三步),所以该s
hell没有
额外的许可权。它不能存取tip运行时设置成uucp的保存的设置-用户-ID,因为该
shell的保
存的设置-用户-ID是由exec复制有效用户ID而得到的。所以在执行exec的子进程中
,所有三
个用户ID都是我们的普通用户ID。如若程序是设置-用户-ID为root,那么我们关于
用户-ID,所以这种调用是许可的(这就是为什么需要保存的设置-用户-ID的原因。
)现在得
到:
实际用户ID=我们的用户ID(末改变)
有效用户ID=uucp
保存设置用户ID=uucp(末改变)
5tip现在可对其锁文件进行操作以释放它们,因为tip的有效用户ID是。
以这种方法使用保存的设置-用户-ID,在进程的开始和结束部分就可以使用由于程
序文件的
设置用户ID而得到的额外的优先权。但是,进程在其运行的大部分时间只具有普通
的许可权
。如果进程不能在其结束部分切换回保存的设置-用户-ID,那么就不得不在全部运
行时间都
保持额外的许可权(这可能会造成麻烦)。
让我们来看一看如果在tip运行时为我们生成一个shell进程(先fork,然后exec)将
发生什么
。因为实际用户ID和有效用户ID都是我们的普通用户ID(上面的第三步),所以该s
hell没有
额外的许可权。它不能存取tip运行时设置成uucp的保存的设置-用户-ID,因为该
shell的保
存的设置-用户-ID是由exec复制有效用户ID而得到的。所以在执行exec的子进程中
,所有三
个用户ID都是我们的普通用户ID。如若程序是设置-用户-ID为root,那么我们关于
就允许一个非特权用户前、后交换这二个用户ID的值,而43BSD中的tip程序就是
用这种功
能编写的。但是要知道,当此版本生成shell进程时,它必须在exec之前,先将实
际用户ID
设置为普通用户ID。如果不这样做的话,那么实际用户ID就可能是uucp(由setreu
id的交换
操作造成。)然后shell进程可能会调用setreuid交换两个用户ID值并取得uucp许可
权。作为
一个保护性的程序设计措施,tip将子进程的实际用户ID和有效用户ID都设置成普
通用户ID
。
seteuid和setegid函数
在对POIX1的建议更改中包含了两个函数seteuid和setegid。它们只更改有效用
户ID和有
效组ID。
#include <sys/typesh>
#include <unistdh>
int seteuid(uid 迹茫模*常病絫 uid);
int setegid(gid 迹茫模*常病絫 gid);
Both return:0 if OK,-1 on error〓两个函数返回:若成功为0,出错为-1
一个非特权用户可将其有效用户ID设置为其实际用户ID或其保存的设置-用户-ID。
对于一个
特权用户则可将有效用户ID设置为uid。(这区别于setuid函数,它更改三个用户I
特权用户则可将有效用户ID设置为uid。(这区别于setuid函数,它更改三个用户I
D。)这一
建议更改也要求支持保存的设置-用户-ID。
SVR4和43+BSD都支持这两种函数。
图88〓摘要列出了本节所述的修改三个不同的用户ID的各个函数。
P217
图88〓设置不同的用户ID的各函数摘要
组ID
至此,在本章中所说明的一切都以类似方式适用于各个组ID。添加组ID不受setgi
d函数的影
响。
811〓解释器文件
SVR4和43+BSD都支持解释器文件。这种文件是文本文件,其起始行的形式是:
#! pathname[optional-argument]
在骛叹号和pathname之间的空格是可任选的。最常见的是以下列行开始:
#!/bin/sh
pathname通常是个绝对路径名,对它不进行什么特殊的处理(不使用PATH进行路径
搜索)。对
这种文件的识别是由系统核作为exec系统调用处理的一部分来完成的。系统核使调
用exec函
数的进程实际执行的文件并不是该解释器文件,而是在该解释器文件的第一行中p
athname所
指定的文件。一定要将解释器文件(文本文件,它以#!开头)和解释器(由该解释器
特权用户则可将有效用户ID设置为uid。(这区别于setuid函数,它更改三个用户I
D。)这一
建议更改也要求支持保存的设置-用户-ID。
SVR4和43+BSD都支持这两种函数。
图88〓摘要列出了本节所述的修改三个不同的用户ID的各个函数。
P217
图88〓设置不同的用户ID的各函数摘要
组ID
至此,在本章中所说明的一切都以类似方式适用于各个组ID。添加组ID不受setgi
d函数的影
响。
811〓解释器文件
SVR4和43+BSD都支持解释器文件。这种文件是文本文件,其起始行的形式是:
#! pathname[optional-argument]
在骛叹号和pathname之间的空格是可任选的。最常见的是以下列行开始:
#!/bin/sh
pathname通常是个绝对路径名,对它不进行什么特殊的处理(不使用PATH进行路径
搜索)。对
这种文件的识别是由系统核作为exec系统调用处理的一部分来完成的。系统核使调
用exec函
数的进程实际执行的文件并不是该解释器文件,而是在该解释器文件的第一行中p
athname所
指定的文件。一定要将解释器文件(文本文件,它以#!开头)和解释器(由该解释器
指定的文件。一定要将解释器文件(文本文件,它以#!开头)和解释器(由该解释器
文件第一
行中的pathname指定)区分开来。
要了解,很多系统对解释器文件第一行有长度限止(32个字符)。这包括#!,path
name,选
择参数以及空格数。
实例
让我们观察一个实例,从中了解当被执行的文件是个解释器文件时,系统核对exe
c函数的参
数及该解释器文件第一行的可任选参数作何种处理。程序810调用execl执行一个
解释器文
件。
P218
程序810〓执行一个解释器文件的程序
下面先显示要被执行的该解释器文件(只有一行)的内容,接着是运行程序810的
结果。
$ cat /home/stevens/bin/testinterp
#!/home/stevens/bin/echoarg foo
$ aout
argv[0]: /home/stevens/bin/echoarg
argv[1]: foo
argv[2]: /home/stevens/bin/testinterp
argv[3]: myarg1
argv[3]: myarg1
argv[4]: MY ARG2
程序ecboarg(解释器)回送每一个命令行参数。(它就是程序72。)注意,当系统
核exec该
解释器(/home/stevens/bin/ecboarg)时,argv[0]是该解释器的路径名,argv[
1]是解
释器
文件中的可任选择参数,其余参数是路径名(/bome/stevens/bin/testinterp),以
及程序8
10中调用execl的第二和第三个参数(myarg1,和MY ARG2)。调用execl时的argv[
1]和arg
v[2]和argv[3]已向右移了两个位置。
注意,系统核取了execl中的路径名以代替第一个参数(testinterp),因为一般路
径名包含
了较第一个参数更多的信息。
实例
在解释器路径名(pathname)后可跟随任选参数,它们常用于为支持-f任选项的程序
指定该任
选项。例如,可以以下列方式执行awk(1)程序:
awk -f myfile
它告诉awk从文件myfile中读awk程序
在很多系统中,有awk的两个版本。awk常常被称为"老awk",它是与version 7一起
分发的
分发的
原始版本。nawk(新awk)包含了很多增强功能,对应于在Aho,Kernighan和Weinber
ger[1988

中说明的语言。此新版本提供了对命令行参数的存取,这是下面的例子所需的。S
VR4提供了
两者,老的awk既可用awk也可用oawk调用,但是SVR4已说明在将来的版本中awk将
是nawk。P
OSIX2章节中将新awk语句就称为awk,这正是在本书中所使用的。
在解释器文件中使用-f任选项,使我们可以写出:
#!/bin/awk -f
(在此解释器文件中后随awk程序)
例如,程序811是一个在/usr/local/bin/awkexample解释器文件中的程序。
P218
程序811〓在一个解释器文件中的awk程序。
如果路径前缀之一是/usr/local/bin,则可以下列方式执行程序811(假定我们已
打开了该
文件的执行位):
$ awkexample filel FILENAME2 f3
ARGV[0]=/bin/awk
ARGV[1]=file1
ARGV[2]=FILENAME2
ARGV[3]=f3
ARGV[3]=f3
执行/bin/awk时,其命令行参数是:
/bin/awk -f /usr/local/bin/awkexample file1 FILENAME2 f3
解释器文件的路径名(/usr/local/bin/awkexample)传送给解释器。因为不能期望
该解释器(
在本例中是/bin/awk)会使用PATH变量定位该解释器文件,所以只传春路径名中的
文件名是
不够的。当awk读解释器文件时,因为'#'是awk的注释字符,所以在awk读解释器
文件时
,它忽略第一行。
可以用下列命令验证上述命令行参数。
$ su〓〓成为超级用户
Password:〓输入超级用户口令
# mv/bin/awk /bin/awksave〓保存原先的程序
# cp /home/stevens/bin/echoarg /bin/awk〓暂时代换它
# suspend〓用作业控制挂起超级用户shell
[1]+Stopped〓〓su
$ awkexample file1 FILENAME2 f3
argv[0]: /bin/awk
argv[1]: -f
argv[2]: /usr/local/bin/awkexample
argv[3]: file1
argv[4]: FILENAME2
argv[4]: FILENAME2
argv[5]: f3
$ fg〓〓用作业控制恢复超级用户shell
su
# mv /bin/awksave /bin/awk〓恢复原先的程序
# exit〓终止超级用户shell
在此例子中,解释器的-f任选项是需要的。正如前述,它告诉awk在什么地方得到
awk程序
。如果在解释器文件中删除-f任选面,则其结果是:
$ awkexample file1 FILENAME2 f3
/bin/awk:syntax error at source line 1
context if
 >>> /user/local <<< /bin/awkexample
/bin/awk: bailing out at source line 1
因为在这种情况下命令行参数是:
/bin/awk /usr/local/bin/awkexampel file1 FILENAME2 f3
于是awk企图将字符串/usr/local/bin/awkexample解释为一个awk程序。如果不能
向解释器
至少传递一个可任选参数(在本例中是-f),那么这些解释器文件只有对shell才是
有用的。

是否一定需要解释器文件呢?那也不完全如此。但是它们确实使用户得到效率方面
的好处,
的好处,
其代价是系统核的额外开销(因为系统核需要识别解释器文件)。由于下述理由,解
释器文件
是有用的。
1某些程序是用某种语言写的脚本,这一事实可以隐藏起来。例如,为了执行程
序811,
只需使用下列命令行:
awkexample optional-arguments
而并不需要知道该程序实际上是一个awk脚本,否则我们就要以下列方式执行该程
序:
awk -f awkexample optional-arguments
2解释器脚本在效率方面也提供了好处。再考虑一下前面的例子。我们仍旧隐藏
该程序是
一个awk脚本的事实,但是将其放在一个shell脚本中:
awk 'BEGIN {
for(i=0;i<ARGC;i++)
printf "ARGV[%d]=%s\n",i,ARGVp[i]
exit
}' $*
这种解决方法的问题是要求做更多的工作。首先,shell读此命令,然后试图exec
lp此文件
名。因为shell脚本是一个可执行文件,但却不是机器可执行的,于是返回一个错
误;然后
误;然后
,execlp就认为该文件是一个shell脚本(它实际上就是这种文件)。然后,exec /
bin/sh,
并以该Shell脚本的路径名作为其参数。shell正确地解释执行我们的shell脚本,
但是为了
运行awk程序,它调用fork,exec和wait。用一个shell脚本代替解释器脚本有更多
的开销。

3解释器脚本使我们可以使用除/bin/sh以外的其它shell来编写shell脚本。当e
xeclp找到
一个非机器可执行的可执行文件时,它总是调用/bin/sh来解释执行该文件。但是
用解释器
脚本,则可编写成:
#!/bin/csh
(在此解释器文件中后随C Shell脚本)
再一次,我们也可将此放在一个/bin/sh脚本中(然后由其调用C shell),但是要有
更多的开
销。
如果三个shell和awk没有用'#'作为注释符,则我们上面说的都不会工作。
812〓System函数
在一个程序中执行一个命令字符串是方便的。例如,假定我们要将时间和日期放到
一个文件
中,则可使用69节说明的函数实现这一点。调用time得到当前日历时间,接着调
中,则可使用69节说明的函数实现这一点。调用time得到当前日历时间,接着调
用localt
ime将日历时间变换为年、月、日、时、分、秒、周日形式,然后调用Strftime对
上面的结
果进行格式化处理,最后将结果写到文件中。但是用下面的system函数则更容易做
到这一点
。
system("date>file");
ANSI C定义了system函数,但是其操作是强烈依赖于系统的。
因为system不属于操作系统界面而是shell界面,所以POSIX1没有定义它,POSI
X2则正
在对其进行标准化。下列说明与POSIX2标准的草案112相一致。
#include <stdlibh>
int system(const char *cmdstring);
Returns:(see below)返回:(见下)
如果cmdstring是一个空指针,则仅当命令处理程序可用时,system返回非0值,使
用这一特
征可以决定在一个给定的操作系统上是否支持system函数。在Unix中,system总是
可用的。

因为system在其实现中调用了fork、exec和waitpid,因此有三种返回值:
1如果fork失败或者waitpid返回除EINTR之外的出错,则system返回-1,而且er
ro中设置
中,则可使用69节说明的函数实现这一点。调用time得到当前日历时间,接着调
用localt
ime将日历时间变换为年、月、日、时、分、秒、周日形式,然后调用Strftime对
上面的结
果进行格式化处理,最后将结果写到文件中。但是用下面的system函数则更容易做
到这一点
。
system("date>file");
ANSI C定义了system函数,但是其操作是强烈依赖于系统的。
因为system不属于操作系统界面而是shell界面,所以POSIX1没有定义它,POSI
X2则正
在对其进行标准化。下列说明与POSIX2标准的草案112相一致。
#include <stdlibh>
int system(const char *cmdstring);
Returns:(see below)返回:(见下)
如果cmdstring是一个空指针,则仅当命令处理程序可用时,system返回非0值,使
用这一特
征可以决定在一个给定的操作系统上是否支持system函数。在Unix中,system总是
可用的。

因为system在其实现中调用了fork、exec和waitpid,因此有三种返回值:
1如果fork失败或者waitpid返回除EINTR之外的出错,则system返回-1,而且er
ro中设置
ro中设置
了错误类型。
2如果exec失败(表示不能执行shell),则其返回值如同shell执行了exit(127)一
样。
3否则所有三个函数(fork,exec和waitpid)都成功,则system的返回值是shell的
终止状态
,其格式是在waitpid中所说明的。
如果waitpid是一个捕捉到的信号中断,则system很多当前的实现都返回一个错误
(EINTR),
要求在这种情况下system不返回一个错误已被加到POSIX2的最近草案中。(在10
5节中将
讨论被中断的系统调用。)
程序812是system函数的一种实现。它对信号没有进行处理。在1018节中将修
改此函数
使其进行信号处理。
Shell的-C任选项告诉shell程序取下一个命令行参数(在这里是cmdstring)作为命
令输入(而
不是从标准输入或从一个给定的文件中读命令)。Shell对以null字符终止的命令字
符串进行
语法分析,将它们分成分隔开的命令行参数。传递给shell的实际命令串可以包含
任一有效
的shell命令。例如,可以用<和>对输入和输出重新定向。
如果不使用shell执行此命令,而是试图由我们自己去执行它,那么这会是相当困
如果不使用shell执行此命令,而是试图由我们自己去执行它,那么这会是相当困
难的。首
先,我们必须用execlp而不是execl,象shell那样使用PATH变量。我们必须将nul
l符结尾的
命令字符串分成各个命令行参数,以便衰和execlp。最后,我们也不能使用任何一
个shell
元字符。
注意,我们调用-exit而不是exit。这是为了防止任一标准I/O缓存(这些缓存会在
fork中由
父进程复制到子进程)在子进程中被刷新。
P223
程序812〓system函数(没有对信号进行处理)
用程序813对这种实现的system函数进行测试(pr-exit函数定义在程序83中)。
运行程序
813得到:
$ aout
Thu Aug 29 14:24:19 MST 1991
normal termination,exit status=0〓〓对于date
sh:nosuchcommand:not found
normal termination,exit status=1〓对于无此种命令
stevens console Aug 25 11:49
stevens ttyp0 〓Aug 29 05:56
stevens ttyp1 〓Aug 29 05:56
如果在一个设置-用户-ID程序中调用system,那么发生什么呢?这是一个安全性方
面的漏洞
,决不应当这样做。程序814是一个简单程序,它只是对其命令行参数调用syte
m函数。

P225
程序814〓用system执行命令行参数
将此程序编译成可执行目标文件tsys。
程序815是另一个简单程序,它打印其实际和有效用户ID。
P225
程序815〓打印实际和有效用户ID
将此程序编译成可执行目标文件printuids运行这两个程序,得到下列结果:
$ tsys printuids〓〓正常执行,无特权
real uid=224,effective uid=224
normal termination,exit status=0
$ su〓〓成为超级用户
Password:〓〓输入超级用户口令
# chown root tsys〓〓更改属主
# chmod u+s tsys〓〓增加设置用户-ID
# ls -1 tsys〓〓检验文件许可权和属主
-rwsrwxr-x 1 root〓105737 Ang 18 11:21 tsys
# exit〓〓终止超级用户shell
$ tsys printuids
$ tsys printuids
real uid=224,effective uid=0〓〓oops,这是一个安全性空洞
normal termination,exit status=0
我们给予tsys程序的超级用户许可权在system中执行了fork和exec之后仍被保持下
来,也就
是说执行system中shell命令的进程也是有了超级用户许可权。
如果一个进程正以特殊的许可权(设置-用户-ID或设置-组-ID)运行,它又想生成另
一个进程
执行另一道程序,则它应当直接使用fork和exec,而且在fork之后,exec之前要改
回到普通
许可权。设置-用户-ID或设置-组-ID程序决不应调用system函数。
这种警告的一个理由是:system调用shell对命令字符串进行语法分析,而shell则
使用IFS
变量作为其输入字段分隔符。较早的shell版本在被调用时不将此变量恢复为普通
字符集。
这就允许一个不怀好意的用户在调用system之前设置IFS,造成system执行一个不
同的程序
。
813〓进程会计
很多Unix系统提供了一个任选项以进行进程会计事务处理。当取了这种任选项后,
每当进程
结束时系统核就写一个会计记录。典型的会计记录是32个字节长的二进制数据,包
括命令名
括命令名
、所使用的CPU时间总量、用户ID和组ID、起动时间等。在本节我们要比较细仔地
说明这种
会计记录,这样也使我们得到了一个再次观察进程的机会,得到了使用59节中所
介绍的fi
lad函数的机会。
任一标准都没有对进程会计进行过说明。本节的说明依据SVR4和43+BSD实现。S
VR4提供了
很多程序处理这种原始的会计数据〖CD2〗例如见runacct和acctcom。43+BSD提
供sa(8)处
理并摘要原始会计数据。
一个至今没有说明过的函数(acct)起动和终止进程会计。唯一使用这一函数的是S
VR4和43
+BSD的acction(8)命令。超级用户执行一个带路径各参数的acction命令起动会计
处理。该
路径名通常是/var/adm/pacct(较老的系统中这是/usr/adm/acct。执行不带任何参
数的acct
命令则停止会计处理。
会计记录结构定义在头文件<sys/accth>中,其样式是:
typedef u 迹茫模*常病絪hort comp 迹茫模*常病絫;  /* 〓3位基数,8位指数
,13位分
数
struct acct
struct acct
{
char〓ac 迹茫模*常病絝lag; /*〓标志(见图89)
char〓ac 迹茫模*常病絪tat; /*〓终止状态(只是信号和core标志)
〓〓〓〓〓/*〓(BSD系统不提供)
uid 迹茫模*常病絫〓ac 迹茫模*常病絬id;〓/*〓实际用户ID
gid 迹茫模*常病絫〓ac 迹茫模*常病絞id;〓/*〓实际组ID
dev 迹茫模*常病絫〓ac 迹茫模*常病絫ty;〓  〓控制终端
time 迹茫模*常病絫〓ac 迹茫模*常病絙time;〓  〓起始日历时间
comp 迹茫模*常病絫〓ac 迹茫模*常病絬time; 〓/*〓用户CPU时间(滴答)
comp 迹茫模*常病絫〓ac 迹茫模*常病絪time;〓/*〓系统CPU时间(滴答)
comp 迹茫模*常病絫〓ac 迹茫模*常病絜time;〓  〓经过的时间(滴答)
comp 迹茫模*常病絫〓ac 迹茫模*常病絤em;〓/〓*平均使用的内存
comp 迹茫模*常病絫〓ac 迹茫模*常病絠o;〓/*〓读、写传输的字节数
comp 迹茫模*常病絫〓ac 迹茫模*常病絩w;〓/*〓块读或写   常?
char ac-comm[B]; 〓/命令名:SVR4是 郏 ],43+BSD是[10 荨常?

};
由于历史传统,贝克莱系统,包括43+BSD不提供ac-stat变量。
其中,ac-flag记录了进程执行期间的某些事件。这些事件如图89中所示。

P227
图89〓会计记录中的ac-flag值
B exec C,
最后C exit),但只写一个会计记录。在该记录中的命令名对应于程序C,但CPU时
间是程序A
、B、C之和。
实例
为了得到某些会计数据以便查看,运行程序816,它调用fork四次。每个子进程
做不同的
事情,然后终止。此程序所做的基本工作示于图810中。
程序817则从会计记录中选择出一些字段并打印出来。
P228
程序816〓产生会计数据的程序
P229
图810〓会计处理实例的进程结构
然后,执行下列操作步骤:
1变成为超级用户,用acction命令起动会计事务处理。注意,当此命令结束时,
会计事务
处理已经起动,因此在会计文件中的第一个记录应来自这一命令。
2运行程序816。这会加五个记录到会计文件中(父进程1个,四个子进程各一个
)。在第
二个子进程中,execl并不创建一个新进程,所以对第二个进程只有一个会计记录
。
3变成为超级用户,停止会计事务处理。因为在acction命令终止时已停止处理会
计事务,
计事务,
所以不会在会计文件中增加一个记录。
4运行程序817,从打印文件中选出字段并打印之。
步骤4的输出如下面所示。在每一行以斜体字对进程加了说明,以便后面讨论。
accton〓e  〓7,chars〓64,stat=〓0:〓S
dd〓e=〓37,chars=221888,stat〓0:〓〓第2子进程
aout〓e=〓128,chars=〓0,stat=〓0:〓〓父进程
aout〓e=〓274,chars=〓0,stat=134:F 摹。亍?子进程
aout〓e=〓360,chars=〓0,stat=〓9:F〓〓X〓第4子进程
aout〓e=〓484,chars=〓0,stat=〓0:F〓〓第3子进程

P230
程序817〓打印从系统会计文件中选出的字段
墙上日历时间值的单位是CLK 迹茫模*常病絋CK。从图26中可见,本系统的值是
60。例如
,在父进程中
的sleep(2)对应于墙上日历时间128个时钟滴答。对于第一个子进程,sleep(4)变
成274时钟
滴答。注意,一个进程睡眠的时间总量并不精确(在第十章中将返回到sleep函数。
)调用for
k和exit也要一些时间。
注意,ac-stat并不是进程的真正终止状态。它只是86节中讨论的终止状态的一
部分。如
计事务,
所以不会在会计文件中增加一个记录。
4运行程序817,从打印文件中选出字段并打印之。
步骤4的输出如下面所示。在每一行以斜体字对进程加了说明,以便后面讨论。
accton〓e  〓7,chars〓64,stat=〓0:〓S
dd〓e=〓37,chars=221888,stat〓0:〓〓第2子进程
aout〓e=〓128,chars=〓0,stat=〓0:〓〓父进程
aout〓e=〓274,chars=〓0,stat=134:F 摹。亍?子进程
aout〓e=〓360,chars=〓0,stat=〓9:F〓〓X〓第4子进程
aout〓e=〓484,chars=〓0,stat=〓0:F〓〓第3子进程

P230
程序817〓打印从系统会计文件中选出的字段
墙上日历时间值的单位是CLK 迹茫模*常病絋CK。从图26中可见,本系统的值是
60。例如
,在父进程中
的sleep(2)对应于墙上日历时间128个时钟滴答。对于第一个子进程,sleep(4)变
成274时钟
滴答。注意,一个进程睡眠的时间总量并不精确(在第十章中将返回到sleep函数。
)调用for
k和exit也要一些时间。
注意,ac-stat并不是进程的真正终止状态。它只是86节中讨论的终止状态的一
部分。如
部分。如
果进程异常终止,则在此字节中的信息只是core标志位(一般是最高位)以及信号编
号数(一
般是低7位)。如果进程正常终止,则从会计文件不能得到进程的退出(exit)状态。
对于第一
个进程,此值是128+6。128是core标志位,6是此系统上信号SIGBART的值(它是由
调用abort
产生的)。第四个子进程的值是9,它对应于SIGKILL的值。从会计文件的数据中不
能得到父
进程在exit时所用的参数值是2,三个子进程exit时所用的参数值是0。
dd进程复制到第二个子进程中的文件/boot的长度是110,888 byte。而I/O字符数
是此值的
二倍,因为读了110,888byta,然后又写了110,888byte。即使输出到null设备,
仍对I/O
字符数进行计算。
ac-flag值与我们所予料的相同。除调用了execl的第二个子进程以外,其它子进程
都设置了
F标志。父进程没有设置F标志,其原因是交互式shell曾调用过fork生成父进程,
然后调用e
xec aout文件。调用了abort的第一个子进程的core转储标志(D)打开。因为abo
rt产生信

SIGABRT以产生core转储。该进程的X标志也打开,因为它是由信号终止的。第四个
SIGABRT以产生core转储。该进程的X标志也打开,因为它是由信号终止的。第四个
子进程的
X标志也打开,但是SIGKILL信号并不产生core转储,它只是终止该进程。
最后要说明的是:第一个子进程的I/O字符数为O,但是该进程产生了一个core文件
。其原因
是写core文件所需的I/O并不由该进程负担。
814〓用户标识
任一进程都可以得到其实际和有效用户ID及组ID。但是有时希望找到运行该程序的
用户的登
录名。我们可以调用getpwuid(getuid()),但是如果一个用户有多个登录名,这些
登录名又
对应着同一个用户ID,这又将如何呢?(一个人在口令字文件中可以有多个记录项,
它们的用
户ID相同,但登录shell则不同)。系统通常保存用户的登录名(67节),用getlo
gin函数可
以存取此登录名。
#include <unistdh>
char *getlogin(void);
Returns:pointer to string giving login name if OK,NULL on error返回:
若成功为
指向登录名字符串的指针,出错为NULL。
如果调用此函数的进程没有连接到用户登录时所用的终端,则本函数会失败。我们
通常称这
通常称这
些进程为精灵进程,将在第十三章对这种进程专门进行讨论。
得到了登录名,就可用getpwnam在口令字文件查找相应记录以确定其登录shell等
。
为了找到登录名,Unix系统在历史上一直是调用tlyname函数(119节),然后在u
tmp文件(6
7节)中找匹配项。43+BSD将登录名存放在进程表项中,并提供系统调用存、取
该登录名
。
系统V提供cuserid函数返回登录名。此函数先调用getlogin函数,如果失败则再调
用getpwu
id(getuid())。IEEE S+d10031-1988说明了cuserid,但是它以有效用户ID而
不是实际
用户ID来调用。POSIX1的1990最后版本,删除了cuserid函数。
FIPS151-1要求登录shell定义一个环境变量LOGNAME,其值为用户的登录名。在4
3+BSD中,
此变量由login设置,并由登录shell继承。但是,要理解一个用户是可以改变环境
变量的,
所以我们不能使用LOGNAME来确认用户。作为代替,应当使用getlogin函数。
815〓进程时间
在110节中说明了墙上时钟时间、用户CPU时间和系统CPU时间。任一进程都可调
用times函
数以获得它自己及终止子进程的上述值。
通常称这
些进程为精灵进程,将在第十三章对这种进程专门进行讨论。
得到了登录名,就可用getpwnam在口令字文件查找相应记录以确定其登录shell等
。
为了找到登录名,Unix系统在历史上一直是调用tlyname函数(119节),然后在u
tmp文件(6
7节)中找匹配项。43+BSD将登录名存放在进程表项中,并提供系统调用存、取
该登录名
。
系统V提供cuserid函数返回登录名。此函数先调用getlogin函数,如果失败则再调
用getpwu
id(getuid())。IEEE S+d10031-1988说明了cuserid,但是它以有效用户ID而
不是实际
用户ID来调用。POSIX1的1990最后版本,删除了cuserid函数。
FIPS151-1要求登录shell定义一个环境变量LOGNAME,其值为用户的登录名。在4
3+BSD中,
此变量由login设置,并由登录shell继承。但是,要理解一个用户是可以改变环境
变量的,
所以我们不能使用LOGNAME来确认用户。作为代替,应当使用getlogin函数。
815〓进程时间
在110节中说明了墙上时钟时间、用户CPU时间和系统CPU时间。任一进程都可调
用times函
数以获得它自己及终止子进程的上述值。
数以获得它自己及终止子进程的上述值。
#include <sys/timesh>
clock 迹茫模*常病絫 times(struct tms *buf));
Returns:elapsed wall clock time in clock ticks if OK,-1 on error
返回:若成功为经过的墙上时钟时间(单位:滴答),出错为-1
此函数填写由buf指向的tms结构,该结构定义是:
struct tms{
clock 迹茫模*常病絫 tms 迹茫模*常病絬time; /* user CPU time */〓用户C
PU时间
clock 迹茫模*常病絫 tms 迹茫模*常病絪time; /* system CPU time */ 系统
CPU时间
clock 迹茫模*常病絫 tms 迹茫模*常病絚utime; /* user CPU time,terminat
ed childr
en */各终止子进程的用户CPU时间
clock 迹茫模*常病絫 tms 迹茫模*常病絚stime; /* system CPU time,termin
ated chil
dren */各终止子进程的系统CPU时间
注意,此结构没有包含墙上时钟时间。作为代替times此函数以函数值返回墙上时
钟时间。
此值是相对于过去的某一时刻度量的,所以不能用其绝对值而必须使用其相对值。
例如,调
用times,保存其返回值。在以后某个时间再次调用times,从新返回的值中减去以
前返回的
进程所使用的CPU时间。
程序818〓时间以及执行命令行参数
让我们再运行110节中的例子:
P235
如同所期望的那样,所有三个值(实际时间和子进程CPU时间)都与110高中的值相
近。
816〓摘要
对在Unix环境中的高级程序设计而言,完整地了解Unix的进程控制是非常重要的。
其中必须熟练掌握的只有几个〖CD2〗fork、exec族、-exit、wait和waitpid。很多
应用程序都使用这些原语。fork原语也给了我们一个了解竟态条件的机会。
在本章中说明了system函数和进程会计,与此同时也使我们可以看到这些进程控制
函数的应用情况。本章还说明了exec函数的另一种变体;解释器文件及它们工作的
方式。对各种不同的用户ID和组ID(实际,保存的)的理解和编写安全的设置用户ID程序
是至
?
重要的。

在下一章我们在了解进程和子进程的基础上,进一步说明进程和其它进程的关系〖
CD2〗对话期和作业控制。在第十章我们将说明信号机制以此结束对进程的讨论。


--
※ 来源:·BBS 水木清华站 smth.org·[FROM: 162.105.8.213]

--
☆ 来源:.BBS 荔园晨风站 bbs.szu.edu.cn.[FROM: bbs@192.168.28.23]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店