目录
参考
文件描述符
在Linux 的世界里,一切设备皆文件。我们可以系统调用中 I/O 的函数 (I: input,输入;O: output,输出),对文件进行相应的操作 ( open()、close()、write()、read()等)。
打开现存文件或新建文件时,系统(内核)会返回一个文件描述符,文件描述符用来指定已打开的文件。这个文件描述符相当于这个已打开文件的标号,文件描述符是非负整数,是文件的标识,操作这个文件描述符相当于操作这个描述符所指定的文件。
程序运行起来后(每个进程)都有一张文件描述符的表,标准输入、标准输出、标准错误输出设备文件被打开,对应的文件描述符 0、1、2 记录在表中。程序运行起来后这三个文件描述符是默认打开的。
// 标准输入的文件描述名1
#define STDIN_FILENO 0
// 标准输出的文件描述符
#define STDOUT_FILENO 1
// 标准错误的文件描述符
#define STDERR_FILENO 2
在程序运行起来后打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将此文件描述符记录在表中。
最大打开的文件个数
Linux中一个进程最多只能打开 NR_OPEN_DEFAULT (即1024)个文件,故当文件不再使用时应及时调用close() 函数关闭文件。
- 查看当前系统允许打开最大文件个数
cat /proc/sys/fs/file-max
- 当前默认设置最大打开文件个数1024
ulimit -a
- 修改默认设置最大打开文件个数为4096
ulimit -n 4096
常用文件IO函数
open函数
#include <sys/types .h>
#include <sys/stat.h>
#include <fcnt1.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags , int mode_t mode) ;
功能:
打开文件,如果文件不存在则可以选择创建
参数:
pathname: 文件的路径及文件名
flags: 打开文件的行为标志,必选项 O_RDONLY,O_WRONLY,O_RDWR
mode:这个参数,只有在文件不存在时有效,指新建文件时指定文件的权限
返回值:
成功:成功返回打开的文件描述符
失败: -1
flags详细说明
必选项:
取值 | 含义 |
---|---|
O_RDONLY | 以只读的方式打开 |
O_WRONLY | 以只写的方式打开 |
O_RDWR | 以可读、可写的方式打开 |
可选项,和必选项按位或起来
取值 | 含义 |
---|---|
O_CREAT | 文件不存在则创建文件,使用此选项时需使用mode说明文件的权限 |
O_EXCL | 如果同时指定了O_CREAT,且文件已经存在,则出错 |
O_TRUNC | 如果文件存在,则清空文件内容 |
O_APPEND | 写文件时,数据添加到文件末尾 |
O_NONBLOCK | 对于设备文件,以O_NONBLOCK方式打开可以做非阻塞I/O |
mode补充说明
- 1)、文件最终权限: mode & ~umask
- 2)、shell进程的umask掩码可以用umask命令查看
umask:查看掩码 (补码)
umask mode:设置掩码,mode为八进制数
umask -S:查看各组用户的默认操作权限
取值 | 八进制 | 含义 |
---|---|---|
S_IRWXU | 00700 | 文件所有者的读、写、可执行权限 |
S_IRUSR | 00400 | 文件所有者的读权限 |
S_IWUSR | 00200 | 文件所有者的写权限 |
S_IXUSR | 00100 | 文件所有者的可执行权限 |
S_IRWXG | 00070 | 文件所有者同组用户的读、写、可执行权限 |
S_IRGRP | 00040 | 文件所有者同组用户的读权限 |
S_IWGRP | 00020 | 文件所有者同组用户的写权限 |
S_IXGRP | 00010 | 文件所有者同组用户的可执行权限 |
S_IRWXO | 00007 | 其他组用户的读、写、可执行权限 |
S_IROTH | 00004 | 其他组用户的读权限 |
S_IWOTH | 00002 | 其他组用户的写权限 |
S_IXOTH | 00001 | 其他组用户的可执行权限 |
示例:
close函数
#include <unistd.h>
int close(int fd);
功能:
关闭已打开的文件
参数:
fd : 文件描述符,open()的返回值
返回值:
成功:0
失败: -1,并设置errno
需要说明的是,当一个进程终止时,内核对该进程所有尚未关闭的文件描述符调用close关闭,所以即使用户程序不调用close,在终止时内核也会自动关闭它打开的所有文件。
但是对于一个长年累月运行的程序(比如网络服务器),打开的文件描述符一定要记得关闭,否则随着打开的文件越来越多,会占用大量文件描述符和系统资源。
write函数
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:
把指定数目的数据写到文件 (fd)
参数:
文件描述符fd :
buf数据首地址
count :写入数据的长度 (字节)
返回值:
成功:实际写入数据的字节个数
失败: - 1
示例:write_demo.c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd = -1;
int ret = -1;
// 1. 以写的方式打开文件,如果文件存在就报错,如果不存在则新建
fd = open("test.txt", O_WRONLY | O_CREAT , 0644);
if(-1 == fd){
perror("open");
return 1;
}
printf("fd == %d \n", fd);
// 2.写文件
char* str = "hello world";
ret = write(fd, str, strlen(str));
if(-1 == ret){
perror("write");
return 1;
}
printf("write len =%d \n", ret);
// 3.close 文件
close(fd);
return 0;
}
read函数
#include <unistd.h>
ssize_t read(int fd, void *buf,size_t count);
功能:
把指定数目的数据读到内存(缓冲区)
参数
fd:文件描述符
buf :内存首地址
count :读取的字节个数
返回值
成功:实际读取到的字节个数
失败: - 1
// 从控制台输入
char buf[SIZE];
memset(buf, 0, SIZE);
// 从控制台输入
ret = read(TDIN)FILENO, buf, SIZE);
示例
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define SIZE 1024
int main()
{
int fd = -1;
int ret = -1;
// 1. 以只读的方式打开文件
fd = open("test.txt", O_RDONLY );
if(-1 == fd){
perror("open");
return 1;
}
printf("fd == %d \n", fd);
// 2.读取文件
char buf[SIZE];
memset(buf, 0, SIZE);
// 从文件描述符中最多读取SIZE个字节保存到buf中,实际读取字节数通过返回值返回
ret = read(fd, buf, SIZE);
if(-1 == ret){
perror("read");
return 1;
}
printf("read len %d, content: %s \n", ret, buf);
// 3.close 文件
close(fd);
return 0;
}
lseek函数
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset,int whence);
功能:
改变文件的偏移量
参数:
fd:文件描述符
offset: 根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸。
whence: 其取值如下
SEEK_SET:从文件开头移动offset个字节
SEEK_CUR:从当前位置移动offset个字节
SEEK_END:从文件未尾移动offset个字节
返回值:
若1seek成功执行,则返回新的偏移量
如果失败,返回-1
所有打开的文件都有一个当前文件偏移量(current file offset),以下简称为 cfo。cfo 通常是一个非负整数,用于表明文件开始处到文件当前位置的字节数。
读写操作通常开始于 cfo,并且使 cfo 增大,增量为读写的字节数。文件被打开时,cfo 会被初始化为 0,除非使用了O_APPEND。
示例 lseek.c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define SIZE 1024
int main()
{
int fd = -1;
int ret = -1;
// 1. 以写的方式打开文件,如果文件存在就报错,如果不存在则新建
fd = open("test.txt", O_RDWR | O_CREAT , 0644);
if(-1 == fd){
perror("open");
return 1;
}
printf("fd == %d \n", fd);
// 2.写文件
char* str = "hello world";
ret = write(fd, str, strlen(str));
if(-1 == ret){
perror("write");
return 1;
}
printf("write len =%d \n", ret);
// 3.lseek操作,从文件开头偏移32
ret = lseek(fd, 32, SEEK_SET);
if(-1 == ret){
perror("lseek");
return 1;
}
write(fd, "1234567890", 10);
// 读取
char buf[SIZE];
memset(buf, 0, SIZE);
// 重置文件偏移量
lseek(fd, 0, SEEK_SET);
ret = read(fd, buf, SIZE);
if(-1 == ret){
perror("read");
return 1;
}
printf("read len %d, content: %s \n", ret, buf);
// 4.close 文件
close(fd);
return 0;
}
写进去的文件内容为
执行结果,虽然读取到42个字符,但事实因为遇到0就认为字符串结束,所以只打印了前面几个字符
root@sony-HP-Notebook:/usr/local/cpp_demo/file# ./a.out
fd == 3
write len =11
read len 42, content: hello world
stat函数
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path, struct stat *buf);
int 1stat(const char *pathname, struct stat *buf);
功能:
获取文件状态信息
stat和1stat的区别:当文件是一个符号链接时,1stat返回的是该符号链接本身的信息;而stat返回的是该链接指向的文件的信息
参数:
path: 文件名
buf: 保存文件信息的结构体
返回值:
成功: 0
失败: -1
stat结构体
struct stat {
dev_t st_dev; //文件的设备编号
ino_t st_ino; //节点
mode_t st_mode; //文件的类型和存取的权限
nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1
uid_t st_uid; //用户ID
gid_t st_gid; //组ID
dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号
off_t st_size; //文件字节数(文件大小)
b1ksize_t st_b1ksize; //块大小(文件系统的I/ 缓冲区大小)
bIkcnt_t st_blocks; //块数
time_t st_atime; //最后一次访问时间
time_t st_mtime;//最后一次修改时间
time_t st_ctime;//最后一次改变时间(指属性)
}
输出文件信息
示例:stat_demo.c,可以通过stat test.txt
命令对比两个结果
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
int ret = -1;
struct stat s;
// 获取指定文件的信息
ret = stat("test.txt",&s);
if (-1 == ret)
{
perror("stat");
return 1;
}
// 文件属性信息
// 设备编码
printf("st_dev; %lu\n", s.st_dev);
// 节点
printf("st_ino: %ld\n",s.st_ino);
// 硬链接数量
printf("st_nlink: %lu\n",s.st_nlink);
// 用户id
printf("st_uid;%d\n",s.st_uid);
// 组户id
printf("st_gid;%d\n",s.st_gid);
// 文件大小
printf("st_size: %ld\n",s.st_size);
return 0;
}
获取文件类型
获取文件类型 stat_demo2.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <linux/stat.h>
//显示文件类型的第一个版本
int show_file_type(struct stat *s)
{
switch(s->st_mode & __S_IFMT)
{
//普通文件
case __S_IFREG:
printf("该文件是普通文件\n");
break;
//目录
case __S_IFDIR:
printf("该文件是目录\n");
break;
//字符设备
case __S_IFCHR:
printf("该文件是字符设备\n");
break;
//块设备
case __S_IFBLK:
printf("该文件是决设备\n");
break;
//套接字
case __S_IFSOCK:
printf("该文件是套接字\n");
break;
//管道
case __S_IFIFO:
printf("该文件是管道\n");
break;
//符号链接
case __S_IFLNK:
printf("该文件是符号链接\n");
break;
default:
printf("未知类型\n");
break;
}
return 0;
}
//显示文件类型的第二个版本
int show_file_type_v2(struct stat *s)
{
if(S_ISREG(s->st_mode))
{
printf("该文件是普通文件\n");
}else if(S_ISDIR(s->st_mode))
{
printf("该文件是目录\n");
}else if(S_ISCHR(s->st_mode))
{
printf("该文件是字符设备\n");
}else if(S_ISBLK(s->st_mode))
{
printf("该文件是决设备\n");
}else if(S_ISSOCK(s->st_mode))
{
printf("该文件是套接字\n");
}else if(S_ISLNK(s->st_mode))
{
printf("该文件是符号链接\n");
}else{
printf("未知类型\n");
}
return 0;
}
int main(int argc, char **argv)
{
int ret = -1;
struct stat s;
// 容错判断
if(2 != argc )
{
printf("usage: error args\n");
return 1;
}
// 获取指定文件的信息
ret = stat(argv[1],&s);
if (-1 == ret)
{
perror("stat");
return 1;
}
// 显示文件类型
show_file_type(&s);
show_file_type_v2(&s);
return 0;
}
运行
root@sony-HP-Notebook:/usr/local/cpp_demo/file# ./a.out /home
该文件是目录
root@sony-HP-Notebook:/usr/local/cpp_demo/file# ./a.out test.txt
该文件是普通文件
root@sony-HP-Notebook:/usr/local/cpp_demo/file# ./a.out /dev/input/mouse0
该文件是字符设备
root@sony-HP-Notebook:/usr/local/cpp_demo/file# ./a.out /dev/sda1
该文件是决设备
获取文件权限
//显示文件权限
int show_file_permission(struct stat *s)
{
// 判断文件所属者读权限
if(s->st_mode & S_IRUSR )
{
printf("r");
}else{
printf("-");
}
// 判断文件所属者写权限
if(s->st_mode & S_IWUSR )
{
printf("w");
}else{
printf("-");
}
// 判断文件所属者执行权限
if(s->st_mode & S_IXUSR )
{
printf("x");
}else{
printf("-");
}
// 判断文件所属组读权限
if(s->st_mode & S_IRGRP )
{
printf("r");
}else{
printf("-");
}
// 判断文件所属组写权限
if(s->st_mode & S_IWGRP )
{
printf("w");
}else{
printf("-");
}
// 判断文件所属组执行权限
if(s->st_mode & S_IXGRP )
{
printf("x");
}else{
printf("-");
}
// 判断文件其他人读权限
if(s->st_mode & S_IROTH )
{
printf("r");
}else{
printf("-");
}
// 判断文件其他人写权限
if(s->st_mode & S_IWOTH )
{
printf("w");
}else{
printf("-");
}
// 判断文件其他人执行权限
if(s->st_mode & S_IXOTH )
{
printf("x");
}else{
printf("-");
}
printf("\n");
}
运行
root@sony-HP-Notebook:/usr/local/cpp_demo/file# ./a.out test.txt
rw-r--r--
access函数
#include <unistd.h>
int access(const char *pathname, int mode);
功能:测试指定文件是否具有某种属性
参数
pathname: 文件名
mode: 文件权限,4种权限
R_OK:是否有读权限
W_OK:是否有写权限
X_OK:是否有执行权限
F_OK:测试文件是否存在
返回值:
0: 有某种权限,或者文件存在
-1: 没有,或文件不存在
chmod函数
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
功能:修改文件权限
参数:
filename: 文件名
mode: 权限(8进制数)
返回值:
成功:0
失败: -1
chown函数
#include <unistd.h>
int chown(const char *pathname, uid_t owner, gid_t group);
功能:修改文件所有者和所属组
参数:
pathname:文件或目录名
owner: 文件所有者id,通过查看 / etc / passwd 得到所有者id
group: 文件所属组id,通过查看 / etc / group 得到用户组id
返回值:
成功:0
失败: -1
truncate函数
#include <unistd.h>
#include <sys/types .h>
int truncate(const char *path, off_t length)
功能:修改文件大小参数:
path: 文件文件名字
length: 指定的文件大小
a)比原来小,删掉后边的部分
b)比原来大,向后拓展
返回值:
成功:0
失败:-1
link函数(硬链接)
#include <unistd.h>
int link(const char *oldpath, const char *newpath);
功能:创建一个硬链接
参数:
o1dpath: 源文件名字
newpath: 硬链接名字
返回值:
成功: 0
失败: -1
symlink函数(软链接)
#include <unistd.h>
int symlink(const char *target,const char *linkpath);
功能:创建一个软链接
参数:
target: 源文件名字
linkpath: 软链接名字
返回值:
成功: 0
失败:-1
readlink函数
#include <unistd.h>
ssize_t readlink(const char *pathname, char *buf,size_t bufsiz);
功能:读软连接对应的文件名,不是读内容(该函数只能读软链接文件)
参数:
pathname:软连接名
buf: 存放软件对应的文件名
bufsiz :缓冲区大小(第二个参数存放的最大字节数)
返回值:
成功: >0,读到buf中的字符个数
失败:-1
unlink函数
#include <unistd.h>
int unlink(const char *pathname);
功能: 删除一个文件(软硬链接文件)
参数:
pathname:删除的文件名字
返回值:
成功: 0
失败:-1
rename函数
#include <stdio.h>
int rename(const char *oldpath,const char *newpath);
功能: 把o1dpath的文件名改为newpath
参数:
oldpath: 旧文件名
newpath: 新文件名
返回值:
成功: 0
失败: -1
阻塞和非阻塞的概念
读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回
从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里
同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定
[注意]阻塞与非阻塞是对于文件而言的,而不是指ead、write等的属性以非阻塞方式打开文件
阻塞程序示例:
#include <stdio.h>
int main()
{
char ch = -1;
// 从标准输入获取一个字符
ch = getchar();
// 从标准输出
putchar(ch);
}
示例
读写示例 file_demo.c
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd = -1;
// 1. 以只读方式打开一个文件,如果文件不存在就报错
//fd = open("txt", O_RDONLY);
// 2. 以只写的方式打开文件,如果不存在则新建
// fd = open("test.txt", O_WRONLY | O_CREAT , 0644);
// 3. 以只写的方式打开文件,如果文件存在就报错,如果不存在则新建
// fd = open("test.txt", O_WRONLY | O_CREAT | O_EXCL , 0644);
// 4. 以读写的方式打开文件,如果不存在则新建
// fd = open("test.txt", O_RDWR | O_CREAT , 0644);
// 5. 以只写的方式打开文件,如果文件存在就清空,如果不存在则新建
// fd = open("test.txt", O_WRONLY | O_TRUNC | O_CREAT , 0644);
// 6. 以只写的方式打开文件,如果文件不存在则报错,如果存在则追加写
fd = open("test.txt", O_WRONLY | O_APPEND , 0644);
if(-1 == fd){
perror("open");
return 1;
}
printf("fd == %d \n", fd);
// close 文件
close(fd);
return 0;
}