【Linux】基础/IO

一、open/read/write/close等文件相关系统调用接口

谈到系统调用接口,首先回顾一下系统调用的定义以及库函数与系统调用的区别:
在这里插入图片描述

  • 系统调用接口:在开发角度操作系统对外会表现一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。如下:

open: 打开文件或设备
read: 从打开的文件或设备中读取数据
write: 向打开的文件或设备中写入数据
close: 关闭文件或设备
ioctl: 把控制信息传递给设备驱动文件

系统调用是通向操作系统本身的接口,是面向底层硬件的。通过系统调用,可以使得用户态运行的进程与硬件设备(如CPU、磁盘、打印机等)进行交互,是操作系统留给应用程序的一个接口。
用户进程需要发生系统调用时,内核将调用内核相关函数来实现(如sys_read(),sys_write(),sys_fork())。用户程序不能直接调用这些函数,这些函数运行在内核态,CPU 通过软中断切换到内核态开始执行内核系统调用函数。用户态–>系统调用–>内核态–>返回用户态实际上使用系统调用会影响系统的性能,在执行调用时的从用户态切换到内核态,再返回用户态会有系统开销。为了减少开销,因此需要减少系统调用的次数,并且让每次系统调用尽可能的完成多的任务。硬件也会限制对底层系统调用一次所能写的数据块的大小。为了给设备和文件提供更高层的接口,Linux系统提供了一系列的标准函数库。使用标准库函数,可以高效的写任意长度的数据块,库函数在数据满足数据块长度要求时安排执行底层系统调用。
一般地,操作系统为了考虑实现的难度和管理的方便,它只提供一少部分的系统调用,这些系统调用一般都是由C和汇编混合编写实现的,其接口用C来定义,而具体的实现则是汇编,这样的好处就是执行效率高,而且,极大的方便了上层调用。

  • 库函数:系统调用在使用上,功能基础基础,对用户的要求也比较高,所以,有心的开发者可以对部分系统调用接口进行适度封装,就形成了库。有了库,就很有利于更上层用户或者开发者进行二次开发。

库函数(Library function)是把函数放到库里,供别人使用的一种方式。.方法是把一些常用到的函数编完放到一个文件里,供不同的人进行调用。一般放在.lib文件中。库函数调用则是面向应用开发的,库函数可分为两类,一类是C语言标准规定的库函数,一类是编译器特定的库函数。(由于版权原因,库函数的源代码一般是不可见的,但在头文件中你可以看到它对外的接口)。
glibc 是 Linux 下使用的开源的标准 C 库,它是 GNU 发布的 libc 库,即运行时库。这些基本函数都是被标准化了的,而且这些函数通常都是用汇编直接实现的。glibc 为程序员提供丰富的 API(Application Programming Interface),我们经常说到的POSIX(Portable Operating System Interface of Unix)是针对API的标准,即针对API的函数名,返回值,参数类型等。POSIX兼容也就指定这些接口函数兼容,但是并不管API具体如何实现。
随着系统提供的这些库函数把系统调用进行封装或者组合,可以实现更多的功能,这样的库函数能够实现一些对内核来说比较复杂的操作。比如,read()函数根据参数,直接就能读文件,而背后隐藏的比如文件在硬盘的哪个磁道,哪个扇区,加载到内存的哪个位置等等这些操作,程序员是不必关心的,这些操作里面自然也包含了系统调用。而对于第三方的库,它其实和系统库一样,只是它直接利用系统调用的可能性要小一些,而是利用系统提供的API接口来实现功能(API的接口是开放的)。部分Libc库中的函数的功能的实现还是借助了系统掉调用,比如printf的实现最终还是调用了write这样的系统调用;而另一些则不会使用系统调用,比如strlen, strcat, memcpy等。
在这里插入图片描述

  • 区别

(1)库函数是语言或应用程序的一部分,而系统调用是内核提供给应用程序的接口,属于系统的一部分
(2)库函数在用户地址空间执行,系统调用是在内核地址空间执行,库函数运行时间属于用户时间,系统调用属于系统时间,库函数开销较小,系统调用开销较大
(3)库函数是有缓冲的,系统调用是无缓冲的
(4)系统调用依赖于平台,库函数并不依赖
在这里插入图片描述
了解了系统调用,接下来一起看看几个简单系统调用接口:

1. open详解
//头文件 
#include<sys/types.h> 
#include<sys/stat.h> 
#include<fcntl.h> 

//函数原型 
int open(const char *pathname , int flags); 
int open(const char *pathname ,int flags , mode_t mode); pathname: 表示要打开或创建的目标文件 
flags:打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。 

//参数: 
O_RDONLY:只读打开 
O_WRONLY:只写打开 
O_RDWR :读,写打开 注意:这三个常量,必须指定一个且只能指定一个 
O_CREAT:若文件不存在,则创建它。需要使用mode选项(权限标志): 来指明新文件的访问权限。 
O_APPEND:追加写 

//返回值: 
成功:新打开的文件描述符 
失败:-1

mode_t理解:文件权限标志也可以使用加权数字表示,这组数字被称为umask变量,它的类型是mode_t,是一个无符号八进制数。
umask变量表示方法:
在这里插入图片描述
注意:open函数具体使用哪个,和具体应用场景相关,如果目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限。否则,使用两个参数的open。

2. close详解
//头文件
#include<unistd.h>

//函数原型
int close(int fd);

//返回值
若成功返回0,出错返回-1

注意:关闭一个文件时也释放该进程加在该文件上的所有记录锁,当一个进程终止时,它所有的打开文件都由内核自动关闭。

3. read 详解
//头文件
    #include<unistd.h>

//函数原型
    ssize_t read(int fd,void *buf,size_t count);

//参数说明:
fd:文件描述符,用来指向要操作的文件的文件结构体   
buf:一块内存空间
count:请求读取的字节数,读上来的数据保存在缓冲区buf中,同时文件的当前读写位置向后移。
ssize_t:有符号整型

//返回值
成功返回读取的字节数,
出错返回-1并设置errno,如果在调用read之前已到达文件末尾,则这次read返回0

有很多种情况可使实际读到的字节数少于要求读字节数:
(1)读普通文件时,在读到要求字节数之前已到达了文件尾端,如,若在到达文件尾端之前还有30字节,而要求读100个字节,则read返回30,下一次在调用read时,它将返回0.
(2)当从终端设备读时,通常一次最多读一行。
(3)当从网络读时,网络中的缓冲机构可能造成返回值小于所要求读的字节数。
(4)某些面向记录的设备,如磁带一次最多返回一个记录。
注意:读操作从文件的当前位移量处开始,在成功返回之前,该位移量增加实际读的字数。

  • 参考代码
int main() 
{ 
	int fd = open("myfile", O_RDONLY); 
	if (fd<0)
	{ 
		perror("use open"); 
		exit(1); 
	} 
	char buf[1024]; 
	const char *msg = "hello\n"; 
	while (1)
	{ 
		ssize_t s = read(fd, buf, strlen(msg)); 
		if (s>0)
		{ 
			buf[s] = 0; 
			printf("%s", buf); 
		} 
		else
		{ 
			break; 
		} 
	} 
	close(fd); 
	return 0; 
}

4. write详解
//头文件 
#include<unistd.h> 

//函数原型 
ssize_t write(int fd,const void *buf,size_t count); 

//返回值 
成功返回写入的字节数,
出错返回-1并设置errno写常规文件时,write的返回值通常等于请求写的字节数count,而向终端或者

  • 参考代码
 int main()
 {
  int fd = open("myfile", O_WRONLY | O_CREAT, 0664);
  if (fd <= 0)
  {
  		perror("use open");
  		exit(1);
  }
  const char* msg = "hello,xikeda\n";
  int count = 5;
  while (count--){
  		write(fd, msg, strlen(msg));
  }
  close(fd);
  return 0;
 }
二、文件描述符fd与FILE结构体的理解

对于内核而言,所有打开的文件都是通过文件描述符引用的。内核( kernel)利用文件描述符(file descriptor)来访问文件,基于文件描述符的I/O操作兼容与POSIX标准,在UNIX、Linux系统调用中,大量的系统调用都是依赖于文件描述符的。

1. 文件描述符

文件描述符(file descriptor):文件描述符在形式上是一个非负的整数,实际上,它是一个索引值,指向内核的每一个进程所维护的该进程打开该文件的记录表。当程序打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。(一般只适用于UNIX、Linux)。

标准文件描述符:在Centos中,存在于/usr/include/unistd.h头文件中。

[root@localhost include]# more unistd.h
/* Standard file descriptors.  */
#define STDIN_FILENO    0       /* Standard input.  */
#define STDOUT_FILENO   1       /* Standard output.  */
#define STDERR_FILENO   2       /* Standard error output.  */

文件描述符的有效范围:是 0 到OPEN_MAX。一般来说,每个进程最多可以打开 64 个文件(0 — 63)。对于 FreeBSD 5.2.1、Mac OS X 10.3 和 Solaris 9 来说,每个进程最多可以打开文件的多少取决于系统内存的大小,int 的大小,以及系统管理员设定的限制。

但是在实际实现中会做相应的处理,一般最大打开文件数系统内存的10%称为系统级别限制),查看系统级别的最大文件打开数可以使用一下命令:

[root@localhost include]# sysctl -a | grep -i file-max

fs.file-max = 185745

但是,内核为了不让某一个进程消耗掉所有的文件资源,会对单个进程最大打开文件数做默认处理,称之为用户级别限制默认值一般为1024,使用下列命令查看:

[root@localhost include]# ulimit -n         

1024

修改最大打开文件描述符的方法如下:(临时生效,关机后失效

[root@localhost include]#  ulimit -HSn 2048 
[root@localhost include]# ulimit -n        

2048

文件描述符的分配:当我们打开一个新文件时,其文件描述符从几开始呢,我们不妨测试一下:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{

   int fd = open("my.txt",O_WRONLY|O_CREAT,644);
   if(fd == -1)
   {
      perror("open");
      return 1;
   }
   const char* msg = "hello fd\n";
   int i = 0;
   while(i < 10)
   {
      write(fd,msg,strlen(msg));
      i++;
   }
   printf("fd:%d\n",fd);
   close(fd);
}
[Hunter@localhost mytest]$ ./a.out 

fd:3

我们发现文件描述符fd为3,这是因为程序在启动的时候,已经打开了标准输入0,标准输出1以及标准错误2。

但是,当我们关闭某一个标准文件描述符时,新打开的文件的文件描述符是从3开始还是占据空位呢?

int main()
{
   close(0);
   int fd = open("my.txt",O_WRONLY|O_CREAT,644);
   if(fd == -1)
   {
      perror("open");
      return 1;
   }
   const char* msg = "hello fd\n";
   int i = 0;
   while(i < 10)
   {
      write(fd,msg,strlen(msg));
      i++;
   }
   printf("fd:%d\n",fd);
   close(fd);
}
[Hunter@localhost mytest]$ ./a.out 

fd:0

关闭标准输入0后,此时新打开的文件描述符fd变成了0。

因此我们得到一个结论文件描述符的分配是从0~n中最小未被使用的来做文件描述符。本质上是数组的下标,特定地址与特定文件之间的映射关系。

我们关闭1(标准输出)时,本来要在屏幕上显示的文件内容,写到了代替1这个位置的文件中,这就是在这里插入代码片,与此对应的还有输入重定向和追加重定向。

追加重定向:>>    echo "123" >> file 
输出重定向:>     echo "123" > file
输入重定向:<     cat < file

文件描述符与文件之间的映射关系如下图所示:
在这里插入图片描述
总结每一个文件描述符会与一个打开文件相对应,同时,不同的文件描述符也会指向同一个文件。相同的文件可以被不同的进程打开也可以在同一个进程中被多次打开。系统为每一个进程维护了一个文件描述符表,该表的值都是从0开始的,所以在不同的进程中你会看到相同的文件描述符,这种情况下相同文件描述符有可能指向同一个文件,也有可能指向不同的文件。

2. FILE结构体

FILE结构体中最重要的两个成员变量文件描述符fd和缓冲区的大小。

在了解文件结构体之前,首先要搞清楚文件指针(FILE*)的作用,在C语言中使用文件指针作为I/O的句柄,文件指针指向进程用户区中的一个被称为FILE结构的数据结构(FILE结构体)。

进程打开一个文件的过程:

进程通过系统调用open()来打开一个文件,实质上是获得一个文件描述符,以便于进程通过文件描述符来读写该文件
②进程打开文件时,会为该文件创建一个file对象,并将一个指向该file对象的指针存入进程描述符表进程描述符数组),进而确定了打开文件的文件描述符(数组下标)。
open()系统调用是在内核里通过sys_open()实现的,sys_open()将创建文件的dentry、inode和file对象。
创建file对象时,将file对象指向了所属文件系统的操作函数集file_operations,而该函数集又来自具体文件的i节点,于是虚拟文件系统就与实际文件系统的操作就衔接起来了。并在files_struct结构体的进程打开文件表 fd_array[NR_OPEN_DEFAULT] 中查找一个空闲表项(也就是此时数组中最小的未被占用的表项),然后返回该表项的下标(文件描述符)。

我们可以在C语言的库函数stdio.h头文件中查看FILE结构体的定义:

#ifndef _FILE_DEFINED  
struct _iobuf {  
    char *_ptr; //文件输入的下一个位置  
    int _cnt; //当前缓冲区的相对位置  
    char *_base; //指基础位置(即是文件的其始位置)  
    int _flag; //文件标志  
    int _file; //文件描述符  
    int _charbuf; //检查缓冲区状况,如果无缓冲区则不读取  
    int _bufsiz; //缓冲区大小  
    char *_tmpfname; //临时文件名 
        };  
typedef struct _iobuf FILE;  
#define _FILE_DEFINED  
#endif

files_struct结构体定义描述符数组存放在进程打开的文件表files_struct
结构中,文件描述符数组中存放了一个进程所打开的所有文件。

struct files_struct { atomic_t count; /* 共享该表的进程数 */
rwlock_t file_lock; /* 保护以下的所有域,以免在tsk->alloc_lock中的嵌套*/
int max_fds; /*当前文件对象的最大数*/
int max_fdset;/*当前文件描述符的最大数*/
int next_fd;*已分配的文件描述符加1*/
struct file ** fd; /* 指向文件描述符数组的指针 */
fd_set *close_on_exec; /*指向执行exec( )时需要关闭的文件描述符*/
fd_set *open_fds; /*指向打开文件描述符的指针*/
fd_set close_on_exec_init;/* 执行exec( )时需要关闭的文件描述符的初值集合*/
fd_set open_fds_init; /*文件描述符的初值集合*/
struct file * fd_array[32];/* 文件对象指针的初始化数组*/};

file结构体:

struct file
{
struct list_head f_list; /*所有打开的文件形成一个链表*/
struct dentry *f_dentry; /*指向相关目录项的指针*/
struct vfsmount *f_vfsmnt; /*指向VFS安装点的指针*/
struct file_operations *f_op; /*指向文件操作表的指针*/
mode_t f_mode; /*文件的打开模式*/
loff_t f_pos; /*文件的当前位置*/
unsigned short f_flags; /*打开文件时所指定的标志*/
unsigned short f_count; /*使用该结构的进程数*/
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;
/*预读标志、要预读的最多页面数、上次预读后的文件指针、预读的字节数以及
预读的页面数*/
int f_owner; /* 通过信号进行异步I/O数据的传送*/
unsigned int f_uid, f_gid; /*用户的UID和GID*/
int f_error; /*网络写操作的错误码*/

unsigned long f_version; /*版本号*/
void *private_data; /* tty驱动程序所需 */
};

几个重要的成员变量:

f_flags:表示打开文件的权限 。
f_pos:表示当前读写文件的位置。
f_count:表示打开文件的引用计数,如果有多个文件指针指向它,就会增加f_count的值。
f_mode:设置对文件的访问模式,例如:只读,只写、可读可写等。

file_operations结构体:

struct file_operations {
    struct module *owner;               
    //指向拥有该模块的指针;
    loff_t (*llseek) (struct file *, loff_t, int);   
    //llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值.
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 
    //用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    //发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.
    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    //初始化一个异步读 -- 可能在函数返回前不结束的读操作.
    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    //初始化设备上的一个异步写.
    int (*readdir) (struct file *, void *, filldir_t);
    //对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对**文件系统**有用.
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    //mmap 用来请求将设备内存映射到进程的地址空间. 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV.
    int (*open) (struct inode *, struct file *);
    //打开一个文件
    int (*flush) (struct file *, fl_owner_t id);
    //flush 操作在进程关闭它的设备文件描述符的拷贝时调用;
    int (*release) (struct inode *, struct file *);
    //在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.
    int (*fsync) (struct file *, struct dentry *, int datasync);
    //用户调用来刷新任何挂着的数据.
    int (*aio_fsync) (struct kiocb *, int datasync);
    int (*fasync) (int, struct file *, int);
    int (*lock) (struct file *, int, struct file_lock *);
    //lock 方法用来实现文件加锁; 加锁对常规文件是必不可少的特性, 但是设备驱动几乎从不实现它.
    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (*check_flags)(int);
    int (*flock) (struct file *, int, struct file_lock *);
    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (*setlease)(struct file *, long, struct file_lock **);
};

总结

文件描述符(file_struct)操作系统用来管理文件的数据结构,当我们创建一个进程时,会创建文件描述符表,进程控制块PCB中的fs指针指向文件描述符表。当我们创建文件时,会为指向该文件的指针FILE*关联一个文件描述符添加在文件描述符表中。在文件描述符表中fd相当与数组的索引FILE*相当于数组的内容指向一个文件结构体。

参考博客

文件描述符与文件指针的区别
fd与FILE*区别

三、编写简单的add/sub/mul/div函数,并打包成动/静态库,并分别使用。
1.什么是静态库和动态库
  • 静态库

静态库是程序在编译链接时把库的代码链接到可执行文件中去。程序运行的时候不需要依赖库。它以.a结尾。

  • 动态库

动态库是在程序运行起来的时候才去链接动态库的代码,多个程序可以共享使用库的代码。它以.so结尾。

动态函数库与共享函数库语义相同(在linux上叫共享对象库, 文件后缀是.so,windows上叫动态加载函数库, 文件后缀是.dll)

  • Linux中命名系统中共享库的规则
    在这里插入图片描述
    最前面使用前缀"lib", 中间是库的名字,后缀是".so", 最后面跟着的是三个数字组成的版本好。x表示主版本号, y表示此版本号, z表示发布版本号

三个版本号含义不同:

  1. 主版本号: 表示库的重大升级, 不同主版本号的库之间是不兼容的, 依赖于旧的主版本号的程序要改动相应的部分,并且重新编译,才可以在新版本的共享库中运行; 或则,系统必须保留就版的共享库,使得那些依赖于旧版共享库的程序能够正常运行。
  2. 次版本号: 表示库的增量升级,即增加一些新的接口符号,且保持原来的符号不变。在主版本号相同的情况下,高的次版本号的库向后兼容低的此版本号的库。一个依赖于旧的次版本好共享库的程序,可以在新的次版本好共享库中运行,应为新版本中保留了原来所有的接口,并且不改变他们的定义和含义
  3. 发布版本号:表示库的一些错误的修正,性能的改进等,并不添加任何新的接口, 也不对接口进行更改。.发布版本号:表示库的一些错误的修正,性能的改进等,并不添加任何新的接口, 也不对接口进行更改。
2.动态库和静态库的优缺点及区别
  • 静态库的优点及缺点

优点:
可移植性较强,一旦可执行程序编译成功不需要依赖静态库

缺点
每一份程序都需要将静态库的代码链接进去,生成的可执行程序较大,浪费空间

  • 动态库的优点及缺点

优点:
在程序运行起来,才链接库,生成的可执行代码较小,节约内存
可以被多个程序共享(虚拟内存机制)

缺点:

可移植性差。一旦库丢失了,所有用到它的程序都会崩溃

  • 区别

(1).它们都是对二进制文件(.o)进行打包;
(2).在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该动态库中赋值到内存中,这个过程称之为动态链接
(3).动态链接的可执行文件只需要包含函数入口地址的一个表,而静态链接需要外部函数的所有机器码;
(4).动态库可以被多个进程共享(采用虚拟内存机制),所以动态链接使得可执行文件更小,节约了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该库的所有进程使用,节省了内存和磁盘空间。而静态库只能在编译阶段将库的代码链接到可执行文件中;

3.生成并使用静态库和动态库
  • 参考代码
    * 在这里插入图片描述在这里插入图片描述
  • 经过编译阶段只经编译器生成.o文件
    Ly9ibG9nLmNzZG4ubmV0L3FxXzQxMDM1NTg4,size_27,color_FFFFFF,t_70)
  • 生成静态库

第一步:将.o文件通过ar工具打包成静态库
ar -rc libmymath.a sub.o sum.o(-r:打包,-c:创建)
在这里插入图片描述第二步:查看静态库中的目录文件
ar -tv libmymath.a(-t:列出静态库中的文件,-v:详细信息)
在这里插入图片描述第三步:使用静态库(静态库在当前目录)
gcc main.c -L. -lmymath(-L:指定库路径,-l:指定库名,把lib和后缀删掉)
在这里插入图片描述

实际使用中,我们会把头文件和静态库给别人,防止源代码泄露。我们可以新建一个文件夹,结构如下。lib下放的是静态库,而include下放的头文件
在这里插入图片描述
在使用时,需要加上-I + 头文件所在的路劲-L+静态库所在路劲-l+库名称即可编译成功。
gcc main.c -L ./staticlib/lib -lmymath -I ./staticlib/include
在这里插入图片描述对于库搜索路径可以有三种方式:

1.用-L指定库目录路径
2. 用环境变量LIBRARY_PATH指定库目录路径
3. 把库放入系统指定搜索目录/usr/lib或者/usr/local/lib

Makefile可以这样编写
在这里插入图片描述

  • 生成动态库

第一步:产生与位置无关代码
gcc -fPIC -c sub.c sum.c(-fPIC参数会产生与位置无关代码,是为了能够在多个应用程序间共享,.so要求PIC码以达到动态链接的目的,否则,无法实现动态链接。)
在这里插入图片描述
第二步:生成动态库
gcc -shared -o libmath.so *.o,加上-shared参数表示生成共享库的格式
在这里插入图片描述
以上两步可以合并为一步:gcc -fPIC -shared -o libmath.so *.o
第三步:使用动态库
gcc main.c -L. -lmath ,-l:代表链接动态库,只要去掉lib和后缀即可、-L:链接库所在的路径
在这里插入图片描述
第三步这个可以编译通过,但是链接时找不到相应库,因为 库文件在链接(静态库和动态库)和运行(仅限于使用动态库的程序)时被使用,其 搜索路径 是在系统中进行设置的。一般Linux系统把 /lib 和 /usr/lib(也可能是/usr/local/lib)三个目录作为默认的库搜索路径 所以使用这两个目录中的库时不需要进行设置路径即可直接使用。对于处于 默认库搜索路径之外 的库,需要将库的位置添加到搜索路径之中。所以第三步出现链接错误,所以应该对库路径进行设置。

要想运行动态库,可以通过以下三种方法:

1. 把动态库拷贝到系统默认搜索路径,/usr/lib或者/lib此种做法会污染标准库,强烈不建议
2. 把动态库的路径加入到环境变量LD_LIBRARY_PATH中推荐做法
在这里插入图片描述
3. ldonfig配置/etc/ld.so.config.d/, ldconfig更新,此方法难理解,就是进入ld.so.conf.d新建目录手动添加库路径,推荐第二种,在此放上第三种方法教程 详细教程

同样,我们也可以把头文件和库放在一个目录中,供别人使用,防止源码泄露:
在这里插入图片描述

附加

静态库与动态库图片详解
Vs 2013打包静动态库(.lib/.dll)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农印象

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值