所有操作系统都为它们所允许的程序提供服务。
典型的服务包括:执行新程序、打开文件、读文件、分配存储区以及获得当前时间等。
可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境。
通常将这种软件称为内核,因为它相对较小,而且位于环境的核心。
内核的接口被称为系统调用。
公用函数库构建在系统调用接口之上,应用程序既可使用公用函数库,也可使用系统调用。
shell是一个特殊的应用程序,为运行其他应用程序提供了一个接口。
广义上,操作系统包括了内核嗯哼哼一些其他软件,这些软件使得计算机能够发挥作用,并使计算机具有自己的特性。
其他软件包括系统实用程序、应用程序、shell以及公用函数库等。
1. 登录名
用户在登录UNIX系统时,先键入登录名,然后键入口令。
系统在其口令文件(通常是/etc/passwd文件)中查看登录名。
口令文件中的登录项由7个以冒号分隔的字段组成,依次是:登录名、加密口令、数字用户ID(205)、数字组ID(105)、注释字段、起始目录(/home/sar)以及shell程序(/bin/ksh)。
sar:x:205:105:Stephen Rago: /home/sar:/bin/ksh
目前,所有的系统已将加密口令移到另一个文件中。
2. shell
用户登录后,系统通常先显示一些系统信息,然后用户就可以向 shell 程序键入命令。(当用户登录时,某些系统启动一个视窗管理程序,但最终总会有一个shell程序运行在一个视窗中)。
shell是一个命令行解释器,它读取用户输入,然后执行命令。
shell的用户输入通常来自于终端(交互式shell),有时则来自于文件(称为shell脚本)。
系统从口令文件中相应用户登录项的最后一个字段中了解到应该为该登录用户执行哪一个shell。
1. 文件系统
UNIX 文件系统是目录和文件的一种层次结构,所有东西的起点是称为根的目录,这个目录的名称是一个字符“ / ”。
目录是一个包含目录项的文件。
在逻辑上,可以认为每个目录项都包含一个文件名,同时还包含说明该文件属性的信息。
文件属性是指文件类型(是普通文件还是目录等)、文件大小、文件所有者、文件权限(其他用户能否访问该文件)以及文件最后的修改时间等。
stat 和 fstat 函数返回包含所有文件属性的一个信息结构。
2. 文件名
目录中的各个名字称为文件名。
只有斜线( / )和空字符这两个字符不能出现在文件名中。
斜线用来分隔构成路径的个文件名,空字符则用来终止一个路径名。
好的习惯还是只使用常用印刷字符的一个子集作为文件名字符。
为了移植性,POSIX.1 推荐将文件名限制在以下字符集之内: 字母(a ~ z、A ~ Z)、数字(0~9)、句点( . )、短横线( - )和下划线( _ )。
创建新目录时会自动创建了两个文件名: . (称为点)和 . . (称为点点)。
点指向当前目录,点点指向父目录。
在最高层次的根目录中,点点与点相同。
3. 路径名
由斜线分隔的一个或多个文件名组成的序列(也可以斜线开头)构成路径名,以斜线开头的路径名称为绝对路径名,否则称为相对路径名。
相对路径名指向相对于当前目录的文件。
文件系统根的名字( / )是一个特殊的绝对路径名,它不包含文件名。
ls(1) 这种表示方法是 UNIX 系统的惯用方法,用以引用 UNIX 系统手册中的一个特定项。
ls(1) 引用第一部分中的 ls 项。
个部分通常用数字 1~8 编号,在每个部分中的各项则按字母顺序排列。
可用下面的命令查看 ls 命令手册页:
main l ls
或
main -sl ls
4. 工作目录
每个进程都有一个工作目录,有时称其为当前工作目录。
所有相对路径名都从工作目录开始解释。
进程可以用 chdir 函数更改其工作目录。
例如,
相对路径名 doc/memo/joe 指的是当前工作目录中的 doc 目录中的 memo 目录中的文件(或目录) joe 。
从该路径名可以看出, doc 和 memo 都应当是目录,但是却不能分辨 joe 是文件还是目录。
路径名 /urs/lib/lint 是一个绝对路径名,它指的是目录中的 urs 目录中的 lib 目录中的文件(或目录)lint 。
5. 起始目录
登录时,工作目录设置为起始目录,该起始目录从口令文件中相应用户的登录项中取得。
1. 文件描述符
文件描述符通常是一个小的非负整数,内核用以标识一个特定进程正在访问的文件。
当内核打开一个现有文件或创建一个新文件时,它都返回一个文件描述符。
在读、写文件时,可以使用这个文件描述符。
2. 标准输入、标准输出和标准错误
每当运行一个新程序时,所有的 shell 都为其打开3个文件描述符,即标准输入、标准输出以及标准错误。
如果不做特殊处理,则这3个描述符都链接向终端。
大多数 shell 都提供一种方法,使其中任何一个或所有这3个描述符都能重定向到某个文件。
ls > file.list
执行 ls 命令,其标准输出重定向到名为 file.list 的文件。
3. 不带缓冲的 I/O
函数 open、read、write、lseek 以及 close 提供了不带缓冲的 I/O。这些函数都使用文件描述符。
4. 标准 I/O
标准 I/O 函数为那些不带缓冲的 I/O函数提供了一个带缓冲的接口。
使用标准 I/O函数无需担心如何选取最佳的缓冲区大小。
使用标准 I/O 函数还简化了对输入行的处理(常常发生在 UNIX 的应用程序中)。
标准 I/O函数库提供了使我们能够控制该库所使用的缓冲风格的函数。
最熟悉的标准 I/O函数是 printf 。
在调用 printf 的程序中,总是包含
1. 程序
程序是一个存储在磁盘上某个目录中的可执行文件。
内核使用 exec 函数(7个 exec函数之一),将程序读入内存,并执行程序。
2. 进程和进程 ID
程序的执行实例被称为进程。
某些操作系统用任务表示正在被执行的程序。
UNIX 系统确保每个进程都有一个唯一的数字标识符,称为进程 ID(process ID)。
进程 ID 总是一个非负整数。
3. 进程控制
有3个用于进程控制的主要函数: fork、exec 和 waitpid 。(exec 函数有7种变体,但经常把它们统称为 exec 函数。)
4. 线程和线程 ID
通常,一个进程只有一个控制线程——某一时刻执行的一组机器指令。
多个控制线程也可以充分利用多处理其系统的并行能力。
一个进程内的所有线程共享同一地址空间、文件描述符、栈以及与进程相关的属性。
因为它们能访问同一存储区,所以各线程在访问共享数据时需要采取同步措施以避免不一致性。
与进程相同,线程也用ID标识。
线程ID只在它所属的进程内起作用。
一个进程中的线程ID在另一个进程中没有意义。
当在一进程中对某个特定线程进行处理时,可以使用该线程的ID引用它。
当UNIX系统函数出错时,通常会返回一个负值,而且整型变量 errno 通常被设置为具有特定信息的值。
文件
这些常量都以字符 E 开头。
UNIX中,intro(2) 列出了所有出错常量。
POSIX 和 ISO C 将 errno 定义为一个符号,它扩展称为一个可修改的整型左值(lvalue)。它可以是一个包含出错编号的整数,也可以是一个返回出错编号指针的函数。
在支持线程的环境中,多个线程共享进程地址空间,每个线程都有属于它自己的局部 errno 以避免一个线程干扰另一个线程。
对于 errno 应当注意两条规则。
C标准定义了两个函数,打印出错信息:
出错恢复
可将在
与资源相关的非致命性出错包括: EAGAIN、ENFILE、ENOLCK、ENOSPC、EWOULDBLOCK,有时 ENOMEM 也是非致命性出错。
当 EBUSY 指明共享资源正在使用时,也可将它作为非致命性出错处理。
当 EINTR 中断一个慢速系统调用时,可将它作为非致命性出错处理。
对于资源相关的非致命性出错的典型恢复操作是延迟一段时间,然后重试。
1. 用户 ID
口令文件登录项中的用户 ID 是一个数字,它向系统标识各个不同的用户。
系统管理员在确定一个用户的登录名的同时,确定其用户ID。
用户不能更改其用户 ID。
通常每个用户有一个唯一的用户 ID。
用户 ID 为0的用户为根用户或超级用户。
在口令文件中,通常有一个登录项,其登录名为 root ,称这种用户的特权为超级用户特权。
如果一个进程具有超级用户特权,则大多数文件权限检查都不再进行。
某些操作系统功能只向超级用户提供,超级用户对系统有自由的支配权。
2. 组 ID
口令文件登录项也包括用户的组ID(group ID),它是一个数值。
组ID也是由系统管理员在指定用户登录名时分配的。
组文件将组名映射为数值的组ID。组文件通常是 /etc/group 。
对于用户而言,使用名字比使用数值方便,所以口令文件包括了登录名和用户ID之间的映射关系,而组文件则包含了组名和组ID之间的映射关系。
程序调用 getuid 和 getgid 以返回用户ID和组ID。
3. 附属组 ID
大多数UNIX系统版本还允许一个用户属于另外一些组。
这一功能是从 4.3BSD 开始的,它允许一个用户属于多至16个其他的组。
登录时,读文件 /etc/group,寻找列有该用户作为其成员的前16个记录像就可以得到该用户的附属组ID。
信号(signal)用于通知进程发生了某种情况。
进程有以下3中处理信号的方式。
终端键盘上有两种产生信号的方法,分别称为中断键(interrupt key,通常是 Delete 键或 ctrl+C)和退出键(quit key , 通常是 Ctrl+\),它们被用于中断当前运行的进程。
另一种产生信号的方法是调用 kill 函数。
在一个进程中调用此函数就可向另一个进程发送一个信号。
限制:当向一个进程发送信号时,我们必须是那个进程的所有者或者是超级用户。
日历时间:
自1970.1.1 00:00:00这个特定时间以来所经过的秒数的累计值。
系统用基本数据类型 time_t 保存这种时间值。
进程时间:
也称为CPU时间,用于度量进程使用的中央处理器资源。
进程时间以时钟滴答计算。每秒曾经取为50/60/100个时钟滴答。
系统用基本数据类型 clock_t 保存这种时间值。
当度量一个进程的执行时间时,UNIX系统为一个进程维护了3个 进程时间值:
系统调用和库函数都以C函数形式出现,都为应用程序提供服务,但其性质不同。
系统调用处于更底层,一般库函数会借助系统调用实现功能,反之,则不会。
系统调用通常提供一种最小接口,而库函数通常提供比较复杂的功能。
学习参考资料:
《UNIX 环境高级编程》第3版