打开文件的方式:
FILE *fopen( const char *path, const char *mode );
读文件的方式:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
写文件的方式:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE*stream);
偏移文件流指针的方式:
int fseek(FILE *stream, long offset, int whence)
关闭文件的方式:
int fclose(FILE* fp);
stdin & stdout & stderr
C默认会打开三个输入输出流,分别是stdin, stdout, stderr。
仔细观察发现,这三个流的类型都是FILE*,fopen返回值类型,文件指针。
hello.c写文件:
#include
#include
int main()
{
FILE *fp = fopen("myfile", "w");
if (!fp){
printf("fopen error!\n");
}
const char *msg = "hello bit!\n";
int count = 5;
while (count--){
fwrite(msg, strlen(msg), 1, fp);
}
fclose(fp);// 关闭文件流指针
return 0;
}
hello.c读文件:
#include
#include
int main()
{
FILE *fp = fopen("myfile", "r");
if (!fp){
printf("fopen error!\n");
}
char buf[1024];
const char *msg = "hello bit!\n";
while (1){
//注意返回值和参数,此处有坑,仔细查看man手册关于该函数的说明
ssize_t s = fread(buf, 1, strlen(msg), fp);
if (s > 0){
buf[s] = 0;
printf("%s", buf);
}
if (feof(fp)){
break;
}
}
fclose(fp);
return 0;
}
打开文件的方式:
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
写文件的方式:
ssize_t write(int fd, const void* buf, size_t count)
读文件的方式:
ssize_t read(int fd, void *buf, size_t count)
偏置文件流指针的方式:
off_t lseek(int fd, off_t offset, int whence)
关闭文件的方式:
int close(int fd)
hello.cpp 写文件:
#include
#include
#include
#include
#include
#include
int main()
{
umask(0);
int fd = open("myfile", O_WRONLY | O_CREAT, 0644);
if (fd < 0){
perror("open");
return 1;
}
int count = 5;
const char *msg = "hello bit!\n";
int len = strlen(msg);
while (count--){
write(fd, msg, len);//fd: 后面讲, msg:缓冲区首地址, len: 本次读取,期望写入多少个字节的数据。 返回值:实际写了多少字节数据
}
close(fd);
return 0;
}
hello.cpp 读文件:
#include
#include
#include
#include
#include
#include
int main()
{
int fd = open("myfile", O_RDONLY);
if (fd < 0){
perror("open");
return 1;
}
const char *msg = "hello bit!\n";
char buf[1024];
while (1){
ssize_t s = read(fd, buf, strlen(msg));//类比write
if (s > 0){
printf("%s", buf);
}
else{
break;
}
}
close(fd);
return 0;
}
0 & 1 & 2
Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.
0,1,2对应的物理设备一般是:键盘,显示器,显示器 所以输入输出还可以采用如下方式:
#include
#include
#include
#include
#include
int main()
{
char buf[1024];
ssize_t s = read(0, buf, sizeof(buf));
if (s > 0){
buf[s] = 0;
write(1, buf, strlen(buf));
write(2, buf, strlen(buf));
}
return 0;
}
文件描述符就是从0开始的小整数
文件描述符的分配规则
在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。最小未占用规则,只要fd_array数组当中从小到大,哪个位置没有占用,则新打开的文件就会占用该位置。
#include
#include
#include
#include
#include
int main()
{
close(1);
int fd = open("myfile", O_WRONLY | O_CREAT, 00644);
if (fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
fflush(stdout);
close(fd);
exit(0);
}
此时,我们发现,本来应该输出到显示器上的内容,输出到了文件myfile 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有:>, >>, <。
使用 dup2 系统调用:
函数原型如下:
#include
int dup2(int oldfd, int newfd);
oldfd和newfd都是文件描述符
eg:想要标准输出变成一个程序员打开的文件dup2(2,3);
注意事项:
因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。
#include
#include
int main()
{
const char *msg0 = "hello printf\n";
const char *msg1 = "hello fwrite\n";
const char *msg2 = "hello write\n";
printf("%s", msg0);
fwrite(msg1, strlen(msg0), 1, stdout);
write(1, msg2, strlen(msg2));
fork();
return 0;
}
运行结果:
hello printf
hello fwrite
hello write
但如果对进程实现输出重定向呢? ./hello > file , 我们发现结果变成了:
hello write
hello printf
hello fwrite
hello printf
hello fwrite
我们发现printf 和fwrite (库函数)都输出了2次,而write 只输出了一次(系统调用)。为什么呢?肯定和fork有关!
综上: printf fwrite 库函数会自带缓冲区,而write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。
那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是write 没有缓冲区,而printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。
静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。
动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
生成静态库
[root@localhost linux]# ls
add.c add.h main.c sub.c sub.h
[root@localhost linux]# gcc -c add.c -o add.o
[root@localhost linux]# gcc -c sub.c -o sub.o
生成静态库
[root@localhost linux]# ar -rc libmymath.a add.o sub.o
ar是gnu归档工具,rc表示(replace and create)
查看静态库中的目录列表
[root@localhost linux]# ar -tv libmymath.a
rw-r–r-- 0/0 1240 Sep 15 16:53 2017 add.o
rw-r–r-- 0/0 1240 Sep 15 16:53 2017 sub.o
t:列出静态库中的文件
v:verbose 详细信息
[root@localhost linux]# gcc main.c -L. -lmymath
-L 指定库路径
-l 指定库名
测试目标文件生成后,静态库删掉,程序照样可以运行。
库搜索路径
生成动态库
shared: 表示生成共享库格式
fPIC:产生位置无关码(position independent code)
库名规则:libxxx.so
示例:
[root@localhost linux]# gcc -fPIC -c sub.c add.c
[root@localhost linux]# gcc -shared -o libmymath.so*.o
[root@localhost linux]# ls add.c add.h add.o libmymath.so main.c sub.c sub.h sub.o
使用动态库
编译选项
l:链接动态库,只要库名即可(去掉lib以及版本号)
L:链接库所在的路径.
示例: gcc main.o -o main –L. -lhello
运行动态库
1、拷贝.so文件到系统共享库路径下, 一般指/usr/lib
2、更改LD_LIBRARY_PATH
3、ldconfig 配置/etc/ld.so.conf.d/,ldconfig更新
使用外部库
系统中其实有很多库,它们通常由一组互相关联的用来完成某项常见工作的函数构成。
我们使用ls -l的时候看到的除了看到文件名,还看到了文件元数据。
[root@localhost linux]# ls -l
总用量 12
-rwxr-xr-x. 1 root root 7438 “9月 13 14:56” a.out
-rw-r–r--. 1 root root 654 “9月 13 14:56” test.c
每行包含7列:
stat命令能够看到更多信息
[root@localhost linux]# stat test.c
File: “test.c”
Size: 654 Blocks: 8 IO Block: 4096 普通文件
Device: 802h/2050d Inode: 263715 Links: 1
Access: (0644/-rw-r–r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2017-09-13 14:56:57.059012947 +0800
Modify: 2017-09-13 14:56:40.067012944 +0800
Change: 2017-09-13 14:56:40.069012948 +0800
inode
为了能解释清楚inode我们先简单了解一下文件系统。
Linux ext2文件系统,上图为磁盘文件系统图(内核内存映像肯定有所不同),磁盘是典型的块设备,硬盘分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。
例如mke2fs的-b选项可以设定block大小为1024、2048或4096字节。
而上图中启动块(Boot Block)的大小是确定的。
创建一个新文件主要有一下4个操作:
硬链接:
[root@localhost linux]# touch abc
[root@localhost linux]# ln abc def
[root@localhost linux]# ls -1i
abc def 263466 abc 263466 def
创建:
In [源文件] [待创建 出来的硬链接文件名称]
注意:
源文件和硬链接文件两者具有同样的inode节点信息
软链接:
类比:类似于win环境当中创建快捷方式
263563 -rw-r–r--. 2 root root 0 9月 15 17:45 abc
261678 lrwxrwxrwx. 1 root root 3 9月 15 17:53 abc.s -> abc
263563 -rw-r–r--. 2 root root 0 9月 15 17:45 def
创建:In -s [源文件] [待创建出来的软链接文件]
“-” 代表普通文件,软链接文件–>,“l”代表软链接文件
注意:
1.创建出来的软链接文件,指向源文件,文件类型是“l”,不论是修改软链接文件还是修改源文件是一回事!
2.软链接文件和源文件具有不同的inode节点号,相当于 两个inode节点,都保存了文件在磁盘当中的存储位置
3.如果使用“ll”命令看到的软链接文件指向的源文件一直在闪烁,说明源文件不存在
4.警告!!如果要删除源文件,一定记着把软链接文件也一起删除掉