Linux中文件分为以下几类:
普通文件,即一般意义上的文件、磁盘文件;
设备文件,代表的是系统中一个具体的设备;
管道文件、FIFO 文件,一种特殊文件,常用于进程间通信;
套接字(socket)文件,主要用在网络通信方面。
跟C/C++类似,Linux对文件IO的操作也无非就是打开 关闭 读 写
大致是类似的
我开始确实是这样以为的。
学了一下才发现,原来open read之流,也只不过九牛一毛 沧海一粟 。。
先说一下文件描述符
是一个非负的整数,它是一个索引值,并指向内核中每个进程打开文件的记录表。当打开一个现存文件或创建一个新文件时,
内核就向进程返回一个文件描述符;当需要读写文件时, 也需要把文件描述符作为参数传递给相应的函数。
或者叫句柄
类似于你号码牌,通过这个号,就可以找到指定的文件
通过这个操作可以查看文件描述符的取值范围,也就是说一个进程最多只能打开1024个文件
然后看一下基本的操作:
首先惯例加入头文件
#include <sys/types.h> /* 定义数据类型,如 ssize_t,off_t 等 */ #include <fcntl.h> /* 定义 open,creat 等函数原型,创建文件权限的符号常量 S_IRUSR 等 */ #include <unistd.h> /* 定义 read,write,close,lseek 等函数原型 */ #include <errno.h> /* 与全局变量 errno 相关的定义 */ #include <sys/ioctl.h> /* 定义 ioctl 函数原型 */
打开文件
int open(const char *pathname, int flags, ... /* mode_t mode */);
参数:
返回值:成功返回文件描述符,失败返回 -1
关闭文件
int close(int fd);
直接传入刚才获得的文件描述符,则可关闭文件
当一个进程终止时,内核会自动关闭该进程所打开的文件
我们看个例子
int main(void) { //文件描述符 int fd; /*调用 open 函数,以可读写的方式打开,注意选项可以用“|”符号连接*/ if((fd = open("/tmp/hello.c", O_CREAT | O_TRUNC | O_WRONLY , 0600 ))<0){ perror("open:"); exit(1); } else{ printf("Open file: hello.c %d ",fd); } //关闭该文件 if( close(fd) < 0 ){ perror("close:"); exit(1); } else printf("Close hello.c "); exit(0); }
例子中使用
O_CREAT | O_TRUNC | O_WRONLY
对照参数表,可知表示:只写方式打开文件,打开时擦除文件内容,如果文件不存在则创建。
这里说一下 return exit(0) 和 exit(1)
exit(0):正常运行程序并退出程序;
exit(1):非正常运行导致退出程序;
return():返回函数,若在主函数中,则会退出函数并返回一值。
详细说:
1. return返回函数值,是关键字; exit 是一个函数。
2. return是语言级别的,它表示了调用堆栈的返回;而exit是系统调用级别的,它表示了一个进程的结束。
3. return是函数的退出(返回);exit是进程的退出。4. return是C语言提供的,exit是操作系统提供的(或者函数库中给出的)。
5. return用于结束一个函数的执行,将函数的执行信息传出个其他调用函数使用;exit函数是退出应用程序,删除进程使用的内存空间,并将应用程序的一个状态返回给OS,这个状态标识了应用程序的一些运行信息,这个信息和机器和操作系统有关,一般是 0 为正常退出,非0 为非正常退出。
6. 非主函数中调用return和exit效果很明显,但是在main函数中调用return和exit的现象就很模糊,多数情况下现象都是一致的。
读取数据
ssize_t read(int fd, void *buf, size_t count);
先看一下这个返回值类型 ssize_t
/* 在 32 位系统中 */ typedef int ssize_t; /* 32 位有符号整型值 */ typedef unsigned int size_t; /* 32 位无符号整型值*/ /* 在 64 位系统中 */ typedef long int ssize_t; /* 64 位有符号整型值 */ typedef unsigned long int size_t; /* 64 位无符号整型值 */
也就是说,这个东西就是 int
几个参数很好理解
fd 文件描述符
buf 存放数据的地址
count 读出数据的大小
返回值:如果成功,返回读出的数据字数;如果失败返回 -1 ;读至文件尾 ,返回 0.
写入数据
ssize_t write(int fd, const void *buf, size_t count);
和read相同,buf是要写入的数据的地址。
返回值: 成功返回写入数据数,失败返回-1
看个例子:
int main(void) { int i,fd,size,len; char *buf="Hello! I'm writing to this file!"; char buf_r[10]; len = strlen(buf); /*首先调用 open 函数,并指定相应的权限*/ if((fd = open("/tmp/hello.c", O_CREAT | O_TRUNC | O_RDWR,0666 ))<0){ perror("open:"); exit(1); } else printf("open file:hello.c %d ",fd); /*调用 write 函数,将 buf 中的内容写入到打开的文件中*/ if((size = write( fd, buf, len)) < 0){ perror("write:"); exit(1); } else printf("Write:%s ",buf); /*调用 lsseek 函数将文件指针移到文件起始,并读出文件中的 10 个字节*/ lseek( fd, 0, SEEK_SET ); if((size = read( fd, buf_r, 10))<0){ perror("read:"); exit(1); } else printf("read form file:%s ",buf_r); /*关闭文件*/ if( close(fd) < 0 ){ perror("close:"); exit(1); } else printf("Close hello.c "); exit(0); }
这里有个问题,你使用write函数,写入一串数据进文件,不一定真的写入了磁盘等储存设备中
为了确保文件保存了你修改的数据,我们这里使用一个函数用于强制文件数据同步
int fsync(int fd);
成功返回 0 ;失败返回 -1 .
刚才我们的例子中,是直接从文件头读了数据
那如果你要从随机或者指定的一个地方读数据,就需要重新定位读写位置
off_t lseek(int fd, off_t offset, int whence);
这里 off_t 也是有符号整型,可以理解为32位的 long int
fd 文件描述符
offset 偏移量 可正可负 分别表示向前向后移动
whence 基点 有三种:
SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小
SEEK_CUR:当前位置为文件指针的位置,新位置为当前位置加上偏移量
SEEK_END:当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小
返回值:成功返回当前偏移量,失败返回 -1。
看个例子:
#include <sys/types.h> /* 定义数据类型,如 ssize_t,off_t 等 */ #include <fcntl.h> /* 定义 open,creat 等函数原型,创建文件权限的符号常量 S_IRUSR 等 */ #include <unistd.h> /* 定义 read,write,close,lseek 等函数原型 */ #include <errno.h> /* 与全局变量 errno 相关的定义 */ #include <sys/ioctl.h> /* 定义 ioctl 函数原型 */ #include <string.h> #include <stdio.h> #include <stdlib.h> int main() { int fd; char *readbuf; char *writebuf = "111111111111111111"; char *buf = "000000000"; if((fd = open("/mnt/hgfs/share/test.c", O_CREAT | O_RDWR,0777 ))<0){ //打开失败 exit(1); } //写入数据 write(fd, writebuf, strlen(writebuf)); //文件数据同步 fsync(fd); system("cat test.c"); lseek(fd, 6, SEEK_SET); //再次写入数据 write(fd, buf, strlen(buf)); fsync(fd); system("cat test.c"); close(fd); return 0; }
执行hello之后,出现了2次cat test.c
第一次全是111111111111
第二次中间加入了00000000
还有一些IO操作归不到read write中,所以加入了一个函数 ioctl()
一些硬件操作 寄存器操作也都在该函数中
int ioctl(int fd, int cmd, …);
参数比较简单,分别是文件描述符 和 操作指令
这里的操作指令肯定都是宏定义。
后记:
我在后面试用的时候出现了问题,记录一下。
linux环境下,我代码如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> /* 定义数据类型,如 ssize_t,off_t 等 */ #include <fcntl.h> /* 定义 open,creat 等函数原型,创建文件权限的符号常量 S_IRUSR 等 */ #include <unistd.h> /* 定义 read,write,close,lseek 等函数原型 */ #include <errno.h> /* 与全局变量 errno 相关的定义 */ #include <sys/ioctl.h> /* 定义 ioctl 函数原型 */ int main(int argc, char const *argv[]) { int fd; int ret; char *buf = "hello hello hello"; fd = open("/mnt/hgfs/share/txt",O_RDWR|O_CREAT,0x777); if(0 > fd) { perror("open file failed"); }
lseek(fd,0,SEEK_END); write(fd,buf,strlen(buf)); fsync(fd); close(fd); exit(0); }
运行如下:
也就是说,再没有txt这个文件的时候,可以正常创建。
已经创建完成后,在运行该代码就会失败提示错误:拒绝访问
然后我使用 ls -la 查看了一下文件的权限
没有写入权限
那么我用 chmod 给他加满权限
则可以正常运行了
那么问题来了,为什么我创建的 txt 文件会没有写入权限
所以我将
改成了
这样就可以运行很多次不会报错了
中途我 试过 0x700 也不可以
不知道为什么,记住以后这样做,用宏定义。
接下来我又:
char *readbuf = NULL; ..... ..... lseek(fd,5,SEEK_SET); ret = read(fd,readbuf,5); printf("%d ",ret);
想要读出距离文件头5个字节处的数据
结果被报错
为了解决错误,我尝试了2种方案
1、
char readbuf[5];
2、
char *readbuf = (char *)malloc(sizeof(char*));
运行结果显示,这两种方法都可以正常读取文件。
经过排查发现如果我注释掉
fsync(fd);
那么即便在栈区申请空间也不会报错了。
目前这个函数对我来说仍是个迷 - -
解决再更新
lseek(fd,5,SEEK_SET);ret = read(fd,readbuf,5);printf("%d ",ret);