目录
1.文件IO是什么?
首先我们要了解IO的定义 IO:input output 指的是对文件的输入和输出操作的基本函数接口
文件IO分为系统IO和标准IO
Linux有一个设计思想:Everything is a file in Linux
这对对文件的操作接口是很重要的
文件系统:是用来存储,组织和管理文件的一套方法和规则
存储文件一般分为两个部分:
文件的属性:i-node唯一的标识一个文件的存在
文件本身的内容(用户数据)
Linux中到底如何组织和存放文件的呢? 如图:
大概步骤:
struct inode{}
用来描述一个文件的物理inode信息.系统识别到一个文件的存在,就会为它创建一个struct inode的结构体,一个文件只会唯一的对应一个struct inode
如果打开了某一个文件
使用struct file的结构体表示这个打开的文件
一个文件可以同时被多个进程打开,一个进程也可以同时打开多个文件
一个进程同时打开了多个文件,意味着需要保存每一个打开的文件的struct file
使用一个数组保存了所有struct file结构体的地址(结构体指针数组)
对于用户来说,我们操作文件的时候只需要知道数组的下标,就可以去操作这个文件,这个下标在用户的眼中,叫做文件描述符
操作文件的内部流程:
数字(文件描述符)
------->
进程文件表项的内容(结构体指针数组)
------->
struct file
------->
struct inde
------->
硬件上面的inode
------->
文件的本身的内容
为了方便,Linux把上面所有的流程都封装起来了,用户不需要知道具体的操作细节
只需要调用OS提供给我们的API函数接口就可以了
Linux提供的这些用于操作文件的接口(如:open,read,write...)我们称之为系统IO
系统IO:操作系统提供给用户操作文件的接口!!!!
2.Linux中具体的一些API函数接口(系统IO)
注意:
系统IO提供的API函数接口有很多,在这里我们只提及其中一小部分(注重方法和基础的API)
1.打开文件(open)
NAME
open, openat, creat - open and possibly create a file
打开或者创建一个文件
SYNOPSIS
#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);
注释:
pathname:要打开或者创建的文件的路径名(如果不写路径,默认就是程序的当前路径)
如: "/home/china/1.txt" or "1.txt"
flags:打开文件的标记(使用位域实现,可以添加多个标记)
O_RDWR read and write 读写打开
O_RDONLY read only 只读打开
O_WRONLY write only 只写打开
以上的三个标记只能选一个
O_APPEND
追加标记,打开文件的时候,文件的偏移量(光标)在文件的末尾
默认情况下文件的偏移量在文件开头
偏移量可以看做是文件的光标(读和写的位置)
O_CREAT
创建标记,如果文件不存在,则创建这个文件
O_EXCL
和O_CREAT配合使用,用来测试文件是否存在
同时指定这两个标记,文件存在则会报错,并且errno被设置为EEXIST,文件不存在则创建
O_TRUNC
truncate截短,在文件打开的时候,把文件的内容清空
O_NONBLOCK
以非阻塞的方式打开文件,文件的默认打开方式是阻塞打开的
阻塞:等待
如果文件暂时没有内容可读,read这个文件就会等待(直到有数据可读)
如果文件暂时没有空间,write这个文件就会等待(直到有空间)
非阻塞:不等待
如果文件暂时没有内容可读,read这个文件就不会等待,直接返回一个错误
如果文件暂时没有空间,write这个文件不会等待,直接返回一个错误
....
多个标记可以使用 | 连接
(Linux中的标志大部分都是使用位域实现的,一个整数的某些bit有特殊的含义)
O_RDWR | O_CREAT | O_TRUNC
以读写的方式打开,文件不存在则创建,文件存在则清空内容
ps(权限的指定方法):
指定文件的权限,当第二个参数中带有"O_CREAT"时,必须指定创建的文件的权限,有两种指定方式:
1.使用OS定义的宏标识权限
S_IRWXU 00700 user (file owner) has read, write, and execute permission
S_IRUSR 00400 user has read permission、
S_IWUSR 00200 user has write permission
S_IXUSR 00100 user has execute permission
S_IRWXG 00070 group has read, write, and execute permission
S_IRGRP 00040 group has read permission
S_IWGRP 00020 group has write permission
S_IXGRP 00010 group has execute permission
S_IRWXO 00007 others have read, write, and execute permission
S_IROTH 00004 others have read permission
S_IWOTH 00002 others have write permission
S_IXOTH 00001 others have execute permission
U/USR 用户(文件的拥有者)
G/GRP 组用户
O/OTH 其他用户
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
rw-r--r--
0644
2.直接使用八进制数字标识权限
0777
111 111 111
rwx rwx rwx
0664
110 110 100
rw- rw- r--
函数返回值:
打开成功
返回打开的文件的文件描述符(进程文件表项的下标),是一个整数
>2 && int && 未使用中的最小值
因为操作系统会为每一个进程打开三个文件
标准输入文件(键盘) 文件描述符 STDIN_FILENO 0
标准输出文件(终端) 文件描述符 STDOUT_FILENO 1
标准错误文件(终端) 文件描述符 STDERR_FILENO 2
后序操作这个文件的时候,就可以直接使用这个数字表示
打开失败
返回-1,同时errno被设置
errno是一个全局变量,表示的是最后一次调用系统函数出错的错误码
NAME
perror - print a system error message
打印系统的错误信息
SYNOPSIS
#include <stdio.h>
void perror(const char *s);
perror可以把当前的错误码转化为对应的字符串并且打印出来
perror("用户提示性字符串:");
======>
用户提示性字符串:errno转化之后的错误字符串
int creat(const char *pathname, mode_t mode);
int openat(int dirfd, const char *pathname, int flags);
int openat(int dirfd, const char *pathname, int flags, mode_t mode);
open("/home/china/1.txt",O_RDWR);
----->
dirfd = open("/home/china",I_RDONLY);
openat(dirfd,"1.txt",O_RDWR);
2.关闭文件(close)
NAME
close - close a file descriptor
SYNOPSIS
#include <unistd.h>
int close(int fd);
3.读写文件(read\write)
3.1write:把自己的数据放到文件里面去
NAME
write - write to a file descriptor
SYNOPSIS
#include <unistd.h>
write的作用是把buf指针指向的内存的前面count个字节写入到fd表示的文件中去
ssize_t write(int fd, const void *buf, size_t count);
fd:你要把内容写入带哪一个文件中去(open的返回值)
buf:指针,指向一段内存地址,存储了你要写入的数据
为什么要使用const呢?
从语义来说,在write函数的内部不应该通过buf去修改数据
count:字节数量,表示你要写入多少个字节
返回值:
>0 返回实际写入到文件中的数据(有可能小于count)
=0 表示什么也没写入
-1 表示写入失败,同时errno被设置
写入的位置位于文件的光标位置
fd1=write(fd,buf,1024);
if(fd1==-1)
{
perror("write 2.text");
return -1;
}
read:从文件中把数据拿出来
NAME
read - read from a file descriptor
SYNOPSIS
#include <unistd.h>
read是从指定的文件中(fd)读取count个字节,存放到buf指向的内存空间中去
ssize_t read(int fd, void *buf, size_t count);
fd:你要从哪一个文件中读取内容(open的返回值)
buf:指针,指向一段可用的内存空间,表示你要把读取到的数据存放到哪一个位置,不能是野指针,也不能是空指针
count:字节数量,表示你要读取多少个字节
返回值:
>0 返回实际读取到的数据数量(有可能小于count)
=0 表示什么也没读到
-1 表示读取失败,同时errno被设置
读取的位置位于文件的光标位置
注意:
文件的偏移量由内核自动维护,一般来说,打开文件的时候,offset=0
每一次成功的读和写都会让偏移量改变
你读/写了count个字节
offset += count
在读写文件的时候,要注意文件的光标所在位置
char buf[1024]={0};
int fd1=read(fd,buf,1024);
if(fd1==-1)
{
perror("read 1.text");
return -1;
}
4.定位文件的光标(偏移量 lseek)
NAME
lseek - reposition read/write file offset
SYNOPSIS
#include <sys/types.h>
#include <unistd.h>
定位fd表示的文件的偏移量
off_t lseek(int fd, off_t offset, int whence);
fd:你要定位的文件的文件描述符
offset:偏移量,具体的新位置需要结合第三个参数使用
whence:定义标记,有三种
SEEK_SET 基于文件开头定位
新位置 = 文件开头 + offset(>=0)
SEEK_CUR 基于文件当前位置定位
新位置 = 文件当前光标位置 + offset(可正可负)
SEEK_END 基于文件结尾定位
新位置 = 文件结尾 + offset(可正可负)
如:
定位到文件开头
lseek(fd,0,SEEK_SET);
定位到文件结尾
lseek(fd,0,SEEK_END);
返回值:
成功返回新光标位置离文件开头的字节数量
失败返回-1,同时errno被设置
也可以利用lseek计算文件大小
size = lseek(fd,0,SEEK_END);
5.设置文件的掩码(umask)
umask 表示创建文件时权限的掩码
创建文件时,不能指定umask中值为1的bit umask ------>0002 000 000 010
在创建文件的时候,不能指定umask中值为1的bit(指定了也会忽略)
mode = mode & (~umask)
umask: 0002 000 000 010
mode: 0777 111 111 111
&
~umask: 0002 111 111 101
-------------------------------
111 111 101
默认情况下,组用户和其他用户的写权限是不能指定的(0022)
可以通过命令或者函数修改:
命令:
umask + 新的掩码
函数:
NAME
umask - set file mode creation mask
SYNOPSIS
#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
mask:你要指定的新的文件掩码
返回值:
返回上一次的文件掩码
6.获取和修改程序的当前工作路径
在Linux中,任意一个程序都有一个工作路径
工作路径:在哪一个目录里面运行这个程序,这个程序的工作路径就在哪里(不管你的程序存储在哪一个位置)
如:
你有 /home/china/123/abc/a.out
当前在: /home/china/123/abc 运行:a.out
运行命令: ./a.out
工作路径: /home/china/123/abc
当前在: /home/china 运行:a.out
运行命令: ./123/abc/a.out
工作路径: /home/china
有什么意义呢?
你如果在a.out中写了
int fd = open("1.txt", O_RDWR | O_CREAT ,0664);
这里的 "1.txt" 是相对路径,此相对路径是相对于工作路径而言的
如果你的工作路径是:/home/china/123/abc
就会去/home/china/123/abc找1.txt
如果你的工作路径是:/home/china
就会去/home/china找1.txt
如何获取当前工作路径
NAME
getcwd, getwd, get_current_dir_name - get current working directory
SYNOPSIS
#include <unistd.h>
把获取到的工作路径(绝对路径)保存到buf指向的内存空间(必须可用)
char *getwd(char *buf);
buf:是用来保存获取到的工作路径的
返回值:
成功返回获取到的工作路径字符串的首地址(buf)
失败返回NULL,同时errno被设置
警告: the `getwd' function is dangerous and should not be used.
getwd有一个bug,不应该被使用,可能会造成内存越界
如果buf指向的空间不够大(当前目录字符串长度超出了buf指向的空间)
getwd就会访问buf后面的空间(造成数据会误修改)
getcwd是getwd的升级版本
char *getcwd(char *buf, size_t size);
buf:是用来保存获取到的工作路径的
size:指定了buf可用空间的大小,如果当前的目录长度字符串超过了size-1,这个函数就会报错
返回值:
成功返回获取到的工作路径字符串的首地址(buf)
失败返回NULL,同时errno被设置
get_current_dir_name也是获取当前的工作路径,只不过这个函数不需要你给定空间,会在函数内部malloc自动分配足够长的空间,保存获取到的工作路径,并且返回首地址
所以为了防止内存泄漏,使用者使用完毕之后.应该free这个空间
char *get_current_dir_name(void);
返回值:
成功返回获取到的工作路径字符串的首地址
失败返回NULL,同时errno被设置
修改当前的工作路径:
改变当前的工作路径
NAME
chdir, fchdir - change working directory
SYNOPSIS
#include <unistd.h>
int chdir(const char *path);
path:要切换到的工作路径的目录字符串
chdir("/home/china/");
int fchdir(int fd);
int fd = open("/home/china/",O_RDONLY) ;
fchdir(fd);
返回值:
成功会改变当前的工作路径,返回0
失败返回-1,同时errno被设置
7.文件截短(truncate)
NAME
truncate, ftruncate - truncate a file to a specified length
截短一个文件到指定的长度
SYNOPSIS
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
path: 你要截短的文件的路径名(相对路径/绝对路径)
绝对路径------>工作路径+path
length: 截短之后的文件长度
length < 原来的长度
"截短":文件变成指定的长度
length > 原来的长度
"留空洞"
返回值:
成功返回0
失败返回-1,同时errno被设置
int ftruncate(int fd, off_t length);
=====>
int fd = open(...);
ftruncate(fd,length);
8.删除文件
rm 删除文件
rmdir 删除空目录
unlink //删除一个普通文件
rmdir //删除一个空目录
remove //删除一个普通文件或者一个空目录
NAME
unlink, unlinkat - delete a name and possibly the file it refers to
SYNOPSIS
#include <unistd.h>
int unlink(const char *pathname);
删除一个文件的时候仅仅只是标记inode没有被使用了
NAME
rmdir - delete a directory
SYNOPSIS
#include <unistd.h>
int rmdir(const char *pathname);
必须是空的
NAME
remove - remove a file or directory
SYNOPSIS
#include <stdio.h>
int remove(const char *pathname);
ps.文件描述符是进程文件表项的下标(数组的下标),是大于0的整数
系统IO是操作系统提供给用户操作文件的接口(一系列的API函数的统称)
9.获取文件的属性(stat)
任何一个文件都有自己的属性(inode中的内容)
man -a inode
NAME
stat, fstat, lstat, fstatat - get file status
SYNOPSIS
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
stat是用来获取pathname指定的文件的属性的,获取到的属性保存到statbuf指针指向的内存中
int stat(const char *pathname, struct stat *statbuf);
pathname:你要获取哪一个文件的属性(路径名)
statbuf:指针,指向一块可用的空间,用来保存获取到的文件的属性的
返回值:
成功返回0,失败返回-1,同时errno被设置
struct stat *statbuf = NULL;
int r = stat("1.txt", statbuf); //ERROR
==============>
struct stat statbuf;
//struct stat *p = malloc(sizeof(*p));
int r = stat("1.txt", &statbuf);
fstat功能和stat类似,只不过需要提供文件描述符,需要提前打开文件
int fstat(int fd, struct stat *statbuf);
lstat功能和stat类似,只不过当pathname是一个符号链接的时候,lstat获取的是符号链接本身的属性信息(软连接同样有自己的inode)
int lstat(const char *pathname, struct stat *statbuf);
文件B是文件A的符号链接(ln -s A B)
B----->A
stat(B) 获取的是A的inode的信息
lstat(B) 获取的是B的inode的信息 文件的属性:
实际上Linux系统是使用一个结构体保存文件的所有属性信息
struct stat {
dev_t st_dev; /* ID of device containing file */
//容纳该文件的设备的设备号码
ino_t st_ino; /* Inode number */
//inode号码
mode_t st_mode; /* File type and mode */
//保存了文件的权限和类型
nlink_t st_nlink; /* Number of hard links */
硬链接数量
uid_t st_uid; /* User ID of owner */
文件的所有者ID
gid_t st_gid; /* Group ID of owner */
文件的组ID
dev_t st_rdev; /* Device ID (if special file) */
如果文件是一个特殊的设备,设备号码
off_t st_size; /* Total size, in bytes */
文件的大小(字节数量)
blksize_t st_blksize; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
/* Since Linux 2.6, the kernel supports nanosecond
precision for the following timestamp fields.
For the details before Linux 2.6, see NOTES. */
struct timespec st_atim; /* Time of last access */
最后访问时间
struct timespec st_mtim; /* Time of last modification */
最后修改时间(修改了用户数据)
struct timespec st_ctim; /* Time of last status change
*/ 最后修改时间 (修改了属性信息,inode中的内容)
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
解析权限和类型的方式:
mode_t st_mode; /* File type and mode */
//保存了文件的权限和类型
st_mode使用位域实现,可以使用以下的宏去解析这个变量
如:
//获取文件的属性
struct stat st;
int r = stat("1.txt", &st);
st.st_mode就保存了1.txt的文件的权限和类型
文件的类型
S_IFMT 0170000 bit mask for the file type bit field
S_IFSOCK 0140000 socket
S_IFLNK 0120000 symbolic link
S_IFREG 0100000 regular file
S_IFBLK 0060000 block device
S_IFDIR 0040000 directory
S_IFCHR 0020000 character device
S_IFIFO 0010000 FIFO
to test for a regular file (for example)
if ((st.st_mode & S_IFMT) == S_IFREG)
{
//当前文件是一个普通文件
}
或者:
printf("File type: ");
switch (sb.st_mode & S_IFMT)
{
case S_IFBLK: printf("block device\n"); break;
case S_IFCHR: printf("character device\n"); break;
case S_IFDIR: printf("directory\n"); break;
case S_IFIFO: printf("FIFO/pipe\n"); break;
case S_IFLNK: printf("symlink\n"); break;
case S_IFREG: printf("regular file\n"); break;
case S_IFSOCK: printf("socket\n"); break;
default: printf("unknown?\n"); break;
}
或者:
S_ISREG(m) is it a regular file?
S_ISDIR(m) directory?
S_ISCHR(m) character device?
S_ISBLK(m) block device?
S_ISFIFO(m) FIFO (named pipe)?
S_ISLNK(m) symbolic link? (Not in POSIX.1-1996.)
S_ISSOCK(m) socket? (Not in POSIX.1-1996.)
stat(pathname, &st);
if (S_ISREG(st.st_mode))
{
//当前文件是一个普通文件
}
文件的权限
printf("Mode: %lo (octal)\n",(unsigned long) st.st_mode);
if(st.st_mode & S_IRUSR)
{
//用户拥有可读的权限
}else
{
//用户,不拥有可读的权限
}
但是不管你使用的是哪一个函数(stat/fstat/lstat)都是获取到上面结构体的信息
注意时间格式转化
struct timespec
{
time_t tv_sec; /* seconds */
秒,记录的是从1970年1月1日到现在的秒数
long tv_nsec; /* nanoseconds */ //纳秒
};
1s == 1000 ms
1ms == 1000 us
1us == 1000 ns
既然你给出的是一个秒数,如何把一个秒数转化为时间呢?
#include <time.h>
time_t time(time_t *tloc); //获取本地时间秒数
char *ctime(const time_t *timep); //把秒数转化为时间字符串
timep:你要转化的秒数
可以把当前的秒数转化为一个表示时间的字符串
如:
printf("%s\n",ctime(&st.st_atim.tv_sec));
or
printf("%s\n",ctime(&st.st_atime));
struct tm *localtime(const time_t *timep);
可以把一个当前的秒数转化为一个表示时间的结构体
struct tm {
int tm_sec; /* Seconds (0-60) */
int tm_min; /* Minutes (0-59) */
int tm_hour; /* Hours (0-23) */
int tm_mday; /* Day of the month (1-31) */
int tm_mon; /* Month (0-11) */
int tm_year; /* Year - 1900 */
int tm_wday; /* Day of the week (0-6, Sunday = 0) */
int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */
int tm_isdst; /* Daylight saving time */
};
如何获取系统时间:
NAME
time - get time in seconds
SYNOPSIS
#include <time.h>
time_t time(time_t *tloc);
=============================
time_t tm = time(NULL);
or
time_t tm;
time(&tm);
如何获取更加精准的时间:
NAME
gettimeofday, settimeofday - get / set time
SYNOPSIS
#include <sys/time.h>
//微秒级别的时间
int gettimeofday(struct timeval *tv, struct timezone *tz);
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
//纳秒级别的时间
int clock_gettime(clockid_t clk_id, struct timespec *tp);
struct timespec
{
time_t tv_sec; /* seconds */
秒,记录的是从1970年1月1日到现在的秒数
long tv_nsec; /* nanoseconds */ //纳秒
};
练习:
尝试使用上面的函数(获取文件的属性)
10.目录操作
目录在Linux中也是文件,我们能不能按照操作普通文件的方式去操作目录呢?
普通文件:
打开文件
读写文件
关闭文件
如果可以,那么目录的内容是什么呢?
在Linux中,目录也是文件,也可以使用open打开(O_RDONLY)
也会返回一个文件描述符,但是我们使用read去读取内容的时候,会失败
read failed: Is a directory
那么目录文件应该如何操作呢?
10.1目录和普通文件的区别
在Linux中,任何一个文件只要存在,就有自己的inode编号
目录文件也有自己的inode,同样保存了文件的属性信息(stat同样可以获取属性)
但是目录文件的内容和普通文件的内容有很大的差别
普通文件的内容就是用户记录的一些用户数据
目录文件的内容记录的文件和文件之间的组织关系,叫做"目录项"
可以理解为一个"二维表格",记录着当前目录下面的文件名和inode的对应关系
在创建一个空目录的时候,系统会自动的为目录预留一个"目录项数组"
把该目录下面的所有文件(第一层)都记录在这个"数组"中
10.2目录操作的API函数
a.打开目录(opendir)
NAME
opendir, fdopendir - open a directory
SYNOPSIS
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
DIR *dir = opendir("/home/china");
========================
DIR *fdopendir(int fd);
int fd = open("/home/china",O_RDONLY);
DIR *dir = fdopendir(fd);
name/fd:你要打开的目录的路径名/文件描述符
返回值:
成功就会返回一个DIR指针(指向当前目录项的指针)
失败返回NULL,同时errno被设置
在Linux中,使用DIR结构体表示一个打开的目录,至于目录里面有什么,不需要关心,只需要知道DIR类型的指针表示一个已经打开的目录就可以了,后序操作这个目录的时候,使用这个指针表示这个目录即可
b.读取目录项(readdir)
通过读取目录项,就可以知道目录中有哪些文件了
NAME
readdir - read a directory
SYNOPSIS
#include <dirent.h>
readdir是用来从dirp指向的目录中,读取下一个"目录项"的指针,一个目录中有多少个"目录项",就有多少个文件
每一次调用readdir,都会给你返回一个指向目录项的指针,并且让指针指向下一个目录项(偏移量),直到返回NULL,表示读取完毕
struct dirent *readdir(DIR *dirp);
dirp:指向你要读取目录项的目录(是opendir的返回值)
In the glibc implementation, the dirent structure isdefined as follows:
struct dirent {
ino_t d_ino; /* Inode number */
当前读取到的目录项的inode编号
off_t d_off; /* Not an offset; see below */
unsigned short d_reclen; /* Length of this record */
unsigned char d_type; /* Type of file; not supported
by all filesystem types */
//文件的类型
char d_name[256]; /* Null-terminated filename */
//文件的名字
};
注意:
该结构体的成员,只有d_ino,和d_name两个成员是所有文件系统都支持的,如果你想要你的代码有更好的兼容性和可移植性,在代码中尽量只使用这两个成员
返回值:
On success, readdir() returns a pointer to a dirent structure. (This structure may be statically allocated; do not attempt to free(3) it.)-------(这意味着它的空间不是在运行时分配的,例如堆栈或空闲存储内存,而是:它在可执行文件本身中,与字符串文字相比,区别在于写入字符串文字是未定义的行为。)
If the end of the directory stream is reached, NULL is returned and errno is not changed.
If an error occurs, NULL is returned and errno is set appropriately.
c.关闭目录(closedir)
NAME
closedir - close a directory
SYNOPSIS
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);