文件I/O:系统调用
一、文件I/O基本概念
1.什么是文件I/O?
posix(可移植操作系统接口)定义的一组函数,不提供缓冲机制,每次读写操作都引起系统调用,核心概念是文件描述符,访问各类型文件,在Linux下,标准I/O基于文件I/O实现。
2.文件描述符
文件描述符(fd)是一个非负整数,Linux为程序中每个打开的文件分配一个文件描述符。文件描述符从0开始分配,依次递增,在ubuntu系统中一个正在执行的程序,文件描述符的范围是 0~1023(1024个)。每一个正在执行的程序都有自己的一套文件描述符,相互不干扰。
一个正在执行的程序中0,1,2文件描述符已经被占用了,分别对应的是标准输入,标准输出,标准出错。
#include <stdio.h>
int main(int argc, const char* argv[])
{
printf("in fd = %d\n", stdin->_fileno); // 0
printf("out fd = %d\n", stdout->_fileno); // 1
printf("err fd = %d\n", stderr->_fileno); // 2
return 0;
}
ulimit -a 显示当前所有的资源限制等。
ulimit -n 2048 修改文件描述符范围为2048。
二、文件I/O函数
head.h
编写head.h的头文件
将head.h放到/usr/include路径下
修改head.h文件所属用户和组,sudo chown linux:linux /usr/include/head.h
修改用户代码片段(c.json),将#include <stdio.h>改成#include <head.h>
#ifndef __HEAD_H__
#define __HEAD_H__
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define PRINT_ERR(msg) \
do \
{ \
perror(msg); \
return -1; \
} while (0)
#endif
1.open、close(打开、关闭文件)
1.1open、close函数API
#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);
功能:打开文件
参数:
@pathname:打开文件的路径及名字
@flags :打开文件的方式
O_RDONLY :只读
O_WRONLY :只写
O_RDWR :读写
O_APPEND :追加
O_CREAT :创建,如果flags中填写了O_CREAT,就必须填写第三个参数,第三个参数代表创建文件的权限
O_TRUNC :清空
O_EXCL :和O_CREAT结合使用,在创建文件的时候加上这个选项,如果文件存在就返回EEXIST,如果文件不存在就会不返回错误
O_NOCTTY :使用本参数时,如文件为终端,那么终端不可作为调用open()系统调用的那个进程的控制终端。
@mode:创建文件的权限,为8进制表示法
实际创建出来的文件的权限 = (mode & ~umask)
umask:文件的掩码:通过umask命令可以查看掩码的默认值是0002
~umask:文件掩码的取反是在最大默认文件的权限的基础上取反的,最大默认文件的权限是0666
实际创建出来的文件的权限 = (0666 & ~(0002)) = (0666 & 0664) = 0664
返回值:成功返回文件描述符,失败返回-1置位错误码
#include <unistd.h>
int close(int fd);
功能:关闭文件
参数:
@fd:文件描述符
返回值:成功返回0,失败返回-1置位错误码
1.2文件I/O和标准I/O文件打开方式对比
文件IO | 标准IO | 功能 |
---|---|---|
O_RDONLY | “r” | 以只读的方式打开文件,将光标定位到开头 |
O_RDWR | “r+” | 以读写的方式打开文件,将光标定位到开头 |
O_WRONLY | O_CREAT | O_TRUNC,0666 | “w” | 以只写的方式打开文件,如果文件不存在就创建,如果文件存在清空,光标在开头 |
O_RDWR | O_CREAT | O_TRUNC,0666 | “w+” | 以读写的方式打开文件,如果文件不存在就创建,如果文件存在清空,光标在开头 |
O_WRONLY | O_CREAT | O_APPEND,0666 | “a” | 以只写和追加的方式打开文件,如果文件不存在就创建,如果存在光标在结尾 |
O_RDWR | O_CREAT | O_APPEND,0666 | “a+” | 以读写和追加的方式打开文件,如果文件不存在就创建,如果文件存在读光标在开头,写光标在结尾 |
1.3open函数实例1(只读、只写)
只读、只写方式打开文件。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define PRINT_ERR(msg) \
do \
{ \
perror(msg); \
return -1; \
} while (0)
int main(int argc, const char *argv[])
{
int fd1, fd2;
// 只读方式打开文件
if (-1 == (fd1 = open("./1.txt", O_RDONLY)))
PRINT_ERR("open error");
// 只写方式打开文件
if (-1 == (fd2 = open("./hello.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666)))
PRINT_ERR("open error");
close(fd1);
close(fd2);
return 0;
}
1.4open函数实例2(参数O_EXCL)
如果文件不存在,创建文件,以只写方式打开文件。
如果文件存在,以只读方式打开文件。
#include <head.h>
int main(int argc, const char *argv[])
{
int fd;
// 如果文件不存在,创建文件,以只写方式打开文件
// 如果文件存在,以只读方式打开文件
if (-1 == (fd = open("./1.txt", O_WRONLY | O_CREAT | O_EXCL, 0666))) {
if (errno = EEXIST) {
if (-1 == (fd = open("./1.txt", O_RDONLY)))
PRINT_ERR("open error");
puts("文件存在,以只读方式打开文件");
} else {
PRINT_ERR("open error");
}
} else {
puts("文件不存在,创建文件,以只写方式打开文件");
}
printf("fd = %d\n", fd);
close(fd);
return 0;
}
1.5close函数实例3(关闭标准输出)
关闭标准输出(fd = 1),终端不会输出。
#include <head.h>
int main(int argc, const char *argv[])
{
puts("--------------start");
close(1);
puts("--------------end"); //关闭标准输出(fd = 1),终端不会输出这行
return 0;
}
2.read、write(读取、写入文件)
2.1read、write函数API
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
功能:读取文件中的内容
参数:
@fd:文件描述符
@buf:存储读取到数据的首地址
@count:想要读取的字节的个数
返回值:成功返回读取到字节的个数,如果返回0代表读取到文件的结尾了
失败返回-1置位错误码
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
功能:向文件中写数据
参数:
@fd:文件描述符
@buf:想要写的数据的首地址
@count:想要写的字节的个数
返回值:成功返回写入的字节的个数,0表示不会写任何的数据
失败返回-1置位错误码
2.2read函数实例1(读取整数、字符串、结构体)
配合2.3wirte函数实例2使用
#include <head.h>
typedef struct stu {
char name[20];
int age;
char sex;
}stu_t;
int main(int argc, const char *argv[])
{
int fd;
if (-1 == (fd = open("./1.txt", O_RDONLY)))
PRINT_ERR("open error");
/*
// 1.验证读取整数
int num;
read(fd, &num, sizeof(num));
printf("num = %d\n", num);
// 2.验证读取字符串
char buf[20] = {0};
read(fd, buf, sizeof(buf));
printf("buf = %s\n", buf);
*/
// 3.验证读取结构体
stu_t stu;
read(fd, &stu, sizeof(stu));
printf("name = %s, age = %d, sex = %c\n", stu.name, stu.age, stu.sex);
close(fd);
return 0;
}
2.3write函数实例2(写入整数、字符串、结构体)
配合2.2read函数实例1使用
#include <head.h>
typedef struct stu {
char name[20];
int age;
char sex;
}stu_t;
int main(int argc, const char *argv[])
{
int fd;
if (-1 == (fd = (open("./1.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666))))
PRINT_ERR("open error");
/*
// 1.验证写入整数
int num = 12345;
write(fd, &num, sizeof(num));
// 2.验证写入字符串
char buf[100] = "hello world!";
write(fd, buf, strlen(buf));
*/
// 3.验证写入结构体
stu_t stu = {
.name = "xiaoming",
.age = 21,
.sex = 'm'
};
write(fd, &stu, sizeof(stu_t));
close(fd);
return 0;
}
2.4write函数实例3(计算文件大小)
#include <head.h>
int main(int argc, const char *argv[])
{
if (2 != argc) {
fprintf(stderr, "Usage: %s <file>\n", argv[0]);
return -1;
}
int fd;
if (-1 == (fd = open(argv[1], O_RDONLY)))
PRINT_ERR("open error");
int total = 0;
int ret;
char buf[10] = {0};
while (0 < (ret = read(fd, buf, sizeof(buf)))) {
total += ret;
}
printf("%s size total is %d\n", argv[1], total);
close(fd);
return 0;
}
2.5read、write函数实例4(文件拷贝)
#include <head.h>
int main(int argc, const char *argv[])
{
// 1.校验用户输入的参数是否正确
if (3 != argc) {
fprintf(stderr, "Usage: %s <src_file> <dest_file>\n", argv[0]);
return -1;
}
// 2.只读方式打开源文件,写的方式打开目标文件(创建)
int sfd, dfd;
if (-1 == (sfd = open(argv[1], O_RDONLY)))
PRINT_ERR("open src_file error");
if (-1 == (dfd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666)))
PRINT_ERR("open dest_file error");
// 3.拷贝
int ret;
char buf[10] = {0};
while (0 < (ret = read(sfd, buf, sizeof(buf)))) {
write(dfd, buf, ret);
}
// 4.关闭文件
close(sfd);
close(dfd);
return 0;
}
2.6fgets、write函数实例5(键盘输入内容到文件)
#include <head.h>
int main(int argc, const char *argv[])
{
if (2 != argc) {
fprintf(stderr, "Usage: %s <file>\n", argv[0]);
return -1;
}
int fd;
if (-1 == (fd = open(argv[1], O_RDWR | O_APPEND | O_CREAT, 0666)))
PRINT_ERR("open error");
char buf[10] = {0};
while (NULL != fgets(buf, 10, stdin)) {
if (0 == strcmp(buf, "quit\n")) break; //终端输入quit退出
write(fd, buf, strlen(buf));
}
close(fd);
return 0;
}
3.lseek(定位文件指针)
3.1lseek函数API
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
功能:设置光标的位置
参数:
@fd:文件描述符
@offset:偏移量
>0:向后偏移
=0:不偏移
<0:向前偏移
@whence:从那个位置开始偏移
SEEK_SET:从文件的开头
SEEK_CUR:从当前位置
SEEK_END:从文件的结尾
返回值:成功返回光标到开头的字节数,失败返回-1置位错误码
3.2lseek函数实例1
#include <head.h>
int main(int argc, const char *argv[])
{
// abcdefg1234567
// 1.读写方式打开文件
int fd;
if (-1 == (fd = open("./1.txt", O_RDWR)))
PRINT_ERR("open error");
// 2.文件开头向后偏移5个字节
lseek(fd, 5, SEEK_SET);
char ch;
read(fd, &ch, 1);
printf("ch = %c\n", ch); //读取到第6个字符,f
ch = 'Q';
write(fd, &ch, 1); //写入到第7个字符,abcdefQ1234567
close(fd);
return 0;
}
3.3lseek函数实例2
如果使用读写和追加的方式打开文件,即使lseek偏移了文件指针,写指针永远是在结尾。
#include <head.h>
int main(int argc, const char *argv[])
{
// abcdefg1234567
// 1.以读写和追加的方式打开文件
int fd;
if (-1 == (fd = open("./1.txt", O_RDWR | O_APPEND | O_CREAT, 0666)))
PRINT_ERR("open error");
// 2.文件开头向后偏移5个字节
lseek(fd, 5, SEEK_SET);
char ch;
read(fd, &ch, 1);
printf("ch = %c\n", ch); //读取到第6个字符,f
ch = 'T';
write(fd, &ch, 1); //T字符写到了文件的结尾,写永远是在结尾
int ret;
ch = 0;
ret = read(fd, &ch, 1); //光标已经在结尾了,这里读是读取不到内容的,read返回0(end of file)
printf("ch = %c, ret = %d\n", ch, ret); //ch = , ret = 0
close(fd);
return 0;
}
4.stat(获取文件属性)
4.1stat函数API
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
//stat不能获取软连接文件,如果向测试软链接,使用lstat完成,用法和stat完全相同
功能:获取文件的属性信息
参数:
@pathname:文件的路径及名字
@statbuf:获取的属性信息结构指针
struct stat {
dev_t st_dev; //磁盘设备号,底层驱动课程讲解
ino_t st_ino; //文件的inode号,文件系统识别文件的唯一编号,通过ls -i查看
mode_t st_mode; //文件的类型和权限
//1.文件的类型 文件的类型占4bit位,12-15就是文件的类型
S_IFMT 0170000 bit mask for the file type bit field
S_IFSOCK 0140000 socket //套接字文件
S_IFLNK 0120000 symbolic link //软连接文件
S_IFREG 0100000 regular file //普通文件
S_IFBLK 0060000 block device //块设备文件
S_IFDIR 0040000 directory //目录
S_IFCHR 0020000 character device //字符设备文件
S_IFIFO 0010000 FIFO //管道
eg:
if((st_mode & S_IFMT) == S_IFREG){
printf("普通文件\n");
}
//2.文件的权限 文件的权限占9bit位,0-8就是文件的权限
文件的权限 = st_mode & 0777
nlink_t st_nlink; //硬链接数
uid_t st_uid; //用户的uid
gid_t st_gid; //用户的gid
dev_t st_rdev; //设备号,底层驱动课程讲解
off_t st_size; //文件的大小,单位是字节
blksize_t st_blksize; //操作块设备的最小单位block,512字节
blkcnt_t st_blocks; //文件的大小,单位是block
struct timespec st_atim; //最后一次访问这个文件的时间
struct timespec st_mtim; //最后一次修改这个文件的时间
struct timespec st_ctim; //最后一次状态改变的时间
};
返回值:成功返回0,失败返回-1置位错误码
4.2根据uid获取用户名,getpwuid函数
#include <sys/types.h>
#include <pwd.h>
struct passwd *getpwuid(uid_t uid);
功能:根据uid获取用户信息结构体
参数:
@uid:用户的id
返回值:成功返回用户信息结构指针,失败返回NULL,置位错误码
struct passwd {
char *pw_name; //用户名
char *pw_passwd; //用户的密码
uid_t pw_uid; //uid
gid_t pw_gid; //gid
char *pw_gecos; //用户的信息
char *pw_dir; //用户的主目录
char *pw_shell; //shell程序
};
4.3根据gid获取组名,getgrgid函数
#include <sys/types.h>
#include <grp.h>
struct group *getgrgid(gid_t gid);
功能:根据gid获取group结构体
参数:
@gid:组号
返回值:成功返回结构体指针,失败返回NULL置位错误码
struct group {
char *gr_name; //组名
char *gr_passwd; //组的密码
gid_t gr_gid; //组号
};
4.4stat函数实例1(实现 ls -l)
#include <head.h>
void print_type(mode_t mode);
void print_permission(mode_t mode);
int print_time(struct timespec time);
int main(int argc, const char *argv[])
{
// 1.校验参数输入是否正确
if (2 != argc) {
fprintf(stderr, "Usage: %s <file>\n", argv[0]);
return -1;
}
// 2.获取文件的属性
struct stat st;
if (stat(argv[1], &st))
PRINT_ERR("stat get msg failed");
// 3.将属性打印出来
print_type(st.st_mode); //打印文件类型
print_permission(st.st_mode); //打印文件权限
printf(" %ld", st.st_nlink); //打印硬链接个数
printf(" %s", getpwuid(st.st_uid)->pw_name); //打印用户名
printf(" %s", getgrgid(st.st_gid)->gr_name); //打印组名
printf(" %ld", st.st_size); //打印文件大小(字节)
print_time(st.st_atim); //打印时间戳
printf(" %s", argv[1]); //打印文件名
puts("");
return 0;
}
// 打印文件类型
void print_type(mode_t mode) {
switch (mode & S_IFMT) {
case S_IFSOCK:
putchar('s'); break;
case S_IFLNK:
putchar('l'); break;
case S_IFREG:
putchar('-'); break;
case S_IFBLK:
putchar('b'); break;
case S_IFDIR:
putchar('d'); break;
case S_IFCHR:
putchar('c'); break;
case S_IFIFO:
putchar('p'); break;
}
}
// 打印文件权限
void print_permission(mode_t mode) {
for (int n = 8; n >= 0; n--) {
if ((mode & 0777) & (1 << n)) {
switch (n % 3) {
case 2:
putchar('r'); break;
case 1:
putchar('w'); break;
case 0:
putchar('x'); break;
}
} else {
putchar('-');
}
}
}
// 打印文件时间戳
int print_time(struct timespec time) {
struct tm* tm;
if (NULL == (tm = localtime(&time.tv_sec)))
PRINT_ERR("change time error");
printf(" %02d月 %2d %02d:%02d", \
tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min);
}