1,C库IO函数中,FILE* ptr是不可避免的会使用到文件指针,文件指针实际上是一个结构体,里面包括三部分内容。其一:文件描述符,作用是所用到对应的磁盘文件;其二:文件读写位置指针(读写操作的时候只有一个这样的指针),其作用是,帮助进行读写,可以使用fseek函数对文件读写位置指针进行重置;其三:IO缓冲区,默认大小是8kb,是为了减少对硬盘操作的次数才引入IO缓冲区的。两种常见的会把缓冲中的数据写到硬盘的情况,其一:使用fflush刷新缓冲区的时候;其二:使用fclose正常关闭文件的时候。
2,虚拟地址空间
Linux中,32位的系统每运行一个程序,系统都会为其分配一个0~4G的虚拟地址空间。0~3G属于用户区,用户可以操作。3~4G属于内核区,用户访问不到。Linux中可执行程序的后缀是.elf,使用file 文件名就可以知道文件的信息。下面介绍虚拟内存空间的各个部分:
- Linux内核区,在内核区有一个PCB进程控制块,这是一个控制进程的复杂结构,PCB里面有一张文件描述符表,这个表有1024个位置, 每个位置都代表可以打开一个文件。没打开一个文件都会占用一个文件描述符,而且使用的是尽可能小的文件描述符的位置。
- ELF段包括三部分内容,其一:.text(代码段,二进制机器指令),其二:.data(已经初始化的全局变量),其三:.bss(未初始化的全局变量)
- 栈段:存放局部变量
- 堆区:存放new或者malloc分配的地址空间
- 共享区:需要加载动态库时,把动态库的代码加载到这个位置。
- 环境变量:存放所需的环境变量
考虑一下为什么引入虚拟空间,主要有三点原因:
- 方便编译器和操作系统安排程序的地址分布
程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的物理地址。
- 方便进程之间的隔离
使每个进程只能操作自己进程内部的程序
- 方便操作系统使用拿点可怜的内存
有可能操作系统已经没有那么多内存了
3,系统IO函数的介绍
- open函数
open函数的返回值是一个文件描述符,当发生错误,文件描述符是-1,这个时候会有一个全局的变量,叫errno,这个变量不同值会代表不同的错误信息。可以通过perror函数拿到对错误的描述。
open打开方式:
必选项: O_RDONLY,O_WRONLY,O_RDWR
可选项:
O_CREAT(创建出来的文件权限:本地有一个掩码(使用umask可以查看),给定的权限 & (本地掩码取反) 得出来的才是实际的文件的权限,使用三个参数的open函数需要知道实际权限是怎么计算出来的)
- read函数和write函数
返回值:
读/写文件失败 -1; 文件读/写完了 0; 读/写取的字节数 大于0的数
//open, read, write函数的使用
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
//打开一个已经存在的文件
int fd = open("test.txt", O_RDONLY);
if(fd == -1){
perror("open");//这个参数主要是给自己看的,随便取名
exit(1);
}
//创建一个新文件--写操作
int fd1 = open("newfile", O_CREAT | O_WRONLY, 664);
if(fd1 == -1){
perror("open1");//这个参数主要是给自己看的,随便取名
exit(1);
}
//read file
char buf[2048];
int count = read(fd, buf, sizeof(buf));
if(count == -1){
perror("read");
exit(1);
}
while(count){
//将读取的数据写入另一个文件中
int ret = write(fd1, buf, count);
printf("write types %d\n", ret);
count = read(fd, buf, sizeof(buf));
}
close(fd);
close(fd1);
return 0;
}
- lseek函数
注意,如果不知道函数的作用,可以先把光标放到函数名处,然后按K即可以跳转过去。
//使用lseek函数获取文件的长度和文件的扩展
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
//打开一个已经存在的文件
int fd = open("test.txt", O_RDWR);
if(fd == -1){
perror("open");//这个参数主要是给自己看的,随便取名
exit(1);
}
//获取文件的长度
int ret = lseek(fd, 0, SEEK_END);
printf("file length = %d\n", ret);
//做一个文件的拓展,只能向后拓展
int ret1 = lseek(fd, 2000, SEEK_END);
printf("file length = %d\n", ret1);
//实现文件拓展,需要再最后做一次写操作
write(fd, "a", 1);
close(fd);
return 0;
}
- stat函数(重要)
属于linux文件操作的系统函数,其实对于文件操作的系统函数都有自己的命令。像stat,chmod等。
stat函数的作用是获取文件的属性信息,可以使用如下命令获取: stat 文件的名字
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
int main(int argc, char* argv[1])
{
if(argc < 2){
printf("./a.out filename\n");
exit(1);
}
struct stat st;
//struct stat* st;
int ret = stat(argv[1], &st);
if(ret == -1){
perror("stat");
exit(1);
}
//获取文件大小
int size = (int)st.st_size;
printf("file size = %d\n", size);
return 0;
}
- lstat函数
lstat是不穿透不追踪函数,是针对软连接的时候,如果使用lstat 来查询软连接文件的信息时,查询出来的是软连接的文件的额大小,而stat则不然,使用stat函数查询出来的是软连接所连接到的文件的信息。
- access函数
测试指定文件是否拥有某种权限,具体参数返回值看帮助文档
- chmod函数的作用是修改文件的权限
- truncate函数(重要)
作用:使用这个函数的作用可以完成文件的扩展或截取,将参数path指定的文件大小改为参数length指定的大小。如果原来的文件大小比参数length大,则超过的部分会被删除。使用lseek进行文件扩展的时候末尾需要进行写操作,而truncate函数是不需要的。
- link函数,作用创建硬链接
- symlink函数,作用是创建软连接
- readlink函数,作用是读一个软连接,读出来的内容是软连接的内容,而不是读取软连接所指文件的内容。
- opendir函数,readdir函数,closedir函数
使用方法见代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
int get_file_count(char* root)
{
DIR* dir;
struct dirent* ptr = NULL;
int total = 0;
char path[1024];
dir = opendir(root);
if(dir == NULL)
{
perror("opendir");
exit(1);
}
while((ptr = readdir(dir)) != NULL)
{
if(strcmp(ptr->d_name, ".") == 0 || strcmp(ptr->d_name, "..") == 0)
{
continue;
}
if(ptr->d_type == DT_DIR)
{
sprintf(path, "%s/%s", root, ptr->d_name);
total += get_file_count(path);
}
if(ptr->d_type == DT_REG)
{
total ++;
}
}
closedir(dir);
return total;
}
int main(int argc, char* argv[])
{
if(argc < 2)
{
printf("a.out path\n");
exit(1);
}
int total = get_file_count(argv[1]);
printf("%s has %d files!\n", argv[1], total);
return 0;
}
- dup和dup2函数的作用,作用可以实现文件描述符的复制
dup的使用如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("a.txt", O_RDWR);
if(fd == -1)
{
perror("open");
exit(1);
}
printf("file open fd = %d\n", fd);
// 找到进程文件描述表中 ==第一个== 可用的文件描述符
// 将参数指定的文件复制到该描述符后,返回这个描述符
int ret = dup(fd);
if(ret == -1)
{
perror("dup");
exit(1);
}
printf("dup fd = %d\n", ret);
char* buf = "你是猴子派来的救兵吗????\n";
char* buf1 = "你大爷的,我是程序猿!!!\n";
write(fd, buf, strlen(buf));
write(ret, buf1, strlen(buf1));
close(fd);
return 0;
}
dup2的使用如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("english.txt", O_RDWR);
if(fd == -1)
{
perror("open");
exit(1);
}
int fd1 = open("a.txt", O_RDWR);
if(fd1 == -1)
{
perror("open");
exit(1);
}
printf("fd = %d\n", fd);
printf("fd1 = %d\n", fd1);
int ret = dup2(fd1, fd);
if(ret == -1)
{
perror("dup2");
exit(1);
}
printf("current fd = %d\n", ret);
char* buf = "主要看气质 ^_^!!!!!!!!!!\n";
write(fd, buf, strlen(buf));
write(fd1, "hello, world!", 13);
close(fd);
close(fd1);
return 0;
}
- 函数fcnt1
作用:改变已经打开的文件的属性
使用方法代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(void)
{
int fd;
int flag;
// 测试字符串
char *p = "我们是一个有中国特色的社会主义国家!!!!!!";
char *q = "呵呵, 社会主义好哇。。。。。。";
// 只写的方式打开文件
fd = open("test.txt", O_WRONLY);
if(fd == -1)
{
perror("open");
exit(1);
}
// 输入新的内容,该部分会覆盖原来旧的内容
if(write(fd, p, strlen(p)) == -1)
{
perror("write");
exit(1);
}
// 使用 F_GETFL 命令得到文件状态标志
flag = fcntl(fd, F_GETFL, 0);
if(flag == -1)
{
perror("fcntl");
exit(1);
}
// 将文件状态标志添加 ”追加写“ 选项
flag |= O_APPEND;
// 将文件状态修改为追加写
if(fcntl(fd, F_SETFL, flag) == -1)
{
perror("fcntl -- append write");
exit(1);
}
// 再次输入新内容,该内容会追加到旧内容的后面
if(write(fd, q, strlen(q)) == -1)
{
perror("write again");
exit(1);
}
// 关闭文件
close(fd);
return 0;
}