一.约定
学习语言的方法:
1. 环境
人手一台电脑
2. 多练
以实际的项目
把语法抛开。
编程技巧
3. c/c++的开发知识体系结构
C(c标准库)
C++ (STL 内存分配器Allocator,类和继承,抛出异常exception)
(一般情况下我们都使用系统IO,因为比c/c++提供的IO快 )
数据结构
内核
网络(socket, udp, tcp, ip, arp, icmp)
GUI(QT, curses)
数据库链接
分布式组建开发(corba, windows中叫com)
音视频多媒体
就业方向:网络通信, 服务器后台,GUI,网络安全
二.课程体系
语言
C
C++
算法
算法 解决空间和时间的冲突
数据结构
基础(系统核心(Kernel)编程)
|
-----------------------------
Linux/Unix Window MacOSX
运行的平台 PC机 服务器 ARM
内核编程=核心编程
任何的操作系统的主要学习的:必须掌握的
设备驱动/进程管理/内存管理/文件目录系统管理/IO 必须掌握的
内存管理
文件目录管理
IO
进程管理
进程创建
进程控制
进程通信
进程同步
线程管理
线程创建
线程同步
线程通信
应用 (这也是这个老师讲的)
网络
数据库(pro*c/c++)
UI
shell
不讲( xml处理
分布式与中间件(Tuxedo/Coreba/MQ)
web service
OpenGL 多媒体
)
定位:提供编程的能力,为设备驱动与Window应用奠定基础.
三.内存管理
硬件层次
内存结构管理 (页面映射)
内核层次(这是程序员掌握的)
内存映射
堆扩展
语言层次
c:malloc
c++:new
数据结构
STL(sgi公司开发的。内存管理被内存托管,对多进程共享有问题)
智能指针
补充:帮助手册
man 节 关键字
1-8 1:Linux系统(shell)指令
2:系统函数
3:标准C函数
7:系统编程帮助
1 shell编程
2 内存的管理
3 文件与目录操作
4 IO
5 进程
6 IPC
7 socket 编程
8 IO高级编程
9 多线程
10 多线程的同步
11 QT
12 SQL
13 pro*c/c++
补充shell编程
shell启动时做了几个工作:
加载如下脚本:
/etc/profile
~/.bashrc
~/.bash_profile或者 ~/.bash_login或者~/.profile中的一个
登录shell全部加载
0.问题:
malloc怎么分配空间? malloc底层用brk/sbrk实现
#include <stdio.h>
#include <stdlib.h>
void main()
{
int *p1=malloc(4);
int *p2=malloc(4);
int *p3=malloc(4);
int *p4=malloc(4);
int *p5=malloc(4);
printf("%p\n",p1); //输出*08
printf("%p\n",p2); //输出*18
printf("%p\n",p3); //输出*28
printf("%p\n",p4); //输出*38
printf("%p\n",p5); //输出*48
}
每次分配与上面相差16字节,如果在堆上联系申请int,是按16字节分配,棧上还是按类型本身大小连续分配。
1. Linux对内存的结构描述
1./proc/${pid}/ 存放进程运行时候所有的信息(包括内存结构)
ps aue 查看进程
到正在运行的程序的目录,进程运行的所有信息都在这
exe 执行程序所执行的文件,指向执行的程序。
cwd 指向当前工作路径
fd 是程序打开的所有文件描述符
cpuset 是程序的cpu信息
cmdline 是命令行
environ 是环境参数和环境变量 main带的参数
cat maps 程序运行时的内存结构
2.理解程序的变量与内存空间的关系
a1:0x80497c0 全局变量
a2:0x80497c4 静态全局变量
a3:0x8048600 全局常量
b1:0xbff995bc 局部变量
b2:0x80497c8 局部静态变量
b3:0xbff995b8 局部常量
MA:0x804842f
ad:0x8048424
c1:0x927a008
13884下文件全是0,这些文件是映射到内存的,文件和地址绑定,地址和内存绑定.
常量在代码区
代码空间以上部分的大小理论上是1个G,是这段空间映射到内核的动态库和共享库
程序的应从0804800开始, 4k =0x1000大小,4k是一页的大小,是16进制的1000
常量依据作用范围不同,放不同区,有放代码区的,也有放局部棧的,局部常量放临时棧区。
全局常量(字符串"djiao33"类似这些)放代码区。
内存空间权限 r是可读,x可执行,p是受保护的,私有的,s共享。
这个空间大小刚好4G。对照下图进程内存中的结构。注意高地址对应高地址
结论:
任何程序的内存空间分成4个基本部分
1.代码区 特征是权限是可执行的
2.全局栈区
3.堆
4.局部栈 (下面如果是两个,那都是棧)
linux的内存管理理解
通过地址直接访问内存,非保护模式。
不能直接通过地址访问内存,保护模式。用户看到的地址是地址逻辑编号,就是虚拟地址,(0x00000000~0xffffffff)
每个虚拟地址要映射一个实际的物理存储空间才能访问。否则就错误,这个错误就是段错误,就是每个实际物理空间映射。
这就叫内存映射<==>每个虚拟的地址必须要映射到物理的存储空间才能访问。
比如当我们常规的内存不够的时候,把文件和磁盘映射到我们的地址上面,加大内存。
内存映射的管理?
1内存映射的基本单位是页(page=4k=4096byte)
因为c/c++语言中地址变量都是4字节的整数
实际c能表示的内存是4G
每个程序都能访问4G的内存空间。
strace 可执行程序 跟踪程序的执行过程
用mmap2进行内存映射,会把虚拟的地址转为实际的物理地址。
linux哪些地址是可以访问的,程序员基本素质。
任何程序都有自己的自加载器ld-linux.so.2, 他是标准的可执行程序,负责把执行程序拷贝到代码空间,并把指针指向首地址,分配全局棧。
结论:
1.内存分四个区.
2.各种变量对应存放区
3.堆栈是一种管理内存的数据结构.
4.查看程序的内存地址.
2.理解malloc的工作的原理 c/c++的内存是由一个链表结构维护的。
malloc使用一个数据结构(链表)维护分配空间, 所以free才能不用传入数据大小,而用指针释放空间。
链表的构成:表明当前内存空间位置的节点,上一个空间节点,下一个空间节点,当前区域大小的节点。(分配的空间/上一个空间数据/下一个空间/空间大小等信息.)
struct node
{ T value; //数据区
node* next;
node* prev;
size length;
}
对malloc分配的空间不要越界访问.因为容易破坏后台维护结构.导致malloc/free/calloc/realloc不正常工作.
main()
{
int *p1=malloc(4);
int *p2=malloc(4);
int *p3=malloc(4);
*p1=1;
*(p1+1)=2;
*(p1+2)=3; //越界访问
*(p1+3)=4;
*(p1+4)=5;
*(p1+5)=6;
*(p1+6)=7;
*(p1+7)=8;
free(p1); //越界访问后free,执行出现错误:main:free():invalid next size(fast):ox099d4008
printf("%d\n",*p2); //输出 5
}
越界访问破坏链表结构,出现常见错误。牛啊!
3.C++的new与malloc的关系
malloc new new[]
realloc new()
calloc new[]
free delete delete[]?
结论:new的实现使用的是malloc来实现的.
区别:new使用malloc后,还要初始化空间为20,或者调用类型的构造函数.
基本类型,直接初始化成默认值.
UDT(user design type 用户自定义类型)类型,调用指定类型的构造器
delete调用free实现.
区别:delete负责调用析构器,将数据清零.然后再调用free
new与new[]区别
new只调用一个构造器初始化.
new[]循环对每个区域调用构造器.
delete 与delete[]
内存分配:
系统分配->c的分配方式->c++分配方式->stl->智能指针
void* malloc(size_t) 分配不初始化
void* calloc(size_t num, size_t size) 分配初始化为0
void* realloc(void *ptr, size_t size)在指定位置重现分配指定大小的空间,初始化为0
size=0 作用=free
ptr=null 作用= malloc
size!=0 && ptr!=null
1 ptr的地址空间大小>size 返回指针等于ptr
释放ptr,并且在这个位置分配size空间
2ptr的地址空间大小<size 返回指针不等于ptr
释放ptr,并且在其他位置分配size空间
内存操作
1初始化
void* bzero(void*, size_t) unix函数<string.h>中
//内存初始化为0
void* memset(void* ptr,int value, size_t n);
把指定位置ptr,指定大小n初始化为value,
2使用内存
void* memcpy(void* dset, const void* src, size_t n);
内存拷贝
int memcmp(const void* s1, const void* s2, size_t n);
内存比较,返回0表示内存相同。
5.函数调用栈空间的分配与释放
5.1.总结:
1.函数执行的时候有自己的临时栈.
2.函数的参数就在临时栈中.如果函数传递实参.则用来初始化临时参数变量.
3.通过积存器返回值.(使用返回值返回数据)
4.通过参数返回值.(参数必须是指针)
指针指向的区域必须事先分配.
5.如果参数返回指针.参数就是双指针.
5.2.__stdcall __cdecl __fastcall 决定函数棧的产生方式
1.决定函数栈压栈的参数顺序.
2.决定函数栈的清空方式
3.决定了函数的名字转换方式.
6.far near huge指针
near 16
far 32
huge 综合
4.虚拟内存
( 每次我们在linux下面查看程序的内存结构时。可以看到代码区,全局栈区,局部栈区和堆区的内存地址总是从一个地址开始(每个操作系统是不一样的,但同一台电脑每次运行都会看到所有程序的起始地址都是同一个)。这个时候我们看到哦地址都是虚拟地址。
虚拟地址是什么概念呢?就是系统返回给我们看的一个地址。因为我们是不可能去直接操作物理地址的,所有对物理地址的的操作就是由内核去完成。首先我们向内 核发送一个请求,然后内核响应请求然后去物理内存划分一块(就是映射)然后把这个划分的内存地址返回给我们。这个返回的内存地址就是刚才看到的虚拟地址。
而由物理地址到虚拟地址的这个转换过程就是映射。
通过映射,我们可以需要虚拟地址进行内存操作。内核负责修改真正的物理内存地址。作为C、C++程序员,也只能操作都虚拟地址这个层次了,物理地址那是驱动开发兄弟的地盘了。
这就解释了为什么每个程序的开始地址都是同一个地址了,那个地址只是一个给我们看的地址。我们操作这个地址的时候,内核会将这个地址转换成真正的物理地址进行操作。
映射说完了,我们就开始谈brk/sbrk吧。)
问题:
一个程序不能访问另外一个程序的地址指向的空间.
理解:
1.每个程序的开始地址0x80084000
2.程序中使用的地址不是物理,而是逻辑地址(虚拟内存).保护模式
逻辑地址仅仅是编号.编号使用int 4字节整数表示.
4294967296
每个程序提供了4G的访问能力
问题:
逻辑地址与物理地址关联才有意义:过程称为内存映射.
背景:
虚拟内存的提出:禁止用户直接访问物理存储设备.
有助于系统的稳定.
结论:
虚拟地址与物理地址映射的时候有一个基本单位:
4k 就是0x1000大小 内存页.
段错误原因:无效访问.
合法访问:比如malloc分配的空间之外的空间可以访问,但访问非法.
5.虚拟内存的分配.
栈:编译器自动生成代码维护
堆:地址是否映射,映射的空间是否被管理.
棧和堆这两个数据结构是系统管理的。
这个时候就要使用brk/sbrk函数了。这两个函数都是我们自己申请一块内存,这块内存不再由系统托管。完完全全是我们自己管理。也就是说我们自己要负责这一块内存的使用和释放,系统不再负责了。
1.brk/sbrk 内存映射函数 unix的函数
问题1 怎么知道空闲空间?
问题2 内核的内存分配方式?
每个进程可访问的虚拟内存空间为3G,但在程序编译时,不可能也没必要为程序分配这么大的空间,只分配并不大的数据段空间,程序中动态分配的空间就是从这 一块分配的。如果这块空间不够,malloc函数族(realloc,calloc等)就调用sbrk函数将数据段的下界移动,sbrk函数在内核的管理 下将虚拟地址空间映射到内存,供malloc函数使用。所以sbrk要分配新空间给进程。
内核数据结构mm_struct中的成员变量start_code和end_code是进程代码段的起始和终止地址,
start_data和 end_data是进程数据段的起始和终止地址,
start_stack是进程栈段起始地址,
start_brk是进程动态内存分配起始地址(堆的起始地址),
还有一个 brk(堆的当前的终止地址),就是动态内存分配当前的终止地址。
C语言的动态内存分配基本函数是malloc(),在Linux上的基本实现是通过内核的brk系统调用。brk()是一个非常简单的系统调用,
brk只是简单地改变mm_struct结构的成员变量brk的值。
分配释放内存:
int brk(void *end);//分配空间,释放空间 实际是改变end_ptr的值
移动访问的范围
end_ptr > sbrk(0) //分配
end_ptr < sbrk(0) //释放
void *sbrk(int size);//只做空间分配,返回分配空间的首地址
size = 0 得到大块空闲空间的首地址指针. 但这个指针没有映射还不能用
> 0 分配指定空间,end_ptr移动到末尾,每次分配从end_ptr开始,返回这片空间的首地址指针,并且把end_ptr指针位置+size
< 0 释放空间 返回释放的空间的end_ptr的地址(这个地址已经不能用了),并将空间指针回移。
应用:
1.使用sbrk分配空间
2.使用sbrk得到没有映射的空间的虚拟地址.
int *p = sbrk(0); //p这个指针还不能用,还没映射。
*p = 40;
//
给没有映射的空间赋值,
段错误。
如果参数不是0,就帮你映射size的空间,以页为单位的分配
。
3.使用brk分配空间
int* p = sbrk(0); // int* p = sbrk(9);这里的效果一样,因为后面brk又把末尾指针往前移动了。
brk(p+1); //这里虽然只是加1个字节,但是
映射了一个页4k
。
int* q = sbrk(0);
//这时候返回的地址与p相差4个字节,按brk移动的字节数末尾,开始向高地址(未分配区)分配。brk指向的地方是标记为已经用的。
*p=40;
*(p+40)=60;//证明映射的不只1个字节,不会有段错误,但逻辑上不这么用
brk + 链表结构管理 = malloc
malloc+初始化 = new
4.使用brk释放空间
理解:
sbrk(int size)
sbrk与brk后台系统维护一个指针.
指针默认是null.
调用sbrk,判定指针是否是0,是:得到大块空闲空间的首地址初始化指针.
同时把指针+size
否:返回指针,并且把指针位置+size
应用案例:
写一个程序查找1-10000之间所有的素数.
并且存放到缓冲,然后打印.
缓冲的实现使用sbrk/brk
流程:
循环
判定是否素数(isPrimer)
是,分配空间存放
不是,继续下步.
#include <stdio.h>
#include <unistd.h>
int isPrimer(int a)
{
int i;
for(i=2;i<a;i++)
{
if(a%i==0)
{
return 1;
}
}
return 0;
}
main()
{
int i=2;
int b;
int *r;
int *p;
p=sbrk(0);
r=p;
for(;i<100;i++)
{
b=isPrimer(i);
if(b==0)
{
brk(r+1); //末尾指针end_ptr=(r+1); 所以r被映射了。
*r=i;
r=sbrk(0);
}
}
i=0;
r=p;
while(r!=sbrk(0))
{
printf("%d\n",*r);
r++;
}
brk(p);//free
}
总结:
智能指针
stl
new
malloc (小而多数据) 多指数据类型多
下面两种速度和效率快:
brk/sbrk (同类型的大块数据,动态移动指针)
mmap/munmap(控制内存访问/使用文件映射/控制内存共享)性能对于brk/sbrk没有优势,只是可以用文件映射,这点别人没有。
异常处理
int brk(void*)
void *sbrk(int);
如果成功.brk返回0 sbrk返回指针
失败 brk返回-1 sbrk返回(void*)-1
linux的内存映射
mmap 映射空间
映射到内存
映射磁盘文件
munmap 解除映射空间 //只有整个页不用了,才释放掉。