第一章 UNIX基础知识
1.1 引言
所有操作系统都为它们所运行的程序提供服务。典型的服务包括:执行新程序、打开文件、读文件、分配存储区以及获得当前时间等。
1.2 UNIX体系结构
操作系统是一种软件,它控制计算机硬件资源,提供程序运行环境。通常将这种软件称为内核,因为它相对较小,且位于环境的核心。
内核的接口称为“系统调用“。公用函数库构建在系统调用接口之上,应用程序既可使用公用函数库,也可使用系统调用。shell是一个特殊的应用程序,为运行其他应用程序提供了一个接口。
广义上说,操作系统包括了内核和一些其他软件,这些软件使得计算机能够发挥作用,并使计算机具有自己的特性。上面所说的其他软件包括:系统应用程序(system utility)、应用程序、shell以及公用函数等。
Linux是GNU操作系统的内核。
1.3 登录
登录名
用户在登录UNIX系统时,先键入登录名,然后键入口令。系统在其口令文件(通常是/etc/passwd文件)中查找登录名。口令文件中的登录项由7个冒号分割的字段组成,分别为:登录名,加密口令,数字用户ID(205),数字组ID(105),注释字段,起始目录(/home/sar)以及shell程序(/bin/ksh)。
shell
用户登录后,系统通常会先显示一些系统信息,然后用户就可以向shell程序键入命令(当用户登录时,某些系统启动一个视窗管理程序,但最终总会有一个shell程序运行在一个视窗中)。**shell是一个命令行解释器,它读取用户输入,然后执行命令。**shell的用户输入通常来自于终端(交互式shell),有时则来自于文件(称为shell脚本)。UNIX系统中常见的shell如下:
名称 | 路径 | FreeBSD | Linux 3.2.0 | Mac OS X 10.6.8 | Solaris 10 |
---|---|---|---|---|---|
Bourne shell | /bin/sh | . | . | bash的副本 | . |
Bourne-again shell | /bin/bash | 可选的 | . | . | . |
C shell | /bin/csh | 链接至tcsh | 可选的 | 链接至tcsh | . |
Korn shell | /bin/ksh | 可选的 | 可选的 | . | . |
TENEX C shell | /bin/tcsh | . | 可选的 | . | . |
1.4 文件和目录
文件系统
UNIX文件系统是目录和文件的一种层次结构,所有东西的起点是称为根(root)的目录,这个目录的名称是一个字符”/”。
目录是一个包含目录项的文件。在逻辑上,可以认为每个目录项都包含一个文件名,同时还包含说明该文件属性的信息。**文件属性是指文件类型(是普通文件还是目录文件等)、文件大小、文件所有者、文件权限(其他用户能够访问该文件)以及文件最后的修改时间等。**stat和fstat函数返回包含所有文件属性的一个信息结构。
文件名
目录中各个名字称为文件名。只有斜线(/)和空字符这两个字符不能出现在文件名中。斜线用来分割成路径名的各文件名,空字符则用来终止一个路径名。好的习惯是使用常用印刷字符的一个子集作为文件名字符(如果在文件名中使用了某些shell的特殊字符,则必须使用shell的引号机制来引用文件名)。为了可移植,POSIX.1推荐将文件名限制在以下字符集之内:(a~z、A~Z)、数字(0~9)、句点(.)、短横线(-)和下滑线(_)。
创建新目录时会自动创建两个文件名:.(称为点)和..(称为点点)。点指向当前目录,点点指向父目录。在最高层次的根目录中,点点与点相同。
路径名
由斜线分割的一个或多个文件名组成的序列(也可以斜线开头)构成路径名,以斜线开头的路径名称为绝对路径名,否则称为相对路径名。相对路径名指向相对于当前目录的文件。文件系统根的名字(/)是一个特殊的绝对路径名,它不包含文件名。
工作目录
每个进程都有一个工作目录,有时称其为当前工作目录。所有相对路径名都从工作目录开始解释。进程可以用chdir函数更改其工作目录。
起始目录
登录时,工作目录设置为起始目录,该起始目录从口令文件中相应用户的登录项中取得。
1.5 输入和输出
文件描述符
文件描述符通常是一个小的非负整数,内核用以标识一个特定进程正在访问的文件。当内核打开一个现有文件或创建一个新文件时,它都返回一个文件描述符。在读、写文件时,可以使用这个文件描述符。
标准输入、标准输出和标准错误
按惯例,每当运行一个新程序时,所有的shell都为其打开3个文件描述符,即标准输入、标准输出、标准错误。如果不做特殊处理,如ls命令,这3个描述符都链接向终端。大多数shell都提供一种方法,使其中任何一个或所有这3个描述符都链接向终端。大多数shell都提供一种方法,使其中任何一个或所有这3个描述符都能重新定向到某个文件。
不带缓冲的I/O
函数open、read、write、lseek以及close提供了不带缓冲的I/O。这些函数都使用文件描述符。
标准I/O
标准I/O函数为那些不带缓冲的I/O函数提供了一个缓冲的接口。使用标准I/O函数无需担心如何选取最佳的缓冲区大小。使用标准I/O函数还简化了对输入行的处理。
1.6 程序和进程
程序
程序是一个存储在磁盘上某个目录中的可执行文件。内核使用exec函数(7个exec函数之一),将程序读入内存,并执行程序。
* 进程和进程ID*
程序的执行实例被称为进程。某些操作系统用任务(task)表示正在被执行的程序。
UNIX系统确保每个进程都有一个唯一的数字标识符,称为进程ID。进程ID总是一个非负整数。
进程控制
有3个用于进程控制的主要函数:fork、exec和waitpid。
* 线程和线程ID*
通常,一个进程只有一个控制线程——某一个时刻执行的一组机器指令。对于某些问题,如果有多个控制线程分别作用于它的不同部分,那么解决起来就容易得多。另外,多个控制线程也可以充分利用多处理器系统的并行能力。
一个进程内的所有线程共享同一地址空间、文件描述符、栈以及进程相关的属性。因为它们能访问同一存储区,所有各线程在访问共享数据时需要采取同步措施以避免不一致性。
与进程相同,线程也用ID标识。但是,线程ID只在它所属的进程内起作用。一个进程中的线程ID在另一个进程中没有意义。当在一个进程中对某个特定线程进行处理时,可以使用该线程的ID引用它。
控制线程的函数与控制进程的函数类似,但另有一套。线程模型是在进程模型建立很久之后才被引入到UNIX系统中的,然后这两种模型之间存在复杂的交互。
1.7 出错处理
当UNIX系统函数出错时,通常会返回一个负值,而且整型变量errno通常被设置为具有特定信息的值。如open函数如果成功执行则返回一个非负文件描述符,如出错则返回-1。在open出错时,有大约15种不同的errno值(文件不存在,权限问题等)。而有些函数对于出错则使用另一种约定而不是返回负值。如大多数返回指向对象指针的函数,在出错时会返回一个null指针。
文件
1.8 用户标识
用户ID
口令文件登录项中的用户ID是一个数值,它向系统标识各个不同的用户。系统管理员在确定一个用户的登录名的同时,确定其用户ID。用户不能更改其用户ID。通常每个用户有一个唯一的用户ID。
用户ID为0的用户为根用户或超级用户。在口令文件中,通常有一个登录项,其登录名为root,称这种用户的特权为超级用户特权。如果一个进程具有超级用户特权,则大多数文件权限检查都不再进行。某些操作系统功能只向超级用户提供,超级用户对系统有自由的支配权。
* 组ID*
口令文件登录项也包括用户的组ID,它是一个数值。组ID也是由系统管理员在指定用户登录名时分配的。一般,在口令文件中有多个登录项具有相同的组ID。组被用于将若干用户集合到项目或部门中去。这种机制允许同组的各个成员之间共享资源(如文件)。
组文件将组名映射为数值的组ID。组文件通常是/etc/group。
使用数值的用户ID和数值的组ID设置权限是历史上形成的。对于磁盘上的每个文件,文件系统都存储该文件所有者的用户ID和组ID。存储这两个值只需4个字节。如果使用完整ASCII登录名和组名,则需更多的磁盘空间。另外,校验期间,比较字符串之比较整型数更消耗时间。
但对于用户而言,使用名字比使用数值更方便,所以口令文件包含了登录名和用户ID之间的映射关系,而组文件则包含了组名和组ID之间的映射关系。
* 附属组*
除了在口令文件中对一个登录名指定一个组ID之外,大多数UNIX系统版本还允许一个用户属于另外一些组。从4.2BSD开始,允许一个用户属于多至16个其他的组。登录时,读文件/etc/group,寻找列有该用户作为其成员的前16个记录项就可以得到该用户的附属组ID。
1.9 信号
信号用户通知进程发生了某种情况。进程存在三种处理信号的方式:
1)忽略信号。有些信号表示硬件异常,如除以0或访问进程地址空间以外的存储单元等,因为这些异常产生的后果不确定,所以不推荐使用这种处理方式。
2)按系统默认方式处理。对于除0,系统默认方式是终止该进程。
3)提供一个函数,信号发生时调用该函数,这被称为捕捉该信号。通过提供自编的函数,就知道什么时候产生了新信号,并按期望的方式处理它。
很多情况都会产生信息。终端键盘上有两种产生信号的方法,分别称为中断键(Delete键或Ctrl + C)和退出键(Ctrl + \),他们被用于中断当前运行的进程。另一种产生信号的方法是调用kill函数。在一个进程中调用此函数就可向另一个进程发送一个信号。当然这样做也有些限制:当向一个进程发送信号时,必须是那个进程的所有者或者是超级用户。
1.10 时间值
UNIX系统使用过两种不同的时间值。
1)日历时间。
2)进程时间。UNIX系统为一个进程维护了3个进程时间值,时钟时间,用户CPU时间,系统CPU时间。
时钟时间又称为墙上的时钟时间,它是进程运行的时间总量,其值与系统中同时运行的进程数有关。用户CPU时间是执行用户指令所用的时间量。系统CPU时间为该进程执行内核程序所经历的时间。
1.11 系统调用和库函数
所有的操作系统都提供了多种服务的入口点,由此程序向内核请求服务。各种版本的UNIX实现都提供良好定义、数量有限、直接进入内核的入口点,这些入口点被称为系统调用。
UNIX中定义了程序员可以使用的通用库函数。这些函数可能会调用一个或多个内核的系统调用,但是他们并不是内核的入口点。
从实现角度看,系统调用和库函数都以C函数形式出现,两者都为应用程序提供服务。可以替换库函数,但是系统调用通常是不能被替换的。
另一个说明系统调用和库函数的不同是判断当前时间和日期的接口。一些操作系统分别提供一个返回时间的系统调用和另一个返回日期的系统调用。任何特殊的处理,如正常时制和夏令时之间的转换,都由内核处理或要求人为干预。
应用程序既可以调用系统调用,也可以调用库函数。很多库函数会调用系统调用。系统函数和库函数的另一个差别是:系统调用通常提供一种最小接口,而库函数通常提供比较复杂的功能。
进程控制系统调用(fork、exec和wait)通常由用户应用程序直接调用。
参考文献
UNIX环境高级编程