文件操作
1、系统调用概述
系统调用,顾名思义,说的是操作系统提供给用户程序调用的一组“特殊”接口。(重要!!!)
一个进程(用户态、内核态)(了解)
2、系统调用 和 库函数的区别(重要!!!)
系统调用是需要时间的,程序中频繁的使用系统调用会降低程序的运行效率。当运行内核代码时,CPU工作在内核态,在系统调用发生前需要保存用户态的栈和内存环境,然后转入内核态工作。系统调用结束后,又要切换回用户态。这种环境的切换会消耗掉许多时间 。
3、文件描述符(重要!!!)
用一个非负整数 来标识一个文件 这个非负的整数 就是文件描述符。
打开一个文件 系统就会为我们非配一个文件描述符 操作文件描述符 就等价于 操作文件.
文件描述符表:进程运行的时候 系统创建文件描述符表 来管理系统中文件描述符。
每一个进程 默认打开了3个文件描述符(0标准输入设备/键盘 1标准输出设备/屏幕 2标准错误输出/屏幕)
案例:读取文件数据
运行结果:
当用户打开一个文件的时候 系统 会给我们分配一个最小可用的文件描述符(重要!!!)
文件描述符表的管理(位图)
4、文件的打开读写关闭
4.1、打开文件open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);//打开已存在的文件
int open(const char *pathname, int flags, mode_t mode);//打开不存在的文件
//flags:read write操作文件的权限
//mode:该文件在磁盘中 相对于 用户的权限
功能:
打开文件,如果文件不存在则可以选择创建。
参数:
pathname:文件的路径及文件名
flags:打开文件的行为标志,必选项 O_RDONLY, O_WRONLY, O_RDWR
mode:这个参数,只有在文件不存在时有效,指新建文件时指定文件的权限
返回值:
成功:成功返回打开的文件描述符
失败:-1
4.1.1、flags宏的介绍(打开方式)
比如:以读写的方式打开 不存在则创建 存在清空 flags=O_RDWR | O_CREAT | O_TRUNC
4.1.2、mode的介绍(权限)
mode的权限分3组:所拥有者、同组用户、其他用户
每组都必须有独立的权限。
4表示读r 2表示写w 1表示可执行x
文件的最终权限 = 默认权限 - 文件掩码
文件掩码:屏蔽其他用户的权限
chmod 777 xxx将xxx的最终权限设置为777
4.2、关闭文件close
#include <unistd.h>
int close(int fd);
功能:
关闭已打开的文件
参数:
fd : 文件描述符,open()的返回值
返回值:
成功:0
失败: -1, 并设置errno
案例:
运行结果:
4.3、文件的写操作write
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:
把指定数目的数据写到文件(fd)
参数:
fd : 文件描述符
buf : 数据首地址
count : 写入数据的长度(字节)
返回值:
成功:实际写入数据的字节个数
失败: - 1
4.4、read读取文件数据
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能:
把指定数目的数据读到内存(缓冲区)
参数:
fd : 文件描述符
buf : 内存首地址
count : 读取的字节个数
返回值:
成功:实际读取到的字节个数
失败: - 1 文件末尾返回0
案例:读文件数据
案例:实现cp命令: cp src_file dst_dir
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
//./main test.txt test
if(argc != 3)
{
printf("please intput:./main src dst_dir\n");
}
char src_file[128]="";
char dst_file[128]="";
strcpy(src_file, argv[1]);//test.txt
sprintf(dst_file,"%s/%s",argv[2], argv[1]);//"test/test.txt"
int fd1 = open(src_file, O_RDONLY);
if(fd1 < 0)
{
perror("open");
return 0;
}
int fd2 = open(dst_file, O_WRONLY|O_CREAT,0777);
if(fd2 <0)
{
perror("open");
return 0;
}
while(1)
{
char buf[128]="";
//从s源文件中读取数据
int ret = read(fd1,buf,sizeof(buf));
if(ret <= 0)
break;
//将读到的w数据写到目的文件中
write(fd2,buf,ret);
}
close(fd1);
close(fd2);
return 0;
}
运行结果:
5、文件的阻塞特性
【注意】阻塞与非阻塞是对于文件而言的,而不是指read、write等的属性
案例:默认终端是读是阻塞的
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(int argc,char *argv[])
{
// /dev/tty --> 当前终端设备
int fd = open("/dev/tty", O_RDONLY);//默认是阻塞的
if(fd<0)
{
perror("open");
return 0;
}
printf("fd = %d\n",fd);
char buf[128]="";
printf("准备读!!!\n");
read(fd,buf,sizeof(buf));
printf("buf=%s\n",buf);
close(fd);
return 0;
}
运行结果:
案例:非阻塞()
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(int argc,char *argv[])
{
// /dev/tty --> 当前终端设备
//int fd = open("/dev/tty", O_RDONLY);//默认是阻塞的
int fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);//不阻塞的
if(fd<0)
{
perror("open");
return 0;
}
printf("fd = %d\n",fd);
char buf[128]="";
printf("准备读!!!\n");
read(fd,buf,sizeof(buf));
printf("buf=%s\n",buf);
close(fd);
return 0;
}
运行结果:
5.1、通过fcntl函数设置文件的阻塞特性(了解)
我们还可以通过fcntl函数来设置文件描述符的阻塞特性
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */);
功能:改变已打开的文件性质,fcntl针对描述符提供控制。
参数:
fd:操作的文件描述符
cmd:操作方式
arg:针对cmd的值,fcntl能够接受第三个参数int arg。
返回值:
成功:返回某个其他值
失败:-1
fcntl函数有5种功能:
1) 复制一个现有的描述符(cmd=F_DUPFD)
2) 获得/设置文件描述符标记(cmd=FGETFD或FSETFD)
3) 获得/设置文件状态标记(cmd=FGETFL或FSETFL)
4) 获得/设置异步I/O所有权(cmd=FGETOWN或FSETOWN)
5) 获得/设置记录锁(cmd=FGETLK, FSETLK或F_SETLKW)
案例:默认阻塞
fcntl函数设置非阻塞
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(int argc,char *argv[])
{
char buf[128]="";
//fcntl设置文件标记的3步曲
//获得文件的标记
int flag = fcntl(0, F_GETFL);
//修改文件的标记(非阻塞标记)
flag |= O_NONBLOCK;
//设置文件的标记
fcntl(0,F_SETFL, flag);
printf("准备读!!!\n");
read(0,buf,sizeof(buf));
printf("buf=%s\n",buf);
return 0;
}
运行结果:
6、通过文件名 直接获取文件的状态(了解)
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
功能:
获取文件状态信息
stat和lstat的区别:
当文件是一个符号链接时,lstat返回的是该符号链接本身的信息;
而stat返回的是该链接指向的文件的信息。
参数:
path:文件名
buf:保存文件信息的结构体
返回值:
成功: 0
失败: -1
struct stat的结构信息:
struct stat {
dev_t st_dev; //文件的设备编号
ino_t st_ino; //节点
mode_t st_mode; //文件的类型和存取的权限
nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1
uid_t st_uid; //用户ID
gid_t st_gid; //组ID
dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号
off_t st_size; //文件字节数(文件大小)
blksize_t st_blksize; //块大小(文件系统的I/O 缓冲区大小)
blkcnt_t st_blocks; //块数
time_t st_atime; //最后一次访问时间
time_t st_mtime; //最后一次修改时间
time_t st_ctime; //最后一次改变时间(指属性)
};
st_mode(16位整数)参数说明:
案例:
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(int argc,char *argv[])
{
struct stat my_stat;
stat("test.txt", &my_stat);
printf("文件的大小:%u\n", my_stat.st_size);
if(S_ISREG(my_stat.st_mode))
{
printf("它是一个普通文件\n");
}
else if(S_ISDIR(my_stat.st_mode))
{
printf("它是一个目录文件\n");
}
if((my_stat.st_mode& S_IRUSR)== S_IRUSR)
{
printf("所有者具备读的权限\n");
}
return 0;
}
运行结果:
7、文件的目录操作(了解)
我们经常需要读取一个目录下的所有文件名,所以一下介绍如何读取一个文件下的目录
步骤:
1、打开文件目录opendir
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
功能:打开一个目录
参数:
name:目录名
返回值:
成功:返回指向该目录结构体指针
失败:NULL
2、读取目录readdir 调用一次 只能读取一个文件
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
功能:读取目录
参数:
dirp:opendir的返回值
返回值:
成功:目录结构体指针
失败:NULL
struct dirent:
struct dirent
{
ino_t d_ino; // 此目录进入点的inode
off_t d_off; // 目录文件开头至此目录进入点的位移
signed short int d_reclen; // d_name 的长度, 不包含NULL 字符
unsigned char d_type; // d_type 所指的文件类型
char d_name[256]; // 文件名
};
d_type取值:
3、关闭closedir
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
功能:关闭目录
参数:
dirp:opendir返回的指针
返回值:
成功:0
失败:-1
案例:
#include <stdio.h>
#include <dirent.h>
int main(int argc,char *argv[])
{
//打开目录
DIR *dir = opendir("test");
if(dir == NULL)
{
perror("opendir");
return 0;
}
//读取目录
while(1)
{
struct dirent *p = readdir(dir);
if(p == NULL)
break;
printf("文件名:%s ", p->d_name);
if(p->d_type == DT_DIR)
{
printf("目录\n");
}
else if(p->d_type == DT_REG)
{
printf("普通文件\n");
}
}
//关闭
closedir(dir);
}
运行结果: