APUE笔记—第三章文件I/O
1. C标准库函数与系统函数的区别
- 用户程序可以直接访问系统函数
- 用户程序也可以调用C标准库函数,C标准库函数间接调用系统函数
2. 文件描述符
对于内核而言,所有打开的文件都是通过文件描述符引用,文件描述符是一个非负整数。通常一个进程默认会打开三个文件描述符
STDIN_FILENO 0
STDOUT_FILENO 1
STDERR_FILENO 2
2.1 打开文件最大个数
命令:ulimit -a
menwen@menwen:~/APUE_code/FILE_IO$ ulimit -a
······
open files (-n) 1024
······
打开文件的最大个数为:1024个
命令:ulimit -n 4096,可以改变打开文件的最大个数
查看本电脑最大打开的文件个数
menwen@menwen:~/$ cat /proc/sys/fs/file-max
201223
3. 函数open和openat
3.1 open和openat函数
调用open和openat函数可以打开一个或创建一个文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int openat(int dirfd, const char *pathname, int flags);
int openat(int dirfd, const char *pathname, int flags, mode_t mode);
//两个函数返回值:若成功,返回文件描述符;若出错,返回-1
path是要打开或创建文件的名字。flags参数可用来说明此函数的多个选
项,用下列一个或多个常量进行“或”运算构成“flag”参数。
必选项 (必须指定一个且只能指定一个)
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 可读可写打开
可选项(部分)
O_CREAT 若此文件不存在则创建它。使用此选项时要提供第三个参数mode,表示该文件的访问权限
O_EXCL 如果同时指定了O_CREAT,并且文件已存在,则出错返回
O_TRUNC 若文件已存在,并且以只写或可读可写方式打开, 则将其长度截断(trun-cate)为0字节。
O_APPEND 每次写都追加到文件尾端
O_NONBLOC 若path应用一个FIFO,一
个块特殊文件或一个字符特殊文件,则该文件本次打开操作的后续的IO操作设置为非阻塞方式
3.2 open函数和C标准IO库函数的细微区别
- 以可写的方式fopen一个文件时,如果文件不存在会自动创建,而open一个文件时必须明确指定O_CREAT才会创建文件,否则会出错。
- 以w或w+方式fopen一个文件时,如果文件存在就会截断为0字节,而open一个文件时必须明确指定O_TRUNC才会截断文件,否则会在原来的数据上改写。
- 第三个mode参数指定文件权限可以用八进制数表示,如0644;也可以是S_IRUSR,,S_IWUSR等宏定义按位或运算表示。
umask掩码命令 umask
当gcc编译生成一个可执行文件时,创建权限是0777,而最终文件权限是0777 & ~0022 = 0755
[root@menwen-linux ]# umask
0022
[root@menwen-linux ]# gcc main.c
[root@menwen-linux ]# ll a.out
-rwxr-xr-x. 1 root root 5370 10月 25 21:19 a.out
3.3 关于open函数的一个测试程序
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd;
fd = open(argv[1] , O_WRONLY | O_CREAT, 0644);//只写打开文件,若不存在则创建,设置文件权限为0644
if(fd < 0){
perror("open abc fail");
exit(EXIT_FAILURE);
}
printf("fd = %d\n", fd);//打印文件描述符
close(fd);//关闭fd对应的文件
return 0;
}
打印结果如下:
menwen@menwen:~$ gcc open.c
menwen@menwen:~$ ./a.out abc
fd = 3
menwen@menwen:~$ ll abc
-rw-r--r-- 1 menwen menwen 0 10月 25 21:28 abc
由于open函数返回的文件描述符一定是最小未用的描述符值。则创建的abc文件fd=3。
3.4 用程序测出最多打开文件个数
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd;
char name[1024];
int i = 0;
while(1){
sprintf(name, "file%d", ++i);//改变每次打开文件的名字
fd = open(name, O_CREAT, 0777);//打开name文件
if(fd == -1){
break;
}
printf("%d\n", i);
}
return 0;
}
最后打印的是1021个,加上默认打开的stdin,stdout,stderr。一共1024个。
4. 函数creat
也可以调用一个creat函数创建一个新文件
int creat(const char *pathname, mode_t mode);
//返回值,若成功,返回打开文件的文件描述符,若出错,返回-1
此函数等效于:
open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);
由此看出,creat的不足之处是它以写方式打开所创建的文件。
5.函数close
可以调用close函数关闭一个打开文件
#include <unistd.h>
int close(int fd);
//返回值:若成功,返回0,若出错,返回-1
关闭一个文件时会释放该进程加在该文件上的所有记录锁。
6. 函数read
调用read函数从打开文件中读数据
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
//返回值:读到的字节数,若已到文件尾,返回0;若出错,返回-1
- fd是要读的文件的文件描述符;
- buf使一个通用类型的指针,让读到的数据保存在buf里;
- count是请求读取的字节数,是一个无符号整型,这就允许一个16位的实现一次读取多达65535个字节;
- 返回值是ssize_t,一个带符号的整型,以保证能返回正整数字节数,0(表示文件尾端)-1(出错);
7. 函数write
调用write函数向打开的文件写数据
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
//返回值:若成功,返回已写的字节数,若出错,返回-1
- fd是文件描述符
- buf是要读数据缓冲区的地址
- count是请求写的字节数,通常与返回值相等,否则表示出错。
- 返回值是ssize_t,一个带符号的整型,以保证能返回正整数字节数,0(表示文件尾端)-1(出错);
7.1 创建一个具有空洞文件
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main()
{
int fd = open("file.hole", O_WRONLY | O_CREAT, 0777);
if(fd < 0){
perror("open file.hole err\n");
exit(EXIT_FAILURE);
}
lseek(fd, 0x1000, SEEK_SET);//从文件头开始写0x1000字节
write(fd, "test", 4);//为空文件必须写一个结尾
close(fd);
return 0;
}
运行结果:
menwen@menwen:~/$ gcc hole.c
menwen@menwen:~/$ ./a.out
menwen@menwen:~/$ ls -l file.hole
-rwxrwxr-x 1 menwen menwen 4100 10月 26 19:13 file.hole
程序里创建了0x1000(4096D)空洞文件,再加上刚才的”test”4个字节一共是4100字节
使用od命令可以查看文件内容,-c参数是以字符方式打印文件内容。
od -c file.hole
0000000 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0010000 t e s t
0010004
由此可以看出,前面未写入的字节都被填写成”\0”
8. 文件共享
Unix系统支持在不同进程间共享打开文件。内核使用3种数据结构表示打开文件,他们的关系决定了在文件共享方面一个进程对另一个进程的影响。
1. 每个进程在进程表都有一个记录表,记录表中包含一张打开文件描述符表,可将其看成一个矢量,每个描述符占用一项。每个文件描述符相关联的是:
- 文件描述符标志(close_on_exec)
- 指向一个文件表项的指针
2. 内核为所有打开文件维持一张文件表。每个表项包含:
- 文件状态标志(读、写、添加、同步、和非阻塞等)
- 当前文件偏移量
- 指向该文件v结点表项的指针
3. 每个打开文件(或设备)都有一个v结点结构,v结点还包含了文件类型和对文件进行各种操作函数的指针,大多数系统还包含了i结点,i结点包括文件的所有者,文件长度,指向文件实际数据块在磁盘上所载位置的指针。
9.原子操作
一般而言,原子操作(atomic operation)指的是由多步组成的一个操作。如果该操作原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。
- 1.追加到一个文件。
用O_APPEND选项代替“先定位到文件尾端,然后写”的操作。 - 2.fread和fwrite函数。
fread函数相当于调用lseek后调用read,fwrite类似。 - 3.创建一个文件。
open的O_EXCL和O_CREAT选项代替open和creat函数。
10. dup和dup2函数
复制一个现有的文件描述符
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
//返回值:若成功,返回新的文件描述符,如出错,返回-1
- dup返回的新文件描述符一定是当前可用文件描述符中的最小值。
- dup2可以用newfd参数指定新描述符的值。如果newfd已经打开,则先将其关闭,如若newfd等于oldfd则返回newfd而不关闭它。dup2是一个原子操作
11. 函数fcntl
函数fcntl可以改变已经打开文件的属性
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /*int arg*/ );
//返回值:若成功,则依赖cmd;若出错,返回-1
fcntl函数有5种功能:
1. 复制一个已有的描述符(cmd = F_DUPFD或F_DUPFD_CLOEXEC)
2. 获取/设置文件描述符标志(cmd = F_GETFD或F_SETFD)
3. 获取/设置文件状态标志(cmd = F_GETFL或F_SETFL)
4. 获取/设置异步IO所有权(cmd = F_GETOWN或F_SETOWN)
5. 获取/设置记录锁(cmd = F_GETLK、F_SETLK或F_SETLKW)
12. ioctl函数
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
//返回值:若出错,返回-1;若成功,返回其他值
ioctl函数用于向设备发控制和配置命令,有些命令也需要读写一些数据,但这些数据是不能用read/write读写的。例如,在串口线手法数据通过read/write操作,而串口的波特率、校验位、等通过ioctl设置。
12.1使用ioctl函数获取终端窗口大小
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
int main()
{
struct winsize size;//终端抽象的结构体
if(isatty(STDOUT_FILENO) == 0)//是否是一个终端
exit(1);
if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) < 0){//获取终端文件的信息写到size中。TIOCGWINSZ是获取终端窗口大小的选项
perror("ioctl TIOCGWINSZ error");
exit(1);
}
printf("%d rows, %d columns\n", size.ws_row, size.ws_col);
return 0;
}
运行结果:
menwen@menwen:~/$ ./a.out
26 rows, 82 columns
menwen@menwen:~/$ ./a.out
26 rows, 87 columns