赞
踩
进程一启动,内核就打开了三个描述符,0(标准输入 STDIN),1(标准输出STDOUT), 2(标准错误输出STDERR)。Linux用整形数做文件操作,因此称为文件描述符,文件描述符是一个较小的整数(0~1023)。
内核为进程维护一个已打开文件的记录表,文件描述符代表记录表里的一项,通过描述符和操作函数,即可实现文件操作。
常用基于文件描述符的函数有:open(打开)、creat(创建)、close (关闭)、read(读取)、write(写入)、ftruncate(改变文件大小)、lseek(定位)、fsync (同步)、fstat(获取文件状态)、fchmod(权限)、flock(加锁)、fcntl(控制文件属性)、 dup(复制)、dup2、select
flags和Mode都是一组掩码的合成纸,flag表示打开方式,mode表示访问权限
以下为flags:
掩码 | 作用 |
---|---|
O_RDONLY | 以只读的方式打开 |
O_WRONLY | 以只写的方式打开 |
O_RDWR | 以读写的方式打开 |
O_CREAT | 若文件不存在则创建文件 |
O_EXCL | 若文件已存在则创建失败返回已存在 |
O_TRUNC | 若文件存在,将长度截为0 |
O_APPEND | 用追加的方式打开文件,每次调用write时,文件指针自动移到文件尾。(一般用于多进程写同一个文件) |
O_NONBLOCK | 以非阻塞的方式打开,无论有没有数据读取或者等待,都会立刻返回进程。 |
O_SYNC | 同步打开文件,当数据被真正写入**物理设备(磁盘)**后才返回 |
#include <func.h> int main(int argc,char *argv[]) { FILE *fp=fopen(argv[1],"rb+"); if(NULL==fp) { perror("fopen"); return -1; } int fd=fileno(fp);//将文件指针转换为文件描述符fd printf("fd=%d\n",fd); int arr[3]={1,2,3}; //char buf[128]="hello"; //write(3,buf,strlen(buf)); write(fd,arr,sizeof(arr)); //close(fp); return 0; }
#include <func.h> void change(int fd)//可以传入fd而不用传入地址,因为fd描述的是一个打开的文件对> { ftruncate(fd,1);//将文件大小改为1个字节 } int main(int argc,char *argv[]) { int fd=open(argv[1],O_RDWR); if(-1==fd) { perror("open"); return -1; } ftruncate(fd,4);//将文件大小改成4个字节 change(fd); close(fd); return 0; }
#include <func.h> int main(int argc,char *argv[]) { ARGS_CHECK(argc,2); int fd=open(argv[1],O_RDWR); if(-1==fd) { perror("open"); return -1; } int ret; ret=lseek(fd,1000,SEEK_SET);//文件空洞 printf("ret=%d\n",ret); write(fd,"1",1); close(fd); return 0; }
获取文件信息
int fstat(int fd, struct stat *buf); //文件描述符 stat结构体指针
stat结构体
struct stat {
dev_t st_dev; /* ID of device containing file */ //设备,返回设备描述符,没有设备则返回0
ino_t st_ino; /* inode number */ //文件inode信息
mode_t st_mode; /* protection */ //文件类型
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; /* blocksize for filesystem I/O */ //块大小,LINUX下每一块为512B
blkcnt_t st_blocks; /* number of 512B blocks allocated */ //块数
time_t st_atime; /* time of last access */ //最后访问时间
time_t st_mtime; /* time of last modification */ //最后修改时间
time_t st_ctime; /* time of last status change */ //最后权限修改时间
};
文件描述符的复制
系统调用函数dup和dup2可以实现米文件描述符的复制,常用于重定向进程的STDIN(0),STDOUT(1),STDERR(2)
int dup(int oldfd);
int dup2(int oldfd, int newfd);
#include <func.h> int main(int argc,char *argv[]) { ARGS_CHECK(argc,2); int fd=open(argv[1],O_RDWR);//此时fd在内核结构体指针数组为3 if(-1==fd) { perror("open"); return -1; } int fd1=dup(fd);//成功例子,此时fd1在内核结构体指针数组为4 //int fd1=fd;//失败例子,fd无法直接赋值给fd1,需要dup printf("fd1=%d\n",fd1);//for test fd1在内核结构体指针数组下标为4,因此打印出来为4 close(fd); char buf[128]={0}; int ret=read(fd1,buf,sizeof(buf)); if(-1==ret) { perror("read"); return -1; } printf("ret=%d,buf=%s\n",ret,buf); return 0; }
失败例子的原理:
int fd1=fd;
close(fd);
read(fd,buf,sizeof(buf);
perror(“read”);
一旦close(fd)后,3的结构体指针被free,fd1无法指向文件对象,因此执行后显示bad file descriptor
内核原理如下图所示:
dup不同,dup会在结构体指针数组中复制出3号的复制品4号fd1,指向相同的文件对象( int fd1=dup(fd) ),具体内核原理如下图:
因此可知,文件描述符的复制是指用另外一个文件描述符指向同一个打开的文件,它完全不同于直接给文件描述符变量赋值。两个描述符( fd和fd1 )共享同一个数据结构,需注意,dup 返回新的文件描述符是没有使用的文件描述符的最小编号。
由于dup 返回新的文件描述符是没有使用的文件描述符的最小编号,则可以进行重定向标准输出
假设将标准输出1关闭(close(1)),再使用 dup ,则 fd1 的值为1;
#include <func.h> // 重定向标准输出 int main(int argc,char *argv[]) { ARGS_CHECK(argc,2); int fd=open(argv[1],O_RDWR); if(-1==fd) { perror("open"); return -1; } printf("\n");//刷新标准输入缓冲区 close(1);//关闭标准输入缓冲区 int fd1=dup(fd);//此时fd1的值则为1(没有使用的文件描述符的最小编号为1) printf("fd1=%d\n",fd1);//由于标准输入缓冲区关闭了,printf会写到文件中 close(fd); printf("I am mark2 \n");//测试是否会写到文件里 return 0; }
dup会自动去找没有使用的文件描述符的最小编号,当要指定一个文件描述符的位置复制时,则可以使用dup2(fd,1) //则会复制 fd 并将 fd1 的文件描述符设置为1,具体代码如下:
#include <func.h> // 重定向标准输出 int main(int argc,char *argv[]) { ARGS_CHECK(argc,2); int fd=open(argv[1],O_RDWR); if(-1==fd) { perror("open"); return -1; } printf("\n");//刷新标准输入缓冲区 int fd1=dup2(fd,1);//指定1为fd1的文件描述符编号,dup2会自动close(1) printf("fd1=%d\n",fd1);//由于标准输入缓冲区关闭了,printf会写到文件中 close(fd); printf("you cant see me\n");//测试是否会写到文件里 return 0; }
代码如下:
#include <func.h> int main(int argc,char *argv[]) { ARGS_CHECK(argc,2);//参数设置 int fd=open(argv[1],O_RDWR);//以读写的方式打开文件描述符 if(-1==fd) { perror("open"); return -1; } char *p; struct stat buf;//定义一个文件状态结构体 fstat(fd,&buf);//获取文件大小 p=mmap(NULL,buf.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); if((char *)-1==p) { perror("mmap"); return -1; } strcpy(p,"HELLOWORLD"); munmap(p,buf.st_size);//回写到磁盘中 close(fd); return 0; }
int munmap(void *addr, size_t length); //写回磁盘的接口
int msync(void *addr, size_t length, int flags); //同步写入磁盘的接口
Q:mmap和read,write有什么区别,为什么要使用mmap?
A:read,write会进行数据的多(两)次拷贝,而mmap为零拷贝。read和write要进行多次的数据搬移,而mmap不需要,因此性能较read和write较好。
以下为两次数据搬移(即两次拷贝)
以下为mmap原理图:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。