一.文件描述符及文件处理
1.文件及文件描述符
linux下的设备和文件都可以看作文件,linux中有七种文件类型:普通文件,目录文件,链接文件,块设备文件,字符设备文件,套接字文件,管道文件。
普通文件:包含纯文本文件(ASCII,Unicode等编码文件),二进制文件(可执行文件,压缩文件等)
目录文件:记录目录的文件
链接文件:与windows下的快捷方式文件类似
块设备文件:可存储数据的外部设备,包括硬盘等存储设备
字符设备文件:即串行端口的接口设备,例如键盘、鼠标等。
套接字文件:这类文件通常用在网络数据连接。
管道文件:管道文件是一种很特殊的文件,主要用于不同进程的信息传递。
文件类型标识 | 文件类型 |
---|---|
- | 普通文件 |
d | 目录 |
l | 符号链接 |
s | 套接字 |
b | 块设备 |
c | 字符设备 |
p | 管道 |
linux使用文件描述符来操作特定的文件,文件描述符是一个非负整数。通常一个进程启动时会打开三个文件:标准输入,标准输出,标准错误,它们的文件描述符分别是0,1,2。
2.系统调用
linux为了更好的保护内核空间,将程序运行空间分为内核空间和用户空间,它们运行在不同的级别上,逻辑上相互隔离,因此,用户程序通常不能直接访问内核数据。但是,某些情况下用户需要获得一定的系统服务,因此,操作系统需要向用户提供“特殊接口”---系统调用,进行系统调用时,程序从用户态进入内核态,处理完后返回用户态。
3.文件处理
linux系统调用和C/C++标准函数库都能处理文件,不同的是,C/C++标准函数库的I/O操作是基于系统调用实现的,它们是独立于操作系统的,不同的系统上其内部实现不同。
二.I/O操作的相关函数
1.open函数
open函数用于打开或创建文件,调用open函数需要包含的头文件:
#include<stdio.h>
#include<fcntl.h>
其函数原型为:
int open(const char *pathname, int flag, int perms);//打开成功则返回文件描述符,出错则返回-1
pathname:要打开或创建的文件名,可包含路径。
flag:函数功能选项,供选择参数有:
O_RDONLY:以只读方式打开文件。
O_WRONLY:以只写方式打开文件。
O_RDWR:以读写方式打开文件。
以上参数只能指定一个,其他可选参数:
O_APPEND:每次写时写入文件末尾。
O_CREAT:文件不存在则创建,使用此选项需要指定perms的值。
O_TRUNC:如果文件存在且以只读或只写打开,则会先全部删除文件原来的数据。
perms:可选参数,指定新建文件的存取权限,采用八进制表示。
测试代码:
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdio.h>
#define FILEA "a.txt"
#define FILEB "b.txt"
#define FILEC "c.txt"
int main(void)
{
int fd_a,fd_b,fd_c;
fd_a=open(FILEA,O_RDONLY);
if(fd_a==-1)
{
printf("open %s failed!\n",FILEA);
printf("fd_a=%d\n",fd_a);
}
else
{
printf("open %s successed!\n",FILEA);
printf("fd_a=%d\n",fd_a);
}
fd_b=open(FILEB,O_RDWR);
if(fd_b==-1)
{
printf("open %s failed!\n",FILEB);
printf("fd_b=%d\n",fd_b);
}
else
{
printf("open %s successed!\n",FILEB);
printf("fd_b=%d\n",fd_b);
}
fd_c=open(FILEC,O_RDWR|O_CREAT|O_TRUNC,0666);
if(fd_c==-1)
{
printf("open %s failed!\n",FILEC);
printf("fd_c=%d\n",fd_c);
}
else
{
printf("open %s sucessed!\n",FILEC);
printf("fd_c=%d\n",fd_c);
}
if(fd_a!=-1)
close(fd_a);
if(fd_b!=-1)
close(fd_b);
if(fd_c!=-1)
close(fd_c);
return 0;
}
测试结果:
由于a.txt不存在,所以a.txt打开失败,文件描述符为-1;b.txt存在,返回文件描述符为3;新建了c.txt文件,返回文件描述符为4。返回的文件描述符总是在进程中当前没有打开的最小描述符。
2.read函数
read函数从打开断地文件中读取数据,read函数原型为:
ssize_t read(int fd, void *buf, size_t count);//返回实际读取到的字节数,若返回0则已到达文件尾部,若返回-1则出错。
fd:文件描述符
buf:读取数据缓冲区
count:指定的读取字节数
测试代码:
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
#include<fcntl.h>
#define max_size 5
int main(void)
{
int fd,count;
char buf[max_size+1];
fd=open("test.txt",O_RDONLY);
if(fd==-1)
{
printf("test.txt not exist!\n");
return -1;
}
count=read(fd,buf,max_size);
if(count>0)
{
buf[count]='\0';
printf("content of test.txt:%s\n",buf);
}
else
{
printf("read test.txt failed!\n");
}
close(fd);
return 0;
}
测试结果:
3.write函数
write函数用于向打开的文件写入数据。write函数原型为:
ssize_t write(int fd, void *buf, size_t count);//返回实际写入的数据字节数,若出错则返回-1
fd:文件描述符
buf:待写入数据缓冲区
count:指定的写入字节数
测试代码:
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdio.h>
int main(void)
{
int fd,count;
char buf[]="hello,world!";
fd=open("a.txt",O_RDWR|O_CREAT,0666);
if(fd==-1)
{
printf("failed to open a.txt!\n");
return -1;
}
count=write(fd,buf,sizeof(buf));
if(count<0)
{
printf("failed to write buf to a.txt!\n");
close(fd);
return -1;
}
else
{
printf("write %d bytes to a.txt!\n",count);
}
close(fd);
return 0;
}
测试结果:
4.lseek函数
lseek函数用于将文件指针定位到相应位置以进行读写操作,lseek的函数原型:
off_t lseek(int fd, off_t offset, int whence);//返回文件的当前位移,若返回-1则出错。
fd:文件描述符
offset:偏移量,可正可负
whence:参考的位置基点,有3种参数:
SEEK_SET:以文件开头作为偏移的基点
SEEK_CUR:以当前指针位置作为偏移的基点
SEEK_END:以文件尾部作为偏移的基点
测试代码:
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
#include<fcntl.h>
#define max_size 5
int main(void)
{
int fd,count;
char buf[max_size+1];
fd=open("test.txt",O_RDONLY);
if(fd==-1)
{
printf("test.txt not exist!\n");
return -1;
}
lseek(fd,5,SEEK_SET);
count=read(fd,buf,max_size);
if(count>0)
{
buf[count]='\0';
printf("content of test.txt:%s\n",buf);
}
else
{
printf("read test.txt failed!\n");
}
close(fd);
return 0;
}
测试结果:
可以看到,读取的数据跳过了前5个数字,这正是预期的结果。