alin的学习之路(Linux系统编程:四)(Makefile,文件I/O)
编码格式的调整:
visual stuio 中用的是GBK的编码,我们在Linux vim 里用的是 utf-8 编码。
vim 的末行模式下:
:set fileencoding=utf8 // 将文件修改为 utf-8 编码
1.Makefile
-
概述:Makefile是文件编译工具,方便源文件编译成可执行文件。Makefile 是一个文本文件,一个包含了编译规则的文本文件。在项目中敲击一个命令:make ,即可完成编译。
-
文件命名:Makefile 或 makefile
-
编写规则
目标 : 依赖 由依赖生成目标的命令
- 目标:要生成的文件
- 依赖:制作目标文件的材料
- 命令:执行该命令能够通过依赖文件制作出目标文件
- 如果Makefile有多个规则,默认执行第一个规则
- 第一个规则中的依赖文件如果不存在,则从文件中向后找其他规则有没有能够生成该依赖文件的
-
书写Makefile
# $(wildcard 文件路径) 表示指定路径下的所有匹配文件 # 假设要获取当前路径下的所有.c文件 # $(wildcard ./*.c) # # $(patsubst 被替换模式,替换模式,进行操作的文件内容) # 假设要将所有.c 替换成.o # $(patsubst %.c,%.o,a.c b.c c.c) srcs=$(wildcard ./*.c) objs=$(patsubst %.c,%.o,$(srcs)) target=app $(target) : $(objs) gcc $^ -o $@ -I include/ # gcc $(objs) -o $(taregt) -I include/ %.o : %.c gcc -c $< -o $@ -I include/ # .PHONY 指的该文件是一个虚拟文件,不需要创建出来这个文件,仅执行该目标文件下的命令 .PHONY:clean clean: rm $(target) $(objs)
-
srcs、objs、target都是定义变量,用来减少下面书写的代码,使用
$(变量名)
在后面使用 -
< 表示上面规则中的依赖文件中的第一个,$@ 表示目标文件
-
规则中的%表示可以匹配任意字符,例如:%.c 表示匹配任意名字的 .c 文件
-
注意:在Makefile规则中%表示通配,路径中*表示通配
-
Makefile中特有的函数(通俗理解为字符串操作函数)
-
$(wildcard 文件路径) 表示指定路径下的所有匹配文件
假设要获取当前路径下的所有.c文件
$(wildcard ./*.c)
-
$(patsubst 被替换模式,替换模式,进行操作的文件内容)
假设要将所有.c 替换成.o
$(patsubst %.c,%.o,a.c b.c c.c)
-
-
.PHONY:目标名
表示虚拟文件,即不需要创建出这个文件,仅执行目标文件下的命令
-
2.Linux I/O
-
标准I/O与系统I/O的区别:
- 标准I/O自带缓冲区,系统I/O不带缓冲区
- 标准I/O通过调用系统I/O完成I/O操作
- 标准I/O的fopen返回的是一个FILE类型的指针,而系统I/O的open返回的是一个大小为(0-1023)的文件描述符
-
fopen与open的效率对比:
- fread 会从用户空间的缓冲区读,如果用户空间的缓冲区没有内容了,会进入到内核空间调用一次read从磁盘读数据到内核缓冲区,再把内核缓冲区的数据拷贝到用户缓冲区,fread读用户缓冲区即可
- read 会直接进入内核空间从内核缓冲区读数据,如果内核缓冲区没有数据了,从磁盘读数据到内核缓冲区
- 所以综上所述,fread不是每次都会进入内核空间,read每次都会进入内核空间,从用户空间进入内核空间会花很多时间,所以fread的效率要比read高
- 当调用fclose函数时释放缓冲区
-
程序和文件:
程序运行时,会默认打开 3 个文件:标准输入文件、标准输出文件、标准错误文件。- 对于标准IO函数而言:stdin、stdout、stderr , 这 3 个都是 FILE 类型的指针。
- 对于系统IO函数而言:STDIN_FILENO(0)、STDOUT_FILENO(1)、STDERR_FILENO(2), 这三个是数
字,文件描述符。这三个定义在 unistd.h 头文件中
3.Linux I/O 函数
1.文件打开、关闭、读写
-
open函数:
-
int open (const char *file, int oflag, ...)
-
头文件:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>
-
当打开一个文件的时候,如果文件不存在则创建。Linux 中文件是有权限的,创建时候需要指定权
限。由 mode 参数指定文件权限。 -
int open(const char *pathname, int flags); 用于打开已经有的文件,不需要传递第三个参数。
-
mode 参数
int open(const char *pathname, int flags, mode_t mode);
如果文件不存在,要创建,则
需要传递第三参数 mode,实际创建文件的权限 = 指定的权限 - 掩码。- mode 参数可以写数字法:0777(八进制,前面一定要加0)
- 指定宏写法:比如:希望文件的权限是,用户读、组用户写、其他用户执行:S_IRUSR | S_IWGRP |
S_IXOTH
-
oflag 参数:
- O_RDONLY(只读打开文件)、O_WRONLY(只写打开文件)、O_RDWR(读写打开文
件)、O_CREAT(创建文件)、O_APPEND(追加方式打开文件) - 使用位或运算符组合多个打开模式。
- O_WRONLY | O_CREAT,写方式打开文件,不存在则创建。
- O_RDONLY(只读打开文件)、O_WRONLY(只写打开文件)、O_RDWR(读写打开文
-
open 函数的打开模式中并没有区分文本模式和二进制模式:因为Linux系统函数仅在系统中使用,没有行结尾的相异
-
成功则返回文件描述符,失败则返回-1,此时 errno 会被设置为相应的错误码,使用 perror函数
可打印详细的错误描述。
-
-
write 函数
ssize_t write(int fd, const void *buf, size_t count);
- 头文件:
include <unistd.h>
- 将 buf 中的 count 字节数据写入到 fd 文件中。
- 写入成功,则返回写入的字节数量。失败,则返回 -1,并且全局变量 errno 会被设置为相应的错
误码。我们可以使用 perror 函数打印错误信息。
-
read 函数
ssize_t read(int fd, void *buf, size_t count);
- 头文件:
#include <unistd.h>
- 从 fd 文件中读取 count 字节的数据,并存储到 buf 空间中。
- 成功则返回读取的字节数量,失败则返回-1,并且全局变量 errno 会被设置为合适的错误码,使用
perror 可以打印该错误码对应的详细错误描述。
-
close 函数
- 头文件:
#include <unistd.h>
- 关闭文件描述符,释放文件资源。
- 成功返回0,失败返回-1,errno 会被设置为合适的值,使用 perror 函数可打印错误信息。
- 头文件:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
void test01()
{
int fd;
fd = open("demo01.txt",O_WRONLY | O_CREAT,0777);
if(fd == -1)
{
perror("open");
return;
}
const char* data = "hello world\n";
int len = write(fd,data,strlen(data));
if(len == -1)
{
perror("write");
return;
}
int ret = close(fd);
if(-1 == ret)
{
perror("close");
return;
}
}
void test02()
{
int fd;
fd = open("demo01.txt",O_RDONLY);
if(fd == -1)
{
perror("open");
return;
}
char buf[128] = { 0 };
int len = read(fd,buf,128);
if(len == -1)
{
perror("write");
return;
}
printf("buf = %s",buf);
int ret = close(fd);
if(-1 == ret)
{
perror("close");
return;
}
}
int main()
{
//test01();
test02();
return 0;
}
2.文件随机读写
lseek 函数可以实现移动文件指针,从而实现随机文件读写。
off_t lseek(int fd, off_t offset, int whence);
头文件:#include <sys/types.h> #inlude <unistd.h>
- fd 文件描述符
- offset 偏移量
- whence 相对位置
- SEEK_SET 开始位置
- SEEK_CUR 当前位置
- SEEK_END 尾部位置
- 案例:向文件写入三个struct Person,读出第一个和第三个
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
struct Person{
char name[32];
int age;
};
void test01()
{
struct Person persons[] = {
{"Obama",50},
{"Smith",60},
{"Trump",70}
};
int fd = open("person.txt",O_WRONLY | O_CREAT,0777);
if(-1 == fd)
{
perror("open");
return;
}
int len = write(fd,persons,sizeof(persons));
if(-1 == len)
{
perror("write");
return;
}
int ret = close(fd);
if(-1 == ret)
{
perror("close");
return;
}
}
void test02()
{
int fd = open("person.txt",O_RDONLY);
if(-1 == fd)
{
perror("open");
return;
}
struct Person p1;
int len = read(fd,&p1,sizeof(struct Person));
if(-1 == len)
{
perror("read");
return;
}
printf("Name:%s Age:%d\n",p1.name,p1.age);
int ret = lseek(fd,sizeof(struct Person),SEEK_CUR);
if(-1 == ret)
{
perror("lseek");
return;
}
struct Person p2;
len = read(fd,&p2,sizeof(struct Person));
if(-1 == len)
{
perror("read");
return;
}
printf("Name:%s Age:%d\n",p2.name,p2.age);
ret = close(fd);
if(-1 == ret)
{
perror("close");
return;
}
}
int main()
{
test01();
test02();
return 0;
}
3.文件信息获取
stat 函数可以获取文件的信息。
-
int stat(const char *pathname, struct stat *statbuf);
-
头文件:
#include <sys/types.h> #include <sys/stat.h> #include <unistd.h>
-
pathname 指的是文件的路径。
-
statbuf (传入传出参数)保存获取的文件信息。
-
成功返回0,失败返回 -1,并且 errno 会被设置为合适的值,使用 perror 函数打印。
-
**manpage查看:**stat结构体在
man 2 stat
中,st_mode中一些宏的查看在man 7 inode
-
struct stat 结构体:
struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* Inode number */ mode_t st_mode; /* 文件的类型、文件的权限*/ nlink_t st_nlink; /* 文件的硬链接数 */ uid_t st_uid; /* 文件所有者ID */ gid_t st_gid; /* 文件所有者组ID */ dev_t st_rdev; /* Device ID (if special file) */ off_t st_size; /* 文件大小*/ blksize_t st_blksize; /* Block size for filesystem I/O */ blkcnt_t st_blocks; /* Number of 512B blocks allocated */ struct timespec st_atim; /* Time of last access */ struct timespec st_mtim; /* Time of last modification */ struct timespec st_ctim; /* Time of last status change */ };
-
示例代码:
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> void test01() { struct stat info; int ret = stat("demo01.txt",&info); if(-1 == ret) { perror("stat"); return; } printf("文件大小为:%ld\n",info.st_size); if(S_ISREG(info.st_mode)) { printf("普通文件\n"); } if(S_ISDIR(info.st_mode)) { printf("目录文件\n"); } if(S_ISLNK(info.st_mode)) { printf("硬链接\n"); } if(info.st_mode & S_IRUSR) { printf("用户的读权限\n"); } if(info.st_mode & S_IWUSR) { printf("用户的写权限\n"); } if(info.st_mode & S_IXUSR) { printf("用户的执行权限\n"); } if(info.st_mode & S_IRGRP) { printf("用户组的读权限\n"); } if(info.st_mode & S_IWGRP) { printf("用户组的写权限\n"); } if(info.st_mode & S_IXGRP) { printf("用户组的执行权限\n"); } if(info.st_mode & S_IROTH) { printf("其他用户的读权限\n"); } if(info.st_mode & S_IWOTH) { printf("其他用户的写权限\n"); } if(info.st_mode & S_IXOTH) { printf("其他用户的执行权限\n"); } } int main() { test01(); return 0; }
4. 文件大小扩展
使用 truncate 函数可将指定文件扩展为指定大小。
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
注意:off_t 是 long 类型
头文件:#include <unistd.h> #include <sys/types.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
void test()
{
int fd = open("download.txt",O_WRONLY | O_CREAT);
if(-1 == fd)
{
perror("open");
return;
}
int ret = ftruncate(fd,1024);
if(-1 == ret)
{
perror("ftruncate");
return;
}
ret = close(fd);
if(-1 == ret)
{
perror("close");
return;
}
}
int main()
{
test();
return 0;
}