UP | HOME

基础知识

Table of Contents

手册页

Unix 参考手册页是进行系统编程必备的参考工具。它通常通过 man 命令直接联机阅读。手册页通常分为 9 节。采用 名称(章节号) 的方式来描述 Unix 术语时,表示此名称同时也是联机手册对应章节号下的一个条目

$ man 2 fork #查看系统调用库函数fork

章节

以 Linux 为例,参考手册页各节内容如下:

  1. 系统命令 手册:如 cp(1)
  2. 系统调用 手册:如 read(2)
  3. 库函数 手册:如 malloc(3)
  4. 系统特殊文件 手册:如 tty(4)
  5. 标准的系统资源文件 ( 全局配置 文件)手册:如 services(5)
  6. 游戏程序手册 ,包括屏保等各种图形化娱乐工具:如 glmatrix(6)
  7. 杂类手册 ,包括各类 头文件 :如 socket.h(7)
  8. 系统管理 工具:如 useradd(8)
  9. 内核例程

系统调用与库函数

  • 系统调用 :提供了 用户程序访问内核的接口 ,用户程序通过执行系统调用间接的 获取内核所管理资源的访问权 。执行系统调用时,程序 控制权交给内核 ,内核在 进程上下文 中运行
  • 库函数 :通过 包含相应的.h 头文件 引用,用户程序可以通过 调用操作系统提供的库函数 间接进行 系统调用 。如 printf(3) 库函数,它除了管理缓冲区和格式化文本之外,执行了 write 系统调用 访问文件
    库函数也可以由其它程序提供,例如 GTK 库等
    用户也可以自己重新实现库函数接口以替代所提供的库函数功能

    系统调用实际上也以库函数的形式直接提供给用户程序,如 write(2)、fork(2)等
    对用户程序来说,不必专门区别那些是库函数、哪些是系统调用接口

对于 x86 平台的 Linux 可以通过 /usr/include/asm/unistd _ 32.h 查看其系统调用的接口

文件和目录

Unix 系统将计算机中的大部分 资源都抽象为文件 的形式,这样就可以通过 open(2) , read(2)write(2) 等函数去直接访问

  • 文件系统的结构:Unix 系统采用 树状结构 ,全局具有唯一的一个根目录,通过 / 访问,除交换分区外,其它文件系统均挂载在/或其子目录下作为子目录存在
  • 文件名:除字符 '/' 与 'NUL' 外,都可以作为 Unix 文件的文件名,但为访问方便起见,一般建议采用可打印字符作为文件名,尽量不要使用 shell 和其它正则表达式的扩展字符,如 * 、? 等
  • 路径名:访问一个文件可使用 绝对路径 (以 / 开头)或者 相对路径 (不以 / 起头的均被认作相对路径)
  • 目录中的特殊文件 .../ 下的 ... 指向它自身。可以使用 shell 命令 ls ­ai 验证这两个特殊文件的用法
  • 工作目录:每个进程都有一个 当前工作目录 ,用途为 解释相对路径
  • 起始目录(home directory): 用户登录后的初始目录 ,由文件 /etc/passwd 指定

输入和输出

  • 文件描述符 (file descriptor):用以 标识引用一个打开的文件
    • 文件描述符是 进程的属性
    • 进程不同值相同的文件描述符 引用的可以是 不同的文件
  • 标准输入标准输出标准出错进程创建自动打开 的三个文件描述符
    • 默认为 012
    • 一般通过用宏 STDIN _ FILENOSTDOUT _ FILENOSTDERR _ FILENO 来引用
  • 不带缓冲 的 I/O: 标准系统调用 open(2) 、read(2) 、write(2) 、lseek(2) 、close(2) 不使用缓冲区,直接把输入/输出送到内核,这里的缓冲位于进程地址空间
  • 标准 I/O: C 函数库中的标准 I/O 函数 (定义在 stdio.h (7))使用缓冲区进行输入/输出、以提高吞吐效率
使用标准 C 函数的 IO 函数时候必须注意:
I/O 重定向
fork(2) 导致复制地址空间等情况下

标准 I/O 函数对缓冲区的使用(是否刷新)

程序与进程

  • 程序 是一个静态的概念,指 存放在存储介质上可被内核通过 exec(3) 系统调用 读入并执行其中指令的文件
    • 可能是 二进制 也可能是 文本 形式
    • Unix 上的程序需要 执行权限
  • 进程执行中的程序 ,除了程序本身的 代码文本数据 外,进程还包括其它自身 拥有的资源 ,诸如进程 id(PID)、打开的文件、地址空间、未决信号集、控制线程等

程序线程

对一个用户进程, 内核实际执行调度处理的是它的执行线程 。一个进程可以拥有 一个或多个执行线程 ,对进程来说这些线程是并发执行的。进程内的多个线程共享进程资源

异步、并发、通信

  • 异步:对 系统资源不可预测的 时间顺序 执行 的操作。例如等待外部输入、监听 TCP 端口等都是异步操作
    • 对于异步操作,需要设计 阻塞等待超时 等机制,一般都是基于 信号机制 实现
  • 并发: 多个 并行的异步 进程/线程 同时在 同一个资源 上会合 时,产生并发。这时由于各自的异步性,读写等 I/O 操作可能交错发生乱序,从而出现意外的结果。为使它们的操作被强制的顺序化,需要进行同步处理
    • 同步处理属于广义上的通信的范畴,有多种同步方式,一般采用 锁机制
    • 同步处理还需要注意竞争条件出现的 死锁 。这是 Unix 系统编程中较为复杂、容易出现 bug 的地方
  • 通信:通过某种机制,在 不同实体间交换数据 。对 Unix 编程来说发送/接收、通知/响应、输入/输出等都属于比较广义的通信的范畴。狭义点的说法就指数据的发送/接收,Unix编程的主要通信方式为 进程间通信线程通信
    • 广义的进程间通信包括了
      • 信号管道IPC 对象套接字
      • 条件变量取消 等操作可以用作线程间的通信(通知)
      • 建议性锁全局变量 也是一种通信机制
    • 不同应用场合的通信使用不同的机制和做法

出错处理

Unix 系统一般通过设置 全局变量 errno(3) 标识程序执行时发生的错误

     在系统所提供的库函数中,除非再次发生错误,否则不会改动 errno 的值也不会将其清零

errno 可以通过下面两个函数转换为对应的错误消息字符串:

  • 指定的 errno 错误号转换为字符串并返回这个字符串的指针
#include <string.h>
char *strerror(int errnum);
  • 执行一个格式化输出,指定的字符串以及当前的 errno 值对应的字符串

    #include <stdio.h>
    void perror(const char *msg);
    

相当于

printf ("%s: %s", msg, strerror(errno));

用户和用户标识

用户

正常下, UID 唯一标识了一个用户

  • 系统的用户信息定义在 /etc/passwd (passwd(5))
  • 用户的 创建修改删除 通常由系统管理员通过 useradd(1)userdel(1)usermod(1) 等系统命令执行,而不应直接修改/etc/passwd 文件
  • 超级用户 (root)的 UID 为 0

将一个或多个用户组织起来 ,以使它们能以指定的相同权限访问资源。正常下,GID 唯一标识了一个组

  • GID 定义在 /etc/group (group(5))
  • 组的创建、修改、删除通常由系统管理员通过 groupadd(1)、groupdel(1)、groupmod(1)等系统命令执行,而不应直接修改/etc/group 文件
  • 用户可以同时加入多个组,命令 groups(1) 可以打印出用户所在的组,第一个以外的均为 附加组

信号

信号是 软件中断机制 ,作为系统 某种事件发生的通知

  • 进程可以通过调用 信号函数 ,在信号递送到进程时选择对其的处理方式
  • 进程还可以选择在 信号递送之前屏蔽之 ,使之成为 未决信号延后处理
  • 进程 捕捉到信号时将被中断,直到 信号处理函数返回 时方恢复执行

信号处理

  1. 忽略(丢弃)
  2. 按系统的默认方式处理
  3. 使用自定义信号捕捉函数来处理

系统时间

Unix系统使用两种方式标识时间

  • 日历 时间:指自 UTC 时间 1970 年 1 月 1 日 00:00:00 以来流逝的秒数,这个时间以 time _ t 类型保存
time_t 是一个 32 位整型数。在 2038 年 1 月 18 日将发生溢出错误

例如,在 Unix 系统上无法创建一个时间为 2038 年 1 月以后的文件(touch ­t 命令)
  • 进程 时间:用以衡量进程资源耗时情况

    • 实际时钟时间(rtime) : 一个进程执行开始至今的全部时间
    • 用户 CPU 时间(utime) : 程序在用户地址空间中执行的时间
    • 系统 CPU 时间(stime) : 程序执行系统调用后在内核空间中执行的时间
          如果进程执行了 sleep(3) 等调用,睡眠的时间不计算为 CPU 时间,但计入实际时钟时间
    

    Next:标准和实现

    Home:目录