内存
进程中内存分布
top和free可以查看系统中的内存情况。
- 堆(heap):动态分配,手动释放
- 栈(stack):大小在编译后确定;栈被放置在高地址: 调用函数(加帧)是减 esp 的,函数返回(减帧)是加 esp 的,调用在前,所以栈是向低地址扩展的,放在高地址再合适不过了。
- 只读数据段:文字常量区,存放常量字符串
- 静态存储区域:初始化/未初始化的全局变量
- 代码段:函数体的二进制代码
int a = 0; //全局初始化区
char *p1; //全局未初始区
void main()
{
int b; //栈
char s[] = "abc"; //栈,运行时赋值
char *p2; // 栈
char *p3 = "123456"; //123456\0在常量区,p3在栈上,编译时确定
static int c = 0; //全局(静态)初始化区
p1 = (char *)malloc(10); //p1指向堆区
}
虚拟内存/交换空间
一种内存管理的技术,将实际上多个被分隔的
内存操作
malloc/calloc/realloc(头文件stdlib.h,free释放)
函数名 | 原型 | 作用 |
---|---|---|
malloc | void *malloc(size_t size); | 动态分配内存,分配size个字节的内存空间 |
calloc | void *calloc(size_t n, size_t size); | 动态分配内存并清零,分配n个长度为size的连续空间 |
realloc | void *realloc(void *mem_address, unsigned int newsize); | 动态调整内存,指针名=(数据类型*)realloc(要改变内存大小的指针名,新的大小) |
memset | void *memset(void *s, int ch, size_t n) | char型初始化函数,n个字节的内容全部设置为ch(string.h) |
malloc(size) + memset = calloc(1,size)
malloc在分配了大量的内存之后,会变得越来越慢,因为malloc的分配过程是在内存管理模块的”空闲链表”里找到一个合适大小的内存返回,如果空闲链表太长,势必影响速度。
new在堆上动态创建一个对象时,它实际上做了三件事:获得一块内存空间、
调用构造函数 、返回正确的指针。和delete相对应。数据的对齐是指数据的地址和由硬件决定的内存块大小之间的关系,一个变量的地址是变量本身大小的倍数时,称为
自然对齐 。比如一个32bit的变量,大小是4个字节,自然对齐的情况下,地址应该是4的倍数(一个地址对应一个字节)。不对齐的数据载入可能会引起性能的下降 或进程的陷入 。malloc,calloc,realloc返回的地址对于任何的C类型都是对齐的,在32位系统以8字节为边界对齐,在64位系统以16字节为边界对齐。但对于更大的边界,如
页面 ,需要动态对齐。posix_memalign: 函数原型:int posix_memalign(void **memptr, size_t alignment, size_t size); 分配长度为size的连续空间,地址以alignment为边界对齐,memptr保存返回的内存块地址。 成功时返回0,失败时,memptr没有被定义,错误码:
EINVAL (alignment不是2的幂,或不是void指针的倍数),ENOMEM (没有足够内存),
char *buf;
int ret;
ret = posix_memalign(&buf, 256, 1024);
if(ret){
fprintf(stderr, "posix_memalign:%s\n",strerror(ret));
return -1;
}
free(buf); //释放内存
内存池(Memory Pool)
通常的内存分配是使用new,malloc申请,缺点:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。
内存池,作为一种内存分配方式,是在真正使用内存前,先申请分配一定数量的、大小相等的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升。
具体代码实现。
本地进程通信(IPC)
- 无名管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
- 高级管道(popen):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。
- 有名管道(named pipe): 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
- 消息队列(message queue): 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 信号量(semophore): 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 信号(sinal): 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
- 共享内存(shared memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
- 套接字(socket) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
fork
一个进程,包括代码、数据和分配给进程的资源。fork通过系统调用创建一个与原来进程几乎完全相同的新进程,系统为新进程分配资源,相当于克隆了一个自己。
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
线程与进程
具体见这里
一个进程中可以包含若干个线程,每个线程可以利用进程所拥有的资源。进程是分配资源的基本单位,线程是独立运行和独立调度的基本单位。
子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文。多线程可以节约CPU时间。
地址空间和其它资源 :进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。通信 :进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。调度和切换 :线程上下文切换比进程上下文切换要快得多。- 在多线程OS中,进程不是一个可执行的实体。