1.文件IO和标准IO
文件IO: 遵照POSIX(可移植操作系统接口)规则,无缓冲,通过文件描述符操作。
标准IO: 遵照ANSIC规则,带缓冲区,流FILE,
Linux下,标准IO基于文件IO
2.文件描述符
每个打开的文件都对应一个文件描述符;
文件描述符是一个非负数。linux为程序中每个打开的文件分配一个文件描述符
文件描述符从0开始分配,依次递增。
每个程序分配的文件描述符都是从0开始,不同程序不影响。
标准输入,标准输出和标准错误分别占用0,1和2。
3.打开文件
open函数用开创建或打开一个文件
#include "fcontl.h"
int open(cosnt char *path, int oflag, ...);
path: 要打开的文件的路径。
oflag: 指定打开方式
O_RDONLY : 只读方式打开
O_WRONLY : 可写方式打开
O_RDWR : 读写方式打开
注:这三个参数互斥,只能设置一个。
O_CREAT : 如果文件不存在,就创建一个新文件,此时必须设置第三个参数用于权限设置。
O_EXCL : 如果使用O_CREAT是文件存在,则返回错误消息。这一参数可测试文件是否存在。
O_TRUNC : 如果文件已经存在,那么打开文件时先删除源文件数据
O_APPEND : 以添加的方式打开文件,所以对文件的写操作都在文件的末尾进行。
返回值: 返回文件的文件描述符。出错时返回EOF。
打开文件时,使用两个参数。
创建文件是第三个参数指定新文件的权限(具体的权限与umask有关)。
注:只能打开设备文件。
例子:
以只写的方式打开文件1.txt,如果文件不存在则创建,如果文件存在则清空。
int fd; if((fd = open("1.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666) < 0) { perror("open"); return -1; }
以只读的方式打开文件1.txt,如果文件不存在则创建,如果文件存在则报错。
int fd; if((fd = open("1.txt", O_RDONLY | O_CREAT | O_EXCL, 0666) < 0) { if(error == EEXIST) { perror("exist error"); } else { perror("other error"); } return -1; }
4.关闭文件
close函数用开关闭一个打开的文件
#include <unistd.h>
int close(int fd);
fd : 文件标识符
成功返回0;出错是返回EOF。
文件一旦关闭就不能在使用这个文件了。
5.读取文件
read函数用来从文件中读取数据:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
fd: 文件标识符
buf: 缓冲区首地址
count: 读取大小,不能超过缓冲区的大小,一般指定为缓冲区的大小。
返回值:成功是返回实际读取的字节数;出错返回EOF。
读到文件末尾返回0;
6.写文件
write函数用来从文件中读取数据:
#include <unistd.h>
ssize_t write(int fd, void *buf, size_t count);
fd: 文件标识符
buf: 缓冲区首地址
count: 指定写入的个数,一般指定为缓冲区的大小或者小于buf的大小。
返回值:成功是返回实际写入的字节数;出错返回EOF。成功返回值等于第三个参数。
7.文件定位
lseek函数用于定位文件
#include <unistd.h>
off_t lseek(int id, off_t offset, intt whence);
id : 文件描述符
offset 指的是相对于whence的偏移量,可正可负,不能小于0。
whence 预定义宏,
SEEK_SET 文件开头
SEEK_CUR 当前位置
SEEK_END 文件结尾
返回值:
成功返回当前的文件读写位置,出错返回EOF。
#include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #define N 64 int main(int argc, char const *argv[]) { /* code */ int fds, fdt, n; char buf[N]; if(argc != 3) { printf("usage :%s <str_file> <dst_file> ", argv[0]); return -1; } if((fds = open(argv[1], O_RDONLY)) == -1) { fprintf(stderr, "open %s : %s error ", argv[1],strerror(error)); return -1; } if((fdt = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666)) == -1) { fprintf(stderr, "open %s : %s error ", argv[2],strerror(error)); //关闭第一个已经打开的文件 close(fds); return -1; } while( (n = read(fds, buf, N)) > 0) { write(fdt,bug,n); } close(fds); close(fdt); return 0; }
8.文件知识补充
1、硬盘中的静态文件和inode(i节点)
(1)文件平时都在存放在硬盘中的,硬盘中存储的文件以一种固定的形式存放的,我们叫静态文件。
(2)一块硬盘中可以分为两大区域:一个是硬盘内容管理表项,另一个是真正存储内容的区域。操作系统访问硬盘时是先去读取硬盘内容管理表,从中找到我们要访问的那个文件的扇区级别的信息,然后再用这个信息去查询真正存储内容的区域,最后得到我们要的文件。
(3)操作系统最初拿到的信息是文件名,最终得到的是文件内容。第一步就是去查询硬盘内容管理表,这个管理表中以文件为单位记录了各个文件的各种信息,每一个文件有一个信息列表,我们叫inode,i节点(其实质是一个结构体,这个结构体有很多元素,每个元素记录了这个文件的一些信息,其中就包括文件名、文件在硬盘上对应的扇区号、块号那些东西·····)
强调:硬盘管理的时候是以文件为单位的,每个文件一个inode,每个inode有一个数字编号,对应一个结构体,结构体中记录了各种信息。
(4)联系实际,格式化硬盘(U盘)时发现有:快速格式化和底层格式化。
快速格式化非常快,格式化一个32GB的U盘只要1秒钟,普通格式化格式化速度慢。这两个的差异?
快速格式化只删除了U盘中的硬盘内容管理表(其实就是inode),真正存储的内容没有动。这种格式化的内容是有可能被找回的。
2、内存中被打开的文件和vnode(v节点)
(1)在程序中打开的文件就属于某个进程。每个进程都有一个数据结构用来记录这个进程的所有信息(叫进程信息表),表中有一个指针会指向一个文件管理表,文件管理表中记录了当前进程打开的所有文件及其相关信息。文件管理表中用来索引各个打开的文件的index就是文件描述符fd,我们最终找到的就是一个已经被打开的文件的管理结构体vnode
(2)vnode中就记录了一个被打开的文件的各种信息,我们只要知道这个文件的fd,就可以很容易的找到这个文件的vnode进而对这个文件进行各种操作。
9.文件共享
1、文件共享就是同一个文件(指的是同一个inode,同一个pathname)被多个独立的读写体(可以理解为多个文件描述符)去同时(一个打开尚未关闭的同时另一个去操作)操作。
2、3种文件共享的情况:
第一种是同一个进程中多次使用open打开同一个文件
第二种是在不同进程中去分别使用open打开同一个文件(这时候因为两个fd在不同的进程中,所以两个fd的数字可以相同也可以不同)
第三种情况是后面要学的,linux系统提供了dup和dup2两个API来让进程复制文件描述符。
10 复制文件描述符
1、dup和dup2函数介绍
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
oldfd:要复制的文件描述符
返回值:返回一个新的文件描述符
区别:dup返回的文件描述符由系统自动分配,dup2可以指定新的文件描述符。
使用案例:可以将close(1)与dup配合使用进行输出重定位。
使用复制文件描述符的方式进行操作时时接续写。
二 目录操作
1.打开目录
opendir函数用开打开一个目录文件
#include <dirent.h>
DIR *opendir(const char *name);
DIR是用来描述一个打开的目录文件的结构体类型。
成功返回目录流指针;失败返回NULL。
注:
struct __dirstream { void *__fd; /* `struct hurd_fd' pointer for descriptor. */ char *__data; /* Directory block. */ int __entry_data; /* Entry number `__data' corresponds to. */ char *__ptr; /* Current pointer into the block. */ int __entry_ptr; /* Entry number `__ptr' corresponds to. */ size_t __allocation; /* Space allocated for the block. */ size_t __size; /* Total valid data in the block. */ __libc_lock_define (, __lock) /* Mutex lock for this structure. */ }; typedef struct __dirstream DIR;
2.读取目录内容:
readdir函数用来读取目录流中的内容
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
dirent 用来描述目录流中一个目录项的结构体类型
包含成员 char d_name[256] 文件名
成功时返回目录流dirp中下一个目录项;
出错或到末尾是返回NULL。
注:
struct dirent { long d_ino; /* inode number 索引节点号 */ off_t d_off; /* offset to this dirent 在目录文件中的偏移 */ unsigned short d_reclen; /* length of this d_name 文件名长 */ unsigned char d_type; /* the type of d_name 文件类型 */ char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */ }
3.关闭目录
closedir函数用于关闭一个目录文件。
#include <dirent.h>
int closedir(DIR *dirp);
成功返回0;出错时返回EOF。
三 文件权限
1.修改文件访问权限
chmod 和 fchmod 用来修改文件的访问权限。
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);
成功返回0,出错时返回EOF。
只有root和文件的所有者才能修改文件的权限。
2.获取文件的属性
stat/lstat/fstat函数
#include <sys/stat.h>
int stat(const char *path, struct stat *buf);
int lstat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
成功返回0,出错时返回EOF。
stat 和 lstat 的区别:
当文件是一个链接文件,stat返回链接文件指向的文件的属性,而lstat指向的是链接文件本身的属性。
其他情况功能一致。
fstat第一个参数为文件描述符,操作要先打开文件。
struct stat常用属性 mode_t st_mode; 类型和访问权限 uid_t st_uid; 所有者id uid_t st_gid; 用户组id off_t st_size; 文件大小 time_t st_mtime; 最后修改时间 st_mode: 该成员描述了文件的类型和权限两个属性。 st_mode是个32位的整型变量,不过现在的linux操作系统只用了低16位 在<sys/stat.h>中,有如下定义。 File type属性区域,位于bit12 ~ bit15. #define S_IFMT 00170000 #define S_IFSOCK 0140000 #define S_IFLNK 0120000 #define S_IFREG 0100000 #define S_IFBLK 0060000 #define S_IFDIR 0040000 #define S_IFCHR 0020000 #define S_IFIFO 0010000 #define S_ISUID 0004000 #define S_ISGID 0002000 #define S_ISVTX 0001000 #define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) //符号链接文件(symbolic link) #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) //普通文件(regular file) #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) //目录(directory) #define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) //字符设备(character device) #define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) //块设备(block device) #define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) //管道(FIFO<pipe>) #define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) //套接口文件(socket) 例子: 判断一个文件是不是文件夹 if ((info.st_mode & S_IFMT) == S_IFDIR) printf("this is a directory"); 或 if (S_ISDIR(info.st_mode)) printf("this is a directory"); Permission属性区域的bit0~bit8,也即st_mode字段的最低9位,代表文件的许可权限, 它标识了文件所有者(owner)、组用户(group)、其他用户(other)的读(r)、写(w)、执行(x)权限。 在<sys/stat.h>中,有如下定义: #define S_IRWXU 00700 /* mask for file owner permissions */ #define S_IRUSR 00400 /* owner has read permission */ #define S_IWUSR 00200 /* owner has write permission */ #define S_IXUSR 00100 /* owner has execute permission */ #define S_IRWXG 00070 /* mask for group permissions */ #define S_IRGRP 00040 /* group has read permission */ #define S_IWGRP 00020 /* group has write permission */ #define S_IXGRP 00010 /* group has execute permission */ #define S_IRWXO 00007 /* mask for permissions for others (not in group) */ #define S_IROTH 00004 /* others have read permission */ #define S_IWOTH 00002 /* others have write permission */ #define S_IXOTH 00001 /* others have execute permission */ 例子: 打印出文件的权限,"-rx-rx-rx" for(int n = 8; n >= 0; n--) { if(buf.st_mode & (1 << n)) { switch(n & 3): { case 2: printf("r"); break; case 1: printf("w"); break; case 0: printf("x"); break; } } else { printf("-"); } }
注:目录的权限与普通文件的权限是不同的。目录的读、写、执行权限含义分别如下:
(1)读权限。读权限允许我们通过opendir()函数读取目录,
进而可以通过readdir()函数获得目录内容,即目录下的文件列表。
(2)写权限。写权限代表的是可在目录内创建、删除文件,而不是指的写目录本身。
(3)执行权限。可访问目录中的文件。
Ps:文件参考:man手册及《朱有鹏课程笔记》