UNIX环境高级编程 - UNIX基础知识

所有操作系统都为运行在它之上的程序提供各种服务,典型的服务包括:执行新程序、打开文件、读写文件、分配存储空间、提供时间等。

UNIX体系结构

严格来说,操作系统是一种软件,它控制计算机硬件资源,提供程序运行环境。这种软件有个专业术语名称:内核。因为它小且位于计算机体系的核心。如下图所示:

img

内核的接口为系统调用,系统调用包裹在内核的外围,隔离开内核以保护内核。同时,系统调用作为和内核沟通的中间桥梁。

  1. 公用函数库通常指的是C/C++的标准库,例如libc、glibc、libstdc++、libc++等标准库,不同的标准库是由于不同的系统平台或编译器厂商造成的差异,但追根究底都是使用了底层系统内核的API接口系统调用。这些公用函数库极大的方便了上层开发者的使用,也是整个计算机软件体系(无论任何编程语言)的基础。

  2. shell也是一个软件,该软件负责人机交互,用户和shell进行“对话”,然后shell理解用户的意图来使计算机按用户的想法工作。

  3. 用户程序也可以直接使用系统调用来请求服务。

对于打印hello world于终端中,对于上面三种请求服务的方式,有:

  1. printf("hello world");
  2. echo 'hello world'
  3. 直接使用write()

登录

用户名和shell由配置文件/etc/passwd决定,密码由配置文件/etc/shadow决定。

口令文件中的登录项由 : 分隔的7个字段组成:

  1. 登录名
  2. 加密口令
  3. 用户ID(数字)
  4. 组ID(数字)
  5. 注释字段
  6. 起始目录
  7. shell程序

例如,我电脑Mac OSX中就有:

root:*:0:0:System Administrator:/var/root:/bin/sh

文件系统

UNIX文件系统是目录和文件的一种层次结构。所有东西的起点称为根目录,即“/”。

在文件系统中,目录本质上也是一个文件,其内容是目录项的记录。每个目录项都是一个文件名,还包含一些文件属性的说明信息,比如权限、大小、时间等。

创建新目录时文件系统会自动创建两个文件夹:.(点)和 ..(点点), .(点)表示当前目录,根目录“/”下的.(点)和 ..(点点)是同一个路径,都是“/”。

由斜线“/”开头的路径都是绝对路径,反之则是相对路径。

ls(1)

ls(1)指的是第一部分的ls项目,UNIX命令通常都会有一个说明手册,手册中对命令有详细说明,但随着命令功能和说明的增加,说明手册页数越来越多,之后便对命令进行了分门别类,通常是1-8总共8中分类,具体是:

1、Standard commands (标准命令)
2、System calls (系统调用)
3、Library functions (库函数)
4、Special devices (设备说明)
5、File formats (文件格式)
6、Games and toys (游戏和娱乐)
7、Miscellaneous (杂项)
8、Administrative Commands (管理员命令)

工作目录:每个进程都有一个工作目录,一般称为当前工作目录,进程可以用chdir来更改其工作目录。

起始目录:登录时,shell程序的工作目录设置为home目录,该目录通常从/etc/passwd配置文件中获得。

输入和输出

在UNIX系统中输入和输出是经过抽象的,所有的输入和输出底层系统实现都是通过文件抽象来完成的。

文件描述符:文字描述符是一个小的非负整数,内核用以标识一个特定进程正在存访的文件。当内核打 开一个现存文件或创建一个新文件时,它就返回一个文件描述符。当读、写文件时,就可使 用它。

每个程序都有默认打开的三个文件描述符:

  1. 标准输入 - fd为0
  2. 标准输出 - fd为1
  3. 标准出错 - fd为2

不带缓存的I/O:系统调用openreadwritelseekclose,它们都使用文件描述符来操作文件,并且不带缓冲。

标准I/O:公用函数库提供的封装过的接口来间接调用系统调用,公用函数库提供的接口是带缓冲的,且无需考虑对缓冲区大小的选择。例如使用printf输出。

程序和进程

程序是静态的进程,而进程是运行着的程序。程序本质上是一个存在硬盘上的可执行文件。程序被加载到内存中之后就开始执行,此时程序变成一个动态刻画抽象的进程。

image-20210907223526668

进程ID:每一个进程都有一个标识符,称为进程ID,其是一个非负数,且在当前时刻是唯一的。

进程控制:有3个可以用于控制进程的系统调用:fork、exec和waitpid。其中exec是一系列函数的统称。


线程和线程ID:

一个进程内的所有线程共享当前进程的所有内存空间、文件描述符号、栈以及进程相关的属性。由于所有进程共享进程的内存空间,因此在访问共享数据时需要采取同步措施以避免数据的不一致。

同进程类似,线程也有一个ID唯一标识每一个线程,但线程的ID只在进程内部有效,进程外部则无意义。

出错处理

当 UNIX 系统函数出错时,通常会返回一个负值,同时整型变量 errno 通常被设置为具有特定信息的值。

  • 文件 <errno.h> 定义了 errno 以及赋予它的各种常量,这些常量以 E 字符开头
  • 在多线程环境中,每个线程都有属于自己的局部 errno,以避免一个线程干扰另一个线程。

对于 errno 的使用要注意两条规则:

  • 如果没有出错,则 errno 的值不会被清除.因此只有在函数的返回值指明出错了时,检查 errno 才有意义
  • 任何函数都不会将 errno 的值清零,且在 <errno.h> 中定义的所有常量都不为0

strerror/perror函数:用于处理错误信息

#include<string.h>
char *strerror(int errnum);

#include<stdio.h>
void perror(const char*msg);
  • strerror: 将 errnum(通常就是 errno 值)映射为一个出错消息字符串,并且返回此字符串的指针
    • 参数: 一个整数(通常是 errno 的值)
    • 返回: 出错消息字符串的指针
  • perror: 基于 errno 的当前值,在标准错误上产生一条出错消息,然后返回。这条出错消息首先是 msg 指向的字符串,后面是冒号,后面是一个空格,后面是对应于 errno 值的出错消息,最后是一个换行符。
    • 参数:附加的出错消息
    • 返回:无返回。但是向标准错误上输出一条出错消息。

用户标识

用户ID:用户标识也是通过ID来进行区分的,该ID称为用户ID,它是一个数字。当一个用户创建时,会在/etc/passwd文件中生成唯一的用户ID,用户不能更改这个ID,除非是root用户才允许修改。ID号码为0的用户是root用户。

组ID:用户除了用ID来进行区分,也用组来进行划分管理,相应的,其也有组ID,也是一个数字。用户的组用户ID不唯一,一个用户可以拥有多个组ID,这表明该用户加入了多个小组。组的目的是为了让多个用户共享一个资源。组相关的配置文件是/etc/group。

可以通过 getuid( )getgid( ) 函数来获得相应的用户ID和组ID。

信号

信号是UNIX系统用于发送通知的一种机制,例如,若某一进程执行除法操作,其除数为0,则将名为SIGFPE的信号发送给该进程。进程收到信号通知后,有3种应对处理方法:

  1. 忽略信号。收到之后什么也不做,当做未发生一样。

  2. 按系统默认方式处理。对于除0,系统默认方式是终止该进程。

  3. 提供一个处理函数。在收到信号之后,用提供的函数来进行处理。

使用Kill -9 杀死进程

ctrl + c 中断进程

ctrl + / 退出进程

时间值

UNIX 系统使用两种时间:

  • 日历时间:自 UTC 1970年1月1日 00:00:00 以来经历过的秒数累计值。用 time_t数据类型来保存这种时间值。
  • 进程时间:也称作CPU时间,用于度量进程使用的CPU资源。进程时间以时间滴答来计算,用clock_t数据类型保存这种时间值。

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

  • 时钟时间: 又称作墙上时钟时间,是进程运行的时间总量,其值与系统中同时运行的进程数有关
  • 用户CPU时间:执行用户指令所用的时间量
  • 系统CPU时间:该进程执行内核程序所经历的时间。如进程执行一个read系统调用,则内核执行该系统调用的时间计入系统 CPU 时间

用户CPU时间和系统CPU时间之和称作 CPU 时间

image-20210907223526668

系统调用和库函数

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

系统调用接口在man手册的第二部分中说明,是使用C语言定义的。

公用函数库接口在man手册的第三部分中说明,也是使用C语言定义的。它们不一定是内核的入口点,部分会间接使用一个或多个内核系统调用,而有些则完全不使用。

从实现角度看,系统调用和公用函数库有着本质区别,一个是伴随内核而产生的,是不可替换的。另一个是编译器厂商根据语言标准而实现的,可以更新和替换。但从用户角度看,它们没有太大区别,显著的区别是公用函数库更好用,功能更加强。

image-20211103164724882 image-20211103164724882
posted @ 2021-11-03 16:52  zju_cxl  阅读(296)  评论(0编辑  收藏  举报