标准I/O实际上是在文件I/O的基础上封装了缓冲机制,标准I/O不是系统调用。
标准I/O的好处:减少了系统开销,先访问缓冲区,必要的时再访问实际文件
标准I/O的坏处:不能实时操作文件,大部分情况都在操作缓冲区,只能访问普通文件,设备文件,管道文件等不能访问。
文件I/O是系统调用
文件I/O的好处:可以实际实时操作文件
文件I/O的坏处:加大了系统了开销
支持POSIX规范的,都支持文件I/O操作
linux系统成功的关键因素就是与其他操作系统和谐共存的能力,
Linux文件系统由两层构成:第一层是虚拟文件系统(VFS),第二层是各种不同的具体的文件系统
它将具体的文件系统公共部分抽取出来形成一层抽象层,是系统内核的一部分,它位于用户和具体的文件系统之间,它对用户程序提供了标准的文件系统调用接口。
对具体的文件系统操作,它通过函数指针调用不同的文件系统函数,完成对应的操作。
以下是VFS的相关图解:
文件描述符:是一个非负的整数,它是一个索引值,并指向在内核中每个进程打开文件的记录表。当打开一个现存或者创建一个新文件的时候,内核就会返回一个文件描述符号。
打开文件:int open(const char *pathname,int flags,int perms);
头文件:
#include<sys/stat.h>
#include<fcntl.h>
返回值:成功:返回文件描述符,失败:返回-1
第一参数:pathname被打开的文件名(可包括路径名)
第二个参数:flags:文件被打开的方式
第三个参数:perms新建文件的存取权限
flags | 文件的打开方式 |
---|---|
O_RDONY | 以只读方式打开文件 |
O_WRONLY | 以只写方式打开文件 |
O_RDWR | 以读写方式打开文件 |
o_CREAT | 如果该文件不存在就创建一个新的文件,并用第3个参数为其设置权限 |
O_EXCL | 如果使用O_CREAT时文件存在,则可返回错误消息。这一参数可测试文件是否存在 |
O_NOCTTY | 使用本参数时,若打开的是终端文件,那么该终端不会成为当前进程的控制终端 |
O_TRUNC | 若文件已存在,那么会删除文件中的全部数据,并且设置文件大小为0 |
O_APPEND | 以追加的方式打开文件,在写文件的时候,文件读写位置自动指向文件的末尾,也就是说讲写入数据添加到文件的末尾 |
----------------------------分割线----------------------------------------------------------------
perms | 新建文件的存取权限 |
---|
可以用一组宏定义:S_I(R/W/X)(USR/GRP/OTH)其中R/W/X分别表示读/写/执行权限|
USR/GSR/OTH分别表示文件所有者/文件所属组/其他用户例如,S_IRUSR|S_IWUSR表示设置文件所有者的可读写属性。八进制表示法中0600表示同样的权限。
关闭文件:int close(int fd);
头文件:#inlcude<unisrd.h>
返回值:成功:返回0,失败:返回-1
参数:文件描述符
例子:
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{
int fd;
if((fd=open("test.txt",O_RDWR|O_CREAT|O_TRUNC,0666))<0)
{
perror("fail to open\n");
return -1;
}
close(fd);
return 0;
}
文件读:size_t read(int fd,void *buf,size_t count);
头文件:#include<unistd.h>
返回值:成功:读到的字节数; 0:已到达文件末尾 ;-1:出错
第一个参数:文件描述符
第二个参数:指定存储器读出数据的缓冲区
第三个参数:指定读出的字节数
文件读:size_t write(int fd,void *buf,size_t count);
头文件:#include<unistd.h>
返回值:成功:已写的字节数;-1:出错
第一个参数:文件描述符
第二个参数:指定存储器写入数据的缓冲区
第三个参数:指定写入的字节数
例子:
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#define N 64
int file_copy(const char *s_file,const char *d_file)
{
int fd_s,fd_d;
int r_count=0;
int buf[N];
if(-1==(fd_s=open(s_file,O_CREAT|O_RDONLY,0600)))
{
perror("open s_file_faile\n");
return -1;
}
if(-1==(fd_d=open(d_file,O_CREAT|O_WRONLY|O_TRUNC,0600)))
{
perror("open s_file_faile\n");
return -1;
}
while(r_count=read(fd_s,buf,N))
{
if(-1==r_count)
{
perror("open s_file_faile\n");
return -1;
}
if(-1==write(fd_d,buf,r_count))
{
perror("open s_file_faile\n");
return -1;
}
}
close(fd_s);
close(fd_d);
return 0
}
int main()
{
file_copy("read.txt","write.txt");
}
文件定位:off_t lseek(int fd,off_t offset,int whence);
头文件
#include<unistd.h>
#include<sys/types.h>
返回值:成功:返回挡墙读写的位置,失败:返回-1
第一个参数:文件描述符
第二个参数:ofset 相对于基准点的偏移位置
第三个参数:whence基准值
whence | 基准值 |
---|---|
SEEK_SET | 代表文件起始位置 |
SEEK_END | 代表文件结束位置 |
SEEK_CUR | 代表文件当前读写位置 |
文件锁:int fcntl.h(int fd,int cmd,struct *flock)
文件锁只是fcntl的其中一个用法
头文件:
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
返回值:成功:0;出错:-1
第一个参数:文件描述符
第二个参数:cmd,对文件描述符代表的文件进行相关操作
第三个参数:一个结构体flocck,决定如何操作文件
cmd | 对文件描述符代表的文件进行相关操作 |
---|---|
F_GETLK | 检查文件锁状态 |
F_SETLK | 设置lock描述的文件锁 |
F_SETLKW | 这是F_SETLK的阻塞版本(命名中的w表示等待) |
flock | 一个表示锁属性的结构体 |
---|---|
l_type | 锁定类型 |
l_start | 加锁区域在文件中的相对位移量,与whence一起决定加锁的起始位置 |
ll_whence | 相对位移量的起点 |
l_len | 加锁区域的长度 |
l_pid | 具有阻塞当前进程的锁,其持有的进程号存放在l_pid中,仅由F_GETLK返回 |
l_type | 锁定类型 |
---|---|
F_RDLCK | 读写锁(共享锁) |
F_WRLCK | 写入锁(排斥锁) |
F_UNLCK | 解锁 |
ll_whence | 相对位移量的起点 |
---|---|
SEEK_SET | 当前位置为文件的开头,新位置为偏移量的大小 |
SEEk_CUR | 当前位置为文件指针的位置,新位置为当前位置加偏移量 |
SEEK_END | 当前位置为文件的结尾,新位置为文件的长度加偏移量的大小 |
关于文件锁示例:
#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
#include<fcntl.h>
int lock_set(int fd,int type)
{
struct flock lock;
lock.l_whence=SEEK_SET;
lock.l_start=0;
lock.l_len=0;
lock.l_type=type;
lock.l_pid=-1;
fcntl(fd,F_GETLK,&lock);
if(lock.l_type!=F_UNLCK)
{
if(lock.l_type!=F_UNLCK)
{
if(lock.l_type==F_RDLCK)
{
printf("Read lock already set by %d\n",lock.l_pid);
}
else if(lock.l_type==F_WRLCK)
{
printf("Write lock already set by %d\n",lock.l_pid);
}
}
}
//l_type maybe change by F_GETLK*
lock.l_type=type;
/*根据不同的typed值进行阻塞式上锁或解锁*/
if(fcntl(fd,F_SETLKW,&lock)<0)
{
printf("Lock failed:type=%d\n",lock.l_type);
return -1;
}
switch(lock.l_type)
{
case F_RDLCK:
{
printf("Read lock by set %d\n",getpid());
}
break;
case F_WRLCK:
{
printf("Write lock set by %d\n",getpid());
}
break;
case F_UNLCK:
{
printf("Release lock by %d\n",getpid());
return 1;
}
break;
}/*endl switch*/
return 0;
}
int main()
{
int fd;
if((fd=open("fcntl.txt",O_RDWR|O_CREAT,0600))<0)
{
perror("faile open fail\n");
}
int n=0;
lock_set(fd,F_WRLCK);
getchar();
char buf[4]="yyyy";
n=write(fd,buf,4);
lock_set(fd,F_UNLCK);
getchar();
close(fd);
return 0;
}
我们打开2个终端
在2个中端上分别运行结果:./aout
第一个终端:Write lock set by 11606
Release lock by 11606
第二个终端:Write lock already set by 11606
Write lock set by 11607
Release lock by 11607
对于读文件锁的例子:只需要在上面例子上修改main()函数
int main()
{
int fd;
if((fd=open("fcntl.txt",O_RDWR|O_CREAT,0600))<0)
{
perror("faile open fail\n");
}
int n=0;
lock_set(fd,F_RDLCK);
char buf[4];
getchar();
read(fd,buf,4);
lock_set(fd,F_UNLCK);
getchar();
close(fd);
return 0;
}
我们打开2个终端
在2个中端上分别运行结果:./aout
第一个终端:Read lock set by 11606
Release lock by 11606
第二个终端:
Read lock set by 11607
Release lock by 11607