《UNIX环境高级编程》第1章 UNIX基础知识

时间:2021-07-15 22:13:37

UNIX基础知识

1.1 引言

操作系统的作用:为它们所运行的程序提供服务。典型的服务包括:执行新程序、打开文件、读写文件、分配内存、获得时间等;

1.2 UNIX体系结构

严格意义上来讲,可将操作系统定义为一种软件,它控制计算机的硬件资源,提供程序运行环境,通常将这种软件称为内核(kernel),因为它相对较小,且位于环境核心。
《UNIX环境高级编程》第1章 UNIX基础知识
内核的接口被称为系统调用(system call);
公用函数库构建在系统调用之上;
应用程序既可以使用公用函数库,也可以使用系统调用;
shell是一个特殊的应用程序,它为运行其他应用程序提供了一个接口;
广义上来说,操作系统包括了内核和一些其他软件,这使得计算机能够发挥作用,并使计算机有自己的特性;这些软件包括:系统实用程序(system utility)、应用程序、shell、以及公用函数库等;例如,Linux是GNU操作系统实用的内核;“操作系统”这个词有双重含义(严格意义上的内核,广义上的内核及其他软件和库),但这样是可以被理解的(这样的叫法更简洁);

1.3 登录

1. 登录

用户在登录UNIX系统时,先键入登录名,然后键入口令(密码);系统将口令加密存储到一个文件中(叫做口令文件);
口令文件有7个字段构成,由“:”号分隔,最后两个字段表示“home”目录和shell程序;

2. shell

用户登录系统后,系统根据用户口令文件中的最后一个字段确定使用哪种shell程序,并运行在一个视窗系统中;
shell是一个命令行解释器,它读取用户输入,然后执行命令;
输入来自终端的shell叫做“交互式shell”;
输入来自文件的shell叫做“非交互式shell”;
linux不同的版本使用的默认shell不同;

1.4 文件和目录

1. 文件系统

UNIX文件系统是目录和文件的一种层次结构,所以东西的起点是称为根(root)的目录,这个目录的名字是一个字符“/”;
目录(directory)一个包含目录项的文件。可以认为每个目录项都包含一个文件名,同时还包含说明该文件属性的信息;(笔者注:目录也是文件,文件中包含的目录项就是各个子文件)

2. 文件名

目录中的各个名字称为文件名(filename)。只有/和空字符不能作为文件名,/用来分隔构成路径的各个文件名,空字符用来终止一个路径名;
创建一个新目录会自动创建两个文件名:. 和..,分别表示当前目录和上一级目录,在最高层次的根目录中,点和点点相同;

3. 路径名

用斜线分隔的一个或多个文件名组成的序列(也可以以斜线开头),组成路径名(pathname);
绝对路径:以斜线开头的路径叫做绝对路径(absolute pathname);
相对路径:以文件名开头的路径名(relative pathname);
/是一个特殊的绝对路径,它不包括任何文件名;


UNIX系统手册使用数字1-8将其分为8个部分,一些UNIX系统进一步用大写字母将各个大部分分解成若干个小部分;如ls(1)表示系统手册第一部分的ls项;fopen(3S)表示系统手册中的第3项S小项;

4. 工作目录

每个进程都有一个工作目录(working directory),有时称其为当前工作目录(current working directory)。所以相对路径都从工作目录开始解释。进程可以用chdir函数更改其工作目录;

5. 起始目录

登录时,工作目录设置为起始目录(home directory),该起始目录从口令文件中获得;

1.5 输入和输出

1. 文件描述符

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

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

每当运行一个新程序时,shell都为其打开3个文件描述符,即标准输入(standard input),标准输出(standard output),标准错误(standard error);
如果不做特殊处理,则这3个文件描述符都链接到终端。shell提供一种方法,使其中一个或3个文件描述符都能重定向到某个文件,例如:
ls > file.list 执行ls命令,使其标准输出重定向到file.list文件。
(笔者注:如何重定向标准错误?)
ls >file.list 2>&1
(2>1的意思是stdout>stderr,&的意思是同时也在屏幕上显示)

3. 不带缓冲的I/O

函数open、read、write、lseek以及close提供了不带缓冲的I/O。这些函数都使用文件描述符。
unistd.h提供了这些函数的原型,以及STDIN_FILENO和STDOUT_FILENO(代表0和1),这些是POSIX标准的一部分。

4. 标准I/O

标准I/O函数为那些不带缓冲的I/O函数提供了一个缓冲的接口。例如:
printf、scanf都在stdio.h中,其中也包括了stdin和stdout等I/O标准常量(代表0和1文件描述符);

1.6 程序和进程

1. 程序

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

2. 进程和进程ID

程序的执行实例被称为进程(process)。某些操作系统用任务(task)表示;
UNIX系统确保每个进程都有个一唯一的数字标识符,称为进程ID(process ID).它是一个非负整数。
使用函数pid_t getpid()可以得到当前的进程ID,pid_t是一个长整型;

3. 进程控制

有3个用于进程控制的主要函数:fork、exec和waitpid。(exec有7种变体)

4. 线程和线程ID

通常一个进程只有一个控制线程(thread)–某一时刻执行的一组机器指令。
与进程相同,线程也用ID标识。
控制线程的函数与控制进程的函数类似,但有另一套。
(将来连接到第12章 线程)

1.7 出错处理

当UNIX系统函数出错时,通常会返回一个负值,整型变量errno被设置为具有特定信息的值。
大多数返回对象指针的函数出错时会返回null指针。
文件errno.h中定义了errno以及可以赋予它的各种常量。这些常量都以E开头。

extern int errno;

C标准定义了两个函数,用于打印出错信息:

char *strerror(int errnum);//打印errnum(errno)的值,并返回此字符串;
void perror(const char *msg);//先输出msg指向的字符串,然后是一个冒号和一个空格,接着是errno值对应的出错消息,最后是一个换行符。

1.8 用户标识

1. 用户ID

口令文件登录项目中的用户ID(user ID)是一个数值,它向系统标识各个不同的用户。
用户ID为0的用户为根用户(root)或超级用户(superuser)。

2. 组ID

口令文件登录项也包括用户的组ID(group ID),它是一个数值。组ID也是由管理员在指定用户登录名时分配。一般来说,口令文件中有多个登录项具有相同的组ID(笔者注:口令文件中的每一个登录项对应一个用户登录信息)。组用来将多个用户集合到项目或部门中去。这种机制允许同组的各个成员之间共享资源(如文件)。
组文件将主名映射为数值的组ID。组文件通常为/etc/group。


使用数值的用户ID和数值的组ID设置权限是历史上形成的。
对于硬盘上的每个文件,文件系统都存储该文件所有者的用户ID和组ID。因为比较数值比比较ASCII字符更快。
程序可用getuid和getgid函数来获得UID和PID.

3. 附属组ID

除了在口令文件中对一个登录名指定一个组ID外,大多数UNIX系统还允许一个用户属于另外一些组。
登录时,读文件/etc/group,寻找列有该用户作为其成员的前16个记录项,就可以得到该用户附属组ID(supplementary group ID),大多数系统支持16个附属组。

1.9 信号

信号用于通知进程发生了某种情况。进程有三种处理信号的方式:
(1)忽略信号。
(2)按系统默认的方式处理。
(3)提供一个函数,信号发生时调用该函数,这被称为捕捉该信号。
很多情况都很产生信号。
中端键盘上有两种产生信号的方式,分别为中断键(interrupt key,通常是Delete或Ctrl+c)和退出键(quit key,通常是Ctrl+\),它们被用于中断当前运行的进程。
另一种是调用kill函数,在一个进程中调用kill可以向另外一个进程发送一个信号。条件是我们必须是那个进程的所有者或超级用户。


在程序中调用signal函数可以捕捉到发送给进程的信号,并再捕捉到信号后调用处理函数(相当于注册),例如:

static void sig_int(int);//our signal-catching function
//...
int main()
{
//...
if(signal(SIGINT,sig_int)==SIG_ERR)
err_sys("signal error.");
//...
}

1.10 时间值

历史上,UNIX使用过两种不同的时间值。
(1)日历时间。该值使用UTC(1970年1月1日 00:00:00)以来的秒数。该时间可用来记录文件最近一次修改的时间值等。系统基本类型time_t用于保存这种时间值。
(2)进程时间。也被称为CPU时间,用于度量进程使用的CPU资源。进程时间以时钟滴答计算。每秒钟曾经取50、60或100个时钟滴答。系统基本类型clock_t用于保存这种时间值。可以使用sysconf函数得到每秒的时钟嘀嗒数。


当度量一个进程的执行时间时,UNIX系统为一个进程维护了3个进程时间值:
*时钟时间;又称为墙上时钟时间(wall clock time),它是进程运行的时间总量,其值与系统中运行的进程数有关。
*用户CPU时间;执行用户指令所用的时间;
*系统CPU时间;为该进程执行内核程序所经历的时间。
使用time (1)命令可以得到这三个时间;

1.11 系统调用和库函数

系统调用接口总是在《UNIX程序员手册》的第2部分说明,是用C实现的,与具体系统如何调用一个系统调用的实现无关。(解释:系统调用与具体的硬件有关,linux的实现是通过0x80中断进入中断处理程序,0x80中断定义为系统调用,再由具体的系统调用表找到相应的系统调用程序,执行系统调用程序后返回到用户空间。)
UNIX所使用的技术是为每个系统调用在标准C库中设置一个具有相同名字的函数。用户进程用标准C库来调用这些函数,然后,函数又用系统所要求的技术调用相应的内核服务。从应用角度考虑,可将系统调用视为C函数。
在本书中,系统调用和库函数都以C函数形式出现,两者都为应用程序提供服务。
除了必须说明,我们将系统调用和库函数都称为函数(function)。