UNIX 系统概述

1. UNIX体系结构(UNIX Architecture)

调用内核的接口叫做系统调用(system call,图1.1中的阴影部分),普通函数库是建立在系统调用接口的基础之上。应用(application)可以同时使用函数库或者

统调用。Shell是一种特殊的应用,它为运行其他应用提供接口。总的来说,一个操作系统由内核和所有其他的软件组成,这些软件包括系统实用程序,应用,shell,普通的函数库等等。

2. 登录(Logging In)

2.1 登录名

系统在password文件中寻找登录名,通常为文件/etc/passwd。每个用户由7部分组成:登录名:加密密码:用户ID:用户组ID:注释字段:home文件夹:shell程序

所有现代的系统都将加密密码移到了另外一个文件中。

2.2 Shell

一个shell是一个命令行解释器,它读取用户输入并执行命令。用户输入通常从终端中读入,有时候也从文件读入(叫做shell脚本)。我们在图1.2中对使用的shell进行总结:

具体执行哪个shell由passwd文件中最后一个字段指定。

3. 文件和文件夹

3.1 文件系统

UNIX文件系统是文件和文件夹的一个分层布置。文件夹也是一个文件,它包含了文件夹入口。我们可以将每个文件夹入口想象成一个文件名和描述文件属性的结构体的组合。文件的属性包括文件类型(文件,文件夹),文件大小,文件拥有者,文件的访问级别(其他用户是不是可以访问这个文件),还有文件的最后修改时间。我们使用stat和fstat函数来返回包含所有文件属性信息的结构体。

3.2 文件名

只有两个字符不能出现在文件名中,反斜杠“/”和null字符。因为反斜杠用于分隔路径中的文件名,null字符用于终止路径名。

4. 输入和输出

4.1 文件描述符

文件描述符是一个非负整数,内核用它来标识进程访问的文件。

4.2 标准输入,标准输出,标准错误

按照惯例,当一个新的程序运行时,所有的shell都会打开三个描述符:标准输入,标准输出和标准错误。如果没有任何特殊操作,这三个描述符都会被连接到终端。大多数shell都提供了将三个描述符重定向到文件的功能。

4.3 无缓冲I/O

无缓冲I/O由函数open,read,write,lseek和close提供。这些函数同文件描述符一块工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "apue.h"
#define BUFFSIZE 4096
int
main(void)
{
int n;
char buf[BUFFSIZE];
while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)
if (write(STDOUT_FILENO, buf, n) != n)
err_sys("write error");
if (n < 0)
err_sys("read error");
exit(0);
}

4.4 标准I/O

标准I/O函数为不带缓冲的I/O函数提供了一个带缓冲的接口。使用标准I/O函数无需担心如何选取最佳的缓冲区大小。

我们最熟悉的标准I/O函数是printf。

用标准I/O将标准输入复制到标准输出:

1
2
3
4
5
6
7
8
9
10
11
12
#include "apue.h"
int
main(void)
{
int c;
while ((c = getc(stdin)) != EOF)
if (putc(c, stdout) == EOF)
err_sys("output error");
if (ferror(stdin))
err_sys("input error");
exit(0);
}

5. 程序和进程

5.1 程序

程序是一个存储在磁盘上某个目录中的可执行文件。内核使用exec函数,将程序读入内存,并执行程序。

5.2 进程和进程ID

程序的执行实例被成为进程(process)。UNIX系统确保每个进程都有一个唯一的数字标识符,称为进程ID。进程ID为非负整数。

5.3 进程控制

有三个用于进程控制的主要函数:fork,exec和waitpid(exec有七种变体,但经常把它们统称为exec函数)

5.4 线程和线程ID

多个控制线程可以使得某些问题解决起来更加容易,并能充分利用多处理器系统的并行行为。

一个进程内的所有线程共享同一地址空间、文件描述符、栈以及进程相关的属性。因为他们要访问同一存储区,因此各线程在访问共享数据时需要采取同步错误以避免不一致性。

线程也用ID标识,一个进程中的线程ID在另外一个进程中没有意义。

6. 出错处理

当UNIX系统函数出错时,通常会返回一个负值,而且整型变量errno通常被设置为具有特定信息的值。有些函数对于出错使用另外一种约定而不是返回负值。例如大多数返回对象指针的函数在出错时会返回一个null指针。

Linux支持多线程存取errno:

1
2
extern int *_ _errno_location(void);
#define errno (*_ _errno_location())

对于errno应当注意两条规则。第一条是:如果没有出错,其值不会被例程清除。第二条是:任何函数都不会将errno值设置为0.

6.1 出错恢复

可将在中定义的各种出错分成两类:致命性的和非致命性的。致命性的错误无法恢复。只能打印日志。对于非致命性的错误,有时可以较为妥善的进行处理。

7. 用户标识

7.1 用户ID

口令文件登录项中的用户ID是一个数值,它向系统标识各个不同的用户。

用户ID为0的用户为根用户(root)或超级用户(superuser)。在口令文件中,通常有一个登录项,其登录名为root,我们称这种用户的特权为超级用户特权。超级用户对系统具有自由的支配权。

7.2 组ID

口令文件登录项也包括用户的组ID,它是一个数值。在口令文件中有多个登录项具有相同的组ID。组被用于将若干用户集合到项目或部门中去。这种机制允许同组的各个成员之间共享资源(如文件)。

组文件通常是/etc/group。

8. 信号

信号用于通知进程发生了某种情况。进程有三种处理信号的方式。

  1. 忽略信号。
  2. 按系统默认方式处理。对于除数为0,系统默认方式是终止该进程。
  3. 提供一个函数,信号发生时调用该函数,这被称为捕捉该信号。

9. 时间值

历史上UNIX系统使用过两种不同的时间值。

  1. 日历时间。这个值是UTC时间(1970年1月1日 00:00:00)这个特定时间以来所经过的描述累计值。系统基本数据类型time_t用来保存这种时间值。
  2. 进程时间。也被称为CPU时间,用以度量进程使用的中央处理器资源。进程时间以始终滴答计算。系统基本数据类型clock_t保存这种时间值。

当度量一个进程的执行时间时,UNIX系统为一个进程维护了三个进程时间值:

  • 时钟时间,它是进程运行的时间总量,其值与系统中同时运行的进程数有关。
  • 用户CPU时间;它是执行用户指令所用的时间量。
  • 系统CPU时间。它是该进程执行内核程序所经历的时间。

可以用time命令来获取上述三个时间值。

10. 系统调用和库函数

所有操作系统都提供多种服务的入口点,由此程序向内核请求服务。各版本的UNIX实现都提供良好定义、数量有限、直接进入内核的入口点,这些入口点被称作系统调用。

库函数可能会调用一个或者多个内核的系统调用,但是它们并不是内核的入口点。

应用程序既可以调用库函数也可以调用内核函数。