本章对一些术语进行简要说明并给出实例。

文件和目录

stat和fstat函数返回包含所有文件属性的一个信息结构。第四章将详细说明文件的各种属性。
目录项的逻辑视图与实际上的磁盘存放方式是不同的。UNIX文件系统大多数实现并不在目录项中存放属性,这是权衡于硬连接带来的一些问题。第四章将讨论。

文件名

只有斜线和空字符不能出现在文件名中。斜线用来分割路径名,空字符终止一个路径名。
opendir函数与readdir函数,包含于dirent.h中,并且包含对dirent结构的定义。
opendir函数返回只想DIR结构的指针。将指针传送给readdir函数,使用循环读取每个目录项,返回指向dirent结构的指针,如果没有了则返回null指针。此目录项顺序非首字母排序。

工作目录

working directory,或current working directory。进程可以使用chdir函数更改工作目录。
登陆时cwd设置为起始目录home directory。该目录从口令文件(通常是/etc/passwd)中相应用户的登录项中取得。

输入输出

文件描述符

file descriptor,通常是一个小的非负整数,内核用以标识一个特定进程正在访问的文件。当内核打开一个现有文件或创建一个新文件时,都将返回一个文件描述符。读写文件时可以使用这个文件描述符。

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

运行一个新程序时shell都将打开3个文件描述符,即以上三者。如果没有特殊处理,则他们都链接向终端。shell可以提供一种方法将它们重新定向到某个文件,例如:

1
ls > file.list

这个就是将标准输出重新定向到file.list文件。

不带缓冲IO

open、read、write、lseek和close函数提供了不带缓冲的IO,这些函数都使用文件描述符。
例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <unistd.h>
#include <stdio.h>

#define BUFFSIZE 4096

int main() {
int n;
char buf[BUFFSIZE];

while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0) {
if (write(STDOUT_FILENO, buf, n) != 0) {
printf("write err.\n");
}
}

if (n < 0) {
printf("read err.\n");
}

return 0;
}

2a3a2a62-496f-4ec1-aa03-8f845a10b7cc
972dcd85-4da0-4dd3-aa75-c927dba522be
3c7743ed-bfc7-4d74-8d2d-06c91a084656
ab90f909-12e1-4553-a724-80093e3c82a6
98d66bb2-f0a2-407a-957a-7969be802de2

标准IO

7767cfdd-82b9-450b-8a24-5fc2f520b5eb
例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int main() {
int c;

while ((c = getc(stdin)) != EOF) {
if (putc(c, stdout) == EOF) {
printf("output error\n");
}
}

if (ferror(stdin)) {
printf("input error\n");
}

return 0;
}

57e761af-c659-4395-981f-44ce80639477

程序和进程

程序

程序是个文件,内核使用7个exec函数中的一个将其读入内存并执行。

进程和进程ID

程序运行实例被称为进程,不多说了。
UNIX系统确保每个进程都有一个唯一的数字成为process ID,总是一个非负整数。
使用getpid函数返回一个pid_t类型数据,按照标准它被存在一个long中。

程序控制

fork、exec(包含7个变体)和waitpid函数是用于进程控制的三个主要函数。
例子:
298aad17-b163-4e22-bb58-6d1c9d80617e
0192be7f-2ad0-4f4f-8525-2d6f7acaf1ab
9fc4e6d1-13c3-45bd-8bd1-10ee3900ebfc

线程和线程ID

线程是对某个特定进程而言的,因此同一进程内的所有线程共享同一地址空间、文件描述符、栈以及与进程相关的属性。
线程也用ID标识,只是只在所属的进程内起作用。

出错处理

UNIX系统函数出错时通常返回一个负值,而且整型变量errno通常被设置为具有特定信息的值。有的函数例如大多数返回指向对象指针的函数在出错时会返回null指针。
errno.h中定义了errno以及各种常量,这些常量都以E开头。
每个线程有属于自己的局部errno以避免相互干扰。
f0892613-83ad-453d-ac77-b36e3ebeb626
注意:

  1. 没有出错,其值不会被清除,因此进党函数返回出错才检验其值。
  2. 任何函数不会将errno设置为0,并且errno.h中的所有常量都非0。

262d79da-b668-4103-9faf-fa989dec407c
例子:
bf420ba4-7e0c-4f42-9cac-7c64a7dc2d0c
b70ed604-d1f3-4b9a-b72d-d6f59b336daf

出错恢复

可以将errno.h中的出错分成两类:

  1. 致命性:无法执行恢复,最多只能输出一条出错,然后quit。
  2. 非致命性:可以较妥善地处理。

f9b1a473-f943-4006-be74-4a4bf6aa4fc2

用户标识

用户ID

user ID是一个数值,用于向系统标识各个不同的用户。用户不能更改ID。
0为root。管理员在确定一个用户登陆名的同时,user ID也就确定了。

组ID

group ID也是在指定用户登录名时分配的。
组文件将组名映射为数值的组ID。通常是/etc/group
现今UNIX系统使用32位整型表示user ID和group ID。
例子:
7676a221-7334-4b59-82d9-73dc023b57be

附属组ID

52e9289c-c65b-4a8a-a61c-2ae7cfd774ad

信号

signal(信号)用于通知进程。例如除数为0时进程将收到浮点异常SIGFPE
三种处理方式:

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

终端键盘有两种产生信号的方法:interrupt key也就是中断键通常是Delete^Cquit key也就是退出键通常是^\,用于中断当前运行的进程。
调用kill函数可以向另一个进程发送一个信号。前提是我们必须是那个进程的所有者或者是超级用户。
例子:
279a13a5-b782-4925-8d0a-35069cbfbd18
d2803344-aa8e-4b62-a6ae-5523ec3cf901

时间值

  1. 日历时间:从协调世界时(Coordinated Universal Time, UTC)1970年1月1日00:00:00 开始到现在的秒数。系统基本数据类型time_t用于保存这种时间值。
  2. 进程时间:也被称为CPU时间,用以度量进程使用的CPU资源。其以时钟滴答计算。每秒钟曾经取为50、60或100个时钟滴答。使用clock_t保存这种时间值。

UNIX系统为每个进程维护了3个时间值:

  • 时钟时间
  • 用户CPU时间
  • 系统CPU时间

时钟时间(wall clock time),是进程运行时间总量。
用户CPU时间是执行用户指令所用的时间量。同理系统CPU时间就是执行内核程序的时间。两者之和被称为CPU时间。
使用time命令获取要执行的命令的三个时间。

系统调用和库函数

Linux 3.2.0 提供了380个系统调用,FreeBSD 8.0 提供的系统调用超过 450 个。
UNIX为每个系统调用在标准C库中设置一个具有同样名字的函数。从应用角度考虑,可将系统调用视为 C 函数(我怎么感觉说反了)。
多的不讲,一张图:
0d844be7-e35e-4273-a3f2-52fd7a3a092c

⬆︎TOP