unix环境高级编程(上)-文件篇

时间:2022-07-26
本文章向大家介绍unix环境高级编程(上)-文件篇,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

目录

前言

unix基础知识

unix标准化和实现

unix提供的文件IO

文件和目录

标准IO

系统数据文件

前言

笔者将《unix环境高级编程》主要内容总结为三篇:文件篇进程篇高级io和进程间通信三大板块。本文是unix环境高级编程系列文章第一篇:文件篇。该篇主要包括:

unix基础知识

介绍了unix的体系结构,以及unix中的文件和目录,输入输出,程序和进程,信号等基本概念

unix标准与实现

标准包括C语言的标准和操作系统标准,实现包括BSD,FreeBSD,Linux,Solari,Mac os等

unix内核提供的文件io函数

包括文件描述符,对文件的打开,关闭,定位,读,写,改变文件属性操作。内核IO调用基于文件描述符。还介绍了文件的底层数据结构,了解数据结构之后就能理解文件是如何支持共享的

文件和目录

主要介绍文件的属性和属性对应的数据结构,以及各个字段控制的问访问权限,文件类型等。unix中一切皆文件,这些文件包括:普通文件,目录文件,块特殊文件,字符特殊文件,FIFO,套接字和符号链接。最后结束UFS文件系统。

标准IO函数

标准io解决了内核io的很多细节问题,包括缓冲区分配。所有操作基于流和File对象

系统数据文件

最后介绍系统提供的一些数据文件,包括口令文件,阴影文件,朱文杰,登录账号文件,服务数据文件,协议数据文件,网络数据文件等

一. unix基础知识

1. unix体系结构

  • 操作系统是一种特殊的软件,它控制计算机硬件资源,提供程序运行环境
  • 此软件称为内核,它相对较小,位于环境的中心
  • 内核的接口被称为系统调用
  • 公共函数库构建在系统调用接口上
  • 系统调用一般比普通函数调用需要花费更多时间
  • 应用软件可以调用公共函数库或者使用系统调用

2. 文件和目录

  • 文件系统是目录和文件组成的一种层次结构
  • 目录的起点称为根,名称为/符号
  • 目录是包含很多目录项的文件
  • 逻辑上可认为每个目录项都包含文件名和文件属性。物理上是不包含的,因为一个文件可被多次硬链接
  • 文件属性包括:类型(普通文件,目录),大小,所有者,权限,修改时间等。state和fstate函数返回文件属性。

3. 输入和输出

3.1 文件描述符

通常是一个小的非负整数,内核用它标识一个特定进程正在访问的文件

3.2 标准输入,标准输出,标准出错

每当运行一个新程序时,shell都为其打开三个文件描述符:

说明

文件描述符

头文件

标准输入

0

STDIN_FILENO

标准输出

1

STDOUT_FILENO

标准出错

2

STDERR_FILENO

3.3 不用缓冲的io

  • 函数open,read,write,lseek,close提供了不用缓冲的io
  • 这些函数都使用文件描述符
  • 头文件为

3.4 标准io

  • 标准io提供一种带缓冲io的接口
  • 使用标准io无需担心如何选取最佳缓冲区大小,且简化了堆输入行的处理
  • 标准io头文件为

4. 程序和进程

4.1 程序

  • 存放在磁盘,处于某个目录中的可执行文件
  • exec函数执行时,内核将程序读入存储器并执行

4.2 进程

  • 程序的执行实例被称为进程
  • 每个进程都有一个唯一的数字标识,称为进程ID

4.3 进程控制

进程控制的主要函数:fork,exec和waitpid

4.4 线程

  • 进程内所有线程共享同一个地址空间,文件描述符,栈,进程相关属性
  • 线程访问共享数据时需要采取同步措施避免不一致性
  • 线程也用ID标识,但是只在它所属进程内起作用

5. 信号

  • 信号是通知进程已发生某种情况的一种技术
  • 进程如何处理信号有三种选择:
    • 忽略该信号
    • 按系统默认方式处理
    • 捕捉该信号:提供一个函数,信号发生时调用该函数。调用signal函数,第一个参数为信号名称,第二个参数为处理函数

6. 时间值

unix系统一直使用两种不同的时间值

  • 日历时间:UTC时间,用time_t表示。记录自1970年1月1日以来锁经过的秒数
  • 进程时间:cpu时间,用clock_t表示。已时钟滴答计算

二. unix标准化和实现

1. unix标准化

  • ISO c:c语言国际化标准
  • POSIX:可移植的操作系统接口(protable operating system interface)

2. unix实现

  • SVR4:AT&T的UNIX系统实验室产品,初版了系统V接口定义
  • BSD:加州伯克利分校研究和开发的,含有AT&T许可证的代码
  • FreeBSD:BSD去除AT&T许可证代码后,完全免费的版本
  • Linux:1991年Linux开发的一款被目前广泛使用的unix操作系统
  • Mac OS:核心系统是Darwin,基于Mach内核和FreeBSD的组合
  • Solaris:sun公司开发的unix系统版本

三. unix提供的文件IO

1. 文件描述符

  • 内核中,所有打开的文件都通过文件描述符引用
  • 打开,新建时,内核向进程返回一个文件描述符
  • 读写文件时,将文件描述符传给read和write

2. open

  • 作用:创建或打开一个文件
  • pathname参数:文件名字
  • flag参数:由以下值进行“或”组成
    • O_RDONLY:只读
    • O_WRONLY:只写
    • O_RDWR:读写
    • O_APPEND:追加到末尾
    • O_CREATE:文件不存在就创建
    • O_EXCL:同时指定O_CREATE时,如果文件存在,就会出错。使测试和创建成为原子操作
    • O_TRUNC:将文件长度截短为0
    • O_NOCTTY:控制终端相关
    • O_NONBLOCK:非阻塞模式
  • mode参数:文件访问权限,仅新建文件时使用该参数

3. create

  • 作用:创建文件
  • 等价于open(pathname, O_WRONLY|O_CREATE|O_TRUNC, mode)

4. close

  • 作用:关闭文件
  • 关闭会释放加在该文件上的所有记录锁
  • 进程终止时,内核自动关闭它打开的文件,故可以不用显示调用close

5. lseek

  • 作用:设置打开文件的偏移量
  • 默认偏移量为0,如果设置O_APPEND属性,默认偏移量为文件末尾
  • whence的取值:
    • SEEK_SET:设置文件偏移为pos值
    • SEEK_CUR:设置文件偏移为当前位置+pos
    • SEEK_END:设置文件偏移为文件长度+pos

6. read

  • 作用:从打开的文件中读数据
  • 读取成功,返回读到的字节数。读到末尾,返回0。
  • 导致读到的字节数小于要求读字节数的情况:
    • 普通文件:读到达到要求字节数时,已经读到文件结尾了
    • 终端设备文件:一次最多读一行
    • 网络数据:缓存区大小小于要读字节
    • 管道文件:管道包含的字节小于要读字节

7. write

  • 作用:向打开文件中写数据
  • 返回值通常与nbyte相同,否则出错
  • 写成功后,文件偏移量增加写入字节数量

9. 文件共享

9.1 打开文件的内核数据结构

unix支持在不同进程间共享打开的文件,unix内核使用什么数据结构来支持这种共享呢?

  • 进程表记录来所有的进程
  • 每个进程都有一个记录项,用来记录打开文件的文件描述表
  • 文件描述符的每一项包括:
    • 文件描述符标识
    • 指向文件表项的指针
  • 文件表项由内核维护,每一项包括:
    • 文件状态标识(读,写,同步,阻塞等)
    • 当前文件偏移量
    • 指向该文件v节点表项的指针
  • 每个打开文件都有v节点(v-node)结构,这些信息是打开文件时从磁盘读入内存的。包括:
    • 文件类型
    • 对此文件进行各种操作的指针
    • i节点信息(索引信息):包括长度,所有者,所在设备,磁盘位置指针等

9.2 两个独立进程各自打开同一文件

  • 给定的文件,只有一个v节点表项
  • 每个进程都有自己的文件表项,以使自己有独立的文件偏移量

9.3 两个独立进程共享同一个文件表项

  • 使用dup和fork函数时,父子进程对于每一个文件描述符,都共享同一个文件表项,达到文件共享的目的

9.4 创建共享文件的函数

  • dup:返回的文件描述符为可用的最小值
  • dup2:返回fieldes2指定的描述符。如果fieldes2已经打开,就关闭。如果fieldes=fieldes2,不关闭,直接返回。
  • fcntl:也可以创建共享文件

10. 原子操作

  • 原子操作:指多步组成的操作,
  • 任何一个需要调用多个函数的操作都不可能是原子操作,因为中间可能会挂起该进程
  • unix提供了一些函数,使多个操作成为一个“原子操作”
  • O_APPEND标识:lseek和write的原子操作
  • pread:lseek和read的原子操作
  • pwrite:lseek和write的原子操作
  • 调用open时,通过制定O_CREAT和O_EXCL参数,将创建文件作为原子操作

11. sync, fsync, fdatasync函数

这几个函数出现的背景:unix提供的延时写功能,通过提供缓冲区以减少磁盘读写次数,但是降低了文件内容更新速度,这几个函数用于保证缓冲区内容与文件内容的同步,保证一致性。

  • sync:将修改的快缓冲区排入写队列,立马返回,不等待真正写磁盘
  • fsync:针对指定的文件描述符起作用,且等待磁盘写完才返回。同步内容包括数据和文件属性。适用于数据库系统。
  • fdatasync:包括fsync的功能,但是只同步数据,不同步文件属性。

12. fcntl函数

  • 作用:改变已打开文件的性质
  • 参数cmd的取值和作用:
    • F_DUPFD:复制一个现有的文件描述符
    • F_GETFD: 设置文件描述符标记
    • F_SETFD: 获得文件描述符标记
    • F_GETFL: 设置文件状态标记:读,写,追加,阻塞等。
    • F_SETFL: 获得文件状态标记
    • F_GETOWN: 设置异步io所有权
    • F_SEGOWN: 获得异步io所有权
    • F_GETLK:获得记录锁
    • F_SETLK:设置记录锁
    • F_SETLKW:设置记录锁

四. 文件和目录

1. 文件属性

1.1 表示文件属性的数据结构:struct stat

```
struct stat {
    mode_t     st_mode;       //文件模式,包含文件类型,用户id,组id,访问权限(9种)等信息
    ino_t      st_ino;       //inode节点号
    dev_t      st_dev;        //设备号码
    dev_t      st_rdev;       //特殊设备号码
    nlink_t    st_nlink;      //文件的连接数
    uid_t      st_uid;        //文件所有者
    gid_t      st_gid;        //文件所有者对应的组
    off_t      st_size;       //普通文件,对应的文件字节数
    time_t     st_atime;      //文件数据最后被访问的时间
    time_t     st_mtime;      //文件数据最后被修改的时间
    time_t     st_ctime;      //文件状态(i节点状态)的最后修改时间
    blksize_t st_blksize;    //文件内容对应的块大小
    blkcnt_t   st_blocks;     //伟建内容对应的块数量
  };
```

1.2 如何获取文件属性

  • state:根据文件名获取属性
  • fstate:根据描述符获取属性
  • lstate:返回符号链接的属性

1.3 修改属性的部分方法

  • 访问时间和修改时间: utime函数,参数为struct utimbuf,每一项都是utc时间
  • 文件用户id和组id:chown,fchown,lchown

2. 文件类型:

2.1 st_mode字段控制的文件类型

  • S_ISREG:普通文件。文本或二进制;可执行文件有固定的可被内核识别的格式。
  • S_ISDIR:目录文件。包含其他文件的名字以及指向与这些文件有关信息的指针。
  • S_ISBLK:块特殊文件。提供堆设备(如磁盘)带缓冲的访问,访问长度固定。
  • S_ISCHR:字符特殊文件。提供堆设备(如磁盘)不带缓冲的访问,访问长度不固定。
  • S_ISFIFO:FIFO,命名管道。用于进程间通信
  • S_ISSOCK:套接字。用于网络间进程通信
  • S_ISLINK:符号链接。指向另一个文件

2.2 stat结构体本身控制的文件类型

  • S_TYPEISMQ:消息队列
  • S_TYPEISSEM:信号量
  • S_TYPEISSHM:共享存储对象

3. 文件访问权限

  • 权限位保存在st_mode属性中
  • 9个访问权限位对应的值为:
  • 更改文件访问权限的函数:chmod和fchmod

4. UFS文件系统

4.1 磁盘,分区和文件系统图

  • 一个磁盘分为多个分区,每个分区可以包含一个文件系统
  • i节点是固定长度的记录项

4.2 详细的柱面组的i节点和数据块

  • 每个柱面包括:i节点数组,数据库,目录块
  • 每个i节点包含文件的大部分信息:文件类型,访问权限,长度,占用的实际数据库。(stat结构大多数信息取自i节点)
  • 每个目录块包括:目录名称,i节点号
  • 同一个i节点,可以被不同的目录指向,i节点的链接计数统计指向的数量
  • 文件改名时,实际内容并未移动,只是构造一个新目录项,指向现有的节点,并解除旧记录项的链接

5. 硬链接

硬链接直接指向文件的i节点

5.1 创建一个指向现有文件的链接:link方法

  • 如果newpath已经存在,返回出错
  • 只能创建newpath中最后一个分量,路径中其他部分必须已经存在
  • 很多文件系统不允许堆目录创建硬链接
  • 超级用户能直接创建目录硬链接

5.2 删除一个现有的链接项:unlink方法

  • 将path所引用的文件的链接数减1
  • 只有当连接技术为0,该文件的内容才被删除
  • 对于文件,可以使用remove功能,和unlink一样
  • 对于目录,可以使用rmdir功能,和unlink一样

6. 符号链接

符号链接是指向一个文件的间接指针。

6.1 符号链接是为了避开硬链接的一些限制

  • 硬链接要求链接和文件位于同一文件系统中
  • 只有超级用户才能创建指向目录的硬链接

6.2 使用符号链接需要注意的事情

  • 当调用某个函数时,需要注意函数处理的是链接的文件,还是链接本身

6.3 符号链接相关的函数

  • 创建符号链接: symlink
  • 打开符号链接:readlink

7. 目录

  • 创建目录:mkdir
  • 删除目录:rmdir。入炉链接计数为0,且没有进程打开次目录,释放目录空间。
  • 读取目录:
  • 更改当前工作目录:chdir,fchdir

五. 标准IO

  • 标准io库不仅在unix上,很多操作系统上都实现了。
  • 标准io处理很多细节,例如:缓冲区分配,优化长度执行io等。便于用户使用。
  • 使用的头文件为。
  • 标准io的底层调用了前面介绍的unix内核io。
  • 标准io的缺点是效率低。这与它需要复制的数据量有关

1. 流和File对象

  • unix内核io提供的io函数都是针对文件描述符的
  • 但是标准io的操作是针对流进行的
  • 标准io文件流可用于单字节或宽字节字符集,由流定向决定(fwide函数)。
  • 标准io打开一个文件(fopen函数)时,返回一个FILE的指针,它包含了实际io的文件描述符,指向用于该流缓冲区的指针,缓冲区长度,缓冲区当前字符数,出错标志,文件结束标志等信息
  • 每个进程预定义三个流:标准输入,标准输出,标准出错

2. 缓冲

2.1 缓冲类型

标准io提供三种类型的缓冲

  • 全缓冲:填满标准io缓冲区后才进行实际的io操作(malloc申请缓冲区,flush执行写操作)。
  • 行缓冲:输入输出中遇到换行符时进行实际的io操作。涉及终端设备时,通常用行缓冲。
  • 不带缓冲:不对字符进行缓冲存储。标准出错流通常不带缓冲。

2.2 设置缓冲类型

  • setbuf
  • setvbuf:第三个参数:
    • _IOFBF:全缓冲
    • _IOLBF: 行缓冲
    • _IONBF:无缓冲

3. 打开流

  • fopen:打开一个指定的文件
  • freopen:将一个文件读到一个指定的流。如果流已经打开,就先关闭,已经定向,就先清除定向。
  • fdopen:通过文件描述符打开文件。因为管道和网络通信等特殊文件不能用标准io函数fopen打开,所以用到该函数。
  • type:指定文件的打开方式

4. 读和写流

读写流有三种不同的方式

  • 每次读写一个字符:
    • 读:getc,fgetc,getchar
    • 写:putc,fputc,putchar

    不带f前缀的从标准输入流读取数据,带f前缀的从指定流读取数据。不带f前缀的函数不推荐使用,因为它不指定缓冲区大小,会导致溢出。

  • 每次读写一行:
    • 读:gets,fgets
    • 写:puts,fputs
  • 每次读写一定数量的对象(直接io,二进制io):
    • 读:fread,需要指定要读取的元素个数和每个元素的大小
    • 写:fwrite
    • 缺点:不同系统间,交换二进制数据会编译期和计算机体系结构不同而有差异,所以必须用更高级的协议。

5. 定位流

定位标准io流有三种不同的方式

  • ftell(获取),fseek(设置):long类型的文件位置
  • ftello和fseeko:off_t类型的文件位置
  • fgetpos和fsetpos:fpos_t的抽象数据类型表示文件位置

6. 格式化io

6.1 格式化输出

  • printf:格式化数据写到标准输出
  • fprintf:格式化数据到指定流
  • sprintf:格式化的数据送入数组buf中,尾部自动加入null。可能会导致缓冲区溢出,需调用者自己保证
  • 转换说明以%开始

6.2 格式化输入

六. 系统数据文件

1. 口令文件

  • 存放目录:/etc/passwd
  • 数据结构:中的passwd结构体
  • 查看指定用户口令的函数接口:
  • 查看所有用户口令的函数接口:

2. 阴影文件(加密口令)

  • 存放目录:/etc/shadow
  • 查看的接口:

3. 组文件

  • 存放目录:/etc/group
  • 数据结构:中的group
  • 查看指定组:
  • 查看所有组:

4. 其他数据文件

  • 服务器提供服务的数据文件:/etc/services
  • 记录协议信息的数据文件:/etc/protocols
  • 记录网络信息的数据文件:/etc/networks

5. 登陆账号文件

  • 当前登陆进系统的用户:/var/run/utmp
  • 跟踪登陆和注销信息:/var/log/wtmp

6. 获取系统信息

  • 获取主机与操作系统相关信息
  • 只获取主机名

7. 时间格式

  • 日历时间(UTC时间)
  • 更高精度的时间
  • 各种时间的转化关系