目录
一、文件I/O描述符
- 内核的一个重要功能就是文件管理,系统有非常多的文件,内核怎样认识每一个文件呢?------ 内核采用ID号的方式来标识这些文件:inode号。(节点号)
- 那么这些内核的文件的ID号,在每个用户的程序中是怎样映射的呢? ------即:文件描述符。所以,文件描述符是指在一个进程中用于区分内核空间中不同的文件。
- 文件描述符是一个非负整数(0~OPEN_MAX-1),即0~1023。
- 在shell中,描述符 STDIN_FILENO(0):标准输入,STDOUT_FILENO(1):标准输出,STDERR_FILENO(2):标准错误。
二、文件I/O操作函数—基础
2.1 open & creat
#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 creat(const char *pathname, mode_t mode);
- creat 函数等价于 open(pathname,O_CREAT|O_TRUNC|O_WRONLY,mode);
- pathname:文件路径名
- flags:
- O_RDONLY 以只读的方式打开
- O_WRONLY 以只写的方式打开
- O_RDWR 以读写的方式打开
- O_CREAT 如果文件不存在,则创建文件
- O_APPEND 已追加的方式打开文件,每次调用 write 时,文件指针自动先移到文件尾,用于多进程写同一个文件的情况。
- O_NONBLOCK 非阻塞方式打开,无论有无数据读取或等待,都会立即返回进程之中。
- O_NODELAY 非阻塞方式打开
- O_SYNC 同步打开文件,只有在数据被真正写入物理设备设备后才返回
- O_EXCL 仅与 O_CREAT 连用,如果文件已存在,则强制 open 失败
- O_SYNC 同步打开文件,只有在数据被真正写入物理设备设备后才返回
- O_TRUNC 如果文件存在,将文件的长度截至 0
- mode:类似于
chmod 775 filename
这功能,mode就相当于775。 - 返回值:成功:文件描述符, 失败:-1,并设置errno值。
2.2 read & write
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
- fd:文件描述符
- buf:读&写的数据缓存区
- count:需要读&写的长度
- 返回值:出错时,返回-1;
- 读成功时,将返回读取的字节数(0表示文件结束),并按此数值设定文件的光标指针位置。如果这个值是小于请求的字节数,可能是因为我们接近文件结尾,或我们正在从管道或终端读取数据,实际可用的字节数较少;也可能因为read()被信号中断。
- 写成功时,将返回写入的字节数(0表示未写入任何内容)。如果这个数字小于请求的字节数,则不是错误;例如,这可能是因为磁盘设备已满。
- size_t 为unsigned long类型 ,ssize_t 为long类型,具体查看内核源码相关定义。
2.3 close
#include <unistd.h>
int close(int fd);
- fd:文件描述符
- 返回值:0成功, -1失败
2.4 lseek
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
- fd:文件描述符
- offset:偏移量
- whence :从哪个位置开始偏移
- SEEK_SET 从文件头开始计算
- SEEK_CUR 从当前指针开始计算
- SEEK_END 从文件尾开始计算
- 返回值:成功完成后,返回从文件开头开始以字节为单位测量的结果偏移位置。出错时,返回值(off_t)-1。并且errno设置为指示错误。
- off_t 为long 类型。
int main(int argc,char *argv[])
{
int fd = open("test.txt", O_WRONLY | O_CREAT);
lseek(fd, 1024, SEEK_SET);
write(fd, "a", 1);
close(fd);
return 0;
}
- 相当于前面填充了1024个空格字符,然后再加一个字符a,刚好1025个字符
2.5 读写例程
#include <stdio.h>
#include <stdlib.h> //包含 exit
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h> //用 perror 输出错误
#include <unistd.h>
int main(int argc,char *argv[])
{
char buf[512] = {0};
int fd1, fd2;
fd1 = open("./test1.txt", O_RDONLY); //打开文件
if (fd1 == -1) {
perror("test1.txt open failed"); //用于输出错误信息.类似于:fputs(”open failed\n”,stderr);
exit(-1);
}
fd2 = creat("test2.txt", 0755); //创建文件
if (fd1 == -1) {
perror("test2.txt creat failed");
exit(-1);
}
int read_cnts = 0, write_cnts = 0;
while ((read_cnts = read(fd1, buf, sizeof(buf))) > 0) {//如果 read 读取成功,返回的是长度,否则,返回-1
write_cnts = write(fd2, buf, fr);
if (write_cnts == -1) {
perror("write failed!");
exit(-1);
}
}
close(fd1);
close(fd2);
}
三、文件I/O操作函数—进阶
3.1 pread & pwrite
- 如果对文件的读写操作很重要,不能被打断,可以用原子操作函数。对文件的读写原子操作有 perad、pwrite。
#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
-
fd:文件描述符
-
buf:数据缓存区指针
-
count:读&写数据的字节数
-
offset:读&写的起始地址的偏移量,读取&写入地址=文件开始+offset。注意,执行后,文件偏移指针不变
-
返回值:成功时,将返回读取或写入的字节数(对于pwrite(),零表示未写入任何内容,或如果是pread(),则为文件结尾,如果是错误,则为-1,如果是错误,则设置errno以指示错误。
3.2 dup & dup2
- 函数功能:文件描述符的复制
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
- 返回值:成功:文件描述符,失败 -1
- 如果说将文件描述符直接赋值给变量类似于
ln -s 源文件 目标文件(删除源文件之后,链接变成无效的了)
的话,那么上面这两个就类似于ln 源文件 目标文件(删除源文件之后,目标没有影响)
了。
- eg: fd1 = fd2; close(fd2); 关闭fd2,fd1也会无效; 引用次数变化1—>1—>1
- eg: fd1=dup(fd2); close(fd2); 关闭fd2;fd1依然有效;引用次数变化 1—>2—>1
- 变量直接赋值,内核引用次数不会增加;使用dup&dup2内核引用次数会增加,每调用一次+1,close一次-1。
- dup与dup2的区别就是dup2可以用户自己设定新的文件描述符(前提是该文件描述符没有被其他文件引用);dup是系统内核自动分配的(由小到大原则)
3.3 fcntl
- 函数功能:改变已打开文件的属性
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
- fcntl函数有5种功能:
- 复制一个现有的描述符(cmd=F_DUPFD).
- 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
- 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
- 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
- 获得/设置记录锁(cmd=F_GETLK , F_SETLK或F_SETLKW).
- fd:文件描述符
- cmd:
- 第三个参数:
- 当fcntl中的
cmd
参数与第2~4功能有关时,第三个参数arg
指向一个long
整型
int fcntl(int fd, int cmd, long arg);
- 当fcntl 中的
cmd
参数与记录锁有关时,第三个参数arg
指向一个struct flock
结构体
int fcntl(int fd, int cmd, struct flock * lock);
- 返回值: 失败 -1 , 成功 :与cmd相关的值
3.3.1 复制一个现有的描述符
int arg = 4;
int newfd = fcntl(fd1, F_DUPFD, arg);
- 新文件描述符将作为一个返回值返回,它是尚未打开的各描述符中
大于或等于arg
的数(由小到大原则:最小值)。即 newfd >= 4
3.3.2 获得/设置文件描述符标记
- 发现有没有第三个参数都对这个值没影响
3.3.3 获得/设置文件状态标记
- 状态值同 open()中参数 flags 一样
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
int ret = 0;
int fd1 = open("test.txt", O_WRONLY | O_CREAT);
ret = fcntl(fd1, F_GETFL, 0);
if (ret < 0) {
printf("fcntl failed \n");
}
switch(ret & O_ACCMODE) {
case O_RDONLY: printf("O_RDONLY \n"); break;
case O_WRONLY: printf("O_WRONLY \n"); break;
case O_RDWR: printf("O_RDWR \n"); break;
default: printf("unknown acc mode");
}
if (ret & O_APPEND)
printf("O_APPEND \n");
if (ret & O_NONBLOCK)
printf("O_NONBLOCK \n");
if (ret & O_SYNC)
printf("O_SYNC \n");
close(fd1);
return 0;
}
3.3.4 获得/设置异步I/O所有权
3.3.5 获得/设置记录锁
struct flock
{
short int l_type; /*锁定的状态,用 F_RDLCK 和 F_WRLCK 和 F_UNLCK*/
short int l_whence; /*决定 l_start 位置,用 SEEK_SET,SEEK_CUR,SEEK_END*/
off_t l_start; /*锁定区域的开头位置*/
off_t l_len; /*锁定区域的大小,用 struct stat 结构体 中的 st_size 可以获得*/
pid_t l_pid; /*锁定动作的进程,用 getpid()函数获得*/
};
l_type:
- F_RDLCK:共享锁(或读锁)
- F_WRLCK:独占锁(或写锁)
- F_UNLCK:解锁,用来清除锁。
l_whence:
- SEEK_SET 以文件开头为锁定的起始位置
- SEEK_CUR 以目前文件读写位置为锁定的起始位置
- SEEK_END 以文件结尾为锁定的起始位置
3.4 stat & fstat & lstat
- 函数功能: 获取文件信息
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
- lstat 函数类似于 stat, 但是当命名的文件是一个符号链接时,lstat 得到该符号链接文件的有关信息,而不是该符
号链接指向的文件的信息。
struct stat {
dev_t st_dev; /*如果是设备,返回设备表述符,否则为 0*/
ino_t st_ino; /* i 节点号 */
mode_t st_mode; /* 文件类型 */
nlink_t st_nlink; /* 链接数 */
uid_t st_uid; /* 属主 ID */
gid_t st_gid; /* 组 ID */
dev_t st_rdev; /* 设备类型*/
off_t st_size; /* 文件大小,字节表示 */
blksize_t st_blksize; /* 块大小*/
blkcnt_t st_blocks; /* 块数 */
time_t st_atime; /* 最后访问时间*/
time_t st_mtime; /* 最后修改时间*/
time_t st_ctime; /* 创建时间 */
};
- 对于结构体的成员 st_mode,有一组宏可以进行文件类型的判断,通常用于判断:
if(S_ISDIR(st.st_mode)){/*...*/}
- 示例:得到文件大小
#include<sys/stat.h>
#include<unistd.h>
int main(int argc,char *argv[])
{
struct stat buf;
stat (“/etc/passwd”,&buf);
printf(“/etc/passwd file size = %d \n”,buf.st_size);//st_size 可以得到文件大小
}
3.5 access
- 函数功能:Linux 下不同的用户对文件的可操作权限不同,access 函数可测试当前用户(运行该函数程序的用户)对某文件是否有相关权限。其是按实际用户 ID 和实际组 ID 进行权限许可测试的。
#include <unistd.h>
int access(const char *pathname, int mode);
- mode :
- R_OK 可读
- W_OK 可写
- X_OK 可执行
- F_OK 测试文件是否存在
- 返回值: 成功时(授予所有请求的权限),返回零。出错时(模式中至少有一位请求如果被拒绝,或发生其他错误),则返回-1,并正确设置errno。
- 示例 :
#include<stdio.h>
#include<unistd.h>
#define FILE_NAME "./test.txt"
int main(int argc,char *argv[])
{
if(access(FILE_NAME, F_OK) == -1) {
printf("文件不存在\n");
return 0;
}
if(!access(FILE_NAME, R_OK))
printf("r");
else
printf("-");
if(!access(FILE_NAME, W_OK))
printf("w");
else
printf("-");
if(!access(FILE_NAME, X_OK))
printf("x");
else
printf("-");
return 0;
}
3.6 ioctl
- ioctl函数一直是I/O操作的杂物箱,上述函数不能进行的I/O操作通常都能用ioctl表示,终端I/O是用ioctl最多的地方。
#include <sys/ioctl.h>
int ioctl(int d, int request, ...);
- 详情见后续
四、 总结
参考:
https://www.cnblogs.com/zxc2man/p/7649240.html
UNIX环境高级编程(中文第三版.pdf