Linux——映射虚拟内存(mmap/unmap)编程工具 静态库和动态库

回顾:
        brk/sbrk
        int brk(void *p);
        void *sbrk(int);
        
        维护一个位置。brk/sbrk改变这个位置
                    brk改变绝对位置。
                    sbrk相对改变位置。
        
        补充:全新的类型。
                    永远记住:c的基本类型就:整数(1字节char,2字节short,4字节int),
                    小数(4字节float,8字节double)
                    unsigned signed
                    所有全新类型都是使用typedef重新定义。
                    新的类型的C的原型。
                    类型重定义的好处:
                            1.维护方便
                            2.移植
                            3.容易理解
                            
        

一.映射虚拟内存 用户空间称为虚拟内存  内核空间为实际物理内存 我们都是在用户空间玩

        没有任何额外维护数据的内存分配。
        mmap(分配)/unmap(释放)

1.函数说明

        void *mmap(
            void *start,//指定映射的虚拟地址 0由系统指定开始位置)
            size_t length,//映射空间大小 pagesize倍数
            int prot,//映射权限  PROT_NONE | PROT_READ PROT_WRITE PROT_EXEC
            int flags,//映射方式MAP_ANONYMOUS MAP_SHARED和MAP_PRIVATE二选一 等
            int fd,//文件描述符号
            offset_t off);//文件中的映射开始位置(必须是pagesize的倍数)
            
            映射方式:
                    内存映射:匿名映射。
                    文件映射:映射到某个文件
                                      只有文件映射最后两个参数有效。            

2.案例     

map.c

#include 
#include 
#include 
#include 
main()
{
	int *p=mmap(
		NULL,//系统指定首地址
		getpagesize(),//一个页(基本单位)
		PROT_READ|PROT_WRITE,
		MAP_ANONYMOUS|MAP_SHARED,//匿名映射
		0,0);//可以在这一页里随便折腾
	*p=20;
	*(p+1)=30;
	*(p+2)=40;
	
	printf("%d\n",p[2]);
	munmap(p,4096);//释放内存
}

        

3.总结

选择什么样的内存管理方法?
智能指针(指针池)

C++       

STL    new       最省事      

C      

malloc (小而多数据)

        brk/sbrk (同类型的大块数据,动态移动指针)

        mmap/munmap(控制内存访问/使用文件映射/控制内存共享)        

4.应用(省略)   

二.编程工具 静态库和动态库

    1.gcc

            -o  输出文件名

            -O  -O0 -O1 -O2 -O3     编译优化

            -g  -g0 -g1 -g2 -g3  产生调试信息

            -W  all  error   

            -Wall  显示所有警告  

            -Werror      把警告当错误

            -w  关闭警告    

            -c  只编译不连接

            -E  预编译

            -S  汇编

            -D  在命令行定义宏。下面有代码示例 两种方式定义宏

                        在代码中定义宏

                        在命令行定义宏

                        

            -x  指定编译的语言类型

                    c++

                    c

                    .S

                    none 自动判定

            -std=c89

                     c99                                                    

        编译过程:-E  -c -S   自动调用连接器

        连接器 ld

        补充:

                .c    c文件

                .cpp    c++

                .CC    c++

                .h    头文件

                .hpp

                .o    目标文件

                .a    静态库

                .so    动态库

                .i        预编译文件

                .s    汇编文件

hong.c  

int printf(const char*,...);
main()
{
	printf("%d\n",NUM);
}
gcc hong.c -omain -DNUM=5; 运行main         结果:5  可以在命令行定义宏

三.静态库的编译

        1.编译过程(*.a   achieve(归档文件))
            1.1.编译成目标文件
                    -static  可选
                    gcc -c -static 代码文件.c 编译成目标文件 .o文件
                       

ku1.c

int add(int a,int b)
{
	return a+b;
}

ku2.c

int sub(int a,int b)
{
	return a-b;
}


callku.c

main()
{
	int r=add(45,55);
	int s=sub(100,45);
}

我们可以这样编译 gcc callku.c ku1.c ku2.c -omain  然后执行main

也可以  1 gcc -static -c ku1.c  生成ku1.o

             2 gcc -static -c ku2.c  生成ku2.o


            1.2.归档成静态库
                    ar工具
                            ar -r
                                 -t
                        ar -r 静态库文件  被归档的文件  

命令行 ar -r ku.a ku1.o ku2.o 生成ku.a(静态库文件)    非标准的 建议使用下面例子的标准命名和使用静态库

执行ar -t ku.a 可以看到静态库中包含ku1.o ku2.o

                       
                    nm工具(察看函数符号表)
 使用方法     nm 静态库或者动态库或者目标文件或者执行文件

执行nm ku.a 可以查看到add 和 sub函数名


            1.3.使用静态库
                    gcc 代码  静态库             

gcc callku.c ku.a -omain 缩短编译时间


        使用静态库完成如下程序:
                    输入一个菱形半径,打印菱形。
                    输入整数封装成IOTool
                    菱形的打印封装成Graphics
                    计划:
                            1.实现输入
                            2.实现菱形
                            3.编译静态库
                            4.调用静态库

iotool.c

#include 
int inputInt(const char *info)
{
	int r;
	printf("%s:",info);
	scanf("%d",&r);
	return r;
}

graphic.c

#include 
void diamond(int r)
{
	int x,y;
	for(y=0;y<=2*r;y++)
	{
		for(x=0;x<=2*r;x++)
		{
			if(y==x+r  || y==x-r 
			|| y==-x+r || y==-x+3*r)
			{
				printf("*");
			}
			else
			{
				printf(" ");
			}
		}
		printf("\n");
	}
}


main.c

#include 
#include 
main()
{
	int r=inputInt("输入菱形半径");
	diamond(r);
	printf("%d\n",getpid());
	while(1);
}


第一步 gcc -c -static iotool.c graphic.c

第二步 ar -r libdemo1.a iotool.o graphic.o

第三步 gcc main.c  -ldemo  -L.  -omain

第四步 main执行


总结:
                1.什么是库?
                        函数等代码封装的二进制已经编译的归档文件
                2.ar归档工具
                3.采用库德方式管理代码优点:
                        容易组织代码
                        复用
                        保护代码版权
                4.静态库的静态的含义:
                        编译好的程序运行的时候不依赖库。
                        库作为程序的一部分编译连接。
                5.静态库本质:
                        就是目标文件集合(归档)
                6.-static可选
                
         2.库的规范与约定                                
                    库命名规则:
                            lib库名.a.主版本号.副版本号.批号
                            lib库名.a
                    库使用规则
                            -l 库名
                            -L 库所在目录   


四.动态库的编译

            1.什么是动态库?(共享库)
                    动态库是可以执行,静态库不能执行
                    但动态库没有main,不能独立执行。
                    动态库不会连接成程序的一部分。
                    程序执行的时候,必须需要动态库文件。
            2.工具
                    ldd  察看程序需要调用的动态库
                            ldd 只能察看可执行文件(包括动态库文件).
                    readelf -h 察看执行程序头.
                    nm   察看库中的函数符号
            3.动态库的编译
                    3.1.编译
                            -c -fpic(可选)   

                                                        第一步 gcc -c iotool.c

                                                        第二步 gcc -c graphic.c

                    3.2.连接
                            -shared                第三步gcc -shared -olibdemo4.so iotool.o graphic.o
            4.使用动态库                       第四步gcc  main.c  -ldemo4  -L.  -omain

                                                        执行程序时会发现找不到动态库 (见下面系统对动态库查找规则

                                                        解决方法export  LD_LIBRARY_PATH=.:~ 只对本shell有效

                    方法一 gcc 代码 动态库文件名
                    方法二 gcc 代码   -l库名(把lib去掉后) -L动态库所在路径
                    
            标准命名规则:
                    lib库名.so 动态库
                    lib库名.a   静态库
                    
                    -l 库名  -L 库所在路径
                            
            问题:
                    4.1.执行程序怎么加载动态库?
                    4.2.动态库没有作为执行程序的一部分,为什么连接需要指定动态库以及目录?
                            连接器需要确定函数在动态库的中的位置
                            
            动态库的加载:
                    1.找到动态库  我们只能在这一步进行操作
                    2.加载动态库到内存
                    3.映射到用户的内存空间
            系统对动态库查找规则:

                  1 到环境变量LD_LIBRARY_PATH指定的路径中查找

                  2 从/etc/ld.so.cache缓存文件列表中查找(该缓存文件由ldconfig工具维护)
                  3 /lib
                  4/usr/lib

              
            缓冲机制:
                    linux启动的时候就把/lib:/usr/lib:LD_LIBRARY_PATH加载到缓冲ld.so.cache文件中
                    
                    /sbin/ldconfig -v 刷新缓冲中so的搜索数据


ldconfig工具简介

ldconfig是一个动态链接库管理命令,为了让动态链接库为系统所共享,还需运行动态链接库的管理命令--ldconfig。 ldconfig 命令的用途,主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态 链接库(格式如前介绍,lib*.so*),进而创建出动态装入程序(ld.so)所需的连接和缓存文件.缓存文件默认为 /etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表.


linux下的共享库机制采用了类似于高速缓存的机制,将库信息保存在/etc/ld.so.cache里边。

程序连接的时候首先从这个文件里边查找,然后再到ld.so.conf的路径里边去详细找。

这就是为什么修改了ld.so.conf要重新运行一下ldconfig的原因

补充一点,ldconfig在/sbin里面。


ldconfig几个需要注意的地方 
1. 往/lib和/usr/lib里面加东西,是不用修改/etc/ld.so.conf的,但是完了之后要调一下ldconfig,不然这个library会找不到 
2. 想往上面两个目录以外加东西的时候,一定要修改/etc/ld.so.conf,然后再调用ldconfig,不然也会找不到 
比如安装了一个mysql到/usr/local/mysql,mysql有一大堆library在/usr/local/mysql/lib下面,这时 就需要在/etc/ld.so.conf下面加一行/usr/local/mysql/lib,保存过后ldconfig一下,新的library才能在 程序运行时被找到。 
3. 如果想在这两个目录以外放lib,但是又不想在/etc/ld.so.conf中加东西(或者是没有权限加东西)。那也可以,就是export一个全局变 量LD_LIBRARY_PATH,然后运行程序的时候就会去这个目录中找library。一般来讲这只是一种临时的解决方案,在没有权限或临时需要的时 候使用。 
4. ldconfig做的这些东西都与运行程序时有关,跟编译时一点关系都没有。编译的时候还是该加-L就得加,不要混淆了。 
5. 总之,就是不管做了什么关于library的变动后,最好都ldconfig一下,不然会出现一些意想不到的结果。不会花太多的时间,但是会省很多的事。


                    
    综合应用:
            输入两个数,计算两个数的和。
            要求:输入与计算两个的数的和封装成动态库调用

           

五.使用libdl.so库

        动态库加载的原理                                                
        
        动态库中函数的查找已经封装成库libdl.so
        dlopen  打开一个动态库
        dlsym   
在打开动态库找一个函数      

        dlerror 返回错误   
        dlclose 关
闭动态库       

dldemo.c

#include 
main()
{
	void *handle=dlopen("./libdemo4.so",RTLD_LAZY);
	void(*fun)(int)=dlsym(handle,"diamond");
	fun(5);
	dlclose(handle);
}

gcc dldemo.c -omain -ldl     //代码中的函数在libdl.so动态库中


总结:
        1.编译连接动态库
        2.使用动态库
        3.怎么配置让程序调用动态库
        4.掌握某些工具的使用:nm ldd lddconfig

六.工具make的使用与makefile脚本

    背景:每次gcc去编译每个程序很累人
            make 编译脚本解释
            
            编译脚本Makefile
            
            make -f  脚本文件  目标
            
    脚本文件
            1.文本文件
            2.基本构成语法:
                    基本单位目标target
                    目标名:依赖目标
                        \t目标指令
                        \t目标指令

Makefile

demo:                     //这一行写依赖目标 次例子可不写
	gcc iotool.c -c   //前面是tab键绝对不可以使用空格
	gcc graphic.c -c
	gcc iotool.o  graphic.o -shared -olibdemo.so
	gcc main.c -ldemo -omain -L.

在命令行输入make 然后main运行


                   
综合案例:
        1.输入一个整数判定是否素数
            a.input.c            
            primer.c            
            demo.c
            b.make脚本
            c.使用make
            d.执行程序

input.c

#include 
int inputInt(const char *info)
{
	int a;
	printf("%s:",info);
	scanf("%d",&a);
	return r;
}

primer.c

int isPrimer(int a)
{
	int i;
	for(i=2;i
main.c

#include 
main()
{
	int a=inputInt("请输入一个整数\n");
	int r=isPrimer(a);
	if(r==1)
	{
		printf("%d是素数!\n",a);
	}
	else
	{
		printf("%d是合数!\n",a);
	}
}

Makefile   最好每次写好一个源代码文件 就在Makefile文件中加入一行命令

compile:
	gcc -c -fpic input.c
	gcc -c -fpic primer.c
lnk:compile
	gcc -shared -olibdemo.so input.o primer.o
demo:lnk
	gcc demo.c -ldemo -L. -omain
如果执行不了 找不到动态库 export  LD_LIBRARY_PATH=.

          
            
任务:
            1、输入一个整数,打印这个数的Goldbach分解
                所有大于等于6的偶数都可以分解成两个奇素数的和。
                要求:
                        封装成共享库。
                        使用make脚本编译
            
            
            2、已知"tom jack rose",写一个程序,统计单词个数
                    并且打印每个单词.                    
                    要求:使用共享库封装。
                                使用make脚本
                                        
            3、输出菜单,选择:1打印菱形,2打印矩形,
                        选择1    , 则输出菱形(半径4)
                        选择2 ,则输出矩形(半径4*4)



你可能感兴趣的:(Linux)