目录
1.文件IO是什么?
2.Linux中具体的一些API函数接口(系统IO)
1.打开文件(open)
ps(权限的指定方法):
1.使用OS定义的宏标识权限
2.直接使用八进制数字标识权限
函数返回值:
打开成功
打开失败
2.关闭文件(close)
3.读写文件(read\write)
3.1write:把自己的数据放到文件里面去
read:从文件中把数据拿出来
4.定位文件的光标(偏移量 lseek)
5.设置文件的掩码(umask)
6.获取和修改程序的当前工作路径
如何获取当前工作路径
修改当前的工作路径:
7.文件截短(truncate)
8.删除文件
9.获取文件的属性(stat)
10.目录操作
10.1目录和普通文件的区别
10.2目录操作的API函数
a.打开目录(opendir)
b.读取目录项(readdir)
c.关闭目录(closedir)
首先我们要了解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:操作系统提供给用户操作文件的接口!!!!
注意:
系统IO提供的API函数接口有很多,在这里我们只提及其中一小部分(注重方法和基础的API)
NAME
open, openat, creat - open and possibly create a file
打开或者创建一个文件
SYNOPSIS
#include
#include
#include
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
以读写的方式打开,文件不存在则创建,文件存在则清空内容
指定文件的权限,当第二个参数中带有"O_CREAT"时,必须指定创建的文件的权限,有两种指定方式:
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
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
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);
NAME
close - close a file descriptor
SYNOPSIS
#include
int close(int fd);
NAME
write - write to a file descriptor
SYNOPSIS
#include
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;
}
NAME
read - read from a file descriptor
SYNOPSIS
#include
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;
}
NAME
lseek - reposition read/write file offset
SYNOPSIS
#include
#include
定位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);
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
#include
mode_t umask(mode_t mask);
mask:你要指定的新的文件掩码
返回值:
返回上一次的文件掩码
在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
把获取到的工作路径(绝对路径)保存到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
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被设置
NAME
truncate, ftruncate - truncate a file to a specified length
截短一个文件到指定的长度
SYNOPSIS
#include
#include
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);
rm 删除文件
rmdir 删除空目录
unlink //删除一个普通文件
rmdir //删除一个空目录
remove //删除一个普通文件或者一个空目录
NAME
unlink, unlinkat - delete a name and possibly the file it refers to
SYNOPSIS
#include
int unlink(const char *pathname);
删除一个文件的时候仅仅只是标记inode没有被使用了
NAME
rmdir - delete a directory
SYNOPSIS
#include
int rmdir(const char *pathname);
必须是空的
NAME
remove - remove a file or directory
SYNOPSIS
#include
int remove(const char *pathname);
ps.文件描述符是进程文件表项的下标(数组的下标),是大于0的整数
系统IO是操作系统提供给用户操作文件的接口(一系列的API函数的统称)
任何一个文件都有自己的属性(inode中的内容)
man -a inode
NAME
stat, fstat, lstat, fstatat - get file status
SYNOPSIS
#include
#include
#include
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_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_t time(time_t *tloc);
=============================
time_t tm = time(NULL);
or
time_t tm;
time(&tm);
如何获取更加精准的时间:
NAME
gettimeofday, settimeofday - get / set time
SYNOPSIS
#include
//微秒级别的时间
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 */ //纳秒
};
练习:
尝试使用上面的函数(获取文件的属性)
目录在Linux中也是文件,我们能不能按照操作普通文件的方式去操作目录呢?
普通文件:
打开文件
读写文件
关闭文件
如果可以,那么目录的内容是什么呢?
在Linux中,目录也是文件,也可以使用open打开(O_RDONLY)
也会返回一个文件描述符,但是我们使用read去读取内容的时候,会失败
read failed: Is a directory
那么目录文件应该如何操作呢?
在Linux中,任何一个文件只要存在,就有自己的inode编号
目录文件也有自己的inode,同样保存了文件的属性信息(stat同样可以获取属性)
但是目录文件的内容和普通文件的内容有很大的差别
普通文件的内容就是用户记录的一些用户数据
目录文件的内容记录的文件和文件之间的组织关系,叫做"目录项"
可以理解为一个"二维表格",记录着当前目录下面的文件名和inode的对应关系
在创建一个空目录的时候,系统会自动的为目录预留一个"目录项数组"
把该目录下面的所有文件(第一层)都记录在这个"数组"中
NAME
opendir, fdopendir - open a directory
SYNOPSIS
#include
#include
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类型的指针表示一个已经打开的目录就可以了,后序操作这个目录的时候,使用这个指针表示这个目录即可
通过读取目录项,就可以知道目录中有哪些文件了
NAME
readdir - read a directory
SYNOPSIS
#include
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.
NAME
closedir - close a directory
SYNOPSIS
#include
#include
int closedir(DIR *dirp);