APUE-Learning-1

UNIX基础知识

主要介绍了基础的UNIX知识,帮助初学者在UNIX环境下“存活下来”

  • 标准系统函数的返回值,一般来解释如下

    • >= 0 -> 表示程序正常执行
    • < 0 -> 表示程序出错,返回的负数可以表征具体的错误码
  • 三个标准“文件描述符”

    • STDIN_FILENO = 0
    • STDOUT_FILENO = 1
    • STDERR_FILENO = 2
  • <unistd.h> : unix std -> 标准头文件,包含了很多UNIX系统服务的函数原型

    • 有很多unistd.h头文件,用户开发应该使用<unistd.h>,其具体指/usr/inlcude/unistd.h
  • 不带缓冲的I/O VS 标准I/O (带缓冲)

    • <unistd.h> -> 提供不带缓冲I/O的函数原型: read, write, lseek。使用时需要提供缓冲区大小参数
    • <stdio.h> -> 提供标准I/O的函数原型: scanf, printf。使用时无需担心最优缓冲区大小,因为开发人员无需提供缓冲区大小参数。此外,还定义了标准常量stdinstdout
  • 用于进程控制的三个主要函数

    • fork:返回值为0,表示处于子进程中;返回值大于0,表示处于父进程中,且返回值为子进程的PID;返回值小于0,fork出错
    • exec:有7个变体,统称exec函数
    • waitpid:
    • fork -> exec函数,创建新进程,执行新的程序
  • 头文件组织

    • /usr/include/linux, /usr/inlcude/asm* 下的头文件是与Linux内核一起分发的
    • /usr/include/sys/*.h, /usr/include/bits/*.h 下的头文件是与C库一起分发的
    • LC_ALL=C gcc -v -xc -E /dev/null 2>&1 | sed -ne '/search starts here/,/End of search list/p'用于输出gcc的头文件搜索路径
  • 时间相关

    • 日历时间:UNIX系统的基本时间服务计算自世界协调时间[1970年1月1日00:00:00]以来经过的秒数,以time_t类型表示,可使用time()获取
    • 进程时间[CPU时间]:用以度量进程使用的中央处理器资源,以时钟滴答计算,每秒钟多少个滴答是一种系统配置,使用clock_t类型表示

    UNIX系统为一个进程维护了3个时间值【进程时间】:

    • 时钟时间[real][墙上时间],进程运行的时间总量,与系统中同时运行的进程数有关

    • 用户CPU时间[user]:执行用户指令所用的时间总量

    • 系统CPU时间[sys]:程序执行内核服务的时间总量

    • POSIX扩展规范增加了对多个系统时钟的支持,通过clockid_t类型标识不同的POSIX系统时钟

      • CLOCK_REALTIME:类型于UNIX系统提供的[real],但是在系统支持高精度时间值的情况下,可以获得更高精度的时间值
      • CLOCK_MONOTONIC:不带负跳数的CLOCK_REALTIME
      • CLOCK_PROCESS_CPUTIME_ID:调用函数获取该时间的进程的CPU时间
        使用clock_gettime(clockid_t id, struct timespec *tsp)获取不同的系统时钟时间,其中 struct timespec采用秒+纳秒的方式来表示时间值

UNIX标准及实现

  • 相关的两个标准
    • ISO C
    • IEEE POSIX -> 不区分接口是系统调用还是库函数,统称为函数
    • 这些标准规范了符号标准的UNIX操作系统必须提供的接口。当然,操作系统要发挥作用,还要有其一般的通用功能。因此可以说,标准定义了任一实际系统的子集
    • 有些属于UNIX系统必须,但在符合POSIX标准的系统中是可选的功能
  • 限制
    • 幻数、常量的值,在不同系统下的值究竟为多少?具体分为如下两种类型:
      • 编译时限制(例如,短整型的最大值是多少?)可在头文件中定义
      • 运行时限制(例如,文件名最多能有多少个字符?),要求进程调用一个相关的函数获取具体的限制值
      • 然而有些限制在一个给定的UNIX实现中可能是固定的,而在另一个UNIX实现中则可能是变动的
    • 为了解决这类问题,提供了以下三种限制:
      • 编译时限制(头文件)
      • 与文件或目录无关的运行时限制(sysconf函数)
      • 与文件或目录有关的运行时限制(pathconffpathconf函数)
      • 如果一个运行时限制在一个给定的UNIX实现上并不改变,则可将其静态地定义在一个头文件中。如果没有将其定义在头文件中,应用程序就必须调用上述的三个conf函数之一以确定其运行时值
    • ISO C 标准的所有限制都在<limits.h>文件中

文件IO

  • Unbuffered I/O: “不带缓冲”指每个read/write都调用内核中的一个系统调用[实际还是会使用内核的缓冲区的]。它们不是ISO C标准,而是POSIX.I的组成部分

    • open(), 除非指定O_APPENDflag,否则打开文件时的偏移量都为0,返回值为文件描述符
    • lseek(), 显式地为一个已经打开的文件设置偏移量。返回值为添加之后新的文件偏移量。它仅将当前的文件偏移量记录在内核,不会引起任何I/O操作
      • 相对文件开始的偏移量,只能为+
      • 相对当前值的偏移量,+-均可
      • 相对文件结尾的偏移量,+-均可
    • read(), 返回读到的字节数
    • write(), 返回实际写入的字节数
  • 多个进程间如何共享文件?【涉及到多进程间资源共享】

    • 文件描述符表,独属于某个进程,每个描述符表项,包含一个指向某文件表项的指针
    • 打开文件表,由内核维护。每个文件表项包含: 文件状态,当前文件偏移量,指向i节点的指针。
    • 文件系统的i节点表,由内核维护。每个打开文件都唯一对应一个i-node节点
    • 如果有两个进程打开同一个文件:则该文件会有两个文件表项分别对应每个进程;该文件在这两个进程中分别有一个文件描述符;但是该文件只有一个i-node
  • 原子操作

    • write() 是原子操作的吗?两个进程对该文件同时进行写,如果不加互斥,会出问题吗?
    • pwrite() 应该是原子的?
  • 文件高级操作

    • dup(int fd), dup2(int fd, int fd2)
      • dup() 返回的新描述符一定是当前可用描述符中最小的
      • dup2() 可由用户指定fd2为新的描述符;如果fd2已经打开,则先关闭fd2
      • 这两个函数返回的新描述符与原描述符共享打开文件表项
    • sync(), fsync(), fdatasync()
      • UNIX系统内核中设有缓冲区,大多数磁盘I/O都通过缓冲区进行。例如,当写文件时,内核通常先将数据复制到缓冲区,然后排入队列,晚些时候再写入磁盘,
        这种方式称为“延迟写”。
      • 提供了上述3个函数以保证磁盘上实际文件内容与缓冲区中内容一致
      • sync(),将修改过的块缓冲区排入队列,返回。不等待磁盘写操作
      • fsync(int fd),只对由fd指定的文件起作用,且等待实际磁盘I/O操作完成
      • fdatasync(int fd),与fsync类型,但是只影响文件数据部分
    • fcntl(int fd, int cmd, /* int arg */)
      • 根据cmd的不同,可以提供5大类的功能
    • ioctl()

APUE-Learning-1
http://example.com/2024/07/10/APUE-Learning-1/
作者
Cyokeo
发布于
2024年7月10日
许可协议