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时间)
- 更高精度的时间
- 各种时间的转化关系
- HDUOJ------1711Number Sequence
- HDUOJ---1712 ACboy needs your help
- HDUOJ---1867 A + B for you again
- HDUOJ--------1420Prepared for New Acmer
- PowerVM虚拟化环境下 CPU 利用率的监控与探究
- 虚函数中构造函数的调用顺序
- HDUOJ-----4512吉哥系列故事——完美队形I(LCIS)
- go语言mongdb管道使用(二)
- HDUOJ--4565 So Easy!
- Go 语言Map(集合)
- 简单的java实验,涉及到 类继承以及接口问题,方法体的重写(区别于重载)
- java 快速求素数
- 狄斯奎诺(dijkstra 模板)
- HDUOJ---汉洛塔IX
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法