APUE —— UNIX 基础知识

UNIX 体系结构

从严格意义上说,可以将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境。我们将这种软件称为内核(kernel)

内核接口被称为系统调用(system call)。公共函数库构建在系统调用接口之上,应用程序可以调用公共函数库,也可以调用系统函数。shell是一个特殊的应用程序,为运行其他应用程序提供了一个接口。

LinuxGUN操作系统使用的内核,所以Linux一般也被称之为GUN/Linux


登录

1、登录名

用户在登录UNIX系统时,先输入登录名,再输入密码。系统在其口令文件中查看登录名,通常是/etc/passwd

口令文件中的登录项由7个以冒号分隔的字段组成,一次为:登录名、加密口令、用户ID、所属组ID、注释字段,家目录以及shell程序。

目前所有的系统已将加密口令移动到另外一个文件中去了(/etc/shadow)。

2、shell

shell是一个命令行解释器, 他读取用户输入,然后执行命令。shell的用户输入通常来自于终端(交互式shell),有时来自于文件(shell脚本)。

名称 路径
Bourne shell /bin/sh
Bourne-agina shell /bin/bash
C shell /bin/csh
Korn shell /bin/ksh
TENEX C shell /bin/tcsh

Bourne-agina shellGUN shell, 所有Linux系统都提供这种shell。设计遵循POSIX标准。兼容Bourne shell, 并且支持C shellKorn shell的特性。

POSIX 1003.2标准对shell进行了标准化。这项规范基于Bourne shellKorn shell


文件和目录

1、 文件系统

UNIX文件系统是目录和文件的一种层次结构,所有东西的起点是称为根(root)的目录,这个目录名称的服务是\

目录是一个包含目录项的文件。在逻辑上,可以认为每个目录项都包含一个文件名,同时还包含说明该文件属性的信息。文件属性是指文件类型(普通文件还是目录等)、文件大小、文件所有者、文件权限以及文件最后修改时间等。statfstat函数返回包含所有文件属性的一个信息结构。

2、文件名

目录中的各个名字称之为文件名。只有\和空字符串者个字符不能出现在文件名中。\用来分隔构成路径的文件名,空字符串用来终止一个路径名。

POSIX.1推荐奖文件名限制在一下字符集内:a-zA-Z0-9.-_

创建目录时会自动创建...2个文件,分别指向当前目录和父目录。在最高层次中...相同。

3、 路径名

由斜线分隔的一个或多个文件名组成的序列构成路径名,以斜线开头的路径名称为据对路径名,反之为相对路径名。

示例:简单实现ls功能。

注:
#include "apue.h"中的apue.h来自于官网http://www.apuebook.com/,更具自己书的版本下载相应的源码。然后进行make操作,就会出现apue.h文件。
编译时候报错,请参考:https://kaiiak.github.io/2016/01/28/%E7%BC%96%E8%AF%91apue-3e%E5%B9%B6%E8%A7%A3%E5%86%B3%E7%BC%96%E8%AF%91%E9%94%99%E8%AF%AF/

之后进行编译运行即可,如下。

4、 工作目录

每个进程都有一个工作目录。所有相对路径名都是从工作目录开始解释。进程可以使用chdir函数更改工作目录。

5、 家目录

登录时,工作目录设置为家目录,该家目录从口令文件中相对应用户的登录项中获取。


输入和输出

1、 文件描述符

文件描述符file descriptor通常是一个小的非负整数,内核用以表示一个特定进程正在访问的文件。

2、标准输入、标准输出和标准错误

每当运行一个新的程序时,shell都会为其打开3个文件描述符,即标准输入标准输出标准错误。如果不做特殊处理,这3个描述符都是连接终端。

3、不带缓冲的I/O

函数openreadwritelseek以及close提供了不带缓冲的I/O。这些函数都是用文件描述符。

实例:从标准输入读,并向标准输出写。

头文件(包含在apue.h中了)以常量STDIN_FILENOSTDOUT_FILENOPOSIX标准的一部分。

STDIN_FILENOSTDOUT_FILENO包含在中,在POSIX标准中,他们的值分别是0和1。

4、标准I/O

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

实例:从标准输入读,并向标准输出写。

标准I/O常量stdinstdout被定义在中。


程序和进程

1、 程序

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

2、进程和进程ID

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

实例:获取进程ID

程序调用getpid函数获取到进程ID。

3、 进程控制

有3个主要用于进程控制的主要函数:forkexecwaitpid

实例:从标准输入读取命令,然后执行这些命令。

4、 线程和线程ID

一个进程只有一个控制线程thread——某一个时刻执行的一组机器指令。

一个进程内的所有线程共享同一地址空间、文件描述符、栈以及与进程相关的属性。所以各线程在访问共享数据时需要采取同步措施以避免不一致性。

线程ID只在它所属的进程内起作用。一个进程中的线程ID在另外一个进程中没有意义。

线程模型实在进程模型建立很久以后才被引入到UNIX系统中的,然后这两种模型之间存在复杂的交互。


出错处理

UNIX系统函数出错时, 通常会返回一个负值。

文件中定义了errno以及可以赋予它的各种常量,这些常量以E开头。

POSIXISO Cerror定义为一个符号,它扩展成为一个可修改的整型左值lvalue。它可以是一个包含出错编号的整数,也可以是一个返回出错编号的指针函数。以前定义是:

但是在支持多线程的环境中,多个线程共享地址空间,每个线程都有属于自己的局部errno以避免一个线程干扰另一个线程。例如,Linux支持多线程存取errno,将其定义为:

对于errno应该注意两条规则。第一条,如果没有出错,其实不会被例程清除。第二条,任何函数都不会将errno值设置为0,而且在中定义的所有常量都不为0。

C标准定义了两个函数,他们用于打印错误信息。

实例: 使用上面2个粗错函数

出错恢复

可以将中定义的各种出错分成两类:致命性的和非致命性的。
对于致命性的错误,无法执行恢复动作。但是可以在用户屏幕上打印一条出错消息或者将错误消息写入日志。
对于非致命性出错,可以较妥善地进行处理。


用户标识

1、 用户ID
口令文件登录项中的用户ID是一个数值,他向系统表示各个不同的用户。
用户ID为0的用户为根用户。如果一个进程具有超级用户特权,则大多数文件权限检查都不再进行。

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

组文件将组名映射为数值的组ID。组文件通常是/etc/group

实例:打印用户的uidgid

3、附属组ID
除了在口令文件中登录名指定一个组ID外,大多数UNIX系统还允许一个用户属于另外一些组。


信号

信号signal用于通知进程发生了某种情况。列入,某一进程执行了除法操作,其除数为0,则将名为SIGFPE(浮点异常)的信号发送给进程。进程又一下3中处理信号的方式:
(1)忽略信号。有些型号表示硬件异常,例如,除0后者访问进程地址空间外的内存等,因为这些异常产生的后果不确定,所以不推荐使用这种处理方式。
(2)按系统默认方式处理。对于除以0,系统默认方式是终止进程。
(3)提供一个函数,信号发生时调用该函数,这个被称作为捕捉该信号。

很多情况都会产生信号。终端键盘有两种产生信号的方法,分别称为中断键DeleteCtrl+C)和退出键Chrl+\),他们被用于中断当前运行的进程。

另一种产生型号的方式是调用kill函数,在一个进程中调用此函数就可以向另一个进程发送一个信号。当向一个进程发送信号时,我们必须是那个进程的所有者活着是超级用户。

实例:注册捕捉信号

启动进程后,另一个总段查询到进程号,调用KILL命令

原终端输出 interrupt\n%%


时间值

UNIX系统使用过两种不同的时间值。
(1)日历时间。该值是自协调世界时(UTC) 1970年1月1日 00:00:00这个特定时间以来所经过的秒数累计值。几桶基本数据类型time_t
(2)进程时间。也被称作为CPU时间,用以度量进程使用的中央处理器资源。进程时间以时钟滴答计算,每秒曾经取为50、60或100个时钟滴答。系统基本数据类型clock_t

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

  • 时钟时间,它是进程运行时间的总量。
  • 用户CPU时间,是执行用户指令所用的时间量。
  • 系统CPU时间,是该进程执行内核程序所经历的时间。

用户CPU时间与系统CPU时间之和,被称作为CPU时间。

执行time命令就可以获取到任意进程的3个时间。


系统调用和库函数

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

从实现者的角度来看,系统调用和库函数之间有根本的区别,但从用户角度来看,其区别并不重要,系统调用和库函数都以C函数的形式出现,两者都为应用程序提供服务。

系统调用和库函数之间的差别:
(1)职责不同。系统调用内核态,库函数用户态。
(2)系统调用通常提供一种最小接口,而库函数通常提供比较复杂的功能。

进程控制系统调用(forkexecwait)通常由用户应用程序直接调用。但是为了简化某些常见的情况,UNIX系统也提供一些库函数,如systempopen