LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
楼主: AMD处理器

[推荐]UNIX 系统(3月30日更新 请勿灌水。谢谢!)

[复制链接]
 楼主| 发表于 2005-4-30 18:25:59 | 显示全部楼层
[推荐]网络时间协议
网络管理-网络时间协议
介绍网络时间协议(NTP)
目标:
完成这一章,你能做以下事情:
列出网络中系统时钟同步的三个理由。
描述NTP中级的概念。
定义如下的术语:
NTP服务器
NTP对等服务器(peer)
NTP广播客户端
NTP直接访问客户端
配置一个NTP服务器。
配置一个NTP广播客户端。
配置一个NTP直接访问客户端。
使用ntpq命令来监视NTP状态
1.介绍网络时间协议(NTP)
以下的应用需要系统之间时钟同步:
-NFS的时钟标记
-加密算法产生的密匙的过期日期
-增量备份使用的时间标记,程序员的make文件,和应用程序。
HP-UX 使用NTP来维持时钟的同步:
没有使用NTP:  主机1(9:02:15) -----主机2(9;03:02)-----主机3(9:01:02)
使用了NTP:   主机1(9:02:15)-----主机2(9:02:15)-----主机3(9:02:15)
在下面几种情况下需要对网络中成员的时钟进行同步:
在备份服务器和客户机之间进行增量备份要求这两个系统之间的时钟同步。
确保系统之间的RPC(远程系统调用)能够正常进行。因为为了保证一个系统调用不会重复进行,一个调用只在一个时间间隔内有效。如果系统间的时钟不同步。一个调用可能在还没有发生之前就因为超时而不能进行。
有的应用程序需要知道一个用户登录系统的时间,或者文件的修改时间。
在一个网络中,系统之间的时钟相差一分钟或者更少的情况很多。如果网络很大,不可能完全依靠系统管理员手工使用date命令来调节各个系统的时钟。
在HP-UX中,网络时间服务是和操作系统绑定在一起的,它是通过调用网络时间协议(NTP)来实现的,这个协议的作用就是同步网络中系统时钟。网络时间服务在系统中是一个叫xntpd的守护进程提供的。
NTP可以通过命令来配置,还可以通过SAM等系统管理工具来配置。
2.NTP时间源
NTP时间源有:
GPS卫星发射的无线的时钟信号
(需要大约$1000,非常精确)
internet上的网络时间源
(免费,但是不是很精确)
内置的系统时钟
(免费,但是非常不精确)
NTP在进行时钟同步的时候可以使用不同的时间源:
HP-UX系统可以通过串口连接一个无线时钟。无线时钟接收GPS(全球卫星定位系统)的卫星发射的信号来决定当前时间。无线时钟是一个非常精确的时间源,但是需要花费几百到1000美元
如果无线时钟对于你来说太昂贵的话,你可以使用网络中NTP时间服务器,通过这个服务器来同步网络中的系统的时钟。http://www.eesic.udel.edu/~ntp ... 时间服务器。
如果只是需要在本局域网内进行系统间的时钟同步,那么你可以使用局域网中任何一个系统的时钟。你需要选择局域网中的一个节点的时钟作为“权威的”的时间源,然后其它的节点就只需要与这个时间源进行时间同步即可。使用这种方式,所有的节点都会使用一个公共的系统时钟,但是不需要和局域网外的系统进行时钟同步。如果一个系统在一个局域网的内部,同时又不能使用无线时钟,这种方式是最好的选择。
3.NTP的级别
时间源的的级别决定它的精确度:
stratum = 1 最精确
stratum = 5 最不精确
服务器1     (使用无线时钟的系统)
 |
 v
服务器2     (从第一级NTP服务器获取时间)
 |
 v
服务器3      (从第二级NTP服务器获取时间)
在一个大型网络中,可以将所有系统分为几个级别来同步网络中系统时钟。最高级别的时钟服务器从外部的时间源接收时钟信号(例如无线时钟),下一级的时钟服务器通过参考同级或者更高一级的时钟服务器的时钟来确定自己的时间。
每一个时间服务器会频繁的检测从远程服务器传送过来的时钟标记,通过这个标记来更新自己的时钟,并且补偿网络传输中的时间延迟。对最低级别的系统来说,它只是一个纯粹的客户端,因为它只是从时间服务器上接收时间而不提供时间给别的系统。
4.NTP的角色
在处理网络中的NTP请求时候,一个系统可能扮演的角色有四种:
服务器       提供时间给其它系统的的NTP服务器
对等服务器(peer) 一个NTP 对等服务器(peer)可以从同级的一个或多个NTP服务器获取时间。NTP使用一种特殊的算法来协调不同服务器时间的差异,形成一个唯一的时间,这个时间被认为十 分精确,这个时间是基于其它服务器的级别还有其它的一些条件。
直接请求客户端   这个客户端通过发送一个请求给一个指定时间服务器,直接取得时间。
广播客户端     这个客户端监听NTP时间服务器广播的时间请求。
在上例中,机器Larry和Frank(两个一级NTP服务器)为系统John服务。John上面的NTP的工作就是比较时间上的差异(大多数很小)并形成唯一一个的时间。如果John与Larry失去了联系,它会单独从Frank取得时间信息。如果John和Larry和Frank都失去了联系,它会查询它的等价服务器(Brian和Darren)来决定当前的时间。
5.通过/etc/ntp.conf来定义一个NTP的角色
/etc/ntp.conf文件定义系统在网络的NTP服务中扮演什么角色。
定义一个服务器使用无线时钟信号:(上图中的Frank)
# vi /etc/ntp.conf
 server 127.127.4.1
 peer Larry
定义一个第二级的时间源: (上图中的John)
# vi /etc/ntp.conf
 server Larry
 server Frank
 peer Brian
 broadcast 128.1.255.255
 driftfile /etc/ntp.drift
/etc/ntp.conf文件的作用之一就是定义系统与网络中的其他服务器之间(NTP)的关系。这个关系是在系统启动的时候定义的。
配置一个一级NTP服务器(使用无线时钟)
想要配置一个一级服务器,在/etc/ntp.conf文件中加上下面两行:
server 127.127.4.1
peer Larry
上面的IP地址是一个“假的”IP地址,其作用是确定一个无线时钟作为时间源。
IP地址的前三段:127.127.4,用来在NTP中定义一个无线时钟为时间源。
第四个段:1,通过和4一起可以确定时钟的设备文件。无线时钟的设备文件是/dev/wwvb1,这个设备文件的最后一个数字和IP地址的最后一段的数字是一致的。
每一个无线时钟服务器在本地的无线时钟失效的时候都会转向去查询同一级的其它服务器,条目peer Larry就说明在本地的无线时钟失效的时候,系统会转从Larry获得时间。
配置一个二级服务器
以下是一个配置二级服务器的例子,其中的John作为上例中的一个二级服务器。注意John已经peer了系统Brian和Darren(在一级服务器失效的时候),同时John担当了本地网络中的一个广播服务器。
server Larry
server Frank
peer Brian
peer Darren
broadcast 128.1.255.255
driftfile /etc/ntp.drift
在上例中:
“server" 定义更高一级的服务器
"peer" 定义同一级的服务器
“broadcast"定义了NTP的广播地址,这个地址用来在网络中广播时钟信号。
"driftfile"指定了一个文件名,这个文件的作用是用来跟踪本地时钟的时间的“漂移”
经过一段时间,NTP会使用driftfile来补偿时钟的漂流,从而使访问NTP服务器的次数减少。
你的位置:首页->系统管理->网络管理->网络时间协议(2)
介绍网络时间协议(NTP)-2
6.更多的/etc/ntp.conf的例子
以下是一些的NTP配置例子:
配置一个使用自己内部时钟的本地NTP服务器
要配置一个使用系统时钟作为时间源的NTP服务器,需要在/etc/ntp.conf文件中添加如下的记录:
server 127.127.1.1
fudge 127.127.1.1 stratun 10
在以上的记录中:
指定的IP地址是一个“伪”IP地址,确定本地系统为时间源。
指定的IP地址127.127.1.1告诉NTP使用内部时钟作为时间源。
"fudge"定义了这个时钟的级别,如果没有这个记录,节点就是一级服务器。将级别重新定义为10是个好的办法,这样客户端在查询这个服务器的时候就会知道这个服务器不是一个可靠的时间源
这种时间同步的方式只应该在本地的网络不能使用外部的时间源的时候使用.
配置一个使用直接查询方式的客户端
要配置一个客户端让它可以直接查询指定的NTP服务器,在客户端的/etc/ntp.conf文件中增加:
server brian
driftfile /etc/ntp.drift
这个客户端会直接向服务器brian发送查询请求来确认自己的时间。
默认的查询的时间间隔为64秒,并可以调节。
driftfile用来跟踪客户机的时间和服务器的时间差异。如果driftfile保持稳定,系统更多的是用它来调节客户端的时间,这样会减少对服务器的查询。
配置一个使用广播方式的客户端
要配置一个监听时间广播信息的客户端,在客户端的/etc/ntp.conf文件中加上:
broadcastclient yes
driftfile /etc/ntp.drift
以上信息的解释:
客户端会被动地监听NTP的广播信息,并依靠它来调节自己的时钟。
对许多节点的大型网络推荐使用这种方式进行时间同步,而不使用直接查询指定服务器的方式。
因为这种方式可以显著减少NTP对网络资源的占用。
客户端必须和NTP服务器在同一个子网内。
7.NTP如何调节系统时钟
NTP中用来保持网络中的节点之间的时钟同步的机制有三种。
ntpdate命令
ntpdate命令被用来立即同步客户端和服务器的时钟。这是同步客户端和服务器的时钟的最快方式。
系统每次启动的时候会使用这个命令,用来确保客户端和服务器的时钟的同步。
这个命令通常指定一个NTP服务器作为一个参数,客户端的时钟被设置为和服务器的时间匹配。然而,如果这个命令使用多个NTP服务器作为参数,客户端会使用最低级的NTP服务器。如果级别相等,客户端会使用平均数。
xntpd守护进程
xntpd守护进程在后台持续运行,并且会定时校验客户端的时钟和NTP服务器的时钟。xntpd守护进程通常是在系统启动的时候启动。
因为客户端的时钟可能产生漂移,以致与实际的NTP时钟有差异。xntpd守护进程会周期性地发送一个NTP服务器的网络请求,并且与客户端的时钟对比,在必要的时候修正客户端的时间,同时将时间差存到一个/etc/ntp.conf文件。
默认的xntpd时间检查的间隔为64秒,在第一次检查的时候,客户端的时钟和NTP服务器上的时钟的差异会被记录下来,同时依靠这个时间差异来计划下一次检查的计划。如果这个时间差异很大,下一次的时间检查会很快发生。如果这个差异很小,下一次的检查到来的时间会相应的延长。
/etc/ntp.conf文件
/etc/ntp.conf文件被用来在记录每次校验发生时候客户端和服务器时钟之间的差(或者叫漂移)。
一旦xntpd守护进程预计到这个时间差,它会开始使用这个“可预期”的漂移,来调节本机的时钟,这样就避免每次都去查询网络中的ntp服务器,从而可以减轻网络负载。
xntpd守护进程会慢慢的转成使用这个drift文件,而查询NTP服务器的次数会慢慢变少。当这个值逐渐稳定并且保持同一个值,对NTP服务器的查询会变得更少,直到达到NTP请求的最小值。
8.配置一个NTP服务器
配置NTP服务器的步骤:
1.编辑/etc/rc.config.d/netdaemons文件,让xntpd守护进程在每次系统启动的时候自动启动。设置XNTPD变量的值为1。
export NTPDATE_SERVER=
export XNTPD=1
export XNTPD_ARGS=
对NTP服务器来说,不要设置NTPDATE_SERVER变量(让它为空)。这个变量是为NTP客户端使用。
2.编辑/etc/TIMEZONE文件来指定系统时区。设置TZ和系统的时区相同。/usr/lib/tztab文件中有所有的时区变量。
TZ=CST6CDT
export TZ
3.编辑/etc/ntp.conf文件同时定义NTP服务器(与上一章中描述的类似)。以下是配置使用本地时钟作为时间源的NTP服务器的例子:
server 127.127.1.1
fudge 127.127.1.1 stratum 10
4.通过执行如下的命令,手工启动xntpd守护进程:
/sbin/init.d/xntpd start
5.等待,xntpd守护进程启动会花费6分钟的时间。
6.检验NTP服务器的配置,执行下列的命令:
ntpq -p
9.配置NTP客户端
配置一个NTP客户端的过程和配置一个NTP服务器相同,不同的只是配置文件的内容。
配置一个NTP客户端的步骤如下:
1.编辑/etc/rc.config.d/netdaemons文件,让xntpd守护进程在每次系统启动的时候自动启动。设置XNTPD变量的值为1,同时指定ntpdate命令查询的是哪一个NTP服务器:
export NTPDATE_SERVER="NTP_server1 NTP_server2'
export XNTPD=1
export XNTPD_ARGS=
2.编辑/etc/TIMEZONE文件,为客户机指定正确的时区。/var/lib/tztab文件中有所有的时区变量的列表。
TZ=CST6CDT
export TZ
3.编辑/etc/ntp.conf文件,定义本机为一个NTP客户端。下面是一个定义NTP广播客户端的例子:
broadcastclient yes
dirftfile /etc/ntp.drift
4.执行如下的命令手工启动xntpd守护进程:
/sbin/init.d/xntpd start
5.等待xntpd守护进程启动,建立与NTP服务器和同伴服务器的联系大约需要6分钟。
6.要验证其它的NTP服务器和peers的联系是否正确建立。执行下面的命令:
ntpg -p
10.验证NTP的功能
NTP查错推荐使用三种方式:
检测syslog文件
当xntpd守护进程启动,它会在/var/adm/syslog/syslog.log文件中添加几条记录,包括:
xntpd启动和停止的时间
与其它的运行NTP的节点之间的关系。
/etc/ntp.conf文件中发现的错误。
验证xntpd守护进程是否正在运行。
xntpd守护进程必须存在,否则就不能同步其它系统的时钟。
浏览NTP服务器节点的状态和与客户端的联系。
命令:
ntpg -p
查询网络中的NTP服务器,同时显示客户端和每个服务器的关系,例如:
# ntpg -p
remote  refid  st  when  poll  reach  delay  offset  disp
-----------------------------
* John   Larry  3   64   64   377   0.87   10.56   16.11
+ Brian  Renay  3  100   264   376   9.89   5.94    16.40
Darren 0.0.0.0 15  -    64   0    0.00   0.00    1600.00
*     指出响应的NTP服务器和最精确的服务器
+     指出响应这个查询请求的NTP服务器
blank   指出没有响应的NTP服务器
remote   响应这个请求的NTP服务器的名称
refid   NTP服务器使用的更高一级的服务器的名称
st     正在响应请求的NTP服务器的级别
when    上一次成功请求之后到现在的秒数
poll    当前的请求的时钟间隔的秒数
Others   其它的被用于决定精确度和不一致性的统计信息
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-4-30 18:28:31 | 显示全部楼层
[推荐]进程调度
UNIX内核-进程调度(1)

进程调度(1)
作为多任务操作系统,进程调度是它的最基本的操作之一。希望在一台单处理器的机器上同时运行多个进程的时候,必须有某种形式的进程调度。这是明显的,因为在任何特定的瞬间,机器只能为一个进程执行一条命令。为了使机器上的若干个进程同时取得进展,必须由准备好运行的进程共享CPU时间。调度程序的任务是选择下一个准备好运行的进程,将CPU时间分配给它。
1. 背景
当调度程序进行调度任务时,它试图达到一些目标。我们将看到有些目标对调度程序提出的要求是互相冲突的。调度程序的最重要的目标有:
• 使每个进程公平地共享CPU时间。
• 使CPU的空闲时间达到最少(即保持CPU处于忙碌状态)。
• 吞吐能力达到最高。这表明在给定时间内完成任务的进程数达到最多。
• 使系统响应用户请求的时间达到最短。
似乎应该以某种方式优先考虑用户请求,但是这和所有进程公平共享CPU时间的目标明显发生冲突。
一般来说调度程序直接面对的问题是:当它启动进程时,对进程的了解很少。如进程平均使用多少CPU时间才停下来等待输入输出;以及进程提出输入输出请求后,平均用多长时间进行等待等是未知数。
另一个问题是:对进行输入/输出前占用很长CPU时间的进程应该采取什么措施?能让它独占CPU一直运行下去?显然不能、否则这一进程可能使所有的其他进程都处于停顿状态。
这说明在当前进程运行足够长的时间后,要有某种方法将CPU切换给另外一个进程。但是从哪里着手,又如何进行这样的调度。
这里有两种可能。第1种可能使让进程在CPU上运行一段时间后,自愿放弃对CPU的控制。第2种情况是找出某种办法强制进程释放对CPU的控制。第一种称为非抢先调度。第二种称为抢先调度。
当我们讨论线程(thread)时将看到;运行由相互协调的程序组成的系统时候,用非抢先调度进行切换是完全行的通的。然而对多用户环境下的进程调度来讲,更安全的做法是:最好的情况:进程间并不知道对方的存在。在最坏的情况:进程之间互相竞争CPU的使用。结果是在多用户的环境下几乎毫无例外地使用抢先调度。
具体的做法是:给每个进程分配一段最长的不间断的CPU时间,同时系统产生快速和周期性的时钟计时中断,用来决定进程什么时候拥有它的时间片。
当分配给当前进程的时间片消逝以后,调度程序投入运行,由它来决定是否还有准备好运行的进程,它是否比刚用完时间片的当前进程更有资格投入运行。如果有,由新的进程取代当前的进程,如果没有,让当前进程继续运行:
从前面的讨论可以看到,进程有几种不同的状态。随着不同事件的出现,在这些状态间进行切换。上图表示CPU调度程序控制下的简化的进程状态转换图,而且标出了在下列情况下发生的6种转换。
1.启动(start)转换。当进程首次被启动时(fork()),并没有让它直接控制CPU。而是将它置为可运行状态,和其他进程一起放在一个队列中。只要给它们分配CPU时间,就立即可以投入运行。
2.将处于可运行状态的进程转换为运行状态。在可运行进程队列中的进程,最终将被调度程序选中,在CPU上执行一段时间。
3.有几种方法使一个在CPU上运行的进程转换为其他状态。转换3就是其中的一种,将当前运行的进程放回可运行的进程的清单中。当在处理器上运行的进程种用完了分配的时间片后,就发生这种转换,使其他进程有机会投入运行。
4.另一种主要方式是当运行中的进程提出输入/输出请求时,它将失去对CPU的控制。机器的硬设备对请求作出响应之前会有一段时间的延迟。在等待输入输出完成时,即使 CPU空闲,进程也不能运行。所以当进程停下等待输入输出完成时,它不能被放回可运行进程的清单中,而是通过转换4将它设置为挂起的状态,等待输入输出的完成。
5.当一个挂起的进程等待的输入输出事件发生了。它再次成为有资格运行的进程。然而并不立即给这个进程提供CPU时间片去处理它的输入输出,只是将它送到可运行进程的清单中,等待再次被调度程序选中。这是转换5完成的工作。
6.在简化图中,最后一种失去对CPU控制的方式发生在运行的进程结束时,转换6表示导致进程结束的事件。
也许出人意料,不管CPU调度程序采用什么算法决定下一个应该运行的进程,一般都能使用这个状态转换图。
CPU调度程序可以使用许多可能的调度算法。也有许多关于在不同环境下哪种算法最好的理论。这里不准备对调度算法进行一般性讨论,将集中于对某些细节做一些说明.........
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-4-30 18:32:38 | 显示全部楼层
[推荐]进程调度的细节
UNIX内核-进程调度的细节

进程调度(2)-细节
以下以linux内核为例讨论进程调度的细节:
在UNIX进程的生存期间,它可能处于几种状态中的一种。下面是它的主要状态:
TASK_RUNNING   仅当进程处于这一状态时,调度程序才认为它是可以运行的。它等同于上一节图1中的可运行状态。
TASK_INTERRUPTIBALE   处于这一状态的进程在休眠之中。但是,如果它接受一个信号,将使他转为TASK_RUNNING状态。这相当于图中的挂起状态。
TASK_UNINTERRUPTIBLE   除了不能用信号改变进程的状态外,类似于上一个状态。通常需要用wake_uup()函数来唤醒休眠中的进程。wake_up()函数用来实现图中的转换5。
LINUX系统中每个进程用task_struct结构来表示。其中包括进程运行环境的许多细节。在下列文件中说明了这一结构:
/usr/src/linux/include/linux/sched.h

当进程刚建立时候,由内核do-fork()函数将这一结构动态地分配给进程。do_fork()函数包含在下列的文件中;
/usr/src/linux/fork.c

task_struct的成分之一称为counter。它在进程调度中起重要作用。处于TASR_RUNNING状态的进程的counter的值越高,当调度程序运行时就越可能被选为下一次执行的进程。counter的值一般在0-70之间。在某些情况下也可能超出这个值。
在下列4种主要情况下,要求在CPU上运行的进程放弃对CPU的控制,使其他进程能运行:
1.当进程要求为它进行输入/输出操作时(如磁盘的存取操作),而这一操作需要一定的时间才能完成。在这种情况下,即使时间延迟只有几毫秒也要重新进行调度。
2.不同信号引起的有关活动也能导致重新进行调度。如调用pause()或sigsuspend()系统调用或者从ptrace()接到一个信号等。
3.当硬设备发出中断信号并唤醒为等待这一事件处于休眠状态的进程时,如果被唤醒的进程的counter的值比当前进程的counter的值高,就要重新进行调度。
4.如果一个进程没有因为其他原因停下来,最终它将用完它的时间片,这也将导致重新进行调度。在task.struct结构中的counter值是分配给这一进程的剩下的CPU时间的计数值(以10ms为计时中断进行计数)。

对单处理器的机器来讲,进程调度很简单,我们将进一步对它进行说明。
调度的任务本身是由内核中称为schedule()的函数独立完成的。这一函数包含在下列文件:
/usr/src/linux/kernel/sched.c
下面的伪码(pseudo code)程序说明支持单处理器的内核的schedule()函数采取的调度步骤:

EXECUTE any functions on the scheduler task queue
FOR each process
 IF the process has a real time interval timer running
  IF the interval timer has expired
    send a SIGALRM to the process
  END IF
 END IF

 IF the process state is TASK_INTERRUPTIBLE
  IF process has had a signal or has an expired timout
   set process state to TASK_RUNNING
  END IF
 END IF
END FOR

FOR EACH PROCESS
  IF process state is TASK_RUNNIG
    Remember the process with highest counter vales
  END IF
END FOR

IF all runnable processes have a counter value of zero
  FOR each process
    Recallculate process counter value from its priority
  END FOR
END IF

switch context to the remembered 'highest counter',process

正像你看到的,这是非常简单的调度算法。它很大程度上依赖counter值。用counter值来确定进程运行的先后顺序。一旦进程投入运行,又将它作为它的时间片的长度。
在支持多处理器的内核中,使用counter值来选择下一个运行的进程的方式稍有变化。除了使用counter值外,还增加了其他项目。使同一个处理器上的进程有某种优势(advantage),同时也给当前进程一些小的优势。
有了这些说明和LINUX的调度程序的源码,通过源码找出它如何进行工作应该不会太困难。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-4-30 18:33:24 | 显示全部楼层
[推荐]内存管理机制
UNIX内核-内存管理机制

内存管理
UNIX内存管理是内核最复杂的任务之一。重要因为它用到许多CPU提供的功能,而且和这些功能关系密切。因此,在我们讨论内存管理时,先要讨论CPU及其在内存管理中的作用。
1.受保护的编址方式
CPU执行的许多指令需要访问内存,看起来很简单,在机器代码指令中指定要提取或修改的内存单元的地址就行了。其实没有这么简单。当需要在内存中同时运行许多进程时,使每个进程都好像在它们自己的机器上运行一样就方便了。做到这一点的办法是在CPU内部完成不同形式的地址转换,将指令指定的地址(称为逻辑地址或有效地址)转换为硬件对实际内存的访问。进程按指令、数据和堆栈分成若干段每一段都有段描述符(segment descriptor)。每一段描述符包含8个字节。内容包括段开始的基地址、段的容量和段的访问权限。段的描述符集中放在段描述表中。一个CPU内部寄存器保存访问段描述符表的基地址。
除此之外,CPU还包含一组段寄存器。每个寄存器指向描述表中的一个段描述符项。如果发生了任何类型的内存访问,将选择一个CPU段寄存器(或者由访问内存的类型的隐含说明,或者由访问内存的指令的明确说明)用来进行地址计算。计算的结果得到线性地址。这里说得是每一次的内存访问,CPU都用一个内部寄存器的内容找到描述表的基地址。基地址和一个段寄存器的内容(它的最低3位的值为0)相加。相加的结果是指向描述符表的一个8字节的项的指针。注意:段的描述府项包含段在内存中的基地址,段的容量和对本段的访问权限。访问内存的指令本身也提供一个地址,这个地址是已取得段描述符的内存段内部的逻辑地址(或有效地址)。指令的逻辑地址和段的基地址相加得到线性地址。必须对线性地址进行检查,保证它落在段的范围内(不超过段的容量),并检查是否允许在段内进行请求的访问类型(读、写或执行)。如果这些检查被通过,CPU现在有了用来访问内存的地址。
我们已经看到的段描述符主表称为全程段描述表或者GDT,而指向GDT起点的CPU寄存器称为GDTR.在多任务环境中,可能愿意每个进程访问它自己的内存段。为满足这一要求,CPU也为每个进程提供另外一个描述符表,称为局部描述符表或者LDT,它通过GDT的项和处理机内部寄存器LDTR进行访问。
如果每次内存访问都要查找描述符表(它本身又在内存中)的值,这将消耗大量的CPU时间。为了大大加速这一过程,CPU内部有一些隐藏的,不能由程序直接访问的寄存器。这些寄存器用作快速缓存,保存当前每个段寄存器所指的段描述符的值。只有有关的段寄存器的值改变时,或者描述符表的GDTR或LDTR的基地址改变时,才用相应的段描述符值加载隐藏的寄存器。
2.分页技术
在前面讨论的组织内存访问的功能的基础上,可以建立起一个完全切实可行的多任务系统。只要在开始运行时每个进程知道它实际需要的内存容量,一开始就可以为这一进程在合适的位置分配它所需的内存块。在进程存在期间也不用再改变内存的分配。然而,有些UNIX版本要比上面的简单模型更为灵活。例如,它允许进程不必考虑周围实际上还有其他的 进程存在的现实而增加它的内存要求。有的UNIX系统还允许同时运行比实际内存所能容纳的更多的进程。之所以能这样做,因为利用了处理机提供的分页(paging)功能。在CPU中是否进行调页由处理机控制寄存器中的1位来进行控制。如果调页位设置,32位的线性地址将一分为二。高20位用作页号,而低12位用作页内的位移地址。这表示32位的线性地址可以用来访问100万4KB页。分页所做的主要工作之一是将线性地址中提供的20位页号转换位对应于机器实际4KB内存页的另一个20位页号。结果是,任何实际4KB内存页可以出现在处理机线性地址空间的100万4KB页边界内的任何位置。地址变换用内存中的对照表完成,其中线性地址的高20位用作大的32位元素的数组的索引。从数组取得的32位值提供所需的20位转换值,其余12位则用来指定类似下面的一些事项:
• 本页允许读、写还是只允许读访问?
• 本页是用户进程访问页,还是管理进程访问页?
• 是否对任何页内地址进行了读或写访问?
• 是否对任何页内地址进行了写操作?
• 本页在内存中还是在磁盘上?
用两级页表取代单个大的页表可以不必用很大的连续线性地址空间来保存页表,两级页表将线性地址空间的高20位划分为两个独立的10位值。CPU的3号控制寄存器包含内存中用作第一级页目录表的4KB的地址。每一4KB页分成1024项,每项占32位。线性地址中页号的前10位正好用来选择页表中的项。这样选出的32位包含20位实际页号,以及前面讨论过的12位。
段的增长
当你希望从包围在中间的内存段(segment)中提高内存容量时,分页技术显得特别重要。
交换
分页的另一个主要用途称为交换(swapping)。交换概念的实质在于允许在系统中同时运行比内存实际能容纳的更多的进程。这包括另外设置交换区(通常在磁盘上)作为内存的溢出区。当需要启动一个新的进程而内存中由没有足够的空间允许这样做时,将调用交换程序,它将选择内存中的一个或者几个新进程将它们写到交换区中,留出内存空间供新的进程装入和运行。当进程运行一段时间后,一些进程因等待输入/输出而挂起。另一些进程将结束,这就给交换出去的进程调回内存的机会(也可能将其他进程交换出去作为代价,使它们能继续执行下去。
利用分页技术实现了交换功能,表示不需要将整个进程交换到磁盘上,只要留出足够的页面供新的进程运行就行了。事实上,还有一种称为按需调页技术(demand paging)。为了实现按需调页技术,页表中的描述符项需要提供说明本页的内容是否实际装入内存的信息。由了这一信息,当CPU试图访问的页不在内存时,将产生异常信息(或称页面故障)。这一异常信息将导致执行异常处理程序,它将所需的页面装入实际内存。导致页面故障的指令将再次被执行,这一次它的内存访问将是成功的。
利用分页技术将新的进程装入内存是好的设想。出现的情况是当运行的进程试图在没有装入内存的地址上执行指令时,将出现页面故障,对它进行处理后,进程又可以继续执行下去。
对执行中的程序进行的研究发现:对一个适度大小的程序来讲,在任何特定的允许时刻,大部分代码并未得到执行。使用按需调页技术,它的含义在于当进程所需的页面调入之后,它将以少量的页面故障稳定允许一段时间,虽然它的大多数页面完全没有装入内存。即使特定的程序在运行时访问所有的程序页,但在任何时刻,在一段时间内进程只用其中少量的页面,所以,从资源利用的角度看,分页技术能取得很大的效益。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-4-30 18:34:13 | 显示全部楼层
[推荐]系统调用的机制
UNIX内核-系统调用的机制

系统调用的机制
1.中断和异常
大多数的微处理器的处理都可以被当前执行的程序具有更高优先级的事件中断。
这些特殊事件有各自的特点,但总的说来,它们可以分为中断和异常(Interrupts and excepton)。中断可以说是硬件设备对处理器产生的“刺激”,它们是由外部事件引起的。这些事件包括硬盘和软盘的活动,键盘的输入,调制解调器的控制活动等等.另一方面,异常则是响应某些系统错误引起的,也可以是响应某些可以在程序中执行的特殊机器指令引起的。不管产生的是中断还是异常,处理器响应的方式是类似的。执行一段被称为中断处理或异常处理的特殊程序。一般来说,在特定的系统中,对每一种中断和异常类型都有单独的处理程序。对每一种中断和异常,都在0~255范围内给以一个唯一的数加以标记,当发生中断或者异常时,这个数(称为向量号)用作进入指针数组(中断或异常向量表)的索引,表中每个指针指向用来处理特定中断或异常类型的子程序的地址。大多数中断和异常象函数一样进行处理。所以当中断或异常处理程序结束时,被中断的程序将重发生中断时的断点继续向下执行。
2.系统调用的机制
在UNIX系统中,系统调用是作为一种异常类型实现的。它将执行相应的机器代码指令来产生异常信号。产生中断或异常的重要效果是系统自动将用户模式切换为内核模式来对它进行处理。这就是说,执行系统调用的异常指令时,将自动地将系统切换为内核模式,并安排异常处理程序的执行。它知道如何处理这一调用。
以LINUX为例,在LINUX中实现系统调用异常的实际指令是:
int $0x80
这一指令使用中断/异常向量号128(即16进制的80)将控制权转移给内核。为达到在系统调用时不必用机器指令编程,在标准的C语言库中为每一个系统调用提供了一段短的子程序,完成机器代码的编程工作。事实上,机器代码非常短。它要做的工作只是将送给系统调用的参数值加载到CPU寄存器中,接着执行 int $0x80指令。然后运行系统调用,系统调用的返回值将送入CPU的一个寄存器中,标准的库子程序取得这一返回值,并将它送回给你的程序。为了使系统调用执行成为一项简单的任务,LINUX中提供了一组预处理宏指令。它们可以用在程序中。这些宏指令取一定的参数,然后扩展为调用指定的系统调用的函数:
这些宏指令具有类似下面的名称格式:
syscallN(parameters)
其中N用系统调用所需的参数数目代替,而parameters则用一组参数代替。这些参数使宏指令完成适合于特定的系统调用的扩展。例如,为了建立调用seuid()系统调用的函数,应该使用;
syscall1(int,setuid,uid_t,uid)
syscallN()宏指令的第一个参数说明产生的函数的返回值的类型(这里是int),第二个参数说明产生的函数的名称(这里是setuid)。
后面是系统调用所需要的每个参数。这一宏指令后面还有两个参数分别用来指定参数的类型和名称(这里是uid_t和uid)。
用作系统调用的参数的数据类型有一个限制,它们的容量不能超过4个字节。这是因为执行int $0x80指令进行系统调用时,所有的参数值都存在32位的 CPU寄存器中。使用CPU寄存器传递参数带来的另外一个限制是可以传递给系统调用的参数的数目。这个限制是最多可以传送5个参数。所以一共定义了6个不同的syscall N()宏指令(从syscall 0()到syscall5() )。
一旦syscall N()宏指令用特定的系统调用的相应参数进行了扩展,得到的结果是一个与系统调用同名的函数,它可以在用户程序中执行这一系统调用。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-4-30 18:34:55 | 显示全部楼层
[推荐]shell编程简介
shell编程-shell编程简介
介绍shell编程
目标:
完成这一章,你能做以下事情:
写出简单的shell程序
通过环境变量传递参数给shell程序
通过位置参数传递参数给shell程序
使用特殊shell变量,*和#
使用shift和read命令
1.1 shell编程概述
shell程序是一个包含UNIX命令的普通文件。
这个文件的许可权限至少应该为可读和可执行。
在shell提示符下键入文件名就可执行shell程序。
shell程序可以通过三种方式接受数据:
  -环境变量
  -命令行参数
  -用户的输入
shell是一个命令解释器,它会解释并执行命令提示符下输入的命令。但是,你可能想要多次执行一组命令,shell提供了一种功能,让你将这组命令存放在一个文件中,然后你可以象unix系统提供的其他程序一样执行这个文件,这个命令文件就叫做shell程序或者shell脚本。当你运行这个文件,它会象你在命令行输入这些命令一样地执行这些命令。为了让shell能读取并且执行你的shell程序,shell脚本的文件权限必须被设置为可读和可执行。为了让shell可以找到你的程序,你可以选择输入完全路径名,或者将这个脚本的路径放在于你的PATH环境变量指定的路径列表中。许多的用户会在他们的 HOME目录下创建一个bin目录来存放他们自己开发的script,然后将$HOME/bin加入到他们的PATH环境变量中。你可以写出非常复杂的 shell脚本,因为shell脚本支持变量、命令行参数、交互式输入、tests(判断))、branches(分支),和loops(循环)等复杂的结构。
1.2 shell程序举例
$ cat myprog
#this is the program myprog
date
ls –F
$ myprog
要创建一个shell程序,考虑进行以下步骤:
$ vi myprog      一个包含shell命令的程序。
#this is the program myprog
date
ls –F
$ chmod +x myprog 增加文件的执行模式
$ myprog
Thu Jul 11 11:10 EDT 1994
F1 f2 memo/ myprog*
首先使用一个文本编辑器创建一个shell程序myprog。在程序执行之前,这个文件必须被赋予可执行的权限。然后在命令提示符下输入这个程序名,如上例所示,当myprog执行的时候,一个子shell会被创建。这个子shell会从shell程序文件myprog读取输入而不是从命令行读取输入,这个shell中的每个命令的执行都会创建一个子shell。一旦所有的命令都被执行,所有的子shell会中止,然后会返回到原始的父shell。
Shell程序中的注释:
推荐在shell程序中提供注释语句来注明程序的内容。注释由一个#符号开始,Shell不会去执行任何在#之后的语句。#能够出现在命令行的任何位置。
注意:你不可以给shell程序取名为test因为test是一个内部的shell命令。
1.3 传递数据给shell程序
$ color = lavender
$ cat color1
echo you are now running program: color1
echo the value of the variable color is : $color
$ chmod +x color1
$ color1
you ar now running program : color1
the value of the variable color is :
$ export color
$ color1
you are now running program : color1
the value of the variable color is : lavender
传递数据给shell脚本的一种方法就是通过环境。在上例中,本地变量color被赋值为lavender,然后创建了shell程序color1;然后更改为可执行权限;然后这个shell程序被执行,color1脚本的意图是显示color变量的值,但是由于color是一个本地变量,属于父 shell私有的,运行color1产生的子shell不能识别这个变量,因此不能打印出它的值,而当color被输出到环境中就可以被子shell读取。
同样,由于shell进程不能够更改父进程的环境,对一个子进程中的环境变量重新赋值不会影响到父进程环境中的值。如以下的shell脚本中的color2。
echo The original value of the variable color is $color
ech0 This program will set the value of color to amber
color=amber
echo The value of color is now $color
echo When your program concludes,display the value of the color variable
观察在你设置了color的值后有什么变化。输出这个变量,然后执行color2:
$ export color=lavender
$ echo $color
lanvender
$ color2
The original value of the variable color is lavender
The program will set the value of color to amber
The value of volor is now amber
When your progam concludes, display the value of the color variable,
$ echo $color
lanvender
1.4 shell 程序的参数
命令行:
 $ sh_program arg1 arg2 . . . argx
   $0    $1   $2 ....  $X
例子:
$ cat color3
echo you are now running program: $0
echo The value of command line argument \#1 is: $1
echo The value of command line argument \#2 is : $2
$ chmod +x color3
$ color3 red green
You are now running program: color3
The value of command line argument #1 is : red
The value of command line argument #2 is: green
大多数的UNIX系统命令可以接收命令行参数,这些参数通常告诉命令它将要操作的文件或目录(cp f1 f2),另外指定的参数扩展命令的能力(ls –l),或者提供文本字符串(banner hi there)。
命令行参数对shell程序同样有效,使用这种方式传送信息给你的程序十分方便。通过开发一个接收命令行参数的程序,你可以传递文件或者目录命令名给你的程序处理,就像你运行UNIX系统命令一样,你也可以定义命令行选项来让命令行使用shell程序额外的功能。
在shell程序中的命令行参数与参数在命令行的位置相关,这样的参数被称为位置参数,因为对每一个特殊变量的赋值依靠一这些参数在命令行中的位置,变量的变量名对应变量在命令行中的位置,因此这些特殊的变量名为数字0,1,2等,一直到最后的参数被传递,变量名的存取也通过同样的方法,在名字前面加上$  符号,因此,为了存取你的shell程序中的命令行参数,你可以应用$0,$1,$2等等。在$9以后,必须使用括号:$(10),$(11),否则, shell会将$10看成是$1后面跟一个0。而$0会一直保存程序或命令的名字
1.4 shell程序的参数(继续)
以下的shell程序会安装一个程序,这个程序作为一个命令行参数被安装到你的bin目录:首先创建程序my_install,注意目录$HOME/bin应该预先存在。
$ cat > my_install
echo $0 will install $1 to your bin directory
chmod +x $1
mv $1 $HOME/bin
echo Installation of $1 is complete
ctrl + d
$ chmod +x my_intalll
$ my_install color3
my_install will install color3 to your bin directory
Installation of color3 is complete
$
这个例子中,程序指明第一个命令行参数为一个文件名,然后加上执行权限,然后移动到你当前目录下的bin目录下。
记住UNIX系统的惯例是存贮程序在bin的目录下。你也许想要在你的HOME目录下创建一个bin目录,在这个目录下你可以存储你的程序文件,记住要将你的bin目录放在PATH环境变量中,这样shell才会找到你的程序。
1.5 一些特殊shell变量- #和*
#    命令行参数的数量
*    完全的参数字符串
例子:
$ cat color4
echo There are $# comand line argument
echo They are $*
ehco The first command line argument is $1
$ chmod +x color4
$ color4 red green yellow blue
They are 4 command line arguments
They are red green yellow blue
The first command line argument is red
$
至今为止我们看到的shell程序都不是很灵活,如color3需要输入两个正确的参数而my_install只需要一个。通常在创建一个接收命令行参数的shell程序的时候,你想要用户输入一个参数的变量号码。你同时要程序执行成功,不管用户键入1个参数或是20个参数。当处理变量参数列表的时候,特殊shell变量会提供你许多的灵活性。通过$#你可以知道有多少参数已经被输入,通过$*可以存取全部的参数列表,而不管参数的数量。请注意参数($0)不在$*这个参数列表里。
每一个命令行参数都是互相独立的,你可以通过$*集中检索这些参数,也可以通过$1,$2,$3等等来独立的检索这些参数。
1.5 一些特殊的shell变量-#和*(继续)
一个可以接收多个命令行参数的安装程序的例子:
$ cat > my_install2
echo $0 will install $# files to your bin directory
echo The files to be installed are : $*
chmod +x $*
mv $* $HOME/bin
echo Installaton is complete
ctril + d
$ chmod +x my_install2
$ my_install2 color1 color2
my_intall2 will install 2 files to your bin directory
The files to be installed are: color1,color2
Intallaiton is complete
这个安装程序更加灵活,如果你有多个文件要安装,你仅需要执行这个程序一次,只要一次输入多个名字即可。
非常重要的是:如果你计划传递整个参数的字符串给一个命令,这个命令必须能够接收多个参数。
在以下的脚本中,用户提供一个目录名作为一个命令行参数。程序会更改到指定的目录,显示当前的位置,并且列出内容。
$ cat list_dir
cd $*
echo You are in the $(pwd) directory
echo The contents of the directory are:
ls –F
$ list_dir dir1 dir2 dir3
sh: cd: bad argument count
由于cd命令不能同时进入到多个目录中,这个程序执行会出错。
1.6 shift 命令
向左移动所有的在*中的字符串n个位置
#的数目减少n个(n的默认值是1)
语法:shift [n]
例子:
$ cat color5
orig_args=$*
echo There are $# command line arguments
echo They are $*
echo Shifting two arguments
shift 2
echo There are $# comand line arguments
echo They are $*
echo Shifting two arguments
shift 2; final_args=$*
echo Original arguments are: $orig_args
echo Final arguments are: $final_args
shift命令会重新分配命令行参数对应位置参数,在shift n以后,所有的*中的参数会向左移动n个位置。同时$#会减n。默认的n为1。 Shift命令不会影响到参数0的位置。一旦你完成一次移动,被移出命令行的参数会丢失。如果你想在你的程序中引用这个参数,你需要在执行shift之前存贮这个参数到一个变量中。
Shift命令可以用于:存取一组参数的位置,例如一系列的x,y的坐标
从命令行删除命令选项,假定选项在参数之前。
例子:
$ color5 red green yellow orange black
There are 6 command line arguments
They are red green yellow blue orange black
Shifting two arguments
There are 4 command line arguments
They are yellow blue orange black
Shiftging two arguments
Original arguments are: red green yellow blue orange black
Final argument are : orange black
$
1.7 read 命令
语法:
read variable [variable......]
例子:
$ cat color6
echo This program prompts for user input
echo “please enter your favorite two colors -> \c”
read color_a color_b
echo The colors you entered are: $color_b $color_a
$ chmod +x color6
$ color6
This program prompts for user input
Please enter your favorite two colors -> red blue
The colors you entered are: blue red
$ color6
This program prompts for user input
Please enter you favorite two colors -> red blue tan
The color you enterd are :blue tan red
如果使用命令行参数传递信息进程序,在命令执行之前用户必须知道正确的语法。有一种情况,你想要在用户执行程序的时候提示他输入这些参数。read命令就是用来在程序执行的时候收集终端键入的信息。
通常会使用echo命令来给用户一个提示,让他知道程序正在等待一些输入,同时通知用户应该输入的类型。因此,每一个read命令应该在echo命令后面。
read命令会给出一个变量名的列表,用户在提示符下输入会给这些变量赋值(变量之间以空格分隔)。如果read命令定义的变量比输入的词要多,多出的变量会被赋空值。如果用户输入的词要比变量多,剩余的数据会赋给列表中的最后一个变量。
一旦被赋值,你就可以象其他的shell变量一样存取这些变量。
注意:不要混淆位置参数和变量read。位置参数在命令被激活时直接在命令行中使用,而read命令给变量赋值是在程序执行之中,用户响应输入的提示而给变量赋值。
1.7 read命令(继续)
以下例子提示用户输入要被安装的文件名:
$ cat > my_install3
echo $0 will install files into your bin directory
echo “Enter the names of the files -> \c”
read filenames
mv $filenames $HOME/bin
echo Instllation is complete
ctrl + d
$ chmod +x my_install13
$ my_install13
my_install13 will install files into your bin directory
Enter the names of the files -> f1 f2
Installaton is complete
这个安装脚本会提示用户输入需要chmod并移动到$HOME/bin的文件的文件名。这个程序给用户更多的关于应该输入数据情况的指引。而不像 install2中用户必须在命令行中提供文件名,用户使用程序不需要特殊的语法,程序让用户确切地知道要输入什么。所有的输入的文件名都会被赋值给变量 filenames。
1.8 另外的技术
#号开始的文档为注释部分。
sh shell_program argumetns 
shell_program 的属性可以不是可执行的。
shell_program 必须是可读的。
sh –x shell_program arguments
每一行在被执行前被打印出来。
在调试程序时有用处。
在shell程序中,#符号的意思是后面是一段注释,而shell会自动忽略#符号以后直到一个回车符号为止的所有字符。
执行一个shell程序的另外一种方法是:
sh shell_program arguments
这种方式激活一个子shell并且指定这个子shell为执行这个程序的命令解释器。这个程序文件的属性不一定必须为可执行。这种方式的用途在:你正在在一种shell下工作,同时想要执行用其他shell命令语言写的shell程序十分有用。
你也可以在你的shell程序的第一行前加入! /usr/bin/ shell_name来指定命令行解释器,这样如果你当前正在POSIX shell下工作,但是想要执行一个C shell的脚本,你的C shell程序的第一行应该为:
#!/usr/bin/csh
虽然shell程序没有调试器,命令:
sh –x shell_program arguments
会在执行每一行时,先在屏幕上打印出shell程序的每一行。这允许你看到shell如何进行文件名产生,变量替代,和命令替代。这个选项对发现打字错误十分有帮助。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-4-30 18:35:57 | 显示全部楼层
[推荐]shell编程-分支语句(1)
shell编程-shell编程-分支语句(1)
Shell编程-分支语句(1)
目标:
完成这一章,你将能够作以下事情:
描述条件分支语句中返回值的作用。
使用test命令来分析一个命令的返回值。
在shell程序中使用if和case结构。
1.返回值
shell变量“?”中保存上一个被执行命令的返回值:
0:   命令成功地执行(真)
非零: 命令由于出现错误而被终止(假)
例子:
$ true           $ false
$ echo $?          $ echo $?
0              1
$ ls            $ cp
$ echo $?          Usage: cp f1 f2 
0                 cp [-r] f1 ....fn d1
$ echo $?         $echo $?
0              1
               $echo $?
              0
UNIX操作系统的所有命令在结束的时候都会产生一个返回值。这个返回值通常被用来判断命令是正常结束(返回0)还是发生了错误(返回非零值)。通过返回的非零值还可以看出发生的是什么错误。例如,语法错误通常返回1,true命令返回的就是0,而false命令返回的是1。
大多数的shell程序中的判断语句都是通过分析这个返回值来进行流程控制的。shell中定义了一个特殊的变量“?”用来保存上一个命令结束后的返回值。
你可以通过以下方式来观察前一个命令的返回值:
echo $?
当你执行一个条件判断(小于,大于,等于)的时候,返回值会指明这个条件是否为真(返回0)或者为假(返回非零)。
条件判断语句会在下几节中讲述。
2.test 命令
语法:
test expression 或者 [expression]
test命令对表达式进行测试,并且设置返回值。
表达式的值   返回值
true      0
false      非零(通常为1)
test命令能够测试的对象有:
整数
字符串
文件
test命令被用来评估表达式并且产生返回值。它用参数组成逻辑表达式并且对表达式的返回值进行评估,test命令不会产生标准输出,你必须必须通过返回值来判断test命令的结果,如果表达式为真,返回值会为0,如果表达式为假,返回值为1。
test命令可以被单独使用,然后你能够看到返回值,但它用的最多的还是在if和while结构中用来提供条件流程控制。
test命令的也可以用[expression]来代替。这种方式可以提高可读性,特别是在处理数字或者字符串的时候。
注意:在"["和"]"符号的周围必须要有空格。
3.test命令之数字test
语法:
[ number relation number ]   通过关系运算符来对数字进行比较
关系运算符:
-lt    小于
-le    小于或者等于
-gt    大于
-ge    大于或者等于
-eq    等于
-ne    不等于
例子(假设X=3):
$ [ "$X" -lt 7]   $ [ "$X" -gt 7]
$ echo $?       $ echo $?
0           1
test命令能被用于比较两个整数之间的数字关系,通常用[.....]语法来调用。test命令的返回值就能说明这个条件为真还是为假。
当test一个变量的值的时候,你应该防止变量不要为空值,例如:
$ [ $XX -eq 3]
sh: test:argument expected
如果变量XX在前面没有被赋值,XX的值会是NULL。当shell执行变量替代的时候,shell会试图执行如下语句:
[ -eg 3]
而这个语句不是一个完整的test语句,并且会导致一个语法错误。解决这个问题的一个简单的方法就是在被测试的变量的周围加上引号。
[ "$XX" -eq 3]
当shell执行变量替代的时候,shell会试图执行如下语句:
["" -eq 3]
这会确保至少有一个NULL值作为一个参数提供给这个test命令使用。
注意:作为一个通用的规则,你应该在所有的$变量加上双引号来避免shell发生不正确的变量的替代。
4.test命令-字符串test
语法:
[ string1 = string2] 判断字符串是否相等
[ string1 !=string2] 判断字符串是否不等
例子;
$ X=abc          $ X=abc
$ [ "$X" = "abc"]     $ ["$X"  != "abc"]
$ echo $?         $ echo $?
0             1
test命令也能够用来计较两个字符串是否相等。
[...] 语法通常用作字符串的比较。你已经看到在[]周围必须要有空格,同时在操作符周围也必须要有空格存在。
字符串操作包括:
string1 = string2     如果string1等于string2就为真
string1 != string2     如果string1不等于string2就为真
-z string         如果string的长度为0就为真
-n string         如果string的长度为非零就为真
string           如果string的长度为非零就为真
为了防止变量中包含空白字符,这里引号同样也能够保护字符串的test,,例如:
$ X="yes we will"
$ [ $X=yes]    会导致一个语法错误
shell会解释这个语句为[yes we will = yes ]
$ [ "$x" = yes ]  正确的语法
shell会解释这个语句为:[ "yes we will" = yes ]
在执行数字比较的时候,shell会将所有的参数当成是数字,在执行字符串比较的时候,shell会把所有的参数当成是字符串。如下例所示:
$ X=03
$ Y=3
$ [ "$X" -eq "$Y" ]    比较数字03和数字3
$ echo $?
0            为真,因为它们是相等的数字
$ [ "$X" = "$Y" ]    比较字符串“03”和字符串“3”
$ echo $?
1            为假,因为它们是不相同的字符串
5.test命令- 文件比较
语法:
test -option filename   通过选项对文件进行test
例子:
$ test -f funfile
$ echo $?
0
$ test -d funfile
$ echo $?
1
shell提供的一个有用的test特性是可以用它来test文件的特征,例如文件类型和许可权限。例如:
$ test -f filename
如果文件存在并且是一个普通文件(不是目录或者设备文件),会返回真(0)。
test -s filename
如果文件存在并且其字节数大于0,会返回真(0)。
其它还有许多有用的文件test方式,比如:
-r file    如果文件存在并且是可读的时候为真
-w file     如果文件存在并且是可写的时候为真
-x file     如果文件存在并且是可执行的时候为真
-d directory  目录存在并且是个目录的时候为真
6.test命令-其他操作符
语法:
-o     OR
-a     AND
\(  \)   GROUPING
例子:
$ [ "$ANS" = y -o "ANS' = Y ]
$ [ "$NUM -gt 10 -a "$NUM" -lt 20 ]
$ test -s file -a -r file
注意:()前面必须要用斜杠。
使用Boolean操作符可以同时测试多个条件。
例子:
$ [ "$ANS" = y -o "$ANS" = Y ]
$ [ "$NUM" -gt 10 -a "$NUM" -lt 20 ]
$ test -s file -a -r file -a -x file
NOT操作符(!)被用作连接其他的操作符,特别是在文件test的时候用的很普遍。在!操作符和其他的操作符之间必须要有空格,例如:
test ! -d file
能够用来代替
test -f file -o -c file -o -b file ....
括号被用来对操作符进行分组,但是在shell中括号还有一个特殊的意义就是优先运算的意义。因此,括号前面必须使用\符号来忽略其原有含义。
以下的命令验证:有两个命令行参数,并且第一个命令行参数是一个-m ,并且最后一个命令行参数是一个目录或者是一个字节数大于0的文件:
[ \( $# = 2 \) -a \( "$1" = "-m" \) -a \( -d "$2" -o -s "$2" \) ] 
7.exit命令
语法:
 exit [arg]
例子:
$ cat exit_test
echo exiting program now
exit 99
$ exit_test
exiting_program now
$ echo $?
99
exit命令结束当前shell程序的执行并且设置返回值。通常0被用来说明正常结束,而非0值用来说明一个错误的条件。如果没有特别指明返回值,返回值将被设置为exit命令上一个命令的返回值。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-4-30 18:36:38 | 显示全部楼层
[推荐]shell编程-分支语句(2)
shell编程-shell编程-分支语句(2)
shell编程-分支语句(2)
8.if语句
语法:(用于单向判断分支)
if
  list A
then
  list B
fi
例子:
if
  test -s funfile
then
  echo funfile exists
fi
echo hello
if 结构是一种基于命令返回值的的流程控制方式。如果测试命令的返回值为0,一个指定的命令列表就会被执行,如果用于判断的命令返回值为非0,指定命令列表会被忽略而不被执行。
上例中表明了if结构的一个通用的格式:每一个命令列表由一个或者多个UNIX系统的shell命令组成,每个命令之间用回车符或者分号分隔,list A中最后被执行的命令决定if语句的结果。
if结构执行的过程如下所示:
1.list A命令被执行。
2.如果list A中的最后一个命令的返回值为0(真),执行list B中的命令,然后继续执行fi以后的命令。
3.如果list A中的最后一个命令的返回值为非0(假),跳到fi并且继续执行fi以后的命令。
test命令通常被用作流程控制,它可以使用任何的UNIX命令,因为所有的UNIX命令都产生一个返回值,以下的例子可以说明:
if
  grep kingkong /etc/passwd > /dev/null
then
  echo found kingkong
fi
if结构也能在程序出错的时候提供流程控制。如下例所示:
if
  [ $# -ne 3 ]
then
  echo Incorrect syntax
  echo Usage: cmd arg1 arg2 arg3
  exit 99
fi
9.if-else 结构
语法:(用在多分支选择的情况)
if
  list A
then
  list B
else
  list C
fi
例子:
if [ "$X" -lt 10 ]
then
 echo X is less than 10
else
 echo X is not less than 10
fi
if-else结构让你能够在控制命令的返回值为0的情况下执行一系列的命令,或者在控制命令的返回值为非0的情况下执行另外一系列的命令。
这种情况下if结构的执行过程是:
1.执行list A中的命令。
2.如果在list A中最后一个命令的返回值是0(真),执行list B中的命令,然后继续执行fi以后的命令。
3.如果list A中最后一个命令的返回值为非0(假),执行list C中的命令,然后执行fi以后的命令。
注意在list C中可以包含任何的UNIX命令,其中也包括if。例如:
if
  [ "$X" -lt 10 ]
then
  echo X is less than 10
else
  if
    [ "$X" -gt 10 ]
  then
    echo X is greater than 10
  else
    echo X is equal to 10
  fi
fi
注意:每一个if必须要有一个fi来结束。
10.case结构
语法:(多路分支)
 case word in
 patterm1) list A
       ;;
 pattern2) list B
       ;;
 patternN)  list N
        ;;
 esac
例子:
case $ANS in
  yes) echo O.K
     ;;
   no) echo no go
     ;;
esac
if-else结构也能支持多路的分支,但是当有两个或者三个分支的之后,程序会变得十分难以阅读。case结构提供了实现多路分支的一种更方便的方法。分支选择是顺序地对一个word与提供的参数之间的比较结果。这些比较是是严格的基于字符串的对比。当一个匹配成功的时候,对应的命令就会被执行。每个命令的列表都以两个分号结束。在完成了相关的比较之后,程序会在esac之后继续执行下去。
word典型的情况下是指向一个shell变量。
pattern的组成格式和文件名的生成原则是一致的。
以下是一些pattern允许的特殊的字符:
*    匹配任何字符串和字符包括空字符
?    匹配任何单个的字符。
[...]  匹配任何一个括号出现中的字符
另外|字符的意义是OR。
注意:在这个结构中的右括号和分号是必须的。
case结构通常被用于菜单选择或者是需要对几个用户输入选项作出选择的时候。
12.shell编程 - 分支:总结
返回值   每一个程序的返回值 - echo $?
数字test  [ "$num1" -lt "$num2" ]
字符串test [ $string1 = $string2 ]
文件test   test -f filename
exit n    终止程序的允许并且设置返回值
if                    case word in
   command listA           pattern1) command list
then                   ;;
   command listB           pattern2) command list
else                   ;;
   command listC           *)     command list
fi                    ;;
                     esac
执行那个语句基于listA中最后一条     字符串word会与每一个pattern比较
命令的返回值
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-4-30 18:37:24 | 显示全部楼层
[推荐]shell编程-循环语句(1)
shell编程-shell编程-循环语句(1)
shell编程之循环语句(1)
目标:
完成这一章,你将能够作以下事情:
使用while语句在条件为真的时候重复地执行一段代码。
使用until语句重复执行一段代码直到条件为真。
使用交互性的for语句进行循环控制。
1.循环的简单介绍
目标:     重复的执行一段命令列表。
控制;     基于一个关键命令的返回值。
三种格式:   while ... do ... done
        until ... do ... done
        for ... do ... done
循环语句让你可以重复执行一个命令列表,而决定是继续循环还是跳出循环是基于一个命令的返回值。test命令常常被用来控制一个循环是否继续。
与分支语句不同的是,在分支语句中开始一个分支语句的关键字和结束一个分支语句的关键字是相反的(if/fi 和case/esac),循环语句由一个关键字和一些条件开始,循环体整个用do/done来包围起来。
2.使用let来进行算术计算
语法:
let expression or (( expression ))
例子:
$ x=10          $ x=12
$ y=2           $ let "x <10"   
$ let x=x+2        $ echo $?
$ echo $x         1
12            $ (( x > 10 ))
$ let "x = x / (y+1)"   $ echo $?
$ echo $x         $ 0
4             $ if ((x > 10 ))
$ (( x = x + 1 ))     >  then echo x greater
$ echo $x         >  else echo x not greater
5             fi
             x greater
循环语句通常使用一个增长的数字变量来进行控制。使用let命令,可以在shell脚本中使用算术表达式。这个命令允许使用长的整数运算。在上例中, expression代表一个shell变量的算术表达式和能够被shell识别的操作符,而((  ))可以替let命令。shell能够识别的表达式如下所示:
操作符     描述
-        减去
!        逻辑相反
* / %      乘,除,余数
+ -       加,减
<=  >=  < > 关系比较
== !=      等于不等于
=        赋值
括号能够被用作改变表达式中计算的顺序,就像在
let "x=x/(y+1)"
中一样
注意双引号被用来忽略括号的特殊含义。同样如果你希望使用空格来分隔操作符和操作符的时候,就必须使用双引号,或者(( ))语句:
let " x = x + (y / 2)" 或者(( x= x+ (y / 2) ))
当使用逻辑和关系操作符,(!,<=,>=,<,>,++,~=),的时候,shell会返回一个代码变量,?会反映结果是真还是假,再一次说明,必须使用双引号来防止shell将大于和小于运算符当作I/O重定向。
3.while语句
重复执行循环体,直到条件为真
语法:
while              
   list A
do
   list B
done
例子:
$ cat test_while
X=1
while (( X <= 10 ))
do
  echo hello X is $X
  let X=X+1
done
$ test_while
hello X is 1
hello X is 2
.
.
.
hello X is 10
while语句是shell提供的一种循环机制,当条件为真的时候它允许循环体中的命令(list B)继续执行。条件判断是通过list A中最后一个命令的返回值来进行。通常一个test或者let命令被用作控制循环的执行,但是任何命令都能被用来产生一个返回值。上面的例子中使用的是test命令可以用let命令来代替,如下:
$ X=1
$ while [ $x -le 10 ]
> do
>   echo hello X is $X
>   let X=X+1
> done
命令执行的过程如下:
1.list A中的命令被执行。
2.如果list A中的最后一个命令的返回值为0(真),执行list B。
3.回到第一步。
4.如果list A中的最后一个命令的返回值不为0(假),跳到下面done关键字后的第一个命令。
提醒:注意while循环会无穷执行下去,因为有一些循环的控制命令的返回值永远为真。
$ cat while_infinite
while
   true
do
   echo hello
done
$ while_infinite
hello
hello
.
.
.
ctrl + c
4. while结构举例
例A:               例B
如果ans为yes就重复执行            当有命令行参数时重复执行
ans=yes               while (($# != 0 ))
while                 do
[ "$ans" = yes ]            if test -d $1
do                   then
  echo Enter a name          echo contents of $1;
  read name              ls -F $1
  echo $name >> file.names      fi
  echo "Continue?"          shift
  echo Enter yes or no        echo There are $# items
  read ans              echo left on the cmd line
done                 done 
上例是两个while语句的例子,例A提示用户输入,然后对用户的输入进行判断,来决定是否继续执行循环,例子B中,循环会对命令行中每一个参数进行判断,如果参数是一个目录,这个目录中的内容会被显示出来。如果这个参数不是一个目录,程序会跳过。注意shift命令的用法,它允许一个一个存取每一个参数。和while命令一起使用可以使这个循环非常灵活。它不关心参数的个数是1个还是100个,循环会继续执行直到所有的参数都被存取。
注意,如果你希望循环至少执行一次,就必须进行某些设置。例A中会执行循环体至少一次因为ans已经被设为yes。在例B中,如果程序的执行不带任何参数($#等于0),这个循环就一次都不会执行。
5.until语句
重复循环直到条件为真为止。
语法:      例子:
until       $ cat test_until
 list A      X=1
do         until (( x > 10 ))
 list B      do
done         echo hello X is $X
           let X=X+1
          done
          $ test_until
          hello X is 1
          hello X is 2
          hello X is 3
           .
           .
           .
          hello X is 10
until语句是shell提供的另一种循环机制,它会持续执行命令(list B)直到一个条件为真为止。同while循环类似,条件判断由list A中的最后一条命令的返回值来决定的。
命令的执行过程如下:
1.list A中的命令被执行。
2.如果list A中最后一条命令的返回值为非0(假),执行list B。
3.返回到第一步。
4.如果list A中的最后一条命令的返回值为0(真),跳到done关键字后的第一条命令。
注意:until循环的无穷执行下去,因为有些循环语句中的控制语句的返回值始终为假。
$ x=1
$ until
> [ $ x -eq 0 ]
> do
> echo hello
> done
hello
hello
hello
.
.
.
ctrl + c
6.until的例子
例A               例B
重复执行循环体直到ans为no为止        重复执行循环直到没有命令行参数为止
ans=yes               until (( $# == 0 ))
until                 do
  [ "$ans" = no ]           if test -d $1
do                   then
echo Enter a name            echo context of $1;
 read name               ls -F $1
 echo $name >> file.names      fi
 echo "Continue?"          shift
 echo Enter yes or no        echo There are $# items
 read ans               echo left on the cmd line.
done                done
上例的结构与while语句中的例子类似,但是使用until语句来。注意在两种语句的test条件的逻辑关系是相反的。
同时请注意用户输入的敏感性由一些轻微的变化。使用while语句,只有用户的输入的字符串为“yes”时循环才会执行,继续执行循环的条件十分严格,使用until语句,循环会在用户使用与no不同的任何字符时都会执行。它对于继续进行循环的条件不太严格,你也许希望在决定那一种结构最符合你的要求的时候考虑这一特征。
预定义ans变量的值不再是必须的,因为它的值会被初始化为NULL,由于NULL不等于no,test会返回假,而循环会被执行。你仅仅需要将$ans用括号引起来,以免在test语句执行时发生语法错误。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2005-4-30 18:38:19 | 显示全部楼层
[推荐]shell编程-循环语句(2)
shell编程-shell编程-循环语句(2)
shell编程之循环语句(2)
7.for语句
对列表的每一条目都进行一次循环过程
,每完成一次循环过程就将var赋予列表中下一个条目,直到完成最后一个条目的循环为止
语法:            例子:
for var in list        $ cat test_for
do               for X in 1 2 3 4 5
   list A          do
done              echo "2 * $X is \c"
                let X=X*2
                echo $X
                done
                $ test_for
                2 * 1 is 2
                2 * 2 is 4
                2 * 3 is 6
                2 * 4 is 8
                2 * 5 is 10
在上例中,关键字为for,in,do和done,var代表一个shell变量的名字,这个变量的赋值会贯穿for循环的执行过程中,list是一串由空格或者tab分割开的字符串,在每一次循环执行都要将一个串赋值给var。
for循环的执行过程如下:
1.shell变量var被设置等于list中的第一个字符。
2.list A中的命令会被执行。
3.shell变量var被设置等于list中下一个字符。
4.list A中的命令被执行。
5.循环会持续执行,直到每一个list中的条目都执行过循环为止。
8.for循环的例子
例A:
$ cat example_A
for NAME in $(grep home /etc/passwd | cut -f1 -d
do
  mail $NAME < mtg.minutes
  echo mailed mtg.minutes to $NAME
done
例B
$ cat example_B
for FILE in *
do
 if
  test -d $FILE
 then
  ls -F $FILE
 fi
done
for结构是一种非常灵活的循环结构,它能够让循环贯穿任何能产生的列表。使用命令替代可以很容易产生生成列表,就像第一个例子使用管道和过滤器可以产生一个列表。如果你要求多次存取相同的列表,你也许想要将它存储到个文件中。你可以使用cat命令来为你的for循环产生列表,正如下例所示:
  $ cat students
  user1
  user2
  user3
  user4
  $ cat for_student_file_copy
  for NAME in $(cat students)
  do
  cp test.file /home/$NAME
  chown $NAME /home/$NAME/test.file
  chmod g-w,o-w /home/$NAME/test.file
  echo done $NAME
  done
  $
存取命令行参数
你可以从命令行参数来产生list:
for i in $*         或者     for i
do                     do 
  cp $i $HOME/backups            cp $i $HOME/backups
done                    done
9.break,continue,和exit命令
break [n]      中止循环过程的执行,并且跳到下一个命令。
continue [n]    停止循环过程的当前一个反复并且跳到循环中的下一个反复过程的开始部分
exit [n]       停止shell程序的执行,并且将返回值设置为n。
在许多情况下,你可能需要在循环的正常中止条件满足之前放弃一个循环的执行。break和continue命令提供了一种无条件的流程控制,通常用在遇到一个错误的情况下来中止当前的循环。而exit命令用在不能从某种情况下恢复出来而必须中止整个程序的运行的时候。
break命令会中止循环并且将控制权传递到done关键字后面的第一个命令。结果是完全跳出这个循环体而继续执行下面的命令。
continue命令有一点不同。当在程序执行过程中遇到这个命令,就会忽略本次循环中剩余的命令,而将控制权交给循环的顶部。这样,continue命令能让你仅仅中止所有循环中的一个循环过程而继续从当前循环的顶部开始执行。
在while和until循环中,这种处理(continue)会导致在初始列表的开始部分继续执行,在for循环中,会将变量设置为列表中的下一个条目,然后继续执行循环。
exit命令停止执行当前的shell程序,并且根据提供的参数为这个shell程序设置一个返回值,如果没有提供返回值参数,当前的shell程序的返回值会被设置为在exit命令之前执行的命令的返回值。
注意:循环的流程控制在正常的情况下应当是通过设置循环开始部分的条件(while,until),或者是列表中的条目都循环完的(for),的情况来结束循环。而对循环过程进行中断操作仅仅应当在循环执行期间遇到没有规律的或者是错误的条件的时候才应当使用。
10.break和continue的例子
while
  true
do
  echo "Enter file to remove: \c"
  read FILE
  if test ! -f $FILE
  then
    echo $FILE is not a regular file
    continue
  fi
  echo removing $FILE
  rm $FILE
  break
done
这个例子显示break和continue命令的一次有效的使用。这个命令的执行是在while循环的test条件为真的情况下,会始终产生一个为真的结果;这意味着这个循环会是一个无限的循环,除非循环体中的某些命令能中止循环的运行(这就是bread命令需要做的)。如果输入的文件不是一个普通文件,一个错误信息会打印,同时continue命令会提醒用户再输入一次文件名。如果这个文件是个普通的文件,它会被删除,并且break命令被用来跳出这个无穷循环。
11.shell编程之循环-总结
let expression              计算一个算术表达式
((expression))              计算一个算术表达式
while condition is true do ...done    while
until condition is true do ... done   until
for var in list do ... done        for
break [n]                break out of loop
continue [n]               中止当前循环中的一次循环过程
exit [n]                 中止这个程序
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表