运行的是:
#include
#include
int main()
{
FILE *fp = fopen("log.txt","w");
if(NULL == fp)
{
perror("fopen");
return 1;
}
int ct = 5; while(ct)
{
fputs("hello sxl\n",fp);
ct--;
}
fclose(fp);
return 0;
}
#include
#include
int main()
{
FILE *fp = fopen("log.txt","r");
if(NULL == fp)
{
perror("fopen");
return 1;
}
char buffer[64];
int ct = 5;
while(ct)
{
//首地址 读多少个字节 从哪里读取
fgets(buffer,sizeof(buffer),fp);
printf(buffer);
ct--;
}
fclose(fp);
return 0;
}
1.任何进程在运行的时候,默认打开三个输入输出流:
stdin:对应键盘 标准输入
stdout:显示器 标准输出
stderr:显示器 标准错误
2.理解:三个默认的标识符分别是0 1 2,另外的标识符从3开始
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;
#include
#include
#include
#include
#include
int main()
{
umask(0);
int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
if(fd < 0)
{
printf("open error!\n");
return 1;
}
int fd1 = open("log.txt",O_WRONLY|O_CREAT,0666);
int fd2 = open("log.txt",O_WRONLY|O_CREAT,0666);
int fd3 = open("log.txt",O_WRONLY|O_CREAT,0666);
int fd4 = open("log.txt",O_WRONLY|O_CREAT,0666);
int fd5 = open("log.txt",O_WRONLY|O_CREAT,0666);
printf("fd:%d\n",fd);
printf("fd1:%d\n",fd1);
printf("fd2:%d\n",fd2);
printf("fd3:%d\n",fd3);
printf("fd4:%d\n",fd4);
printf("fd5:%d\n",fd5);
close(fd);
return 0;
}
3.程序
三个输入输出流从键盘写入和打印到显示器一样的操作
(1)stdin:从键盘读入一句,显示器上显示一句,读取到buffer中
#include
#include
int main()
{
FILE *fp = fopen("log.txt","r");
if(NULL == fp)
{
perror("fopen");
return 1;
}
char buffer[64];
int ct = 5;
while(ct)
{
fgets(buffer,sizeof(buffer),stdin);
printf(buffer);
ct--;
}
fclose(fp);
return 0;
}
#include
#include
int main()
{
FILE *fp = fopen("log.txt","w");
if(NULL == fp)
{
perror("fopen");
return 1;
}
int ct = 5; while(ct)
{
fputs("hello sxl\n",stdout);
ct--;
}
fclose(fp);
return 0;
}
注意:
a:追加,也是写入,从结尾写
w:从开始写(覆盖式写入)
(1)函数声明
int open(const char *pathname, int flags, mode_t mode);
*pathname:文件路径
flags:标识符
标识符 | 意义 |
---|---|
O_RDONLY | 只读 |
O_ERONLY | 只写 |
O_RDWR | 读写 |
O_APPEND | 追加 |
O_CREAT | 创建 |
mode:权限(例如:666) |
(2)包含头文件
#include
#include
#include
(3)程序
#include
#include
#include
#include
int main()
{
umask(0);
int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
printf("fd:%d\n",fd);
return 0;
}
注意:
a.标识符要组合使用,O_ERONLY与O_CREAT一起使用才会创建并别入文件
b.open的返回类型是int,返回-1说明程序错误,大于0说明运行正确
c.文件的权限不是666,受umask的影响,要设置为0,使其不受umask的影响
d.权限的数字要补齐4位,因为umask默认为4位
另:系统函数参数传参标志位:int 32bit 理论上传递32个标志位
判断标志位
//二进制序列中,只有一个比特位是1
#define X 0x1
#define Y 0x2
#define Z 0x3
open(arg1,arg2 = X| Y, arg3)
{
if(arg2 & X)
{
//为真,X是标志位
}
if(arg2 & Y)
{
//为真,Y是标志位
}
}
(1)头文件以及函数声明
#include
ssize_t write(int fd, const void *buf, size_t count);
//fd:想写入的文件
//*buf:实际上写入的内容
//count:期望写入的多少
(2)程序
#include
#include
#include
#include
#include
#include
int main()
{
umask(0);
int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
if(fd < 0)
{
printf("open error!\n");
return 1;
}
int ct = 5;
const char *msg = "hello sxl\n";
while(ct)
{
write(fd,msg,strlen(msg));
ct--;
}
printf("fd:%d\n",fd);
close(fd);
return 0;
}
(1)头文件以及函数声明
#include
ssize_t read(int fd, void *buf, size_t count);
//fd:文件名
//*buf:读取的内容
//count:期望读取的个数
(2)程序
#include
#include
#include
#include
#include
#include
int main()
{
umask(0);
int fd = open("log.txt",O_RDONLY,0666);
if(fd < 0)
{
printf("open error!\n");
return 1;
}
char c;
while(1)
{
ssize_t s = read(fd,&c,1);
if(s <= 0)
{
break;
}
write(1,&c,1); //1实际上是stdout的代替
}
printf("fd:%d\n",fd);
close(fd);
return 0;
}
磁盘文件;
文件=内容+属性(元信息)
内存文件:
已经打开的文件,会用struct file结构体利用双链表链接起来
文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。
从最小的但是没有被使用的开始分配,即关闭0/2,文件描述符就从0/2开始分配
输出重定向的原始原理:
**输出重定向的原始原理:**找到stdout里面封装的下标为1指向的文件
(1)关闭了1,致使不能写入显示器,但是write函数仍然是写入1,所以显示内容写入了log.txt
#include
#include
#include
#include
#include
#include
int main()
{
close(1);
umask(0);
int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
if(fd < 0)
{
printf("open error!\n");
return 1;
}
//close(1);
write(1,"hello sxl\n",10);
write(1,"hello\n",6);
write(1,"hello\n",6);
write(1,"hello\n",6);
write(1,"hello\n",6);
write(1,"hello\n",6);
write(1,"hello\n",6);
close(fd);
return 0;
}
(2)不用write函数,直接close(1),用printf函数,理应会重定向到log.txt中,结果是显示器与log.txt文件中都没有内容,是因为printf输出内容会有一个缓冲区,需要设置一个fflush(stdout),致使内容重定向到log.txt中
#include
#include
#include
#include
#include
#include
int main()
{
close(1);
umask(0);
int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
if(fd < 0)
{
printf("open error!\n");
return 1;
}
printf("hello:%d\n",123);
printf("hello:%c\n",'c');
printf("hello:%f\n",3.14);
fflush(stdout);
close(fd);
return 0;
}
#include
#include
#include
#include
#include
#include
int main()
{
close(1);
umask(0);
int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
if(fd < 0)
{
printf("open error!\n");
return 1;
}
fputs("hello sxl\n",stdout);
fputs("hello sxl\n",stdout);
fputs("hello sxl\n",stdout);
fputs("hello sxl\n",stdout);
fputs("hello sxl\n",stdout);
fflush(stdout);
close(fd);
return 0;
}
FILE是一个结构体,内部封装了fd
fopen究竟在做什么?
1.给调用的用户申请struct FILE结构体变量,并返回地址(FILE*)
2.在底层通过open打开文件,并返回fd,把fd填充进FILE变量中的fileno
(fread,fwrite,fclose,fputs,fgets等都是通过FILE*找到fd使用的)
即把文件中的内容直接重定向输出至显示器上
#include
#include
#include
#include
#include
#include
int main()
{
// close(1);
close(0); //关闭的是stdin
umask(0);
// int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
int fd = open("log.txt",O_RDONLY);
if(fd < 0)
{
printf("open error!\n");
return 1;
}
fputs("hello sxl\n",stdout);
fputs("hello sxl\n",stdout);
fputs("hello sxl\n",stdout);
fputs("hello sxl\n",stdout);
fputs("hello sxl\n",stdout);
char buffer[100];
fgets(buffer,100,stdin); //读进buffer,读100个,从stdin里面读取
printf("%s\n",buffer);
fflush(stdout);
close(fd);
return 0;
}
每执行一次程序,输出的内容就会追加到log.txt文件中
#include
#include
#include
#include
#include
#include
int main()
{
close(1);
umask(0);
int fd = open("log.txt",O_WRONLY|O_APPEND);
if(fd < 0)
{
printf("open error!\n");
return 1;
}
fputs("hello sxl\n",stdout);
fputs("hello sxl\n",stdout);
fflush(stdout);
close(fd);
return 0;
}
凡是显示到显示器上的内容都是字符
凡是从键盘读取的内容都是字符
键盘和显示器都称为字符设备
批量化替换makefile文件
底行模式:%s/myproc/test/ (只替换可执行程序的名字,myproc被替换成test)
底行模式:%s/myproc/test/g (myproc全部被替换成test,包括myproc.c换成test.c)
无缓冲
行缓冲:常见的对显示器进行刷新数据时(效用和可用性做的平衡)
可采用\n或者fflush(stdout)强制行缓冲
全缓冲:对文件写入时采用全缓冲
(1)重定向会更改进程的缓冲方式
(2)C接口打印一次,OS API打印两次,
如果往显示器打印,因为带有\0,所以是进行行刷新,打印的是3行;
若执行fork(),转变缓冲方式,行刷新只是实现打印功能,没有被刷新,相当于是一个父进程的数据,进程具有独立性,此时会发生写时拷贝,刷新两份,即打印了两份
#include
#include
#include
int main()
{
//C语言函数
printf("printf\n");
fprintf(stdout,"fprintf\n"); //往stdout写
//系统调用接口函数
const char *msg = "write\n";
write(1,msg,strlen(msg));
fork();
return 0;
}
缓冲区在哪里?
C语言提供,在FILE中维护,即在struct FILE结构体中,不仅包括fd,还包括用户缓冲区
这个缓冲区是谁提供的?
C语言自带,在FILE维护(fd、用户缓冲区),若缓冲区是OS提供的,那么所有的都会打印两次
OS也是有缓冲的,和文件缓冲有什么区别?
关闭fflush(stdout),打印的数据不能重定向到log.txt中
原因:因为close(1),关闭了stdout,最后close(fd)关闭文件后,要想刷新,要找到1,此时已经发现文件描述符1关闭,不能从缓冲区里面刷新出来,如果最后使用fclose(stdout),可以重定向写入文件,因为程序退出时,stdout会从缓冲区里刷新数据
#include
#include
#include
#include
#include
#include
int main()
{
close(1);
// close(0);
umask(0);
// int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
// int fd = open("log.txt",O_RDONLY);
int fd = open("log.txt",O_WRONLY);
if(fd < 0)
{
printf("open error!\n");
return 1;
}
fputs("hello sxl\n",stdout);
fputs("hello sxl\n",stdout);
// fflush(stdout);
close(fd);
return 0;
}
#include
#include
#include
#include
#include
#include
int main()
{
close(1);
// close(0);
umask(0);
// int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
// int fd = open("log.txt",O_RDONLY);
int fd = open("log.txt",O_WRONLY);
if(fd < 0)
{
printf("open error!\n");
return 1;
}
fputs("hello sxl\n",stdout);
fputs("hello sxl\n",stdout);
fclose(stdout);
// close(fd);
return 0;
}
注意:也可以close(1),但是close(1)尽量与dup2(fd,1)写在一起,防止文件描述符1被别的文件使用。
#include
#include
#include
#include
#include
#include
int main()
{
// close(1);
// close(0);
umask(0);
// int fd = open("log.txt",O_WRONLY|O_CREAT,0666);
// int fd = open("log.txt",O_RDONLY);
int fd = open("log.txt",O_WRONLY);
if(fd < 0)
{
printf("open error!\n");
return 1;
}
dup2(fd,1); //1是fd的一份拷贝
fputs("hello sxl\n",stdout);
fputs("hello sxl\n",stdout);
fflush(stdout);
close(fd);
return 0;
}
另外:
子进程会继承父进程的文件信息
进程替换不会影响进程打开的文件信息
inode:任何一个文件的属性集合,Linux中几乎每一个文件都有一个inode,可能存在大量的inode,区分inode,使用inode编号
内存文件就是加载磁盘文件来的
文件 = 文件属性(元信息)+文件内容(在磁盘直接存储)
(1)磁盘相关概念
磁盘:机械设备,效率低,永久性存储介质(目前所有的 普通文件都是在磁盘中存储的)
扇区:盘片被分成许多扇形的区域,一般一个扇区512KB
磁道:盘片上以盘片中心为圆心,不同半径的同心圆
柱面:硬盘中,不同盘片相同半径的磁道所组成的圆柱
磁盘寻址方案:盘面→柱面→扇区
磁盘:抽象为线性结构
(2)描述一下创建一个文件的过程?以及写入1kb数据的过程(文件:test.c)?
创建一个文件的过程:
遍历inode位图寻找未使用的inode,修改位图,再填充inode属性信息到inode table里面
写入1kb数据的过程:
先找到test.c的inode,再找到inode blocks,申请空间,扫描block Bitmap,发现空的块填入inode blocks中,最后把1kb数据写入到这个块中
(3)删除一个文件是做什么?
对inode Bitmap与block Bitmap两个位图中的对应文件进行清空即可
(4)删除文件,为何可以被恢复?
删除的是inode位图和block位图,inode属性信息和数据还在
(5)删除文件,没有删除属性信息,下次写入时可被覆盖
已经删除inode位图和block位图信息,证明所对用块是无效的,下一次写入可以直接覆盖
(6)如何理解目录创建的过程?
目录也有自己的inode
目录里的内容存放:当前目录下的文件名,对应文件的inode指针(inode号),即:1.文件名没有在inode中保存,包括目录本身;2.目录(文件名->inode编号)也是数据
3.软硬链接
建立软链接:ln -s mytest mytest-s 意思:mytest-s链接mytest
硬链接:ln mytest mytest-h
软硬链接的区别:
(1)软链接是一个独立的文件,有自己的inode,硬链接没有独立的inode
(2)软连接相当于快捷方式(若源文件删除,软连接不能正常指向);
硬链接本质没有创建文件,只是创建了一个文件名和已有的inode的映射关系,并写入当前目录(相当于取别名),在删除源文件的时候,系统则将链接数减1,当链接数为0的时候,inode就会被系统回收,文件的内容才会被删除。
硬链接一个作用:方便目录之间通过相对路径方式进行跳转
可以通过链接数判断该目录下有多少个子目录:链接数-2(.+该目录)
[看到一篇讲述的软硬链接,不错,更详细](https://blog.csdn.net/mahao1107/article/details/46851969?ops_request_misc=%257B%2522request%255Fid%2522%253 A%2522165216927616781818772126%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=165216927616781818772126&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-4-46851969-null-null.142%5Ev9%5Epc_search_result_cache,157%5Ev4%5Econtrol&utm_term=%E8%BD%AF%E8%BF%9E%E6%8E%A5%E5%92%8C%E7%A1%AC%E9%93%BE%E6%8E%A5%E7%9A%84%E5%BA%94%E7%94%A8&spm=1018.2226.3001.4187)
另外,关于acm(三个时间)
access:文件最后访问时间
modify:文件最后修改时间
change:文件属性最后修改时间
stat test.c //查看acm
本质是可执行程序的“半成品”
所有库的本质是:一堆.o的集合,不包含main,但是包含了大量的方法
ldd:查看可执行程序可依赖的库
Linux中
.so:动态库
.a:静态库
libc.so.6:C动态库(去掉前缀lib,去掉后缀.so,.a,剩下的就是库名字)
Win中
.dll:动态库
.lib:静态库
优缺点 | 动态库 | 静态库 |
---|---|---|
优点 | 节省空间(库文件通过地址空间进行共享) | 与库无关,不需要库 |
缺点 | 必须依赖库,没有库,无法运行 | 占空间(自身大、多个C静态程序加载时,在内存中存在大量的重复代码) |
动态库就是映射在堆栈之间的共享区,被所有该库使用进程 |
(1)生成静态库
makefile文件:
mylib=libcal.a
CC=gcc
$(mylib):add.o sub.o
ar -rc $(mylib) $^ //把所有.o文件打包生成静态库
%.o:%.c
$(CC) -c $< //把.c文件生成.o文件
.PHONY:clean
clean:
rm -f $(mylib) *.o
add文件:
add.h
#include
extern int add(int x,int y);
add.c文件
#include"add.h"
int add(int x,int y)
{
return x + y;
}
sub文件同add一样
运行生成了libcal.a静态库文件
(2)把生成的静态库打包利用(若要给别人使用自己写的库,直接给mathlib即可)
makefile文件
mylib=libcal.a
CC=gcc
$(mylib):add.o sub.o
ar -rc $(mylib) $^
%.o:%.c
$(CC) -c $<
.PHONY:clean
clean:
rm -f $(mylib) *.o
.PHONY:output
output:
mkdir -p mathlib/lib
mkdir -p mathlib/include
cp *.h mathlib/include
cp *.a mathlib/lib
把.h文件放在include中,把.a文件放在lib中
-I:头文件在哪里
-L:库文件在哪里
-l:链接哪一个库
可以把所写的库复制到系统的库中(默认路径,但是时间久了容易错乱)
(3)生成动态库:gcc -fPIC -c
a.形成.o文件
b.形成动态库:打包:gcc -shared
c.生成可执行文件
先把*.h文件放在include目录下,把libcal.so放在lib目录下,然后把这两个目录放在mlib目录下
e.运行可执行程序会出现错误,解决方法如下 ;
导出环境变量,再运行程序
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/sxl/code/Linux/testlib/mlib/lib