文章目录
一、回顾 c文件接口
1. 准备工作
a.文件 = 文件内容 + 文件属性
属性也是数据,即使创建一个空文件,也要占据磁盘空间
b.文件操作 = 文件内容的操作 + 文件属性的操作
有可能,在操作文件的过程中,即改变内容,又改变属性
c.所谓的“打开”文件,究竟在干什么?将文件的属性或内容加载到内存中!——冯诺依曼决定
d.是不是所有的文件,都会处于被打开的状态?
绝对不是!没有被打开的文件在哪里?
只在磁盘上静静的储存着!
e.打开的文件(内存文件)和磁盘文件
2.复习文件操作(c语言)
f.通常我们打开文件,访问文件,关闭文件,是谁在进行相关操作?
fopen,fcolse,fread,fwrite… ->代码 ->程序->当我们的文件程序,运行起来的时候,才会执行对应的代码,然后才是真正的对文件进行相关的操作——进程
g.学习文件操作就是学习进程和打开文件的关系
h.当我们以w方式打开文件,准备写入的时候,其实文件已经先被清空了!
写操作:
#include<stdio.h>
int main()
{
FILE* fp = fopen("log.txt","w");//以写入方式打开
if(fp==NULL)
{
perror("fopen");//
return 1;
}
const char* msg = "hello world";
int cnt =1;
while(cnt<20)
{
fprintf(fp,"%s:%d\n",msg,cnt++);
}
fclose(fp);
return 0;
}
读操作:
#include<stdio.h>
#include<unistd.h>
int main()
{
FILE* fp = fopen("log.txt","r");
if(fp==NULL)
{
perror("fopen");
return 2;
}
char buffer[64];
while(fgets(buffer,sizeof(buffer),fp)!=NULL)
{
printf("echo: %s",buffer);
}
return 0;
}
1.默认这个文件会在哪里形成?
当前路径!——当前进程所在的路径下
通过代码,修改进程的工作路径,看看文件形成在哪里?
#include<stdio.h>
#include<unistd.h>
int main()
{
chdir("/home/whc");
FILE* fp = fopen("log.txt","w");//以写入方式打开
if(fp==NULL)
{
perror("fopen");//
return 1;
}
printf("mypid%d\n",getpid());
while(1)
{
sleep(1);
}
const char* msg = "hello world";
int cnt =1;
while(cnt<20)
{
fprintf(fp,"%s:%d\n",msg,cnt++);
}
fclose(fp);
return 0;
}
ls /proc/$PID -l
此时进程工作路径已经修改,同时发现log.txt在/home/whc路径下创建。
2.r,w,r+,w+,a,a+
“r”:只读方式打开文件。文件必须存在,否则打开失败。
“w”:写方式打开文件。如果文件存在,则会被截断(清空),如果文件不存在则会创建新文件。
“a”:追加方式打开文件。写入的数据会追加到文件末尾,不会清空文件内容。如果文件不存在,则会创建新文件。
“r+”:读写方式打开文件。文件必须存在,允许读取和写入操作。
“w+”:读写方式打开文件。如果文件存在,则会被截断(清空),如果文件不存在则会创建新文件。
“a+”:读写方式打开文件。写入的数据会追加到文件末尾,不会清空文件内容。如果文件不存在,则会创建新文件。
3.关注一下文件清空的问题
3.为什么要学系统接口
当我们向文件写入的时候,最终是不是向磁盘写入,那么请问谁有资格向磁盘硬件写入?
操作系统
那么能绕开操作系统写入?
不能,如果绕开,就不清楚磁盘被谁访问,有效空间有哪些。也就是所有上层访问文件的操作,都必须贯穿操作系统。
操作系统是如何被上层使用的?
必须使用操作系统提供相关系统调用!
如何理解printf?
向显示屏打印,显示屏是硬件,需要系统调用,所以printf一定封装了系统接口,所有语言都对系统接口做了封装。
为什么要封装?
a.原生系统接口,使用成本比较高
b.语言直接使用系统接口不具备跨平台性
封装是如何解决跨平台问题的?
穷举所有的底层接口+条件编译!
C库提供的文件访问接口一定调用系统接口,也就是为什么学文件级别的系统接口。
二、文件操作
1.io系统接口 - open - write - read (系统接口)
系统传递标位,是用位图结构进行传递的!
每一个宏标记,一般只需要有一个比特位是1,并且和其他宏对应的值,不能重叠。写一个代码,实现一下:
#include<stdio.h>
#define PRINT_A 0x1//0000 0001
#define PRINT_B 0x2//0000 0010
#define PRINT_C 0x4//0000 0100
#define PRINT_D 0x8//0000 1000
#define PRINT_DFL 0x0//0000 0000
void Show(int flags)
{
if(flags&PRINT_A)
{
printf("hello A\n");
}
if(flags&PRINT_B)
{
printf("hello B\n");
}
if(flags&PRINT_C)
{
printf("hello C\n");
}
if(flags&PRINT_D)
{
printf("hello D\n");
}
if(flags==PRINT_DFL)
{
printf("hello Default\n");
}
}
int main()
{
printf("PRINT_DFL:\n");
Show(PRINT_DFL);
printf("PRINT_A:\n");
Show(PRINT_A );
printf("PRINT_B|PRINT_C:\n");
Show(PRINT_B|PRINT_C );
printf("PRINT_A|PRINT_B|PRINT_D|PRINT_C:\n");
Show(PRINT_A|PRINT_B|PRINT_D|PRINT_C);
return 0;
}
参数 flags :
O_RDENLY :只读
O_WRONLY :只写
O_RDWR :读写
O_APPEND :追加
O_CREAT :如果没有创建
O_TRUNC :截断,清空
打开一个文件,如果没有就创建:
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("log.txt",O_WRONLY|O_CREAT);
if(fd<0)
{
perror("open");
return 1;
}
printf("fd:%d\n",fd);
}
发现权限是乱的,如果要打开并不存在的文件并创建,要用三个参数的open
参数mode :代表你要创建文件的权限。
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
if(fd<0)
{
perror("open");
return 1;
}
printf("fd:%d\n",fd);
}
但权限不是666而是664,因为默认权限掩码umask是002,如果要在程序里创建出666的权限文件,可以设置权限掩码在open前添加:**umask(0);**的代码。
向文件写:
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>
int main()
{
int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
if(fd<0)
{
perror("open");
return 1;
}
printf("fd:%d\n",fd);
int cnt = 0;
const char* str="hello file\n";
while(cnt<5)
{
//请问str最后有一个'\0'要写入吗--不要只是C语言的
write(fd,str,strlen(str));
cnt++;
}
}
修改写入内容为aaaaaa,再次写入:
重写写入没有清空,而是覆盖式的写。
有一个选项叫O_TRUNC就是截断(清空),在打开文件时候清空文件内容。
int fd = open(“log.txt”,O_WRONLY|O_CREAT|O_TRUNC,0666);
加上选项后运行程序:
此时就重现了C语言的fopen(“log.txt”,“w”)
fopen(“log.txt”,“w”) :底层的open,O_WRONLY|O_CREAT|O_TRUNC
foopen(“log.txt”,“a”):底层的open,O_WRONLY|O_CREAT|O_TRUNC|O_APPEND
读取:
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>
int main()
{
int fd = open("log.txt",O_RDONLY);
if(fd<0)
{
perror("open");
return 1;
}
printf("fd:%d\n",fd);
char buffer[128];
ssize_t s = read(fd,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s] = '\0';
printf("%s",buffer);
}
}
注意:系统接口返回的不是C语言风格的,要在字符串结尾加上’\0’
2.理解文件描述符 fd
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>
int main()
{
int fda = open("loga.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
int fdb = open("logb.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
int fdc = open("logc.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
int fdd = open("logd.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
printf("fda: %d\n",fda);
printf("fdb: %d\n",fdb);
printf("fdc: %d\n",fdc);
printf("fdd: %d\n",fdd);
}
open的返回值:
fd<0 :failde
fd>=0: success
-
为什么从3开始,0,1,2 去哪?
被系统默认打开了:
0:标准输入,键盘
1:标准输出,显示器
2:标准错误,显示器
对应C语言的文件流:
FILE* 是文件指针,FILE是什么?
是C库提供的结构体,封装了多个成员,而对文件操作而言,系统接口只认认fd,
FILE内部,必定封装了fd。 -
验证0是标准输入——键盘
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>
int main()
{
char buffer[1024] ;
ssize_t s = read(0,buffer,sizeof(buffer)-1);
if(s>0)
{
printf("echo: %s",buffer);
}
}
运行键盘输入:hello world
- 验证1是标准输出——显示器
int main()
{
char* buffer ="hello world\n";
ssize_t s = write(1,buffer,strlen(buffer));
}
- 验证2是标准错误——显示器
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>
int main()
{
char* buffer ="hello 2 world\n";
ssize_t s = write(2,buffer,strlen(buffer));
}
- 验证0 1 2 和stdin,stdout,stderr的对应关系
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>
int main()
{
printf("stdout: %d\n",stdout->_fileno);
printf("stdin: %d\n",stdin->_fileno);
printf("stderr: %d\n",stderr->_fileno);
}
_fileno这个成员变量存的就是文件描述符fd
至此我们弄清楚函数接口和数据类型对应关系:
系统 | c函数 |
---|---|
open | fopen |
close | fclose |
read | fread |
write | fwrite |
fd | FILE* ->_fileno |
- 0,1,2,3,4,5…你见过什么样的数据,是这样?
数组下标!
用的都是系统接口->操作系统提供的返回值
进程:内存文件的关系,被打开的文件都是在内存里的。
一个进程可以打开多个文件,所以在内核中,进程:打开的文件 = 1:n,所以系统在运行中,有可能会存在大量的被打开的文件,OS需要对被打开的文件进行管理,一说到管理就是先描述再组织。一个文件被打开,在内核中,要创建该被打开的文件的内核数据结构——先描述
struct file
{
//包含了我们想看的文件的大部分内容+属性
struct file *next;
struct file *prev;
}
而在struct task_struct中有有一个struct files_struct files它指向一个数组叫struct file fd_array[] 里面放的都是文件数据结构指针。
从而对被打开的文件管理,转化成为了对链表的增删改查!open返回数字也就是文件指针数组的下标。
- 如何理解Linux下一切皆文件?
如何使用c语言,实现面向对象(类)?
struct file
{
//对象属性
//函数指针
}
3.探索应用特征
1. 文件描述符的分配规则
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>
int main()
{
close(0);
int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
printf("fd: %d\n",fd);
close(fd);
}
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>
int main()
{
close(2);
int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
printf("fd: %d\n",fd);
close(fd);
}
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>
int main()
{
close(1);
int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
printf("fd: %d\n",fd);
close(fd);
}
发现没有打印,也没有写到log.txt里!!!
原因是保存到缓冲区,但磁盘写入,是全缓冲,进程退出前,还没刷新,就将文件描述符关闭,所以导致没有写入磁盘。
在打印后面,使用刷新缓冲区:ffulsh(tdout)
但可以发现,在关闭了文件描述(fd)1后,本来应该往显示器打印,最终却变成了向指定文件打印->这不就是重定向!!!
2.重定向的本质 —— dup2(系统接口)
如果我们要进行重定向,上层只认识0,1,2,3,4,5这样的fd,我们可以在OS内部,通过一定的方式调整数组的特定下标的内容(指向),我们就可以完成重定向操作!
具体操作:
-
复制什么?谁是随的拷贝?
把前者的内容,写入后者的内容,最终只剩oldfd的内容 -
参数怎么传
常规打开一个文件返回的是3存到fd,如果想要输出重定向,就需要将fd的内容复制到1 的内容里。———dup2(fd,1)
输出重定向:
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>
int main()
{
int fd = open("log.txt",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(fd<0)
{
perror("open");
return 1;
}
dup2(fd,1);
fprintf(stdout,"fd:%d\n",fd);
// fflush(stdout);
close(fd);
}
输入重定向:
log.txt:
hello dup2
hello world
hello whc
myfile.c:
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>
int main()
{
int fd = open("log.txt",O_RDONLY);
if(fd<0)
{
perror("open");
return 1;
}
dup2(fd,0);
char buffer[64] ;
while(fgets(buffer,sizeof(buffer),stdin)!=NULL)
{
printf("%s",buffer);
}
}
3.理解缓冲区
什么是缓冲区?
缓冲区的本质:就是一段内存
为什么要有缓冲区?
a.解放使用缓冲区的进程时间
b.缓冲区的存在可以集中处理数据刷新,减少IO的次数,从而达到提高整体机的效率的目的
缓冲区在哪里?
代码验证:
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>
int main()
{
printf("hello printf ");
const char* msg = "hello write";
write(1,msg,strlen(msg));
sleep(5);
}
休眠前:
休眠后:
现象:系统接口write立刻刷新,而printf直到进程结束后才刷新。
1.printf没有立刻刷新的原因,是因为有缓冲区的存在
2.但write立即刷新
3.printf封装了write
那么缓冲区不在哪里?
不在write内部!!!缓冲区不是内核级别的。
所以缓冲区只能是C语言提供的,语言级别的缓冲区。
而printf是向FILE结构体写的,FILE结构体里封装很多属性,就封装了FILE对应的语言级别的缓冲区。
如果在刷新之前,关闭了fd会有什么问题?
不会刷新
刷新策略的问题
什么时候刷新?
常规:
a.无缓冲(立即刷新):
b.行缓冲(逐行刷新):显示器文件
c.全缓冲 (缓冲区满,:刷新) 块设备对应的文件,磁盘文件
特殊:
a.进程退出
b.用户强制刷新
奇怪的问题:
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>
int main()
{
const char* str1 = "hello printf\n";
const char* str2 = "hello fprintf\n";
const char* str3 = "hello fputs\n";
const char* str4 = "hello write\n";
//C库函数
printf(str1);
fprintf(stdout,str2);
fputs(str3,stdout);
//系统接口
write(1,str4,strlen(str4));
//是调用完上面的代码,才执行的fork
fork();
}
显示器打印正常,重定向输出到log.txt,C库出现打印两次。
解释:
1.刷新的本质,把缓冲区的数据write到OS内部,清空缓存。
2.缓冲区,是自己的FILE内部维护的,属于父进程的数据区域。
发生写时拷贝,父进程刷一份,子进程刷一份。
4.简单模拟fputs
#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#define NUM 1024
#define NONE_FLUSH 0x0
#define LINE_FLUSH 0x1
#define FULL_FLUSH 0x2
typedef struct MyFILE{
int _fd;//保存要文件描述符
char _buffer[NUM];//缓冲区
int _flags;//标记刷新策略
int _end;
}MyFILE;
MyFILE* my_fopen(const char* path,const char* method)
{
assert(path);
assert(method);
int flags = O_RDONLY;
if(strcmp(method,"w")==0)
{
flags = O_WRONLY|O_CREAT |O_TRUNC ;
}
int fd = 0;
fd = open(path,flags,0666);
if(fd<0)
{
return NULL;
}
MyFILE* fp = (MyFILE*)malloc(sizeof(MyFILE));
if(fp==NULL)
{
return NULL;
}
memset(fp,0,sizeof(MyFILE));
fp->_fd = fd;
fp->_flags = LINE_FLUSH;
fp->_end = 0;
return fp;
}
void my_fwrite(MyFILE* fp ,const char* s,int len)
{
assert(fp);
assert(s);
if(len>0)
{
//1.把s放到缓冲区
strncpy(fp->_buffer+fp->_end,s,len);
fp->_end +=len;
//2.查看缓冲策略
if(fp->_flags&NONE_FLUSH)
{
}
else if(fp->_flags&LINE_FLUSH)
{
if(fp->_end>0&&fp->_buffer[fp->_end-1]=='\n')
{
write(fp->_fd,fp->_buffer,fp->_end);
fp->_end = 0;
}
}
else if(fp->_flags&FULL_FLUSH)
{
}
}
}
void my_fflush(MyFILE* fp)
{
assert(fp);
if(fp->_end>0)
{
write(fp->_fd,fp->_buffer,fp->_end);
fp->_end = 0;
syncfs(fp->_fd);
}
}
void my_fclose(MyFILE* fp )
{
assert(fp);
my_fflush(fp);
close(fp->_fd);
free(fp);
}
测试代码:
int main()
{
MyFILE*fp = my_fopen("log.txt","w");
if(fp==NULL)
{
printf("my_fopen error\n");
return 1;
}
const char* s = "hello my file\n";
my_fwrite(fp,s,strlen(s));
printf("写入一行数据\n");
sleep(5);
//结果:
//hello my file
const char* ss = "hello 11 ";
my_fwrite(fp,ss,strlen(ss));
printf("写入数据,不是一行\n");
sleep(5);
//结果:
//hello my file
const char* sss = "hello 222\n";
my_fwrite(fp,sss,strlen(sss));
printf("写入一行数据\n");
sleep(5);
//结果:
//hello my file
//hello 11 hello 222\n
const char* ssss = "hello 4444 ";
my_fwrite(fp,ssss,strlen(ssss));
printf("写入数据,不是一行,但强制刷新\n");
my_fflush(fp);
sleep(5);
//结果:
//hello my file
//hello 11 hello 222\n
//hello 4444
const char* sssss = "hello 55555";
my_fwrite(fp,sssss,strlen(sssss));
printf("写入数据,不是一行,但虽然后关闭文件\n");
//结果:
//hello my file
//hello 11 hello 222\n
//hello 4444 hello 55555
//模拟进程退出
my_fclose(fp);
return 0;
}
5.简单实现命令行重定向
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<assert.h>
#include<ctype.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define NUM 1024
#define SIZE 128
#define SEP " "
#define DROP_SPACE(s) do{while(isspace(*s))s++;}while(0)
char command_line[NUM];
char* command_args[SIZE];
#define NONE_REDIR -1
#define INPUT_REDIR 0
#define OUTPUT_REDIR 1
#define APPEND_REDIR 2
int g_redir_flag = NONE_REDIR;
char *g_redir_filename = NULL;
void CheckDir(char* commands)
{
assert(command_line);
//[start,end)
char* start =commands;
char* end = commands+strlen(commands);
// ls -a -l>log.txt
while(start <end)
{
if(*start=='>')
{
if(*(start+1)=='>')
{
//ls -a -l>>log.txt
*start='\0';
start+=2;
g_redir_flag = APPEND_REDIR;
DROP_SPACE(start);
g_redir_filename =start;
break;
}
else
{
//ls -a -l>log.txt
*start = '\0';
start++;
g_redir_flag = OUTPUT_REDIR;
DROP_SPACE(start);
g_redir_filename =start;
break;
}
}
else if(*start=='<')
{
//输入重定向
*start = '\0';
start++;
g_redir_flag = INPUT_REDIR;
DROP_SPACE(start);
g_redir_filename =start;
break;
}
else
{
start++;
}
}
}
int main()
{
while(1)
{
g_redir_flag = NONE_REDIR;
g_redir_filename =NULL;
//1.显示提示符
printf("[张三@我的主机名 当前目录]#");
fflush(stdout);
//2.获取用户输入
memset(command_line,'\0',sizeof(command_line));
fgets(command_line,NUM,stdin);
//去掉回车
command_line[strlen(command_line)-1] = '\0';
//2.1 ls -a -l>log.txt or cat<file.txt or ls -a -l>>log.txt or ls-a -l
//ls -a -l>log.txt -> ls -a -l\0log.txt
CheckDir(command_line);
//3.切割字符串将"ls -a -l"切割成"ls","-a","-l"
command_args[0]= strtok(command_line,SEP);
int index = 1;
//给ls添加颜色
if(strcmp(command_args[0],"ls")==0)
{
command_args[index++] = "--color=auto";
}
while(command_args[index++]=strtok(NULL,SEP));
if(strcmp(command_args[0],"cd")==0 && command_args[1]!=NULL)
{
chdir(command_args[1]);
continue;
}
// //检查打印
// int cnt = 0;
// while(command_args[cnt]!=NULL)
// {
// printf("%s\n",command_args[cnt++]);
// }
// printf(g_redir_filename);
//5.创建子进程,让子进程执行进程替换,执行要执行的程序
pid_t id = fork();
if(id==0)
{
int fd = -1;
switch(g_redir_flag)
{
case NONE_REDIR:
break;
case INPUT_REDIR:
{
fd=open(g_redir_filename,O_RDONLY);
dup2(fd,0);
break;
}
case OUTPUT_REDIR:
{
// printf("开始重定向\n");
// printf("filename: %s\n",g_redir_filename);
fd = open(g_redir_filename,O_WRONLY|O_CREAT|O_TRUNC,0666);
dup2(fd,1);
break;
}
case APPEND_REDIR:
{
fd = open(g_redir_filename,O_WRONLY|O_CREAT|O_APPEND,0666);
dup2(fd,1);
break;
}
default:
printf("bug?\n");
break;
}
if(fd!=-1)
{
close(fd);
}
//6.进程替换
execvp(command_args[0],command_args);
exit(1);
}
//获取进程替换的返回值
int status = 0;
int ret = waitpid(id,&status,0);
if(ret>0)
{
if(status&0x7F!=0||(status>>8)&0xFF!=0)
{
printf("执行失败\n");
}
}
else
{
printf("等待失败!\n");
}
}
return 0;
}
6.命令行重定向真正用法(>,>>,<)和 perror
#include<iostream>
#include<cstdio>
int main()
{
//stdout
printf("hello printf 1\n");
fprintf(stdout,"hello fprintf 1\n");
fputs("hello fputs 1\n",stdout);
//stderr
fprintf(stderr,"hello fprintf 2\n");
fputs("hello fputs 2\n",stderr);
perror("hello perror 2");
//cout
std::cout<<"hello cout 1 "<<std::endl;
//cerr
std::cerr<<"hello cerr 2"<<std::endl;
}
将标准输出重定向到stdout.txt,将标准错误重定向到stderr.txt:
./a.out 1>stdout.txt 2>stderr.txt
为什么怎么做,意义在哪里?
可以区分哪些程序日常输出,哪些是错误。
如果想让它们都重定向到一个all.txt:
./a.out > all.txt 2>&1
关于perror的实现原理:
1.看看系统是怎么做的:
open打开一个不存在的文件会返回-1,和设置errno:
C语言有一个全局变量,记录最近一次C库函数调用失败的原因——errno
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
//没有文件可以被打开
int fd = open("log.txt",O_RDONLY);
if(fd<0)
{
perror("open");
return 1;
}
}
编译运行:
使用自己写my_perror:
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>
#include <errno.h>
void my_peeror(const char* info)
{
fprintf(stderr,"%s: %s\n",info,strerror(errno));
}
int main()
{
//没有文件可以被打开
int fd = open("log.txt",O_RDONLY);
if(fd<0)
{
// perror("open");
my_peeror("open");
return 1;
}
}
7.理解文件系统
1.磁盘存储结构
磁盘上存储的基本单位是扇区512字节。
文件系统:什么文件,对应了几个磁盘块!
只要我们能找到磁盘上的柱面-Cylinder(磁道),磁面-Head,扇区-Sector就能找到一个存储单元。 ——CHS地址
2.磁盘的逻辑抽象结构
我们将磁盘上所有磁道想象成线性结构:
如果我们把它当成数组,sector disk[10000000],定位一个sector,只要找到下标行了,这个下标就是LBA(逻辑块地址)
也就是对磁盘的管理,转化成为了对数组空间的管理。这个过程就是先描述再组织。
现在我们就需要将LBA地址转化成CHS地址:
LBA : 1234
1234/1000=1 :在第一面,也就是LBA除以磁盘单面大小,得到是第几磁盘-Head,就是H的值。
1234%1000=234 ,234 /20 = 11:1个磁道有20个扇区,计算得出在第11磁道-Cylinder,就是C的值。
234 %20 = 14 :1个磁道有20个扇区,计算得出在磁道的第14个扇区-Sector,就是S的值。
C:11
H: 1
S:14
磁盘的基本单位是:扇区(常规512字节)
文件系统访问磁盘的基本单位是:4KB
为什么文件系统要以4KB为单位?
1.提高IO效率
2.不要让软件(OS)设计和硬件(磁盘)具有强相关性,就是耦合!
虽然系统将磁盘以4KB为单位,但要管理一整个磁盘还是太难了!系统又将磁盘分成几个XGB,也就是我们看到的分区。
现在管理文件,只需管理好各个区就可以了,但每个区还是太大了!系统又将每个区拆分成许多组,只要把这些组管理好,就实现了对整体管理了。
Block group :就是上面说的组。
文件 = 内容 + 属性 —— 都要存储 —— Linux采用的是将内容和属性数据分开存储的方案:
Data blocks :以块为单位,进行文件内容的保存!
inode table:以128字节单位,进行inode属性的保存
注:inode属性里有一个inode编号,一般而言一个文件一个inode编号。
Block Bitmap:Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用。
inode Bitmap:inode块是否被占用!inode bitmap表征inode的使用情况!
GDT,Group Descriptor Table:块组描述符,描述块组属性信息
超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量
一个inode(文件,属性)如何和属于直接的内容关联起来?
struct inode
{
//文件的所有的属性
blocks[15]//[0,11]:直接保存的就是该文件对应的boocks编号
//[12,15]:指向一个datablock,但是这个datablock不保存有效数据,而保存该文件所使用的其他块的编号!
}
文件名算文件的属性?
算,但是inode里面,并不保存文件名,Linux下,底层实际都是通过inode编号标识文件的。
关于目录权限:
进入一个目录,x
创建一个文件,w
查看文件名,r
目录是文件吗?
是,文件 = 内容 +属性
内容:放的 文件 :inode编号 的映射关系
创建一个文件的时候,一定是在一个目录下!
当我们创建一个文件,操作系统做了什么?
获得文件名和inode编号->找到中间所处的目录,根据目录的inode找到目录的datablock->将文件名和inode编号的映射关系写入到目录的数据块中!
请问删除一个文件,os做了什么?
根据目录inode找到目录的datablock->要删除的文件名和inode,根据inode找到所处的block group将要删除的inode bitmap对应置0,最后删除目录data block中的映射就完成了删除。
8.软硬链接
建立软链接:
ln -s my.txt my.txt.soft
建立硬链接:
ln my.txt my.txt.hard
区别:
软硬链接是区别:
软链接是一个独立文件,有自己独立的inode。——Linux下的快捷方式
既然是一个独立文件,inode是独立的,软链接的文件内容是什么?
保存的是指向文件内容
硬链接不是一个独立文件(是什么?)他和目标文件使用的是同一个inode!——就是单纯的在Linux指定的目录下,给指定的文件新增文件名和inode编号的映射关系
什么是硬链接数?node编号,不就是一个指针的概念吗?
本质就是该文件属性中的一个计数器,count,标识有几个文件名和我的inode建立了映射关系。简言之,就是有几个文件名指向我的inode(文件本身!)
为什么创建目录硬链接数是2?
因为任何目录里都有一个 . 而两个文件指向目录就是2了!
断开链接:
unlink 链接
9.动态库和静态库
1.定义
静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
一个与动态库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码
在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接(dynamic linking)
动态库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程共用,节省了内存和磁盘空间。
2.铺垫
mymath.h:
#pragma once
#include<stdio.h>
#include<assert.h>
//[from,to] ->累加->result ->return
extern int addToVal(int from,int to);
myprint.h:
#pragma once
#include<stdio.h>
#include<time.h>
void print(const char*msg);
mymath.c:
#include "mymath.h"
int addToVal(int from,int to)
{
assert(from<=to);
int result = 0;
for(int i = from;i<=to;i++)
{
result+=i;
}
return result;
}
myprint.c:
#include"myprint.h"
void print(const char*msg)
{
printf("%s : %lld\n",msg,(long long)time(NULL));
}
3.生产静态库
a.将.c汇编成.o文件
gcc -c 文件名.c -o 文件名.o
b.形成静态库
库名命名规则:lib + 库名 + .a
ar -rc lib库名.a 文件名1.o 文件名2.o
4.生成动态库
a.将.c汇编成.o文件
gcc -fPIC -c 文件名.c -o 文件名.o
注:汇编的时候要加上 -fPIC ,产生位置无关码
b.形成静态库
动库名定义方式为:lib + 库名 + .so
gcc -shared -o lib库名.so 文件1.o 文件2.o
5.如何发布
我们用库的时候,需要什么东西?
a.库文件
b.头文件
将头文件放到 xx库目录/include 目录里
将库文件放到 xx库目录/lib 目录里
现在只要将lib-dyl目录文件给别就可以使用,我们写的库了!
6.如何使用库
头文件的搜索路径:" " <>
1.在当前路径下查找头文件
2.在系统头文件路径下查看头文件
安装库
3.指定头文件和库文件搜索路径
-I 你的头文件搜索路径
-L 你的库文件搜索路径
谁在找头文件?
编译器,vs2019,gcc -> 进程在找
系统头文件一般放在:/usr/include
系统库文件一般放在:/lib64
1.库的安装
将头文件放到 /usr/include
将库文件放到 /lib64
注:要权限的,且不推荐安装自己写的库,会污染库!
要卸载自己写的库,直接将刚才拷贝的删掉即可。
测试代码:
#include"mymath.h"
#include"myprint.h"
int main()
{
int from = 10;
int to = 20;
int result = addToVal(from,to);
printf("result: %d\n",result);
print("hello world");
return 0;
}
2.使用静态库 .a
方法一(安装库):
-l:后面的跟的是库名,是去掉前缀和后缀的
比如说要链接的是libmymath.a
就要写成 -lmymath
gcc 程序.c -l库名
-l :是指名我要链接的第三方库的名称
方法二(指定路径):
-I(是大写 i ) :你的头文件搜索路径
-L:你的库文件的搜索路径
gcc test.c -l mymath -I ./lib-static/include -L ./lib-static/lib
3. 使用动态库 .so
和上面一样编译,编译成功,但运行程序失败。
原因:
-I -L 是告诉gcc在哪里找头文件和库文件,当进程执行程序的时候,进程不知道头文件和库文件在哪,所以导致找不到库。
为什么静态库没有这个问题?
形成可执行程序之后,已经把需要的代码拷贝到我的代码中,运行时,不依赖库!所以不需要运行查找
为什么动态库有这个问题?
程序和动态库是分开加载的,
如何解决进程执行程序找不到库:
1.库拷贝到 /lib64 ——相当于安装
2.通过导入环境变量的方式
程序运行的时候,会在环境变量中查找自己需要的动态库路径——LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库的绝对路径
再次运行程序:
3.系统配置文件
在/etc/ld.so.conf.d/ 里建一个文件,把库目录的绝对路径写进去。
ldconfig /etc/ld.so.conf.d/刚刚建的文件名 :作用将配置文件加载到内存里,使配置文件生效。
注:如果删除配置文件,后要执行ldconfig
1.1 建立软连接