Linux -- 基础IO

文章目录

  • 1. 基础认识
  • 2. 回顾C文件接口
    • 2.1 现象一
    • 2.2 现象二
    • 2.3 fprintf()函数回顾
    • 2.4 fnprintf()函数使用
    • 2.5 "a"模式
  • 3. 系统接口
    • 3.1 open()和close()
    • 3.2 write()
    • 3.3 read()
    • 3.4 C文件接口和系统接口关系
    • 3.5 文件描述符
    • 3.6 深度理解Linux下一切皆文件
    • 3.7 FILE是什么
    • 3.8 文件描述符分配规则
    • 3.9 重定向
      • 3.9.1 输出重定向
      • 3.9.2 输入重定向
      • 3.9.3 追加重定向
      • 3.9.4 回顾现象
      • 3.9.5 重定向指定文件
    • 3.10 dup2()系统调用
  • 4. 理解文件缓冲区
    • 4.1 现象
    • 4.2 文件缓冲区
    • 4.3 C接口和系统调用模拟
  • 5. 磁盘
    • 5.1 磁盘物理组成
    • 5.2 磁盘分区表
  • 6. 文件系统
    • 6.1 文件系统特性
    • 6.2 Linux的Ext2文件系统(inode)
    • 6.3 抽象理解
    • 6.4 查看文件系统
  • 7. 软硬连接
    • 7.1 现象
    • 7.2 软连接初步使用场景
    • 7.3 硬链接理解
  • 8. 动静态库
    • 8.1 初识
    • 8.2 为什么要有
    • 8.3 静态库使用
    • 8.4 动态库使用
    • 8.5 动静态库加载问题

1. 基础认识

  1. 文件=内容+属性,对文件的操作无非就两种:对内容进行操作或者对属性进行操作。
  2. 文件没有被操作的时候,文件一般是放在磁盘上的。
  3. 文件被操作的时候,文件是内存中的,因为是冯诺依曼体系的规定。
  4. 文件被操作的时候,文件被加载内存中的必须得有文件属性。
  5. 打开文件本质就是将需要的文件属性加载到内存中,操作系统一定同时存在大量的被打开的文件,同时操作系统也要管理这些被打开的文件,要管理文件就需要先描述后组织,先描述就是构建在内存中的文件结构体struct file{},这个结构体可以从磁盘上拿到,后组织就是通过数据结构来组织,比如:链表来连接结构体节点。
  6. 文件被分为两大类:磁盘文件、内存文件(被打开的文件)
  7. 文件是被操作系统打开的,是进程让操作系统打开的。
  8. 我们的的文件操作都是进程和被打开文件的关系,也就是struct task_struct 和 struct file的关系。

2. 回顾C文件接口

2.1 现象一

[jyh@VM-12-12-centos IO]$ ll
total 8
-rw-rw-r-- 1 jyh jyh 282 Mar 19 15:52 file.c
-rw-rw-r-- 1 jyh jyh  62 Mar 19 15:01 Makefile
[jyh@VM-12-12-centos IO]$ cat file.c
#include 

#define TEXT "text.txt"

int main()
{
  FILE* fd = fopen(TEXT,"w");
  if(fd == NULL)
  {
    perror("fopen:");
    return 1;
  }
  
  const char* message = "hello world\n";
  int n = 3;
  while(n--)
  {
    fputs(message, fd);
  }


  fclose(fd);
  return 0;
}

[jyh@VM-12-12-centos IO]$ cat Makefile 
myfile:file.c
	gcc -o $@ $^
.PHONY:clean
clean:
	rm -f myfile
[jyh@VM-12-12-centos IO]$ make
gcc -o myfile file.c
[jyh@VM-12-12-centos IO]$ l
-bash: l: command not found
[jyh@VM-12-12-centos IO]$ ll
total 20
-rw-rw-r-- 1 jyh jyh  282 Mar 19 15:52 file.c
-rw-rw-r-- 1 jyh jyh   62 Mar 19 15:01 Makefile
-rwxrwxr-x 1 jyh jyh 8512 Mar 19 15:53 myfile
[jyh@VM-12-12-centos IO]$ ./myfile 
[jyh@VM-12-12-centos IO]$ ll
total 24
-rw-rw-r-- 1 jyh jyh  282 Mar 19 15:52 file.c
-rw-rw-r-- 1 jyh jyh   62 Mar 19 15:01 Makefile
-rwxrwxr-x 1 jyh jyh 8512 Mar 19 15:53 myfile
-rw-rw-r-- 1 jyh jyh   36 Mar 19 15:54 text.txt
[jyh@VM-12-12-centos IO]$ 

结论:"w"模式默认情况下,如果文件不存在,就会自动创建它。

2.2 现象二

[jyh@VM-12-12-centos IO]$ ll
total 24
-rw-rw-r-- 1 jyh jyh  282 Mar 19 15:52 file.c
-rw-rw-r-- 1 jyh jyh   62 Mar 19 15:01 Makefile
-rwxrwxr-x 1 jyh jyh 8512 Mar 19 15:53 myfile
-rw-rw-r-- 1 jyh jyh   36 Mar 19 15:54 text.txt
[jyh@VM-12-12-centos IO]$ cat file.c 
#include 

#define TEXT "text.txt"

int main()
{
  FILE* fd = fopen(TEXT,"w");
  if(fd == NULL)
  {
    perror("fopen:");
    return 1;
  }
  
  const char* message = "hello world\n";
  int n = 3;
  while(n--)
  {
    fputs(message, fd);
  }


  fclose(fd);
  return 0;
}

[jyh@VM-12-12-centos IO]$ cat Makefile 
myfile:file.c
	gcc -o $@ $^
.PHONY:clean
clean:
	rm -f myfile
[jyh@VM-12-12-centos IO]$ ./myfile 
[jyh@VM-12-12-centos IO]$ cat text.txt 
hello world
hello world
hello world
[jyh@VM-12-12-centos IO]$ vim file.c
[jyh@VM-12-12-centos IO]$ make
gcc -o myfile file.c
[jyh@VM-12-12-centos IO]$ ./myfile 
[jyh@VM-12-12-centos IO]$ cat text.txt 
hello world
[jyh@VM-12-12-centos IO]$ 

结论:默认打开文件,文件会自动被清空,同时每次进行写入的时候都是从最开始写入。

2.3 fprintf()函数回顾

[jyh@VM-12-12-centos IO]$ ll
total 12
-rw-rw-r-- 1 jyh jyh 324 Mar 19 16:10 file.c
-rw-rw-r-- 1 jyh jyh  62 Mar 19 15:01 Makefile
-rw-rw-r-- 1 jyh jyh  14 Mar 19 16:08 text.txt
[jyh@VM-12-12-centos IO]$ make
gcc -o myfile file.c
[jyh@VM-12-12-centos IO]$ ll
total 24
-rw-rw-r-- 1 jyh jyh  324 Mar 19 16:10 file.c
-rw-rw-r-- 1 jyh jyh   62 Mar 19 15:01 Makefile
-rwxrwxr-x 1 jyh jyh 8512 Mar 19 16:10 myfile
-rw-rw-r-- 1 jyh jyh   14 Mar 19 16:08 text.txt
[jyh@VM-12-12-centos IO]$ ./myfile 
[jyh@VM-12-12-centos IO]$ ll
total 24
-rw-rw-r-- 1 jyh jyh  324 Mar 19 16:10 file.c
-rw-rw-r-- 1 jyh jyh   62 Mar 19 15:01 Makefile
-rwxrwxr-x 1 jyh jyh 8512 Mar 19 16:10 myfile
-rw-rw-r-- 1 jyh jyh   75 Mar 19 16:10 text.txt
[jyh@VM-12-12-centos IO]$ cat text.txt 
hello world
,4
hello world
,3
hello world
,2
hello world
,1
hello world
,0
[jyh@VM-12-12-centos IO]$ cat file.c 
#include 

#define TEXT "text.txt"

int main()
{
  FILE* fd = fopen(TEXT,"w");
  if(fd == NULL)
  {
    perror("fopen:");
    return 1;
  }
  
  const char* message = "hello world\n";
  int n = 5;
  while(n--)
  {
    fprintf(fd, "%s,%d\n", message, n);
    //fputs(message, fd);
  }


  fclose(fd);
  return 0;
}

[jyh@VM-12-12-centos IO]$ 

2.4 fnprintf()函数使用

int snprintf(char *str, size_t size, const char *format, …); //格式化把内容放进str中

[jyh@VM-12-12-centos IO]$ ll
total 12
-rw-rw-r-- 1 jyh jyh 466 Mar 19 16:18 file.c
-rw-rw-r-- 1 jyh jyh  62 Mar 19 15:01 Makefile
-rw-rw-r-- 1 jyh jyh  75 Mar 19 16:10 text.txt
[jyh@VM-12-12-centos IO]$ make 
gcc -o myfile file.c
[jyh@VM-12-12-centos IO]$ ll
total 24
-rw-rw-r-- 1 jyh jyh  466 Mar 19 16:18 file.c
-rw-rw-r-- 1 jyh jyh   62 Mar 19 15:01 Makefile
-rwxrwxr-x 1 jyh jyh 8568 Mar 19 16:18 myfile
-rw-rw-r-- 1 jyh jyh   75 Mar 19 16:10 text.txt
[jyh@VM-12-12-centos IO]$ cat file.c 
#include 

#define TEXT "text.txt"

int main()
{
  FILE* fd = fopen(TEXT,"w");
  if(fd == NULL)
  {
    perror("fopen:");
    return 1;
  }
  
  const char* message = "hello world";
  
  int n = 5;
  while(n--)
  {
    char buffer[256];
    snprintf(buffer, sizeof(buffer), "%s,%d", message, n);
    fputs(buffer, fd);
  }


  fclose(fd);
  return 0;
}

[jyh@VM-12-12-centos IO]$ ./myfile 
[jyh@VM-12-12-centos IO]$ cat text.txt 
hello world,4hello world,3hello world,2hello world,1hello world,0[jyh@VM-12-12-centos IO]$ 

2.5 "a"模式

[jyh@VM-12-12-centos IO]$ ll
total 8
-rw-rw-r-- 1 jyh jyh 500 Mar 19 16:25 file.c
-rw-rw-r-- 1 jyh jyh  62 Mar 19 15:01 Makefile
[jyh@VM-12-12-centos IO]$ cat file.c 
#include 

#define TEXT "text.txt"

int main()
{
  FILE* fd = fopen(TEXT,"a");
  if(fd == NULL)
  {
    perror("fopen:");
    return 1;
  }
  
  const char* message = "hello world\n";
  
  int n = 1;
  while(n--)
  {
    fputs(message, fd);
  }

  fclose(fd);
  return 0;
}

[jyh@VM-12-12-centos IO]$ make
gcc -o myfile file.c
[jyh@VM-12-12-centos IO]$ ./myfile 
[jyh@VM-12-12-centos IO]$ ll
total 24
-rw-rw-r-- 1 jyh jyh  500 Mar 19 16:25 file.c
-rw-rw-r-- 1 jyh jyh   62 Mar 19 15:01 Makefile
-rwxrwxr-x 1 jyh jyh 8512 Mar 19 16:26 myfile
-rw-rw-r-- 1 jyh jyh   12 Mar 19 16:26 text.txt
[jyh@VM-12-12-centos IO]$ cat text.txt 
hello world
[jyh@VM-12-12-centos IO]$ ./myfile 
[jyh@VM-12-12-centos IO]$ cat text.txt 
hello world
hello world
[jyh@VM-12-12-centos IO]$ ./myfile 
[jyh@VM-12-12-centos IO]$ cat text.txt 
hello world
hello world
hello world
[jyh@VM-12-12-centos IO]$ 

结论:"a"模式可以追加内容,打开文件不会被自动清空。

3. 系统接口

3.1 open()和close()

int open(const char *pathname, int flags); //pathname:路径名,flags:参数选项
int open(const char *pathname, int flags, mode_t mode); //mode:模式

int close(int fd); //fd:file descriptor(文件描述符)

open:成功返回文件描述符,失败返回-1并且errno被设置了。

close:成功返回0,失败返回-1并且errno被设置了。

如何理解flags?

flags是一个int类型的变量,它有32个比特位,这里open这个函数的选项有很多:O_APPEND、O_CREAT、O_TRUNC等等。那么这里是怎么实现的呢?int有32个比特位,那么它的每一个比特位都可以表示一个标志位,这样就有32个标志位,也就是对应的可以有32个选项。这种数据结构是位图。下面用一个demo展示这个位图的数据结构:

[jyh@VM-12-12-centos testFlag]$ ll
total 8
-rw-rw-r-- 1 jyh jyh 359 Mar 19 17:22 flag.c
-rw-rw-r-- 1 jyh jyh  62 Mar 19 17:11 Makefile
[jyh@VM-12-12-centos testFlag]$ cat flag.c 
#include 
#define ONE 0x1
#define TWO 0x2
#define THREE 0x4
#define FOUR 0x8
void out(int flags)
{
  if(flags & ONE) printf("1111\n");
  if(flags & TWO) printf("2222\n");
  if(flags & THREE) printf("3333\n");
  if(flags & FOUR) printf("4444\n");

}


int main()
{
  
  out(ONE);
  out(TWO);
  out(ONE | TWO);
  out(ONE | TWO | THREE);

  return 0;
}
[jyh@VM-12-12-centos testFlag]$ cat Makefile 
myflag:flag.c
	gcc -o $@ $^
.PHONY:clean
clean:
	rm -f myflag
[jyh@VM-12-12-centos testFlag]$ make 
gcc -o myflag flag.c
[jyh@VM-12-12-centos testFlag]$ ls
flag.c  Makefile  myflag
[jyh@VM-12-12-centos testFlag]$ ./myflag 
1111
2222
1111
2222
1111
2222
3333
[jyh@VM-12-12-centos testFlag]$ 

这里就可以通过每个比特位来设置一个选项,上面我们看到的就有ONE、TWO、THREE、FOUR四个选项,可以直接通过传入参数的形式执行不同的功能。下面我们再来看open系统调用中的flags选项:

  1. O_APPEND:文件用追加的方式被打开。
  2. O_CREAT:如果文件不存在就会自动创建
  3. O_RDONLY:只读模式
  4. O_WRONLY:只写模式
  5. O_TRUNC:清理文件
[jyh@VM-12-12-centos IO]$ ll
total 20
-rw-rw-r-- 1 jyh jyh 1205 Mar 19 17:40 file.c
-rw-rw-r-- 1 jyh jyh   62 Mar 19 15:01 Makefile
-rwxrwxr-x 1 jyh jyh 8576 Mar 19 17:40 myfile
[jyh@VM-12-12-centos IO]$ ./myfile 
fd:3, errno:0, errstring:Success
[jyh@VM-12-12-centos IO]$ ll
total 20
-rw-rw-r-- 1 jyh jyh 1205 Mar 19 17:40 file.c
-rw-rw-r-- 1 jyh jyh   62 Mar 19 15:01 Makefile
-rwxrwxr-x 1 jyh jyh 8576 Mar 19 17:40 myfile
---S-ws--T 1 jyh jyh    0 Mar 19 17:41 text.txt
[jyh@VM-12-12-centos IO]$ cat file.c 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define TEXT "text.txt"

int main()
{
  int fd = open(TEXT, O_CREAT | O_WRONLY);
  if(fd == -1)
  {
    printf("fd:%d, errno:%d, errstring:%s\n",fd, errno, strerror(errno));
  }
  else 
  {
    printf("fd:%d, errno:%d, errstring:%s\n",fd, errno, strerror(errno));
  }
  
  close(fd);
  return 0;
}

[jyh@VM-12-12-centos IO]$ 

现象是:test.txt文件的权限乱码了,那么这里需要使用的是int open(const char *pathname, int flags, mode_t mode);此接口。

这里的mode是权限,有以下几种情况:

S_IRWXU  00700 user (file owner) has read, write and execute permission
S_IRUSR  00400 user has read permission
S_IWUSR  00200 user has write permission
S_IXUSR  00100 user has execute permission
S_IRWXG  00070 group has read, write and execute permission
S_IRGRP  00040 group has read permission
S_IWGRP  00020 group has write permission
S_IXGRP  00010 group has execute permission
S_IRWXO  00007 others have read, write and execute permission
S_IROTH  00004 others have read permission
S_IWOTH  00002 others have write permission
S_IXOTH  00001 others have execute permission
//也可以直接给数字形式
[jyh@VM-12-12-centos IO]$ cat file.c 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define TEXT "text.txt"

int main()
{
  int fd = open(TEXT, O_CREAT | O_WRONLY, 0666);
  if(fd == -1)
  {
    printf("fd:%d, errno:%d, errstring:%s\n",fd, errno, strerror(errno));
  }
  else 
  {
    printf("fd:%d, errno:%d, errstring:%s\n",fd, errno, strerror(errno));
  }
  
  close(fd);
  return 0;
}
[jyh@VM-12-12-centos IO]$ ll
total 8
-rw-rw-r-- 1 jyh jyh 1211 Mar 19 19:25 file.c
-rw-rw-r-- 1 jyh jyh   62 Mar 19 15:01 Makefile
[jyh@VM-12-12-centos IO]$ make
gcc -o myfile file.c
[jyh@VM-12-12-centos IO]$ ll
total 20
-rw-rw-r-- 1 jyh jyh 1211 Mar 19 19:25 file.c
-rw-rw-r-- 1 jyh jyh   62 Mar 19 15:01 Makefile
-rwxrwxr-x 1 jyh jyh 8576 Mar 19 19:29 myfile
[jyh@VM-12-12-centos IO]$ ./myfile 
fd:3, errno:0, errstring:Success
[jyh@VM-12-12-centos IO]$ ll
total 20
-rw-rw-r-- 1 jyh jyh 1211 Mar 19 19:25 file.c
-rw-rw-r-- 1 jyh jyh   62 Mar 19 15:01 Makefile
-rwxrwxr-x 1 jyh jyh 8576 Mar 19 19:29 myfile
-rw-rw-r-- 1 jyh jyh    0 Mar 19 19:30 text.txt
[jyh@VM-12-12-centos IO]$ 
//text.txt权限是664,默认权限是受umask的影响,所以是664

3.2 write()

ssize_t write(int fd, const void *buf, size_t count); //成功返回写入的字节总数,失败返回-1并且errno被设置。

[jyh@VM-12-12-centos IO]$ cat file.c 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define TEXT "text.txt"

int main()
{
  int fd = open(TEXT, O_CREAT | O_WRONLY, 0666); //O_CREAT | O_WRONLY并不会自动清空文件
  if(fd == -1)
  {
    printf("fd:%d, errno:%d, errstring:%s\n",fd, errno, strerror(errno));
  }
  else 
  {
    printf("fd:%d, errno:%d, errstring:%s\n",fd, errno, strerror(errno));
  }
  const char* message = "hello world\n";
  int count = 5;
  while(count)
  {
    write(fd, message, strlen(message));
    count--;
  }

  close(fd);
  return 0;
}
[jyh@VM-12-12-centos IO]$ ll
total 24
-rw-rw-r-- 1 jyh jyh 1344 Mar 19 19:35 file.c
-rw-rw-r-- 1 jyh jyh   62 Mar 19 15:01 Makefile
-rwxrwxr-x 1 jyh jyh 8680 Mar 19 19:35 myfile
-rw-rw-r-- 1 jyh jyh   60 Mar 19 19:35 text.txt
[jyh@VM-12-12-centos IO]$ ./myfile 
fd:3, errno:0, errstring:Success
[jyh@VM-12-12-centos IO]$ cat text.txt 
hello world
hello world
hello world
hello world
hello world
[jyh@VM-12-12-centos IO]$ 

3.3 read()

ssize_t read(int fd, void *buf, size_t count); //成功返回读取的字节数,失败返回-1并且errno被设置。(无法按行读取)

[jyh@VM-12-12-centos IO]$ ll
total 12
-rw-rw-r-- 1 jyh jyh 1556 Mar 19 19:47 file.c
-rw-rw-r-- 1 jyh jyh   62 Mar 19 15:01 Makefile
-rw-rw-r-- 1 jyh jyh    8 Mar 19 19:41 text.txt
[jyh@VM-12-12-centos IO]$ cat text.txt 
aaaaaaa
[jyh@VM-12-12-centos IO]$ make
gcc -o myfile file.c
[jyh@VM-12-12-centos IO]$ ll
total 24
-rw-rw-r-- 1 jyh jyh 1556 Mar 19 19:47 file.c
-rw-rw-r-- 1 jyh jyh   62 Mar 19 15:01 Makefile
-rwxrwxr-x 1 jyh jyh 8672 Mar 19 19:48 myfile
-rw-rw-r-- 1 jyh jyh    8 Mar 19 19:41 text.txt
[jyh@VM-12-12-centos IO]$ ./myfile 
fd:3, errno:0, errstring:Success
aaaaaaa

[jyh@VM-12-12-centos IO]$ 

[jyh@VM-12-12-centos IO]$ cat file.c 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define TEXT "text.txt"

int main()
{
  int fd = open(TEXT, O_RDONLY, 0664);
  if(fd == -1)
  {
    printf("fd:%d, errno:%d, errstring:%s\n",fd, errno, strerror(errno));
  }
  else 
  {
    printf("fd:%d, errno:%d, errstring:%s\n",fd, errno, strerror(errno));
  }
  
  char buffer[256];
  size_t ret = read(fd, buffer, sizeof(buffer) - 1);
  if(ret > 0)
  {
    buffer[ret] = '\0';
    printf("%s\n", buffer);
  }

  close(fd);
  return 0;
}

3.4 C文件接口和系统接口关系

C文件接口的底层就是用系统调用接口来封装实现的,只是在封装的时候可能做了相关调整。其他语言的文件操作都是调用的系统调用接口。

3.5 文件描述符

上面我们看到了open()这个系统调用接口调用成功会返回一个file descriptor,这就是返回一个文件描述符,那么这个文件描述符是什么呢?又该如何理解呢?

  1. 首先任何进程在启动的时候都会默认打开三个文件:a.标准输入文件(stdin,cin)、b.标准输入出文件(stdout,cout)、c.标准错误文件(stderr, cerr)。这里只是列举了C语言和C++语言中的文件。这里C语言对应的三个文件语言层面上就是extern FILE* stdin、extern FILE* stdout、extern FILE* stderr。这里的三个文件怎么理解呢?这里的FILE怎么理解呢?

  2. 文件理解

    [jyh@VM-12-12-centos streamFile]$ clear
    [jyh@VM-12-12-centos streamFile]$ ll
    total 8
    -rw-rw-r-- 1 jyh jyh  74 Mar 20 14:26 Makefile
    -rw-rw-r-- 1 jyh jyh 313 Mar 20 14:32 test.cc
    [jyh@VM-12-12-centos streamFile]$ cat Makefile 
    mytest:test.cc 
    	g++ -o mytest test.cc 
    .PHONY:clean
    clean:
    	rm -f mytest
    [jyh@VM-12-12-centos streamFile]$ cat test.cc
    #include 
    #include 
    using namespace std;
    
    int main()
    {
      printf("hello printf->stdout\n"); //向显示器输出
      fprintf(stdout, "hello fprintf->stdout\n"); //向stdout文件输出
      fprintf(stderr, "hello fprintf->stderr\n"); //向stderr文件输出
    
      cout << "hello cout->cout" << endl; //向cout文件输出
      cerr << "hello cerr->cerr" << endl; //向cerr文件输出
      return 0;
    }
    [jyh@VM-12-12-centos streamFile]$ make 
    g++ -o mytest test.cc 
    [jyh@VM-12-12-centos streamFile]$ ll
    total 20
    -rw-rw-r-- 1 jyh jyh   74 Mar 20 14:26 Makefile
    -rwxrwxr-x 1 jyh jyh 9208 Mar 20 14:32 mytest
    -rw-rw-r-- 1 jyh jyh  313 Mar 20 14:32 test.cc
    [jyh@VM-12-12-centos streamFile]$ ./mytest 
    hello printf->stdout
    hello fprintf->stdout
    hello fprintf->stderr
    hello cout->cout
    hello cerr->cerr
    [jyh@VM-12-12-centos streamFile]$
    
  3. 现象

[jyh@VM-12-12-centos streamFile]$ ll
total 20
-rw-rw-r-- 1 jyh jyh   74 Mar 20 14:26 Makefile
-rwxrwxr-x 1 jyh jyh 9208 Mar 20 14:32 mytest
-rw-rw-r-- 1 jyh jyh  313 Mar 20 14:32 test.cc
[jyh@VM-12-12-centos streamFile]$ ./mytest 
hello printf->stdout
hello fprintf->stdout
hello fprintf->stderr
hello cout->cout
hello cerr->cerr
[jyh@VM-12-12-centos streamFile]$ cat test.cc
#include 
#include 
using namespace std;

int main()
{
  printf("hello printf->stdout\n"); //向显示器输出
  fprintf(stdout, "hello fprintf->stdout\n");
  fprintf(stderr, "hello fprintf->stderr\n");

  cout << "hello cout->cout" << endl;
  cerr << "hello cerr->cerr" << endl;
  return 0;
}
[jyh@VM-12-12-centos streamFile]$ ./mytest > text.txt
hello fprintf->stderr
hello cerr->cerr
[jyh@VM-12-12-centos streamFile]$ cat text.txt 
hello printf->stdout
hello fprintf->stdout
hello cout->cout
[jyh@VM-12-12-centos streamFile]$ 

这里的一个现象是重定向会把标准输出的文件内容写入到text.txt,但是标准错误的文件不能写入到文件中,也说明了标准错误不受重定向影响。

  1. 了解file descriptor
[jyh@VM-12-12-centos fileDescriptor]$ ls
file.c  Makefile
[jyh@VM-12-12-centos fileDescriptor]$ cat file.c 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
  int fileDescriptor = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0664);
  
  int cnt = 3;
  const char* str = "hello world\n";
  while(cnt-- > 0)
  {
     
    write(fileDescriptor, str, strlen(str));
  }
  printf(" ----- fd = %d -----\n", fileDescriptor);
  close(fileDescriptor);

  return 0;
}
[jyh@VM-12-12-centos fileDescriptor]$ cat Makefile 
myfile:file.c
	gcc -o myfile file.c
.PHONY:clean
clean:
	rm -f myfile
[jyh@VM-12-12-centos fileDescriptor]$ make
gcc -o myfile file.c
[jyh@VM-12-12-centos fileDescriptor]$ ls
file.c  Makefile  myfile
[jyh@VM-12-12-centos fileDescriptor]$ ./myfile 
 ----- fd = 3 -----
[jyh@VM-12-12-centos fileDescriptor]$ ls
file.c  log.txt  Makefile  myfile
[jyh@VM-12-12-centos fileDescriptor]$ cat log.txt 
hello world
hello world
hello world
[jyh@VM-12-12-centos fileDescriptor]$ 

这里的现象是文件描述符是3这个数字,那么这个数字怎么来的呢?直接先说结论,其实这里的3指的是下标,stdint、stdout、stderr默认对应的下标是0,1,2,这三个文件是默认都有的,再建立其一个文件,那么这个文件都对应到放在3下标的位置中。文件描述符的本质就是数组下标。我们没有定义数组啊,为什么会又数组下标这个概念呢?下面就来谈谈进程和文件的关系

Linux -- 基础IO_第1张图片

上面这个图,我们先从进程聊起,我们再执行一个程序的时候,此时程序变成了一个进程,然而程序中又对应的open()系统调用接口来打开对应的文件,此时的文件是在内存中的,这就是上面的程序的一个过程。但是呢,这里就有很多问题,file descriptor是什么呢?文件又是怎么被进程管理的呢?首先我们的文件当程序执行变成进程后,进行写入和读取操作的时候,文件就会被加载到内存中,如果又很多文件,那么此时操作系统就会对其文件做管理,但是怎么管理的呢?这里就需要进程来让操作系统做管理,进程和内存中的文件要有关系,那么这里有一个数组来进行关联,进程PCB中有类似struct FILE_struct* 指针来指向一个结构体struct FILE_struct结构体,而struct FILE_struct结构体中就会有对应的指针数组,数组中存放的是加载到内存中的文件的结构体的指针,所以这里进程就可以通过指针的方式找到加载到内存中的文件,并对其进行管理操作,这里的指针数组默认的就会有三个文件指针(标准输入,输出,错误文件),所以这里就可以直接通过下标来close掉这个文件。另外文件结构体struct FILE中也有缓冲区,进行写入输出操作,比如write(3, “hello”, 5);这里的写入操作就会通过进程和文件的映射关系把对应的内容拷贝到文件结构体中的。所以这里的read()函数和write()函数本质就是拷贝函数,什么时候刷新到磁盘指定位置是由操作系统决定。

3.6 深度理解Linux下一切皆文件

Linux -- 基础IO_第2张图片

这里驱动程序完成各个设备的输入输出读取数据等等操作,这里的硬件各个操作都不一样,文件对象中就有相关操作函数的函数指针来控制不同的设备的各种操作,当我们执行程序时,程序变成了进程,此时进程会只会对文件对象进程相对应的联系,并不会管底层的各个设备的操作实现的千差万别,所以进程只是和文件对象有关系,并不接触底层驱动程序,所以说从进程角度来看,一切皆是文件。也就是用户视角来看一切皆是文件。

3.7 FILE是什么

FILE就是一个C语言结构体,结构体中包含文件属性信息,其中我们需要注意的是结构体中有int _fileno; //封装的文件描述符。

/usr/include/libio.h
struct _IO_FILE {
	int _flags;
	/* High-order word is _IO_MAGIC; rest is flags. */
	#define _IO_file_flags _flags
	//
	缓冲区相关
	/* The following pointers correspond to the C++ streambuf protocol. */
	/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
	char* _IO_read_ptr;
	/* Current read pointer */
	char* _IO_read_end;
	/* End of get area. */
	char* _IO_read_base; /* Start of putback+get area. */
	char* _IO_write_base; /* Start of put area. */
    char* _IO_write_ptr; /* Current put pointer. */
	char* _IO_write_end; /* End of put area. */
	char* _IO_buf_base;
	/* Start of reserve area. */
	char* _IO_buf_end;
	/* End of reserve area. */
	/* The following fields are used to support backing up and undo. */
	char *_IO_save_base; /* Pointer to start of non-current get area. */
	char *_IO_backup_base; /* Pointer to first valid character of backup area */
	char *_IO_save_end; /* Pointer to end of non-current get area. */
	struct _IO_marker *_markers;
	struct _IO_FILE *_chain;
	int _fileno; //封装的文件描述符
	#if 0
	int _blksize;
	#else
	int _flags2;
	#endif
	_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
	#define __HAVE_COLUMN /* temporary */
	/* 1+column number of pbase(); 0 is unknown. */
	unsigned short _cur_column;
	signed char _vtable_offset;
	char _shortbuf[1];
	/* char* _save_gptr; char* _save_egptr; */
	_IO_lock_t *_lock;
	#ifdef _IO_USE_OLD_IO_FILE
};

下面来看看标准输入、输出、错误对应的file descriptor:

[jyh@VM-12-12-centos streamFile]$ ll
total 20
-rw-rw-r-- 1 jyh jyh   74 Mar 20 14:26 Makefile
-rwxrwxr-x 1 jyh jyh 8840 Mar 20 16:08 mytest
-rw-rw-r-- 1 jyh jyh  452 Mar 20 16:08 test.cc
[jyh@VM-12-12-centos streamFile]$ cat test.cc
#include 
#include 
using namespace std;

int main()
{
  printf("stdin->%d\n", stdin->_fileno);
  printf("stdout->%d\n", stdout->_fileno);
  printf("stderr->%d\n", stderr->_fileno);
  return 0;
}
[jyh@VM-12-12-centos streamFile]$ ./mytest 
stdin->0
stdout->1
stderr->2
[jyh@VM-12-12-centos streamFile]$ 

现象就是stdout->1, stdin->0, stderr->2.

3.8 文件描述符分配规则

[jyh@VM-12-12-centos fdRole]$ ls
Makefile  test.c  test.txt
[jyh@VM-12-12-centos fdRole]$ cat Makefile 
mytest:test.c
	gcc -o mytest test.c
.PHONY:clean
clean:
	rm -f mytest
[jyh@VM-12-12-centos fdRole]$ cat test.c
#include 
#include 
#include 
#include 
#include 

int main()
{
  
  close(0); //close(stdin)
  close(2); //close(stderr)
  //close(1); //close(stdout)
  int fd1 = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
  int fd2 = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
  int fd3 = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);
  printf("fd1:%d\n", fd1);
  printf("fd2:%d\n", fd2);
  printf("fd3:%d\n", fd3);

  close(fd1);
  close(fd2);
  close(fd3);
  return 0;
}
[jyh@VM-12-12-centos fdRole]$ cat test.txt
[jyh@VM-12-12-centos fdRole]$ ls
Makefile  test.c  test.txt
[jyh@VM-12-12-centos fdRole]$ make
gcc -o mytest test.c
[jyh@VM-12-12-centos fdRole]$ ./mytest 
fd1:0 //关闭了stdin
fd2:2 //关闭了stderr
fd3:3
[jyh@VM-12-12-centos fdRole]$ 

分配规则也就是在文件描述符数组中,从最开始的0下标找没有被使用的数组空间,没有使用就分配新的文件。

3.9 重定向

3.9.1 输出重定向

[jyh@VM-12-12-centos redirect]$ ls
Makefile  README.md  redirect.c
[jyh@VM-12-12-centos redirect]$ cat redirect.c 
#include 
#include 
#include 
#include 
#include 

int main()
{
  close(1); //close(stdout)  
  int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664);

  printf("you are very good!\n");
  printf("you are very good!\n");
  printf("you are very good!\n");
  printf("you are very good!\n");
  return 0;
}
[jyh@VM-12-12-centos redirect]$ ls
Makefile  README.md  redirect.c
[jyh@VM-12-12-centos redirect]$ make
gcc -o myexe redirect.c
[jyh@VM-12-12-centos redirect]$ ls
Makefile  myexe  README.md  redirect.c
[jyh@VM-12-12-centos redirect]$ ./myexe 
[jyh@VM-12-12-centos redirect]$ ls
Makefile  myexe  README.md  redirect.c  test.txt
[jyh@VM-12-12-centos redirect]$ cat test.txt 
you are very good!
you are very good!
you are very good!
you are very good!
[jyh@VM-12-12-centos redirect]$ 

上述代码的意思就是首先关闭stdout默认标准输出文件,然后对应的创建并写入并刷新一个test.txt文件,再打印you are very good!,再cat test.txt发现此文件中有了打印的信息,说明了我们打开文件的索引下标是1位置,然后打印的信息本来是放在stdout文件的,但是stdout文件起初被关闭了,所以说是信息输出到了test.txt文件中。所以,重定向的原理是更改进程对应的文件描述符数组特定的下标的指向。

3.9.2 输入重定向

[jyh@VM-12-12-centos inRedirect]$ clear
[jyh@VM-12-12-centos inRedirect]$ ls
inRedirect.c  Makefile  test.txt
[jyh@VM-12-12-centos inRedirect]$ cat inRedirect.c 
#include 
#include 
#include 
#include 
#include 

int main()
{
  close(0); //close(stdin)  
  int fd = open("test.txt", O_RDONLY);
  
  int a = 0;
  int b = 0;
  scanf("%d%d\n",&a, &b);
  printf("a = %d, b = %d\n", a, b);

  return 0;
}

[jyh@VM-12-12-centos inRedirect]$ cat test.txt 
100 200
[jyh@VM-12-12-centos inRedirect]$ make
gcc -o myexe inRedirect.c
[jyh@VM-12-12-centos inRedirect]$ ls
inRedirect.c  Makefile  myexe  test.txt
[jyh@VM-12-12-centos inRedirect]$ ./myexe 
a = 100, b = 200
[jyh@VM-12-12-centos inRedirect]$ 

上面首先是关闭了stdin,打开文件test.txt,此时的test.txt的下标就是0,调用scanf()函数对test.txt写入,此时test.txt中有两个数,就从中读取这两个数给a和b变量赋值,最终输出a = 100, b = 200。

3.9.3 追加重定向

[jyh@VM-12-12-centos appendRedirect]$ ls
append.c  Makefile  test.txt
[jyh@VM-12-12-centos appendRedirect]$ cat append.c 
#include 
#include 
#include 
#include 
#include 

int main()
{
  close(1); //close(stdout)
  int fd = open("test.txt", O_APPEND | O_WRONLY | O_CREAT, 0664);

  printf("you are very good!\n");
  printf("you are very good!\n");
  printf("you are very good!\n");
  printf("you are very good!\n");
  printf("you are very good!\n");

  return 0;
}
[jyh@VM-12-12-centos appendRedirect]$ cat test.txt 
hello test.txt
it is very big

[jyh@VM-12-12-centos appendRedirect]$ ls
append.c  Makefile  test.txt
[jyh@VM-12-12-centos appendRedirect]$ make
gcc -o myappend append.c
[jyh@VM-12-12-centos appendRedirect]$ ls
append.c  Makefile  myappend  test.txt
[jyh@VM-12-12-centos appendRedirect]$ ./myappend 
[jyh@VM-12-12-centos appendRedirect]$ cat test.txt 
hello test.txt
it is very big

you are very good!
you are very good!
you are very good!
you are very good!
you are very good!
[jyh@VM-12-12-centos appendRedirect]$ 

3.9.4 回顾现象

[jyh@VM-12-12-centos streamFile]$ ll
total 20
-rw-rw-r-- 1 jyh jyh   74 Mar 20 14:26 Makefile
-rwxrwxr-x 1 jyh jyh 9208 Mar 20 14:32 mytest
-rw-rw-r-- 1 jyh jyh  313 Mar 20 14:32 test.cc
[jyh@VM-12-12-centos streamFile]$ ./mytest 
hello printf->stdout
hello fprintf->stdout
hello fprintf->stderr
hello cout->cout
hello cerr->cerr
[jyh@VM-12-12-centos streamFile]$ cat test.cc
#include 
#include 
using namespace std;

int main()
{
  printf("hello printf->stdout\n"); //向显示器输出
  fprintf(stdout, "hello fprintf->stdout\n");
  fprintf(stderr, "hello fprintf->stderr\n");

  cout << "hello cout->cout" << endl;
  cerr << "hello cerr->cerr" << endl;
  return 0;
}
[jyh@VM-12-12-centos streamFile]$ ./mytest > text.txt
hello fprintf->stderr
hello cerr->cerr
[jyh@VM-12-12-centos streamFile]$ cat text.txt 
hello printf->stdout
hello fprintf->stdout
hello cout->cout
[jyh@VM-12-12-centos streamFile]$ 

这里的./mytest > text.txt命令执行后,发现了一个现象就是标准输出内容直接输出重定向到了文件中,但是标准错误并没有写入文件text.txt中。解释:stdout\cout是向1号文件描述符对应的文件打印,stderr\cerr是向2号文件描述符对应的文件打印,stdout\cout输出重定向只改的是下标为1的指针的指向,stderr\cerr并不受影响。那么这里实现以下把常规消息打印到logNormal.txt,把异常消息打印到logErr.txt中:

[jyh@VM-12-12-centos test]$ ls
demo.c  Makefile
[jyh@VM-12-12-centos test]$ cat demo.c 
#include 
#include 
#include 
#include 
#include 

#define logN "logNormal.txt"
#define logE "logErr.txt"

int main()
{
  close(1);
  open(logN, O_CREAT | O_WRONLY | O_TRUNC, 0664);
  close(2); 
  open(logE, O_CREAT | O_WRONLY | O_TRUNC, 0664);
  
  printf("printf() -> stdout\n");
  printf("printf() -> stdout\n");
  printf("printf() -> stdout\n");
  printf("printf() -> stdout\n");
  fprintf(stderr, "fprintf() -> stderr\n");
  fprintf(stderr, "fprintf() -> stderr\n");
  fprintf(stderr, "fprintf() -> stderr\n");
  fprintf(stderr, "fprintf() -> stderr\n");

  return 0;
}

[jyh@VM-12-12-centos test]$ make
gcc -o myexe demo.c
[jyh@VM-12-12-centos test]$ ls
demo.c  Makefile  myexe
[jyh@VM-12-12-centos test]$ ./myexe 
[jyh@VM-12-12-centos test]$ ls
demo.c  logErr.txt  logNormal.txt  Makefile  myexe
[jyh@VM-12-12-centos test]$ cat logErr.txt 
fprintf() -> stderr
fprintf() -> stderr
fprintf() -> stderr
fprintf() -> stderr
[jyh@VM-12-12-centos test]$ cat logNormal.txt 
printf() -> stdout
printf() -> stdout
printf() -> stdout
printf() -> stdout
[jyh@VM-12-12-centos test]$ 

3.9.5 重定向指定文件

[jyh@VM-12-12-centos study12]$ ll
total 8
-rw-rw-r-- 1 jyh jyh  67 Mar 21 14:24 Makefile
-rw-rw-r-- 1 jyh jyh 381 Mar 21 14:26 streamFile.cc
[jyh@VM-12-12-centos study12]$ cat Makefile 
myexe:streamFile.cc
	g++ -o $@ $^
.PHONY:clean
clean:
	rm -f myexe
[jyh@VM-12-12-centos study12]$ cat streamFile.cc 
#include 
#include 

int main()
{
  printf("printf() -> stdout\n");
  printf("printf() -> stdout\n");
  printf("printf() -> stdout\n");
  printf("printf() -> stdout\n");
  
  fprintf(stderr, "fprintf() -> stderr\n");
  fprintf(stderr, "fprintf() -> stderr\n");
  fprintf(stderr, "fprintf() -> stderr\n");
  fprintf(stderr, "fprintf() -> stderr\n");
  return 0;
}
[jyh@VM-12-12-centos study12]$ make
g++ -o myexe streamFile.cc
[jyh@VM-12-12-centos study12]$ ls
Makefile  myexe  streamFile.cc
[jyh@VM-12-12-centos study12]$ ./myexe 1>normal.txt 2>err.txt
[jyh@VM-12-12-centos study12]$ ll
total 28
-rw-rw-r-- 1 jyh jyh   80 Mar 21 14:27 err.txt
-rw-rw-r-- 1 jyh jyh   67 Mar 21 14:24 Makefile
-rwxrwxr-x 1 jyh jyh 8808 Mar 21 14:27 myexe
-rw-rw-r-- 1 jyh jyh   76 Mar 21 14:27 normal.txt
-rw-rw-r-- 1 jyh jyh  381 Mar 21 14:26 streamFile.cc
[jyh@VM-12-12-centos study12]$ cat normal.txt 
printf() -> stdout
printf() -> stdout
printf() -> stdout
printf() -> stdout
[jyh@VM-12-12-centos study12]$ cat err.txt 
fprintf() -> stderr
fprintf() -> stderr
fprintf() -> stderr
fprintf() -> stderr
[jyh@VM-12-12-centos study12]$ 

这里就可以直接根据文件描述符来重定向到指定文件,这里的./myexe 1>normal.txt 2>err.txt的意思是:执行的打印内容本来全都是输出到显示器上的,但是这里做了相关重定向操作,把下标为1的指针指向了normal.txt,下标为2的指针指向了err.txt,导致了本来输出的内容到下标为1的stdout文件中变成了输出到normal.txt文件中,本来输出的内容到下标为2的stderr文件中变成了输出到err.txt文件中。

[jyh@VM-12-12-centos study12]$ ll
total 8
-rw-rw-r-- 1 jyh jyh  67 Mar 21 14:24 Makefile
-rw-rw-r-- 1 jyh jyh 381 Mar 21 14:26 streamFile.cc
[jyh@VM-12-12-centos study12]$ cat Makefile 
myexe:streamFile.cc
	g++ -o $@ $^
.PHONY:clean
clean:
	rm -f myexe
[jyh@VM-12-12-centos study12]$ cat streamFile.cc 
#include 
#include 

int main()
{
  printf("printf() -> stdout\n");
  printf("printf() -> stdout\n");
  printf("printf() -> stdout\n");
  printf("printf() -> stdout\n");
  
  fprintf(stderr, "fprintf() -> stderr\n");
  fprintf(stderr, "fprintf() -> stderr\n");
  fprintf(stderr, "fprintf() -> stderr\n");
  fprintf(stderr, "fprintf() -> stderr\n");
  return 0;
}
[jyh@VM-12-12-centos study12]$ make
g++ -o myexe streamFile.cc
[jyh@VM-12-12-centos study12]$ ls
Makefile  myexe  streamFile.cc
[jyh@VM-12-12-centos study12]$ ./myexe 
printf() -> stdout
printf() -> stdout
printf() -> stdout
printf() -> stdout
fprintf() -> stderr
fprintf() -> stderr
fprintf() -> stderr
fprintf() -> stderr
[jyh@VM-12-12-centos study12]$ ./myexe > test1.txt
fprintf() -> stderr
fprintf() -> stderr
fprintf() -> stderr
fprintf() -> stderr
[jyh@VM-12-12-centos study12]$ cat test1.txt 
printf() -> stdout
printf() -> stdout
printf() -> stdout
printf() -> stdout
[jyh@VM-12-12-centos study12]$ ./myexe > test2.txt 2>&1
[jyh@VM-12-12-centos study12]$ ls
Makefile  myexe  streamFile.cc  test1.txt  test2.txt
[jyh@VM-12-12-centos study12]$ cat test2.txt 
fprintf() -> stderr
fprintf() -> stderr
fprintf() -> stderr
fprintf() -> stderr
printf() -> stdout
printf() -> stdout
printf() -> stdout
printf() -> stdout
[jyh@VM-12-12-centos study12]$ 

这里的./myexe > test1.txt的意思是:./myexe运行的结果重定向到test1.txt中,此时只会有stdout内容重定向到文件test1.txt中,而stderr内容输出到显示器上。./myexe > test2.txt 2>&1的意思是:./myexe运行的结果重定向到test2.txt中,此时只会有stdout内容重定向到文件test2.txt中并且下标为2的stderr标准文件指针也指向下标为1的位置。

3.10 dup2()系统调用

int dup2(int oldfd, int newfd);

Dup2()使newfd成为oldfd的拷贝,如有必要首先关闭newfd,但注意以下事项:

  1. 如果oldfd不是有效的文件描述符,则调用失败,newfd不会关闭。
  2. 如果oldfd是一个有效的文件描述符,并且newfd与oldfd具有相同的值,那么dup2()不做任何事情,并返回newfd

怎么理解这里的oldfd和newfd?

Linux -- 基础IO_第3张图片

根据上图有个场景,就是把输出内容不是默认输出到stdout中,而是输出到myfile.txt中,这里的操作是1.dup2(1, 3) 2.dup(3, 1)呢?答案是:dup2(3, 1)。因为newfd是oldfd的一份拷贝,最终只是保留了oldfd下标对应的内容了。

[jyh@VM-12-12-centos dup]$ ll
total 8
-rw-rw-r-- 1 jyh jyh  63 Mar 21 15:06 Makefile
-rw-rw-r-- 1 jyh jyh 413 Mar 21 15:13 testDup.c
[jyh@VM-12-12-centos dup]$ cat testDup.c 
#include 
#include 
#include 
#include 
#include 

int main()
{
  int fd = open("myfile.txt", O_CREAT | O_WRONLY | O_TRUNC, 0664); 
  dup2(fd, 1);
  fprintf(stdout, "fprintf() -> stdout\n"); 
  fprintf(stdout, "fprintf() -> stdout\n"); 
  fprintf(stdout, "fprintf() -> stdout\n"); 
  fprintf(stdout, "fprintf() -> stdout\n"); 
  
  
  close(fd);
  return 0;
}
[jyh@VM-12-12-centos dup]$ cat Makefile 
myexe:testDup.c
	gcc -o $@ $^
.PHONY:clean
clean:
	rm -f myexe
[jyh@VM-12-12-centos dup]$ ls
Makefile  testDup.c
[jyh@VM-12-12-centos dup]$ make
gcc -o myexe testDup.c
[jyh@VM-12-12-centos dup]$ ls
Makefile  myexe  testDup.c
[jyh@VM-12-12-centos dup]$ ./myexe 
[jyh@VM-12-12-centos dup]$ ls
Makefile  myexe  myfile.txt  testDup.c
[jyh@VM-12-12-centos dup]$ cat myfile.txt 
fprintf() -> stdout
fprintf() -> stdout
fprintf() -> stdout
fprintf() -> stdout
[jyh@VM-12-12-centos dup]$ 

4. 理解文件缓冲区

带着疑惑看下面:输出缓冲区和我们上面的文件struct file中的缓冲区是一样的吗?

4.1 现象

[jyh@VM-12-12-centos buffer]$ ll
total 8
-rw-rw-r-- 1 jyh jyh 218 Mar 21 15:30 buf.c
-rw-rw-r-- 1 jyh jyh  55 Mar 21 15:28 Makefile
[jyh@VM-12-12-centos buffer]$ cat Makefile 
buf:buf.c
	gcc -o $@ $^
.PHONY:clean
clean:
	rm -f buf
[jyh@VM-12-12-centos buffer]$ cat buf.c 
#include 
#include 
#include 


int main()
{
  fprintf(stdout, "hello  Linux\n");
  const char* message = "how are you?\n";
  write(1, message, strlen(message));

  fork();

  return 0;
}

[jyh@VM-12-12-centos buffer]$ make
gcc -o buf buf.c
[jyh@VM-12-12-centos buffer]$ ls
buf  buf.c  Makefile
[jyh@VM-12-12-centos buffer]$ ./buf 
hello  Linux
how are you?
[jyh@VM-12-12-centos buffer]$ ./buf  > test.txt
[jyh@VM-12-12-centos buffer]$ ll
total 24
-rwxrwxr-x 1 jyh jyh 8536 Mar 21 15:32 buf
-rw-rw-r-- 1 jyh jyh  218 Mar 21 15:30 buf.c
-rw-rw-r-- 1 jyh jyh   55 Mar 21 15:28 Makefile
-rw-rw-r-- 1 jyh jyh   39 Mar 21 15:32 test.txt
[jyh@VM-12-12-centos buffer]$ cat test.txt 
how are you?
hello  Linux
hello  Linux
[jyh@VM-12-12-centos buffer]$ 

这里为什么调用重定向写入到test.txt中就有两个hello Linux被输出呢?为什么会出现这种现象呢?下面先认识文件缓冲区。

4.2 文件缓冲区

Linux -- 基础IO_第4张图片

./buf在执行fprintf()函数时,此时C语言库中有对应的缓冲区,先把字符串写入到C语言库中的缓冲区,当进程执行起来时就创建了文件描述符表,和文件对象产生映射关系,然后通过映射关系找到对应的文件对象,再讲C语言库中的字符串拷贝到文件对象的文件缓冲区中。所以通过这个例子就可以说明上述的现象:因为"hello Linux"字符串是fprintf()中C语言库的,创建子进程会再拷贝一份代码给子进程,但是write是系统调用是直接写入到文件缓冲区的,并不是通过C语言库缓冲区再到文件缓冲区的。

补充:三种刷新策略:1.无缓冲(不会刷新到缓冲区中,而是直接给操作系统)2.行缓冲(遇到\n终止刷新)3.全缓冲(只有缓冲区写满才会刷新);显示器采用的刷新策略是行缓冲,普通文件采用的是全缓冲;为什么要有缓冲区?节省调用时间。缓冲区在哪里呢?fopen()打开文件时,会有FILE结构体,缓冲区就在其FILE结构体中。

4.3 C接口和系统调用模拟

mystdio.h文件

#pragma once
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define N 1024
#define BUFFER_NONE 0x1
#define BUFFER_LINE 0x2 //行缓冲
#define BUFFER_ALL  0x4 //全缓冲

typedef struct MY_FILE
{
  int fileDescriptor;
  char outputBuffer[N];
  int flags; //刷新方式
  int current; //写入位置
}MY_FILE;

MY_FILE* my_fopen(const char* path, const char* mode); //对应的是fopen()-c接口
size_t my_fwrite(const void* ptr, size_t size, size_t nmemb, MY_FILE* stream); //对应的是fwrite()-c接口
int my_fclose(MY_FILE* fp); //对应的是fclose()-c接口

mystdio.c文件

#include "mystdio.h"

int my_fflush(MY_FILE* fp)
{
  assert(fp);
  write(fp->fileDescriptor, fp->outputBuffer, fp->current);
  fp->current = 0;
  fsync(fp->fileDescriptor); //同步文件的核内状态到存储设备
  return 0;
}

MY_FILE* my_fopen(const char* path, const char* mode) //对应的是fopen()-c接口
{
  //1.辨别打开模式
  int flag = 0;
  if(strcmp(mode, "r") == 0) flag |= O_RDONLY;
  else if(strcmp(mode, "w") == 0) flag |= (O_WRONLY | O_CREAT | O_TRUNC);
  else if(strcmp(mode, "a") == 0) flag |= (O_WRONLY | O_CREAT | O_APPEND);
  // 其他方式就不列举
  
  //2.设置默认mode
  mode_t m = 0664;
  
  //3.调用open()系统调
  int fd = 0;
  if(flag & O_CREAT) fd = open(path, flag, m);
  else fd = open(path, flag);
  
  //4.调用open()失败
  if(fd < 0) return NULL;

  //5.调用open()成功返回MY_FILE结构体指针
  MY_FILE* my_file = (MY_FILE*)malloc(sizeof(MY_FILE));
  if(my_file == NULL) //调用malloc()失败
  {
    close(fd); //关闭文件防止对后续操作受影响
    return NULL;
  } 
  my_file->fileDescriptor = fd;
  my_file->flags = 0;
  my_file->flags |= BUFFER_LINE; //默认采用行刷新
  my_file->current = 0;
  memset(my_file->outputBuffer, '\0', sizeof(my_file->outputBuffer));
  return my_file;
}

size_t my_fwrite(const void* ptr, size_t size, size_t nmemb, MY_FILE* stream)
{
  //1. 缓冲区已满直接写入 
  if(stream->current == N) my_fflush(stream); 
  
  //2. 缓冲区未满,数据拷贝,更新写入位置
  size_t fill_sz = size * nmemb;
  size_t have_sz = N - stream->current;
  size_t write_sz = 0;
  if(have_sz >= fill_sz) 
  {
    memcpy(stream->outputBuffer + stream->current, ptr, fill_sz);
    stream->current += fill_sz;
    write_sz = fill_sz;
  }
  else 
  {
    memcpy(stream->outputBuffer + stream->current, ptr, have_sz);
    stream->current += have_sz;
    write_sz = have_sz;
  }
  
  //3. 刷新
  if(stream->flags & BUFFER_LINE) 
  {
    if((stream->outputBuffer[stream->current - 1]) == '\n') my_fflush(stream);
  }
  else if(stream->flags & BUFFER_ALL) 
  {
    if(stream->current == N) my_fflush(stream);
  } 
  return write_sz;
}

int my_fclose(MY_FILE* fp)
{
  assert(fp);
  //1. 刷新缓冲区
  if(fp->current > 0) my_fflush(fp); 
  
  //2. 关闭文件
  close(fp->fileDescriptor);

  //3. 释放空间
  free(fp);
  fp = NULL;
  return 0; //操作成功
}

test.c文件

#include "mystdio.h"

int main()
{
  MY_FILE* fp = my_fopen("log.txt", "w");
  if(fp == NULL) exit(1);
  
  const char* message = "hello linux";
  
  int cnt = 5;
  while(cnt--)
  {
    char buffer[1024];
    snprintf(buffer, sizeof(buffer), "%s, %d\n", message, cnt);
    size_t size = my_fwrite(buffer, strlen(buffer), 1, fp);
    sleep(1);
    printf("当前成功写入: %zd个字节\n", size);
  }
    
  my_fclose(fp);

  return 0;
}

总结

  1. 不发生刷新的本质是不调用系统调用,并没有刷新到文件中,而是放在了对应的FILE结构体中的缓冲区中,因此,fwrite()函数调用会非常快
  2. 可以在缓冲区中挤压多份数据,随后统一进行刷新。本质:一次IO可以IO更多的数据,提高IO效率
  3. fflush刷新的本质就是将结构体中的缓冲区也就是用户缓冲区中的数据通过系统调用接口写入到操作系统

5. 磁盘

5.1 磁盘物理组成

聊到磁盘文件就需要了解磁盘:

  1. 磁盘是有许多盘片、机械手臂、磁头、主轴马达、接口插座、控制电路所组成。
  2. 实际运行时,主轴马达让盘片转动,然后机械手臂可伸展让磁头在盘片上进行读写操作(这里读写操作对应就是寻找CHS地址,也就是sector(扇区)、header(磁头)、cylinder(柱面)这三者先是磁头找到对应的柱面在找到对应的扇区)。
  3. 磁盘的最小存储单位是扇区,在物理组成分面,每个扇区的大小是512字节。
  4. 所有盘片上的同一磁道可以组成一个柱面,分割硬盘的最小单位就是柱面。

磁盘三维结构是这样的(对应上面3、4条来看):

Linux -- 基础IO_第5张图片

再进一步了解之前先想一想一个问题:操作系统内部会直接使用CHS地址来进行访问修改数据吗?如果不会为什么呢?

不会直接使用CHS地址,因为操作系统是软件,而磁盘是硬件,硬件会持续更新改变,如果操作系统也需要持续改变,这样就会发生耦合,所以要对其解耦操作就不需要持续改变;另外扇区是512字节,硬件的基本单位是4KB(可以调整),所以操作系统有一套自己的地址对其进行块级别访问修改等操作。

5.2 磁盘分区表

盘片中有很多扇区,但是每个扇区都一样重要吗?并不是,磁盘的第一个扇区特别重要了,主要记录主引导分区(Master Boot Record, MBR):可以安装引导加载程序的地方,有446字节;以及分区表(partition table):记录整块硬盘分区的状态,有64字节。这里我们来谈分区表:分区表是什么呢?把硬盘比作一根原木,需要把这个原木分割为很多区段才能做成满意的家具,如果不分割原木不能有效利用,同样的道理硬盘需要分区才能被你充分利用。分区表中的64字节容量中,总共分为四组记录区,每组记录区记录了该区段的启动和结束柱面号码。

Linux -- 基础IO_第6张图片

上图中假设硬盘有400个柱面,共分为四个分区,第四个分区为第301到400号柱面的范围,当你使用Windows操作系统第一到第四个分区应该就是C,D,E,F,那么第301到400号柱面就是对应的F盘。由于分区表只有64自己而已,最多只能容纳四个分区,这四个分区被称为主或扩展分区。重要信息:其实所谓的分区只是针对64字节的分区表进行设置而已;硬盘默认的分区表仅仅能写人四组分区信息;分区的最小单位是柱面。

为什么要分区?

  1. 分区数据是分开的,那么当你需要将某个分区的数据重整时,例如:你要重新安装Windows时,可以将C盘张其他重要数据移动到其他分区,这样就不会丢失数据,所以善用分区,可以让数据更安全。
  2. 按照区段进行搜索数据时,这样有助于提高系统的性能和速度。

分区表就只能把一块硬盘发最多分为四个分区吗?不是的。

Linux -- 基础IO_第7张图片

L1-L5这五个由扩展分区切出来的分区称为逻辑分区。重要信息:主分区和扩展区最多可以有四个;扩展分区最多只能有一个;逻辑分区有扩展分区持续切割出来的分区;能够被格式化后作为数据访问的分区是主分区和逻辑分区,扩展分区不能被格式化;逻辑分区的数量依操作系统而不同,IDE硬盘最多有59个逻辑分区(5到63号),SATA硬盘有11个逻辑分区(5到15号)。

6. 文件系统

6.1 文件系统特性

磁盘分区后需要进行格式化,为什么需要格式化呢?每种操作系统文件系统所设置的文件属性/权限并不相同,为了存放这些文件所需要的数据,因此需要对其格式化。提一句:Linux的正规文件系统是Ext2。传统磁盘和文件系统应用中,一个分区就是只能被格式化成一个文件系统,所以说一个文件系统就是一个分区,但是由于新技术的应用可以将一个分区格式化为多个文件系统,也能将多个分区合成一个文件系统,所以可以称呼一个被挂载的数据为一个文件系统而不是一个分区。那么文件系统是如何运行的呢?文件包含内容和属性,那么文件系统通常会将内容和属性这两部分的数据分别存在不同的块中,权限和属性放在inode中,内容放在data block块中。另外还有一个super block会记录文件系统的整个信息,包括inode和block的总量、使用量等等。每一个inode和block 都有编号方便管理。其中inode记录文件属性,一个文件占用一个inode,同时记录此文件的数据所在的data block号;block是记录文件内容,如果文件太大时,会占用多个block。既然我们知道inode中包含data block号,那么也就是说只要找到文件的inode就可以通过inode中data block编号读写数据,性能比较好。就像下图:

Linux -- 基础IO_第8张图片

6.2 Linux的Ext2文件系统(inode)

Linux文件系统Ext2就是使用inode为基础的文件系统。inode是记录权限和属性,data block是记录文件内容。文件系统一开始就将inode和block规划好了,除非重新格式化,否则inode和block固定后就不再变动。如果仔细考虑一下,如果文件系统高达数百GB时,将所有的inode和block放置在一起很不明智,因此Ext2文件系统在格式化的时候基本上就区分为多个快组,每个块组就有独立的inode/block/superblock系统。

Linux -- 基础IO_第9张图片

data block(数据块):Ext2文件系统中所支持的block大小有1KB、2KB、4KB三种而已。重要的信息:原则上,block大小和数量在格式化后就不能够改变(除非重新格式化);每个block内最多只能够放置一个文件数据,如果文件大于block大小,一个文件会占用多个block,如果文件小于block则block剩余空间不能够再被使用(磁盘空间会浪费)。这里有个问题如果文件都很小,但是你的block在格式化却选用4KB,可就产生很大空间浪费,假设使用4KBblock,有10000个小文件,每个文件50字节,磁盘浪费多少?(4096 - 50)* 10000 = 38.6MB,但是文件容量是50*10000=448.3KB,这里就产生了很大的磁盘容量浪费,但是可不可用1KBblock这样就可以浪费少,但是这里会读写性能下降,所以block容量尽量选择大的,默认的block就是4KB。

inodetable(inode表格):表格中可能有权限和属性信息,也会有block编号;每个inode大小固定为128字节,每个文件都仅仅会占用一个inode而已,因此文件系统能创建的文件数量和inode数量相关。

下面来分析一下inode/block和文件大小的关系,inode记录的数据很多,但是只有128字节大小,而记录一个block就需要4个字节。假设一个文件有400MB,每个block为4KB时,就有至少10万条block号码,inode128字节此时怎么来记录呢?系统采用12个直接索引、1个间接索引、1个双间接索引、1个三间接索引记录区。具体这记录索引方式是什么呢?

Linux -- 基础IO_第10张图片

super block(超级块):记录信息有:block和inode总量、使用量、剩余量,block和inode大小,文件系统挂载时间等等。superblock大小是1024字节。

group descriptor table(文件系统描述表):描述每个block group的开始和结束的block号码,以及说明每个区段分别介于那个block号码之间。

block bitmap(块对照表):就是一个位图用来记录是否存在该block,每个block有4KB,这个就有4096*8 个比特位,说明可以记录这么多个block的存在状态。

inode bitmap(inode对照表):就是一个位图用来记录是否存在该inode,每个inode有128字节,这个就有128*8 个比特位,说明可以记录这么多个inode的存在状态。

6.3 抽象理解

我们知道了磁盘的结构,下面以单个盘片来说,磁道上的扇区全部拿下来就变成了一个数组,第一个扇区中有主引导分区和一个分区表,八个扇区也就是一个block(这样更方便管理),块中存放数据:

Linux -- 基础IO_第11张图片

管理区,对区再把块分组,再对每个块组做管理:

Linux -- 基础IO_第12张图片

6.4 查看文件系统

dumpe2fs [-bh] 设备文件名

作用:查看每个区段和superblock相关的信息

[root@VM-12-12-centos jyh]# df  //调出目前挂载的设备
Filesystem     1K-blocks    Used Available Use% Mounted on
devtmpfs         1012336       0   1012336   0% /dev
tmpfs            1023340      24   1023316   1% /dev/shm
tmpfs            1023340     576   1022764   1% /run
tmpfs            1023340       0   1023340   0% /sys/fs/cgroup
/dev/vda1       41152716 9614720  29760172  25% /
tmpfs             204668       0    204668   0% /run/user/0
tmpfs             204668       0    204668   0% /run/user/1002
[root@VM-12-12-centos jyh]# dumpe2fs /dev/vda1
dumpe2fs 1.42.9 (28-Dec-2013)
Filesystem volume name:   <none> //文件系统名称
Last mounted on:          /
Filesystem UUID:          4b499d76-769a-40a0-93dc-4a31a59add28
Filesystem magic number:  0xEF53
Filesystem revision #:    1 (dynamic)
Filesystem features:      has_journal ext_attr resize_inode dir_index filetype needs_recovery extent 64bit flex_bg sparse_super large_file huge_file uninit_bg dir_nlink extra_isize
Filesystem flags:         signed_directory_hash 
Default mount options:    user_xattr acl
Filesystem state:         clean
Errors behavior:          Continue
Filesystem OS type:       Linux
Inode count:              2621440
Block count:              10485499
Reserved block count:     440360
Free blocks:              8157120
Free inodes:              2520067
First block:              0
Block size:               4096 //block size
Fragment size:            4096
Group descriptor size:    64
Reserved GDT blocks:      1019
Blocks per group:         32768
Fragments per group:      32768
Inodes per group:         8192
Inode blocks per group:   512
Flex block group size:    16
Filesystem created:       Thu Mar  7 14:38:36 2019
Last mount time:          Wed Feb 15 20:35:56 2023
Last write time:          Wed Feb 15 20:35:53 2023
Mount count:              50
Maximum mount count:      -1
Last checked:             Thu Mar  7 14:38:36 2019
Check interval:           0 (<none>)
Lifetime writes:          462 GB
Reserved blocks uid:      0 (user root)
Reserved blocks gid:      0 (group root)
First inode:              11
Inode size:	          256      //inode size
Required extra isize:     28
Desired extra isize:      28
Journal inode:            8
First orphan inode:       25570
Default directory hash:   half_md4
Directory Hash Seed:      58cbc593-e8e9-4c19-9dcf-645326b54c80
Journal backup:           inode blocks
Journal features:         journal_incompat_revoke journal_64bit
Journal size:             128M
Journal length:           32768
Journal sequence:         0x00793319
Journal start:            3001

7. 软硬连接

7.1 现象

[root@VM-12-12-centos jyh]# ll
total 12
-rw-rw-r--  1 jyh  jyh   827 Feb 25 10:03 install.sh
drwxrwxr-x 25 jyh  jyh  4096 Mar 31 15:15 linux_-stu
-rw-r--r--  1 root root   36 Mar 31 22:49 myfile.txt
[root@VM-12-12-centos jyh]# cat myfile.txt 
hello linux
hello linux
hello linux
[root@VM-12-12-centos jyh]# ln -s myfile.txt my-soft //软链接命令
[root@VM-12-12-centos jyh]# ll
total 12
-rw-rw-r--  1 jyh  jyh   827 Feb 25 10:03 install.sh
drwxrwxr-x 25 jyh  jyh  4096 Mar 31 15:15 linux_-stu
-rw-r--r--  1 root root   36 Mar 31 22:49 myfile.txt
lrwxrwxrwx  1 root root   10 Mar 31 22:49 my-soft -> myfile.txt
[root@VM-12-12-centos jyh]# cat my-soft 
hello linux
hello linux
hello linux
[root@VM-12-12-centos jyh]# ls -il
total 12
919566 -rw-rw-r--  1 jyh  jyh   827 Feb 25 10:03 install.sh
790033 drwxrwxr-x 25 jyh  jyh  4096 Mar 31 15:15 linux_-stu
790021 -rw-r--r--  1 root root   36 Mar 31 22:49 myfile.txt
794519 lrwxrwxrwx  1 root root   10 Mar 31 22:49 my-soft -> myfile.txt
[root@VM-12-12-centos jyh]# 
[root@VM-12-12-centos jyh]# ln myfile.txt my-hard //硬链接命令
[root@VM-12-12-centos jyh]# ls -il
total 16
919566 -rw-rw-r--  1 jyh  jyh   827 Feb 25 10:03 install.sh
790033 drwxrwxr-x 25 jyh  jyh  4096 Mar 31 15:15 linux_-stu
790021 -rw-r--r--  2 root root   36 Mar 31 22:49 myfile.txt 
790021 -rw-r--r--  2 root root   36 Mar 31 22:49 my-hard
794519 lrwxrwxrwx  1 root root   10 Mar 31 22:49 my-soft -> myfile.txt
[root@VM-12-12-centos jyh]# 

软链接是一个独立的连接文件,有自己的inode号,就有自己的属性。硬链接和目标文件公用同一个inode,也就是硬链接一定和目标文件使用用一个inode,硬链接本质就是建立了新的文件和inode的映射关系。

Linux -- 基础IO_第13张图片

这个myfile.txt文件对应的有一个inode,inode中就会有链接数变量用来记录链接数,硬链接后my-hard和myfile.txt文件的inode一样,使得此inode中的链接数进行++操作,变成了2,如果删除myfile.txt链接数变为1:

[root@VM-12-12-centos jyh]# ll
total 16
-rw-rw-r--  1 jyh  jyh   827 Feb 25 10:03 install.sh
drwxrwxr-x 25 jyh  jyh  4096 Mar 31 15:15 linux_-stu
-rw-r--r--  2 root root   36 Mar 31 22:49 myfile.txt
-rw-r--r--  2 root root   36 Mar 31 22:49 my-hard
lrwxrwxrwx  1 root root   10 Mar 31 22:49 my-soft -> myfile.txt
[root@VM-12-12-centos jyh]# rm -f myfile.txt 
[root@VM-12-12-centos jyh]# ls -il
total 12
919566 -rw-rw-r--  1 jyh  jyh   827 Feb 25 10:03 install.sh
790033 drwxrwxr-x 25 jyh  jyh  4096 Mar 31 15:15 linux_-stu
790021 -rw-r--r--  1 root root   36 Mar 31 22:49 my-hard //链接数变成1 
794519 lrwxrwxrwx  1 root root   10 Mar 31 22:49 my-soft -> myfile.txt //软连接不能使用
[root@VM-12-12-centos jyh]# 

7.2 软连接初步使用场景

//软链接:当一个文件层次太深时,可以直接软连接在当前目录就可以对其相关操作
[root@VM-12-12-centos test4]# ll
total 4
drwxr-xr-x 3 root root 4096 Mar 31 23:10 d1
lrwxrwxrwx 1 root root   21 Mar 31 23:13 exe-soft -> d1/d2/d3/d4/d5/d6/exe
[root@VM-12-12-centos test4]# tree
.
|-- d1
|   `-- d2
|       `-- d3
|           `-- d4
|               `-- d5
|                   `-- d6
|                       |-- exe
|                       `-- test.cc
`-- exe-soft -> d1/d2/d3/d4/d5/d6/exe

6 directories, 3 files
[root@VM-12-12-centos test4]# ./d1/d2/d3/d4/d5/d6/exe 
softLink sccuess 
[root@VM-12-12-centos test4]# ./exe-soft  //使用软连接更快捷
softLink sccuess 
[root@VM-12-12-centos test4]# 

7.3 硬链接理解

硬链接的本质就是把源文件的属性拷贝一份,然后把文件名改一下即可。也就是建立一个文件名,该文件名对目标文件inode做映射,直接指向目标文件inode即可。这里的硬链接数其实就是引用计数,inode相关的这个结构体中有对应的计数变量。另外inode中是不会包含文件名的,这个用例子来说:

[jyh@VM-12-12-centos link]$ ll
total 4
-rw-rw-r-- 1 jyh jyh 32 Apr  2 16:52 text.txt
[jyh@VM-12-12-centos link]$ ln text.txt hard-link
[jyh@VM-12-12-centos link]$ ll
total 8
-rw-rw-r-- 2 jyh jyh 32 Apr  2 16:52 hard-link
-rw-rw-r-- 2 jyh jyh 32 Apr  2 16:52 text.txt
[jyh@VM-12-12-centos link]$ cat hard-link 
1111111
1111111
1111111
2222222
[jyh@VM-12-12-centos link]$ cat text.txt 
1111111
1111111
1111111
2222222
[jyh@VM-12-12-centos link]$ 

通过上面硬链接就可以说明inode是不会包含文件名的,因为如果包含文件名,那么根据文件名去cat就对应的cat两个不同的文件的,那么硬链接起来的文件是文件名不同映射inode的也就违背了这一点。

硬链接的作用

[jyh@VM-12-12-centos link]$ mkdir directory
[jyh@VM-12-12-centos link]$ ll
total 8
drwxrwxr-x 2 jyh jyh 4096 Apr  2 18:56 directory
-rw-rw-r-- 1 jyh jyh   32 Apr  2 16:52 text.txt
[jyh@VM-12-12-centos link]$ 

为什么目录的硬链接数默认是2呢?为什么普通文件的硬链接数默认是1呢?

[jyh@VM-12-12-centos directory]$ ls -al
total 8
drwxrwxr-x 2 jyh jyh 4096 Apr  2 18:56 . //当前目录 
drwxrwxr-x 3 jyh jyh 4096 Apr  2 18:56 .. //上级目录
[jyh@VM-12-12-centos link]$ ll
total 8
drwxrwxr-x 2 jyh jyh 4096 Apr  2 18:56 directory
-rw-rw-r-- 1 jyh jyh   32 Apr  2 16:52 text.txt
[jyh@VM-12-12-centos link]$ ls -il
total 8
1052356 drwxrwxr-x 2 jyh jyh 4096 Apr  2 18:56 directory
1052355 -rw-rw-r-- 1 jyh jyh   32 Apr  2 16:52 text.txt
[jyh@VM-12-12-centos link]$ cd directory/
[jyh@VM-12-12-centos directory]$ ls -ail
total 8
1052356 drwxrwxr-x 2 jyh jyh 4096 Apr  2 18:56 .
1052354 drwxrwxr-x 3 jyh jyh 4096 Apr  2 18:56 ..
[jyh@VM-12-12-centos directory]$ 
[jyh@VM-12-12-centos directory]$ ls -di /home/jyh/linux_-stu/study14/link/
1052354 /home/jyh/linux_-stu/study14/link/
[jyh@VM-12-12-centos directory]$ 
//发现:directory和directory/.目录的inode是一样的,所以默认为2的原因也就是因为有当前目录
//directory/..和link目录的inode是一样的

所以这里理解为什么执行文件时是这样的:./a.out:原因很简单,就是通用.当前目录来找到对应的inode,进而找到对应的二进制文件,进而拿到数据load到内存,进而运行起来。这里我们观察到当前目录中有.和…两个目录,并且Linux的文件是一个多叉树结构,此时可以联想到文件结构是:

Linux -- 基础IO_第14张图片

注意:不能给目录建立硬链接,现象:

[jyh@VM-12-12-centos link]$ ll
total 4
drwxrwxr-x 2 jyh jyh 4096 Apr  2 18:56 directory
[jyh@VM-12-12-centos link]$ ln directory/ hard-link
ln: ‘directory/’: hard link not allowed for directory
[jyh@VM-12-12-centos link]$ 

为什么呢?这里通过树状结构来理解:

Linux -- 基础IO_第15张图片

上图是给tmp1建立硬链接放在d1目录下,此时就破环了树状结构,导致了环路路径问题。

8. 动静态库

8.1 初识

这里Linux中也有库,在 /usr/lib64目录下,就有动静态库:

[root@VM-12-12-centos lib64]# ls /usr/lib64/libc*
/usr/lib64/libc-2.17.so                              /usr/lib64/libcmdif.a            /usr/lib64/libcroco-0.6.so.3        /usr/lib64/libc.so
/usr/lib64/libc.a                                    /usr/lib64/libc_nonshared.a      /usr/lib64/libcroco-0.6.so.3.0.1    /usr/lib64/libc.so.6
/usr/lib64/libcairo-script-interpreter.so.2          /usr/lib64/libcom_err.so         /usr/lib64/libcrypt-2.17.so         /usr/lib64/libc_stubs.a
/usr/lib64/libcairo-script-interpreter.so.2.11512.0  /usr/lib64/libcom_err.so.2       /usr/lib64/libcrypt.a               /usr/lib64/libcupscgi.so.1
/usr/lib64/libcairo.so.2                             /usr/lib64/libcom_err.so.2.1     /usr/lib64/libcrypto.so             /usr/lib64/libcupsimage.so.2
/usr/lib64/libcairo.so.2.11512.0                     /usr/lib64/libconfig.so.9        /usr/lib64/libcrypto.so.10          /usr/lib64/libcupsmime.so.1
/usr/lib64/libcap-ng.so.0                            /usr/lib64/libconfig++.so.9      /usr/lib64/libcrypto.so.1.0.2k      /usr/lib64/libcupsppdc.so.1
/usr/lib64/libcap-ng.so.0.0.0                        /usr/lib64/libconfig.so.9.1.3    /usr/lib64/libcryptsetup.so.12      /usr/lib64/libcups.so.2
/usr/lib64/libcap.so.2                               /usr/lib64/libconfig++.so.9.1.3  /usr/lib64/libcryptsetup.so.12.3.0  /usr/lib64/libcurl.so.4
/usr/lib64/libcap.so.2.22                            /usr/lib64/libcpupower.so.0      /usr/lib64/libcryptsetup.so.4       /usr/lib64/libcurl.so.4.3.0
/usr/lib64/libcidn-2.17.so                           /usr/lib64/libcpupower.so.0.0.0  /usr/lib64/libcryptsetup.so.4.7.0
/usr/lib64/libcidn.so                                /usr/lib64/libcrack.so.2         /usr/lib64/libcrypt.so
/usr/lib64/libcidn.so.1                              /usr/lib64/libcrack.so.2.9.0     /usr/lib64/libcrypt.so.1
[root@VM-12-12-centos lib64]# 

静态库(.a):程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库。
动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。

库名:开头(lib)+命名+后缀(.so或者.a);比如上面的/usr/lib64/libc.so.6,其中这个库的命名是c,以lib为前提,.so.6为后缀,这里的6是版本编号。

上述描述的是库,下面我们看看头文件,其中头文件是包含在/usr/include这个目录下的:

[root@VM-12-12-centos ~]# ls /usr/include/
Display all 207 possibilities? (y or n)
aio.h               elf.h               gnu/                libio.h             netash/             pcre_stringpiece.h  signal.h            ucontext.h
aliases.h           elfutils/           gnu-versions.h      libiptc/            netatalk/           poll.h              sound/              ucp/
alloca.h            endian.h            grp.h               libipulog/          netax25/            printf.h            spawn.h             ucs/
a.out.h             envz.h              gshadow.h           libmnl/             netdb.h             profile.h           stab.h              uct/
argp.h              err.h               gssapi/             libnl3/             neteconet/          protocols/          stdc-predef.h       ulimit.h
argz.h              errno.h             gssapi.h            libudev.h           netinet/            pthread.h           stdint.h            unistd.h
ar.h                error.h             gssrpc/             limits.h            netipx/             pty.h               stdio_ext.h         ustat.h
arpa/               et/                 iconv.h             link.h              netiucv/            pwd.h               stdio.h             utime.h
asm/                execinfo.h          ieee754.h           linux/              netpacket/          python2.7/          stdlib.h            utmp.h
asm-generic/        fcntl.h             ifaddrs.h           locale.h            netrom/             python3.6m/         string.h            utmpx.h
assert.h            features.h          inttypes.h          lzma/               netrose/            rdma/               strings.h           valgrind/
bits/               fenv.h              ip6tables.h         lzma.h              nfs/                re_comp.h           sys/                values.h
byteswap.h          FlexLexer.h         iptables/           malloc.h            nlist.h             regex.h             syscall.h           verto.h
c++/                fmtmsg.h            iptables.h          math.h              nl_types.h          regexp.h            sysexits.h          verto-module.h
com_err.h           fnmatch.h           kadm5/              mcheck.h            nss.h               resolv.h            syslog.h            video/
complex.h           fpu_control.h       kdb.h               mellanox/           numacompat1.h       rpc/                systemd/            wait.h
cpio.h              fstab.h             keyutils.h          memory.h            numa.h              rpcsvc/             tar.h               wchar.h
cpufreq.h           fts.h               krad.h              mft/                numaif.h            sched.h             termio.h            wctype.h
crypt.h             ftw.h               krb5/               misc/               obstack.h           scsi/               termios.h           wordexp.h
ctype.h             _G_config.h         krb5.h              mntent.h            openssl/            search.h            tgmath.h            xen/
db_185.h            gconv.h             langinfo.h          monetary.h          paths.h             selinux/            thread_db.h         xlocale.h
db.h                gelf.h              lastlog.h           mqueue.h            pcrecpparg.h        semaphore.h         time.h              xtables.h
dirent.h            getopt.h            libdb/              mstflint/           pcrecpp.h           sepol/              ttyent.h            xtables-version.h
dlfcn.h             gio-unix-2.0/       libelf.h            mtcr_ul/            pcre.h              setjmp.h            uapi/               zconf.h
drm/                glib-2.0/           libgen.h            mtd/                pcreposix.h         sgtty.h             uchar.h             zlib.h
dwarf.h             glob.h              libintl.h           net/                pcre_scanner.h      shadow.h            ucm/                

所以这里的库到底是什么呢?头文件又是什么呢?其实我们随便打开一个库文件,我们发现它是个二进制文件,头文件我们打开发现都是函数的声明,那么我们可以想到,我们包含头文件,头文件只有声明我们怎么用呢,说明库中对应的全是函数功能实现,只不过是打包成了二进制文件,打包二进制这里用到了链接相关的知识,后面继续深入。初始我们建立的共同的想法,那么下面谈一谈一些尝试:我们在安装visual studio 2019等编译器软件的时候,其实就是安装头文件和库文件。另外还有使用编译器的时候,编译器会有自动提醒头文件或者函数等功能,这些都是通过遍历头文件和库文件来匹配完成检索的,还有语法报错功能,这也是编译器来进行检索功能。这就达成 了初始目的,下面我们再来谈一谈为什么的问题。

8.2 为什么要有

首先这些头文件和对应的库文件一安装环境就有了,里面有很多对应的轮子,可以拿来用,肯定是用别人的香啊,我们直接拿来用,这样就提高了开发效率。

8.3 静态库使用

场景:假如说现在有个日本人,这个日本人呢没办法设计一个加减方法,此时它就在网上找别人的库中,然后拉取到本地上来自己使用,此时我们是一个设计者,那么我们怎么来让这个日本人用到呢?

[jyh@VM-12-12-centos include]$ ls
add.c  add.h  sub.c  sub.h
[jyh@VM-12-12-centos include]$ cat add.h add.c
#pragma once

int add(int x, int y);

#include "add.h"


int add(int x, int y)
{
  return x + y;
}

[jyh@VM-12-12-centos include]$ cat sub.h sub.c
#pragma once

int sub(int x, int y);
#include "sub.h"


int sub(int x, int y)
{
  return x - y;
}
[jyh@VM-12-12-centos include]$ 
  1. 第一种方式,可以直接把代码发布在github上,这样这个日本人只需要调用对应的函数即可

  2. 上面这种方式可以,但是这里我们想要实现和声明分开,并且不想让这个日本人直接能看到源代码,这怎么办呢?前面我们观察东静态库全是二进制文件,那么怎么把一个.c的c语言文件变成一个二进制文件呢?之前有详细谈到程序编译的过程,程序编译有四个阶段:预处理、编译、汇编、链接,其中形成二进制文件就需要对文件完成汇编,对应的指令就是gcc -c 源文件 -o 重命名。

[jyh@VM-12-12-centos include]$ ls
add.c  add.h  sub.c  sub.h
[jyh@VM-12-12-centos include]$ gcc -c add.c -o add.o
[jyh@VM-12-12-centos include]$ gcc -c sub.c -o sub.o
[jyh@VM-12-12-centos include]$ ls
add.c  add.h  add.o  sub.c  sub.h  sub.o
[jyh@VM-12-12-centos include]$ mkdir lib
[jyh@VM-12-12-centos include]$ mkdir include
[jyh@VM-12-12-centos include]$ ls
add.c  add.h  add.o  include  lib  sub.c  sub.h  sub.o
[jyh@VM-12-12-centos include]$ mv add.o sub.o lib/
[jyh@VM-12-12-centos include]$ mv add.h sub.h include/
[jyh@VM-12-12-centos include]$ ls
add.c  include  lib  sub.c
[jyh@VM-12-12-centos include]$ tree
.
|-- add.c
|-- include
|   |-- add.h
|   `-- sub.h
|-- lib
|   |-- add.o
|   `-- sub.o
`-- sub.c

2 directories, 6 files
[jyh@VM-12-12-centos include]$ 

上述这样的方式也有点不行,原因就是当有很多源文件的时候那么就得形成多少个.o文件,这样就很烦,再通过对lib和include这两个目录进行打包上传到github上就可以供这个日本人使用,但是这里的缺陷就得改进,如何改进呢?可不可以直接把多个.o文件整体打包为一个库文件,是可以的,下面打包成静态库说明:(命令:ar -rc 库文件名 被打包的.o文件列表)

[jyh@VM-12-12-centos include]$ ls
add.c  include  lib  sub.c
[jyh@VM-12-12-centos include]$ tree
.
|-- add.c
|-- include
|   |-- add.h
|   `-- sub.h
|-- lib
|   |-- add.o
|   `-- sub.o
`-- sub.c

2 directories, 6 files
[jyh@VM-12-12-centos include]$ cd lib/
[jyh@VM-12-12-centos lib]$ ls
add.o  sub.o
[jyh@VM-12-12-centos lib]$ ar -rc libmath.a add.o sub.o //生成静态库
[jyh@VM-12-12-centos lib]$ ls
add.o  libmath.a  sub.o
[jyh@VM-12-12-centos lib]$ rm add.o sub.o 
[jyh@VM-12-12-centos lib]$ ls
libmath.a
[jyh@VM-12-12-centos lib]$ 

上面我们说了如何做的问题,下面来模仿这个日本人来完成怎么用的问题(直接用本地目录进行使用,并没有对其github拉去解包使用,这里只是演示本地,至于怎么拉取库解压库这里不做操作):

[jyh@VM-12-12-centos japanese]$ ll
total 8
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:30 include
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:27 lib
[jyh@VM-12-12-centos japanese]$ cp -r include/add.h include/sub.h .
[jyh@VM-12-12-centos japanese]$ ll
total 16
-rw-rw-r-- 1 jyh jyh   38 Apr  3 17:30 add.h
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:30 include
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:27 lib
-rw-rw-r-- 1 jyh jyh   37 Apr  3 17:30 sub.h
[jyh@VM-12-12-centos japanese]$ cp -r lib/libmath.a .
[jyh@VM-12-12-centos japanese]$ ll
total 20
-rw-rw-r-- 1 jyh jyh   38 Apr  3 17:30 add.h
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:30 include
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:27 lib
-rw-rw-r-- 1 jyh jyh 2688 Apr  3 17:30 libmath.a
-rw-rw-r-- 1 jyh jyh   37 Apr  3 17:30 sub.h
[jyh@VM-12-12-centos japanese]$ ls
add.h  include  lib  libmath.a  main.c  sub.h
[jyh@VM-12-12-centos japanese]$ rm -rf include/ lib/
[jyh@VM-12-12-centos japanese]$ ll
total 16
-rw-rw-r-- 1 jyh jyh   38 Apr  3 17:30 add.h
-rw-rw-r-- 1 jyh jyh 2688 Apr  3 17:30 libmath.a
-rw-rw-r-- 1 jyh jyh  209 Apr  3 17:32 main.c
-rw-rw-r-- 1 jyh jyh   37 Apr  3 17:30 sub.h
[jyh@VM-12-12-centos japanese]$ vim main.c
[jyh@VM-12-12-centos japanese]$ ls
add.h  include  lib  libmath.a  main.c  sub.h
[jyh@VM-12-12-centos japanese]$ gcc main.c
/tmp/ccZHGHDN.o: In function `main':
main.c:(.text+0x21): undefined reference to `add'
main.c:(.text+0x33): undefined reference to `sub'
collect2: error: ld returned 1 exit status
[jyh@VM-12-12-centos japanese]$ cat main.c 
#include "add.h"
#include "sub.h"
#include "stdio.h"
int main()
{
  int x = 10;
  int y = 20;
  int addNum = add(x,y);
  int subNum = sub(x,y);
  printf("addNum=%d subNum=%d\n", addNum, subNum);
  return 0;
}
[jyh@VM-12-12-centos japanese]$ 

上面不是自己写的main.c不是包含了"add.h"和"sub.h"头文件吗,为什么函数名没有定义呢?说明这里没有办法直接使用libmath.a这个库文件,那么为什么不能直接使用呢?编译器找不到库,为什么呢?原因是因为libmath.a这个库是第三方库,编译器并不认识,那么怎么才能使得编译器认识呢?带上库名给编译器认识(gcc 源文件 -L路径 -l库名)(所谓的第三方库就是除了第一方和第二方其他都是的,第一方库就是语言库,第二方就是操作系统库)

[jyh@VM-12-12-centos japanese]$ clear
[jyh@VM-12-12-centos japanese]$ ls
add.h  libmath.a  main.c  sub.h
[jyh@VM-12-12-centos japanese]$ gcc -o exe main.c 
/tmp/ccv7jpgR.o: In function `main':
main.c:(.text+0x21): undefined reference to `add'
main.c:(.text+0x33): undefined reference to `sub'
collect2: error: ld returned 1 exit status
[jyh@VM-12-12-centos japanese]$ gcc -o exe main.c -L. -lmath //-L:指定文件路径,-L.:当前路径下查找库 -l:指定库名(真正的库名是去掉前缀lib和后缀.so或者.的)
[jyh@VM-12-12-centos japanese]$ ll
total 28
-rw-rw-r-- 1 jyh jyh   38 Apr  3 17:30 add.h
-rwxrwxr-x 1 jyh jyh 8472 Apr  3 17:42 exe
-rw-rw-r-- 1 jyh jyh 2688 Apr  3 17:30 libmath.a
-rw-rw-r-- 1 jyh jyh  209 Apr  3 17:32 main.c
-rw-rw-r-- 1 jyh jyh   37 Apr  3 17:30 sub.h
[jyh@VM-12-12-centos japanese]$ ./exe 
addNum=30 subNum=-10
[jyh@VM-12-12-centos japanese]$ 

这里也可以把库文件拷贝到系统中,这样就需要根据路径搜索了,不用gcc命令中带上-L选项了,也不用指定头文件路径了,这里把库和头文件拷贝到系统默认路径下,就是在Linux下安装库(这里不建议把库和头文件放进系统中):

[jyh@VM-12-12-centos japanese]$ ll
total 12
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:10 include
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:20 lib
-rw-rw-r-- 1 jyh jyh  209 Apr  3 17:32 main.c
[jyh@VM-12-12-centos japanese]$ cp -rf include/* /usr/include/
cp: cannot create regular file ‘/usr/include/add.h’: Permission denied
cp: cannot create regular file ‘/usr/include/sub.h’: Permission denied
[jyh@VM-12-12-centos japanese]$ sudo cp -rf include/* /usr/include/
[sudo] password for jyh: 
[jyh@VM-12-12-centos japanese]$ ls /usr/include/add.h
/usr/include/add.h
[jyh@VM-12-12-centos japanese]$ ls /usr/include/sub.h
/usr/include/sub.h
[jyh@VM-12-12-centos japanese]$ cp -rf lib/* /usr/lib64
cp: cannot create regular file ‘/usr/lib64/libmath.a’: Permission denied
[jyh@VM-12-12-centos japanese]$ sudo cp -rf lib/* /usr/lib64
[jyh@VM-12-12-centos japanese]$ ls /usr/lib64/libmath.a
/usr/lib64/libmath.a
[jyh@VM-12-12-centos japanese]$ gcc -o exe main.c
/tmp/cc30sUX9.o: In function `main':
main.c:(.text+0x21): undefined reference to `add'
main.c:(.text+0x33): undefined reference to `sub'
collect2: error: ld returned 1 exit status
[jyh@VM-12-12-centos japanese]$ 

这里还是显示错误:add和sub函数未定义。愿意是这里是第三方库,所以这里要指明库:

[jyh@VM-12-12-centos japanese]$ gcc -o exe main.c
/tmp/cc30sUX9.o: In function `main':
main.c:(.text+0x21): undefined reference to `add'
main.c:(.text+0x33): undefined reference to `sub'
collect2: error: ld returned 1 exit status
[jyh@VM-12-12-centos japanese]$ gcc -o exe main.c -lmath
[jyh@VM-12-12-centos japanese]$ ll
total 24
-rwxrwxr-x 1 jyh jyh 8472 Apr  3 18:05 exe
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:10 include
drwxrwxr-x 2 jyh jyh 4096 Apr  3 17:20 lib
-rw-rw-r-- 1 jyh jyh  209 Apr  3 17:32 main.c
[jyh@VM-12-12-centos japanese]$ ./exe 
addNum=30 subNum=-10
[jyh@VM-12-12-centos japanese]$ 

8.4 动态库使用

.o文件生成必须使用:gcc/g++ -fPIC -c 源文件列表(其中fPIC:位置无关码(position independent code)

生成动态库:gcc/g++ -shared -o 库名 .o文件列表

[jyh@VM-12-12-centos study15]$ ll
total 16
-rw-rw-r-- 1 jyh jyh 64 Apr  4 09:33 myadd.c
-rw-rw-r-- 1 jyh jyh 40 Apr  4 09:33 myadd.h
-rw-rw-r-- 1 jyh jyh 63 Apr  4 09:34 mysub.c
-rw-rw-r-- 1 jyh jyh 39 Apr  4 09:34 mysub.h
[jyh@VM-12-12-centos study15]$ gcc -fPIC -c myadd.c
[jyh@VM-12-12-centos study15]$ gcc -fPIC -c mysub.c
[jyh@VM-12-12-centos study15]$ ll
total 24
-rw-rw-r-- 1 jyh jyh   64 Apr  4 09:33 myadd.c
-rw-rw-r-- 1 jyh jyh   40 Apr  4 09:33 myadd.h
-rw-rw-r-- 1 jyh jyh 1240 Apr  4 09:34 myadd.o
-rw-rw-r-- 1 jyh jyh   63 Apr  4 09:34 mysub.c
-rw-rw-r-- 1 jyh jyh   39 Apr  4 09:34 mysub.h
-rw-rw-r-- 1 jyh jyh 1240 Apr  4 09:34 mysub.o
[jyh@VM-12-12-centos study15]$ gcc -shared -o libmymath.so *.o
[jyh@VM-12-12-centos study15]$ ll
total 32
-rwxrwxr-x 1 jyh jyh 7944 Apr  4 09:34 libmymath.so
-rw-rw-r-- 1 jyh jyh   64 Apr  4 09:33 myadd.c
-rw-rw-r-- 1 jyh jyh   40 Apr  4 09:33 myadd.h
-rw-rw-r-- 1 jyh jyh 1240 Apr  4 09:34 myadd.o
-rw-rw-r-- 1 jyh jyh   63 Apr  4 09:34 mysub.c
-rw-rw-r-- 1 jyh jyh   39 Apr  4 09:34 mysub.h
-rw-rw-r-- 1 jyh jyh 1240 Apr  4 09:34 mysub.o
[jyh@VM-12-12-centos study15]$ mkdir include
[jyh@VM-12-12-centos study15]$ mkdir lib
[jyh@VM-12-12-centos study15]$ mv *.h include/
[jyh@VM-12-12-centos study15]$ mv *.so lib
[jyh@VM-12-12-centos study15]$ ll
total 24
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:35 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:35 lib
-rw-rw-r-- 1 jyh jyh   64 Apr  4 09:33 myadd.c
-rw-rw-r-- 1 jyh jyh 1240 Apr  4 09:34 myadd.o
-rw-rw-r-- 1 jyh jyh   63 Apr  4 09:34 mysub.c
-rw-rw-r-- 1 jyh jyh 1240 Apr  4 09:34 mysub.o
[jyh@VM-12-12-centos study15]$ 

那么学会了打包制作操作,接下来模仿other用户来使用相关动态库:

[jyh@VM-12-12-centos japanese]$ ll //github上拿到了包并进行解压后
total 8
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 lib
[jyh@VM-12-12-centos japanese]$ cp -f include/myadd.h include/mysub.h .
[jyh@VM-12-12-centos japanese]$ cp -f lib/libmymath.so .
[jyh@VM-12-12-centos japanese]$ ll
total 24
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 lib
-rwxrwxr-x 1 jyh jyh 7944 Apr  4 09:41 libmymath.so
-rw-rw-r-- 1 jyh jyh   40 Apr  4 09:40 myadd.h
-rw-rw-r-- 1 jyh jyh   39 Apr  4 09:40 mysub.h
[jyh@VM-12-12-centos japanese]$ touch main.c
[jyh@VM-12-12-centos japanese]$ ls
include  lib  libmymath.so  main.c  myadd.h  mysub.h
[jyh@VM-12-12-centos japanese]$ vim main.c 
[jyh@VM-12-12-centos japanese]$ cat main.c //手写测试用例
#include "stdio.h"
#include "myadd.h"
#include "mysub.h"
int main()
{
  int x = 20;
  int y = 10;
  int addNum = myadd(x, y);
  int subNum = mysub(x, y);
  printf("addNum = %d subNum = %d\n", addNum, subNum);
  return 0;
}
[jyh@VM-12-12-centos japanese]$ gcc main.c -o exe
/tmp/cc7mIEOJ.o: In function `main':
main.c:(.text+0x21): undefined reference to `myadd'
main.c:(.text+0x33): undefined reference to `mysub'
collect2: error: ld returned 1 exit status
[jyh@VM-12-12-centos japanese]$ gcc main.c -o exe -L. -lmymath //-L:
[jyh@VM-12-12-centos japanese]$ ll
total 40
-rwxrwxr-x 1 jyh jyh 8432 Apr  4 09:44 exe
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 lib
-rwxrwxr-x 1 jyh jyh 7944 Apr  4 09:41 libmymath.so
-rw-rw-r-- 1 jyh jyh  223 Apr  4 09:42 main.c
-rw-rw-r-- 1 jyh jyh   40 Apr  4 09:40 myadd.h
-rw-rw-r-- 1 jyh jyh   39 Apr  4 09:40 mysub.h
[jyh@VM-12-12-centos japanese]$ ./exe 
addNum = 30 subNum = 10
[jyh@VM-12-12-centos japanese]$ 

但是下面方式导致了错误:

[jyh@VM-12-12-centos japanese]$ ll
total 12
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 lib
-rw-rw-r-- 1 jyh jyh  223 Apr  4 09:42 main.c
[jyh@VM-12-12-centos japanese]$ cat main.c 
#include "stdio.h"
#include "myadd.h"
#include "mysub.h"
int main()
{
  int x = 20;
  int y = 10;
  int addNum = myadd(x, y);
  int subNum = mysub(x, y);
  printf("addNum = %d subNum = %d\n", addNum, subNum);
  return 0;
}
[jyh@VM-12-12-centos japanese]$ gcc main.c
main.c:2:19: fatal error: myadd.h: No such file or directory
 #include "myadd.h"
                   ^
compilation terminated.
[jyh@VM-12-12-centos japanese]$ gcc -I include -L lib/ -lmymath -o exe
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o: In function `_start':
(.text+0x20): undefined reference to `main'
collect2: error: ld returned 1 exit status
[jyh@VM-12-12-centos japanese]$ gcc main.c -I include -L lib/ -lmymath -o exe
[jyh@VM-12-12-centos japanese]$ ll
total 24
-rwxrwxr-x 1 jyh jyh 8432 Apr  4 09:49 exe
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 lib
-rw-rw-r-- 1 jyh jyh  223 Apr  4 09:42 main.c
[jyh@VM-12-12-centos japanese]$ ./exe 
./exe: error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or directory
[jyh@VM-12-12-centos japanese]$ 

这里不是告诉了库文件路径和头文件路径,也告诉了这个第三方库名吗?为什么不能编译通过呢?这里还导致找不到共享库呢?因为这里只是告诉了编译器gcc,并不是告诉系统,并不是在系统的默认路径下所以就找不到了,但是这里静态库为什么不需要放到系统默认路径就可以找到呢?因为静态库是将用户的二进制代码直接拷贝到目标可执行程序中,所以可以直接跑起来,可执行程序中就有相关的.o的二进制代码,但是动态库并不是的,可执行程序中没有相关.o文件的二进制代码,所以这里需要的操作有三种:(这里使用ldd命令:ldd 可执行文件;作用查找对应的依赖第三方库)

  1. 环境变量法(临时方案)
[jyh@VM-12-12-centos japanese]$ ll
total 24
-rwxrwxr-x 1 jyh jyh 8432 Apr  4 09:49 exe
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 lib
-rw-rw-r-- 1 jyh jyh  223 Apr  4 09:42 main.c
[jyh@VM-12-12-centos japanese]$ ldd exe 
	linux-vdso.so.1 =>  (0x00007ffda62ca000)
	libmymath.so => not found
	libc.so.6 => /lib64/libc.so.6 (0x00007f0a98578000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f0a98946000) 
[jyh@VM-12-12-centos japanese]$ echo $LD_LIBRARY_PATH //查看链接库PATH
:/home/jyh/.VimForCpp/vim/bundle/YCM.so/el7.x86_64
[jyh@VM-12-12-centos japanese]$ pwd
/home/jyh/linux_-stu/study15/japanese
[jyh@VM-12-12-centos japanese]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/jyh/linux_-stu/study15/japanese/lib/ //导入路径
[jyh@VM-12-12-centos japanese]$ echo $LD_LIBRARY_PATH
:/home/jyh/.VimForCpp/vim/bundle/YCM.so/el7.x86_64:/home/jyh/linux_-stu/study15/japanese/lib/
[jyh@VM-12-12-centos japanese]$ ldd exe 
	linux-vdso.so.1 =>  (0x00007ffcaeaea000)
	libmymath.so => /home/jyh/linux_-stu/study15/japanese/lib/libmymath.so (0x00007fdd58121000)
	libc.so.6 => /lib64/libc.so.6 (0x00007fdd57d53000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fdd58323000)
[jyh@VM-12-12-centos japanese]$ ./exe 
addNum = 30 subNum = 10
[jyh@VM-12-12-centos japanese]$ 
  1. 软链接方式(永久性的)
[jyh@VM-12-12-centos japanese]$ ll
total 24
-rwxrwxr-x 1 jyh jyh 8432 Apr  4 09:49 exe
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 09:39 lib
-rw-rw-r-- 1 jyh jyh  223 Apr  4 09:42 main.c
[jyh@VM-12-12-centos japanese]$ ldd exe 
	linux-vdso.so.1 =>  (0x00007ffd9cdad000)
	libmymath.so => not found
	libc.so.6 => /lib64/libc.so.6 (0x00007f61e8af8000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f61e8ec6000)
[jyh@VM-12-12-centos japanese]$ sudo ln -s ./lib/libmymath.so /usr/lib64/libmymath.so //不是绝对路径不行
[jyh@VM-12-12-centos japanese]$ sudo ls /usr/lib64/libmath.so
ls: cannot access /usr/lib64/libmath.so: No such file or directory
[jyh@VM-12-12-centos japanese]$ sudo ls /usr/lib64/libmymath.so
/usr/lib64/libmymath.so
[jyh@VM-12-12-centos japanese]$ ldd exe 
	linux-vdso.so.1 =>  (0x00007ffddd50f000)
	libmymath.so => not found
	libc.so.6 => /lib64/libc.so.6 (0x00007f0d1f34b000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f0d1f719000)
[jyh@VM-12-12-centos japanese]$ pwd 
/home/jyh/linux_-stu/study15/japanese
[jyh@VM-12-12-centos japanese]$ sudo ln -s /home/jyh/linux_-stu/study15/japanese/lib/libmymath.so /usr/lib64/libmymath.so
ln: failed to create symbolic link ‘/usr/lib64/libmymath.so’: File exists
[jyh@VM-12-12-centos japanese]$ rm /usr/lib64/libmymath.so 
rm: cannot remove ‘/usr/lib64/libmymath.so’: Permission denied
[jyh@VM-12-12-centos japanese]$ sudo rm /usr/lib64/libmymath.so 
[jyh@VM-12-12-centos japanese]$ sudo ln -s /home/jyh/linux_-stu/study15/japanese/lib/libmymath.so /usr/lib64/libmymath.so //必须使用绝对路径
[jyh@VM-12-12-centos japanese]$ sudo ls /usr/lib64/libmymath.so 
/usr/lib64/libmymath.so
[jyh@VM-12-12-centos japanese]$ ldd exe 
	linux-vdso.so.1 =>  (0x00007ffc4e5b5000)
	libmymath.so => /lib64/libmymath.so (0x00007f452382c000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f452345e000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f4523a2e000)
[jyh@VM-12-12-centos japanese]$ ./exe 
addNum = 30 subNum = 10
[jyh@VM-12-12-centos japanese]$ 
  1. 配置文件方式(配置文件在/etc/ld.so.conf.d目录下)
[jyh@VM-12-12-centos japanese]$ ll
total 24
-rwxrwxr-x 1 jyh jyh 8432 Apr  4 18:43 exe
drwxrwxr-x 2 jyh jyh 4096 Apr  4 18:43 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 18:43 lib
-rw-rw-r-- 1 jyh jyh  223 Apr  4 18:43 main.c
[jyh@VM-12-12-centos japanese]$ ldd exe 
	linux-vdso.so.1 =>  (0x00007fffbf799000)
	libmymath.so => not found
	libc.so.6 => /lib64/libc.so.6 (0x00007fe05040a000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fe0507d8000)
[jyh@VM-12-12-centos japanese]$ ./exe 
./exe: error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or directory
[jyh@VM-12-12-centos japanese]$ ls /etc/ld.so.conf.d/
bind-export-x86_64.conf  dyninst-x86_64.conf  kernel-3.10.0-1160.71.1.el7.x86_64.conf  mariadb-x86_64.conf
[jyh@VM-12-12-centos japanese]$ vim /etc/ld.so.conf.d/dyninst-x86_64.conf 
[jyh@VM-12-12-centos japanese]$ sudo touch /etc/ld.so.conf.d/mymath.conf//必须提权,因为是系统配置文件必须root身份
[jyh@VM-12-12-centos japanese]$ ls /etc/ld.so.conf.d/
bind-export-x86_64.conf  dyninst-x86_64.conf  kernel-3.10.0-1160.71.1.el7.x86_64.conf  mariadb-x86_64.conf  mymath.conf
[jyh@VM-12-12-centos japanese]$ pwd lib/
/home/jyh/linux_-stu/study16/japanese
[jyh@VM-12-12-centos japanese]$ cd lib/
[jyh@VM-12-12-centos lib]$ ls
libmymath.so
[jyh@VM-12-12-centos lib]$ pwd
/home/jyh/linux_-stu/study16/japanese/lib
[jyh@VM-12-12-centos lib]$ cd ..
[jyh@VM-12-12-centos japanese]$ sudo vim /etc/ld.so.conf.d/mymath.conf//必须提权,因为是系统配置文件必须root身份
[jyh@VM-12-12-centos japanese]$ cat /etc/ld.so.conf.d/mymath.conf 
/home/jyh/linux_-stu/study16/japanese/lib
[jyh@VM-12-12-centos japanese]$ ldd exe 
	linux-vdso.so.1 =>  (0x00007ffd29348000)
	libmymath.so => not found
	libc.so.6 => /lib64/libc.so.6 (0x00007fd8b62cd000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fd8b669b000)
[jyh@VM-12-12-centos japanese]$ sudo ldconfig //ldconfig更新配置文件
[jyh@VM-12-12-centos japanese]$ ldd exe 
	linux-vdso.so.1 =>  (0x00007ffc9e5e8000)
	libmymath.so => /home/jyh/linux_-stu/study16/japanese/lib/libmymath.so (0x00007f25b0edf000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f25b0b11000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f25b10e1000)
[jyh@VM-12-12-centos japanese]$ ./exe 
addNum = 30 subNum = 10
[jyh@VM-12-12-centos japanese]$ 

8.5 动静态库加载问题

  • 静态库

上述说到了静态库问题,当程序形成可执行程序的时候,这个可执行程序中已经包含了静态库中被转化为二进制代码的函数功能实现,此时形成.exe文件后,把静态库删除了依旧可以运行正常,这相比动态库,静态库的可执行文件要大的多,因为动态库中被转化为二进制程序的函数功能实现并不是直接拷贝到.exe文件中的。所以总结出一个结论:静态库非常占用资源(磁盘资源等等资源)

[jyh@VM-12-12-centos staticIncludeTest]$ ll
total 28
-rw-rw-r-- 1 jyh jyh  202 Apr  4 19:45 main.c
-rw-rw-r-- 1 jyh jyh   77 Apr  4 19:47 makefile
-rw-rw-r-- 1 jyh jyh  680 Apr  4 19:43 test.c
-rw-rw-r-- 1 jyh jyh  352 Apr  4 19:45 test.h
[jyh@VM-12-12-centos staticIncludeTest]$ cat main.c 
#include "test.h"

int main()
{
  srand(time(NULL));
  Name name;
  giveStorage(&name);
  giveFiveNumber(&name);
  print(&name);
  printf("别点到的名字是:%s\n", getNumber(&name));
  return 0;
}

[jyh@VM-12-12-centos staticIncludeTest]$ cat makefile 
myexe:main.c test.c
	gcc -o $@ $^ -std=c99
.PHONY:clean
clean:
	rm -f myexe

[jyh@VM-12-12-centos staticIncludeTest]$ cat test.c
#include "test.h"

void giveStorage(Name* name)
{
  name->index = 0;
  name = (Name*)malloc(sizeof(Name));
  if(name == NULL) {perror("giveStorage():name malloc failed!\n"); exit(0);}
  for(int i = 0; i < 5; ++i)
  {
    name->s[i] = (char*)malloc(sizeof(char) * 10);
  }
}

void giveFiveNumber(Name* name)
{
  name->s[0] = "Johnson";
  name->s[1] = "Tom";
  name->s[2] = "Timy";
  name->s[3] = "Junisy";
  name->s[4] = "Kangkang";
}


void print(Name* name)
{
  printf("五个成员分别是:\n");
  for(int i = 0; i < 5; ++i)
  {
    printf("%s ", name->s[i]);
  }
  printf("\n");
}


char* getNumber(Name* name)
{
  name->index = rand() % 5;
  return name->s[name->index];
}


[jyh@VM-12-12-centos staticIncludeTest]$ cat test.h
//实现点名器
#include 
#include 
#include 
#include 
#include 

typedef struct Name
{
  char* s[5];
  int index;
}Name;

//申请内存
void giveStorage(Name* name);

//给定成员
void giveFiveNumber(Name* name);

//打印成员
void print(Name* name);

//随机点名
char* getNumber(Name* name);
[jyh@VM-12-12-centos staticIncludeTest]$ mkdir include
[jyh@VM-12-12-centos staticIncludeTest]$ mkdir lib
[jyh@VM-12-12-centos staticIncludeTest]$ mv test.h include
[jyh@VM-12-12-centos staticIncludeTest]$ mv test.o lib
[jyh@VM-12-12-centos staticIncludeTest]$ cd lib/
[jyh@VM-12-12-centos lib]$ ll
total 4
-rw-rw-r-- 1 jyh jyh 2696 Apr  4 19:49 test.o
[jyh@VM-12-12-centos lib]$ ar -rc libRandGetName.a test.o 
[jyh@VM-12-12-centos lib]$ ll
total 8
-rw-rw-r-- 1 jyh jyh 2888 Apr  4 19:55 libRandGetName.a
-rw-rw-r-- 1 jyh jyh 2696 Apr  4 19:49 test.o
[jyh@VM-12-12-centos lib]$ rm test.o
[jyh@VM-12-12-centos lib]$ cd ..
[jyh@VM-12-12-centos staticIncludeTest]$ rm makefile test.c
[jyh@VM-12-12-centos staticIncludeTest]$ ll
total 12
drwxrwxr-x 2 jyh jyh 4096 Apr  4 19:53 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 19:55 lib
-rw-rw-r-- 1 jyh jyh  202 Apr  4 19:45 main.c
[jyh@VM-12-12-centos staticIncludeTest]$ tree
.
|-- include
|   `-- test.h
|-- lib
|   `-- libRandGetName.a
`-- main.c

2 directories, 3 files
[jyh@VM-12-12-centos staticIncludeTest]$ 
[jyh@VM-12-12-centos staticIncludeTest]$ gcc -o exe main.c 
main.c:1:18: fatal error: test.h: No such file or directory
 #include "test.h"
                  ^
compilation terminated.
[jyh@VM-12-12-centos staticIncludeTest]$ gcc -o exe main.c -I include -L lib -lRandGetName
[jyh@VM-12-12-centos staticIncludeTest]$ ll
total 24
-rwxrwxr-x 1 jyh jyh 8920 Apr  4 19:57 exe
drwxrwxr-x 2 jyh jyh 4096 Apr  4 19:53 include
drwxrwxr-x 2 jyh jyh 4096 Apr  4 19:55 lib
-rw-rw-r-- 1 jyh jyh  202 Apr  4 19:45 main.c
[jyh@VM-12-12-centos staticIncludeTest]$ ./exe 
五个成员分别是:
Johnson Tom Timy Junisy Kangkang 
别点到的名字是:Junisy
[jyh@VM-12-12-centos staticIncludeTest]$ ./exe 
五个成员分别是:
Johnson Tom Timy Junisy Kangkang 
别点到的名字是:Kangkang
[jyh@VM-12-12-centos staticIncludeTest]$ ./exe 
五个成员分别是:
Johnson Tom Timy Junisy Kangkang 
别点到的名字是:Timy
[jyh@VM-12-12-centos staticIncludeTest]$ rm lib/libRandGetName.a //删除静态库依旧可以执行./exe
[jyh@VM-12-12-centos staticIncludeTest]$ ll lib/
total 0
[jyh@VM-12-12-centos staticIncludeTest]$ ./exe 
五个成员分别是:
Johnson Tom Timy Junisy Kangkang 
别点到的名字是:Johnson
[jyh@VM-12-12-centos staticIncludeTest]$ ./exe 
五个成员分别是:
Johnson Tom Timy Junisy Kangkang 
别点到的名字是:Johnson
[jyh@VM-12-12-centos staticIncludeTest]$ 
  • 动态库
Linux -- 基础IO_第16张图片

这里需要注意的是虚拟内存空间中有共享区这个区域,这个区域就是用来存放对对应的动态库文件中相关库函数名的地址,然后运行程序时,需要用到某个函数时就对应的把动态库文件也加载到内存中,通过函数名地址在共享区找相对应的函数地址,然后加载到物理内存中,再给CPU进行计算。所以,把对应的动态库文件删除,就好像找不到相对应的函数,并且它是只有一份的,这样就可以随时拿来使用,没有静态库那么占用资源。上面动态库使用中我们用到了-fPIC这个选项,这个与位置无关码是什么呢?也就是这里的函数名调用和函数地址对应关系和位置并不相关,也就是可以这么理解:每次加载到内存中虚拟内存中的共享区这个区间的起始地址是不一样的,这里是根据对应的起始位置的偏移量找到函数实现的,所以和位置无关(和绝对位置无关)。另外,编译器进行链接的时候,有动态库和静态库时,优先动态链接;没有动态库只有静态库时,只能静态链接;如果都没有,则程序运行不了。(这里不做实验了)。云服务器上默认只有动态库,那么安装c静态库:sudo yum install -y glibc-static,安装C++静态库:sudo yum install -y libstdc+±static。

你可能感兴趣的:(Linux,linux,运维,服务器)