Linux_IO复用_select_poll

内容

  1. 基本概念
  2. 了解接口socket
  3. 了解协议tcp/udp
  4. io复用
  5. select
  6. poll/epoll

可以参考学习的文章:

  1. 深入浅出理解select、poll、epoll的实现
  2. linux在系统调用进入内核时,为什么要将参数从用户空间拷贝到内核空间?不能直接访问,或是使用memcpy吗?非要使用copy_from_user才行吗? - 针对为什么select/poll调用一次拷贝一次数组到内核态空间。

I/O复用

  1. select
  2. poll
  3. epoll

select

先观察其API

1
2
#include<sys/select.h>
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
  1. 参数
    1. nfds参数通常被设置为select监听的所有文件描述符中的最大值加1,表示在fd_set集合中,我们关心的描述符的总数。为什么加1呢?因为文件描述符是从0开始计数的。nfdsfd_set容量大小不一样,容量大小指的是FD_SETSIZE,即fd_set容量大小fd_set可容纳描述符的最大大小。
    2. readfds参数是select关心的读事件的集合;
    3. writefds参数是select关心的写事件的集合;
    4. exceptfds参数select关心的异常事件的集合;
    5. timeout参数设置select的超时时间。
  2. 返回值
    1. 集合中有事件就绪的描述符的个数
    2. 但是并没有告诉你具体是哪一个描述符就绪

fd_set结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<typesizes.h>
#define __FD_SETSIZE 1024

#include<sys/select.h>
#define FD_SETSIZE __FD_SETSIZE
typedef long int __fd_mask;
#undef __NFDBITS
#define __NFDBITS (8*(int)sizeof(__fd_mask))
typedef struct
{
#ifdef __USE_XOPEN
__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
#define __FDS_BITS(set) ((set)->fds_bits)
#else
__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
#define __FDS_BITS(set) ((set)->__fds_bits)
#endif
}fd_set;
-------------------
#define FD_SETSIZE 1024
#define NFDBITS (8*(int)sizeof(long int))
typedef struct
{
long int fds_bits[FD_SETSIZE / NFDBITS];
}fd_set;

其中,__FD_SETSIZE指出select可以关注的最大文件描述符个数,默认为1024。

__fd_mask被定义为long int的类型别称,long int在32位机占8个字节。

__NFDBITS计算的是1个__fd_mask元素所占用的位数,一个字节占8位,sizeof算出__fd_mask的字节数,相乘得其占用的bit大小。

接着,定义fds_bits,其是long int型的数组,数组大小为__FD_SETSIZE除以__NFDBITS。比如SETSIZE为1024位,NFDBITS是64位,则数组大小位1024/64=16。这里的计算主要是为了计算出数组的大小,以确定多大的数组可以正好容纳1024个位数,来记录文件描述符信息。

用到的宏函数

fd_set集合对于文件描述符的管理是按位进行的,而位只有0和1两种状态。

假如SETSIZE=1024,则可管理1024个文件描述符,如果文件描述符7有效,我们需要对位操作,使其位变为1

由于位操作过于繁琐,select API中提供了一系列宏函数来方便我们访问、操作fd_set集合状态。

1
2
3
4
5
#include<sys/select.h>
FD_ZERO(fd_set *fdset); /*清除fdset的所有位*/
FD_SET(int fd, fd_set *fdset); /*设置fdset的位fd*/
FD_CLR(int fd, fd_set *fdset); /*清除fdset的位fd*/
int FD_ISSET(int fd, fd_set *fdset);/*测试fdset的位fd是否被设置*/

select编程思路

最好另外定义一个整型数组,其大小为我们预测将要出现的描述符的最多数目。用作我们存放描述符的容器。初始化时将数组值一律设为-1,表示容器中该位置还没有存放描述符。如果在某一时刻有一个描述符有了消息,我们就将该描述符数值覆盖到这个容器中第一个为-1的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#define MAX 10
void fds_init(int fds[])
{
for(int i = 0;i<MAX;++i)
{
fds[i] = -1;
}
}
void fds_add(int fd, int fds[])//向fds容器中添加描述符fd
{
if(fd<0)
{
printf("无效的描述符\n");
return;
}
for(int i = 0;i<MAX;++i)
{
if(fds[i]==-1)
{
fds[i] = fd;
return;
}
}
printf("容器已满,无法添加该描述符\n");
}
void fds_del(int fd, int fds[])
{
for(int i = 0;i<MAX;++i)
{
if(fds[i]==fd)
{
fds[i] = -1;
return;
}
}
printf("没有找到该描述符\n");
}
int main()
{
int fds[MAX];
fds_init(fds);
}

示例-TCP服务使用select处理多个套接字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
int main()
{
int sockfd = socket_init();//socket_init封装了bind(ip:port)的操作,还封装了对sockfd进行listen的操作,并设置了监听队列大小。
assert(sockfd!=-1);

int fds[MAX];
fds_init(fds);

fds_add(sockfd, fds);

fd_set fdset;//此处的fd_set即<sys/select.h>库中API提供的fd_set结构体,实际上是一个有1024个“二进制位”的数组。
while(1)//将fds[MAX]中所有有效的(即>=0)描述符全部“加入”fdset中,
//即把fdset中与某有效描述符对应的[位]的状态设为1。
{
FD_ZERO(&fdset); //把1024位全清零。
int maxfd = -1;//记录当前最大的描述符数值是多少,方便过后调用select传入参数nfds。
for(int i = 0; i<MAX; ++i)
{
if(fds[i] == -1)
{
continue;
}
FD_SET(fds[i],fdset); //fds[i] != -1, 说明有效
if(maxfd<fds[i])//寻找最大描述符数值
{
maxfd = fds[i];
}
}//end for

//此时,我们已经把开始创建的sockfd添加到了fdset中。
//下面就可以用select来监测该套接字是否有消息了。
//比如,sockfd监听到了客户端的connect信息,
//则select就可以探测到fdset中对应的sockfd位处于消息就绪态,
//则select就可以不再阻塞,立马返回。
struct timeval tv = {5,0};
//返回在fdset集合中有信息的描述符的个数。
int n = select(maxfd+1, &fdset, NULL, NULL, &tv);
if(n < 0)
{
printf("select err\n");
}
else if(n == 0)
{
printf("time out\n");
}
else
{
for(int i = 0; i<MAX; ++i)//依然需要根据fdset查询:
//目前是哪个描述符有事件产生,
//fdset的过滤又需要根据fds的记录进行遍历。
{
if(fds[i] == -1)continue;
if(FD_ISSET(fds[i], &fdset))//此处判断ISSET
//即是判断我们关注的描述符是否有事件产生。
//为什么此时标志位为1一定有事件产生?
//因为在这之前我们进行了select,
//select不仅说明有事件产生,它还做了更多的工作:
//将我们关心的描述符却在其上没有事件产生的标志位置0。
//因此目前所有标志位为1的描述符均有事件。
{
//以下才是核心业务代码,抓住了有事件产生的描述符,
//对这些描述符我们的处理流程,对于不同类型的描述符
//需要不同的处理流程。比如sockfd用accept处理,
//accept返回1个新描述符c,则先将其加入fds容器,
//下一轮再用recv处理描述符c的消息。
if(fds[i] == sockfd)//处理监听套接字sockfd
{
struct sockaddr_in caddr;
int len = sizeof(caddr);
int c = accept(sockfd, (struct sockaddr*)&caddr, &len);
if(c < 0)
{
continue;
}
printf("accept c = %d\n", c);
//此处的fds_add是用户自定义的函数,添加的是fds自定义数组
fds_add(c, fds);//只是收到fds容器,下次while扫描才将c加到fdset
}
else//处理收发套接字,在此程序,除了sockfd皆为收发套接字c
{
char buff[128] = {0};
int num = recv(fds[i], buff, 127, 0);
if(num <= 0)
{
printf("client close\n");
close(fds[i]);
fds_del(fds[i], fds);
}
else//num > 0,读到了数据
{
printf("recv(c = %d) = %s\n",fds[i],buff);
send(fds[i], "ok", 2, 0);
}
}
}//end if(ISSET(fds[i], &fdset))
}//end for(int i = 0; i<MAX; ++i)
}//end if(n > 0)
}//while end
}

场景情况:如果客户端与select服务端已建立连接,而客户端进程结束,select会一直阻塞、未感知吗?–不会。

因为客户端的进程结束,也算是一种读事件,相当于通知服务端该套接字连接结束了。那么服务端recv会返回0,达到关闭该套接字的条件,关闭后,别忘了在fds容器中删除掉该描述符。

如果忘记了close该套接字,且忘了fds_del该描述符,那么如果客户端结束进程,服务端就会一直打印"client close",因为select一直在探测此描述符有无读事件,若该套接字连接关闭,那么此描述符一直有读事件,recv返回0,由于没有fds_del,每次都会关注,所以每次都会打印"client close"。

poll

可以理解为加强版的select。

先观察其API

1
2
#include<poll.h>
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
  1. fds参数是一个pollfd结构类型的指针,可指向一段连续空间(数组),因此很灵活,大小可按需声明。它可以指定我们感兴趣的文件描述符上发生的可读、可写和异常等事件。定义如下

    1
    2
    3
    4
    5
    6
    struct pollfd
    {
    int fd; //文件描述符
    short events; //注册的事件类型,按位标志
    short revents; //实际发生的事件,按位标志,由内核填充
    }
    1. 其中,fd成员指定文件描述符。
    2. events成员告诉poll监听fd上的哪些事件类型,他可以是一系列事件类型的按位或。常见的事件类型有:POLLIN(数据可读)、POLLOUT(数据可写)。
    3. revents成员由内核修改,以通知应用程序fd上实际发生了哪些事件。

poll编程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
void poll_fds_init(struct pollfd* fds)
{
for(int i = 0;i<MAX;++i)
{
fds[i].fd = -1;
fds[i].events = 0;
fds[i].revents = 0;
}
}
void poll_fds_add(int fd, struct pollfd* fds)
{
for(int i = 0;i<MAX;++i)
{
if(fds[i].fd == -1)
{
fds[i].fd = fd;
fds[i].events = POLLIN;//只关注读事件
fds[i].revents = 0;
break;
}
}
}
void poll_fds_del(int fd, struct pollfd* fds)
{
for(int i = 0;i<MAX;++i)
{
if(fds[i].fd == fd)
{
fds[i].fd = -1;
fds[i].events = 0;
fds[i].revents = 0;
break;
}
}
}
#define MAX 10
int main()
{
int sockfd = socket_init();
assert(sockfd != -1);
struct pollfd poll_fds[MAX];
poll_fds_init(poll_fds);
poll_fds_add(sockfd,poll_fds);
while(1)
{
int n = poll(poll_fds,MAX,5000);//5000ms timeout
if(n < 0)printf("poll error\n");
else if(n == 0)printf("time out \n");
else
{
for(int i = 0;i<MAX;++i)
{
if(poll_fds[i].fd == -1)continue;
//short has 16bits,POLLIN is 10000000 ...,
//when revents is 10000000 ...,then the read event is going
if(poll_fds[i].revents & POLLIN)//revents & POLLIN 不为0 则代表有读事件产生
{
if(poll_fds[i].fd == sockfd)
{
struct sockaddr_in caddr;
int len = sizeof(caddr);
int c = accept(sockfd, (struct sockaddr*)&caddr, &len);
if(c < 0)
{
continue;
}
printf("accept:%d\n", c);
poll_fds_add(c, poll_fds);
}
else
{
char buff[128] = {0};
int num = recv(poll_fds[i].fd, buff, 127, 0);
if(num <= 0)
{
close(poll_fds[i].fd);
poll_fds_del(poll_fds[i].fd, poll_fds);
printf("client close\n");
}
else
{
printf("recv(%d):%s\n", poll_fds[i].fd, buff);
send(poll_fds[i].fd, "ok", 2, 0);
}
}

}
if(poll_fds[i].revents & POLLOUT)
{
//...
}
}
}
}
}

与select的一处细节区别:

每次select除了监测fd_set有效描述符上有无事件,其次还将没有事件的描述符从fd_set移除(将该描述符对应在fd_set上的位进行置0操作)(这样就得每次select之前都要重新注册一遍我们关注的描述符(即用户和内核共同操作FD_SET、FD_CLR等)),然后下面过滤有事件的描述符时,只要找到fd_set集合哪个位是1状态即就找到了有事件产生的描述符。

而poll的用法是:用户只管注册events,实际上的有无事件由内核来进行对revents的填充,以此来更好地区别该描述符是否有事件产生。这样,就不用在每次poll之前重新注册一遍我们关注的描述符的结构体里的events。我们只要把要关心的描述符的fd置成非-1,以及管理好要关心的哪些事件类型events即可。

与select相比的优点

  1. 可以监听的描述符的最大数目可以超过1024个,大小按需自拟。
  2. 可以监听的事件类型数目变多、变细了,更强大了。
  3. 不用在每次poll之前重新注册一遍我们关注的描述符的结构体里的events

总结

select和poll的总体实现流程:

  • 用户程序

select用fd_set结构体(默认是1024个bit位的数组);poll用struct pollfd fds[MAX]结构体。

select和poll通常被循环调用。每调用一次,就拷贝一次结构体数组给内核。

linux在系统调用进入内核时,为什么要将参数从用户空间拷贝到内核空间?不能直接访问,或是使用memcpy吗?非要使用copy_from_user才行吗? - 针对为什么select/poll调用一次拷贝一次数组到内核态空间。

  • 内核轮询
    内核对若干个文件描述符进行轮询扫描。查看之上是否有事件发生。
    时间复杂度,为O(n)
  • select/poll返回后
    返回值仅仅告诉用户程序发生了事件的描述符个数,并未告诉具体哪个描述符。
    用户程序还需要再次轮询一遍。O(n)
    select

对象池

内容

  1. 关联关系
  2. 一级配置器和二级配置器的关系
  3. 类型萃取
  4. allocator
  5. __malloc_alloc_template静态成员的类外初始化,其中函数指针尤为麻烦

关联关系

弱关联

1
2
3
4
5
6
7
8
class Person
{
private:
Book * pbook;
public:
Student(){}
~Student(){} //不能析构book
}

强关联

1
2
3
4
5
6
7
class Person
{
private:
Book & book;
public:
Student(Book & book) : _book(book){}
}

聚合/组合关系

1
2
3
4
5
6
7
8
9
10
11
class Point
{
private:
float _x;
float _y;
public:
Point(float x = 0.0f, float y = 0.0f) : _x(x), _y(y){}
Point(const Point&) = default;
Point & operator=(const Point&) = default;
~Point(){}
}

new

  1. sizeof
  2. 分配字节空间
  3. 构建 返回地址

delete

  1. 析构
  2. 空间释放

二级配置器

  • 一级配置器的问题
    • 直接的malloc后的数据上下各有一个越界标记。上越界标记之外还有信息,包括申请了多少字节,还有next域、prev域以便连接到链表中进行内存管理。除此之外可能还有有效/失效(是否释放)的标记。
    • 除了包装信息占空间较多之外,还有malloc时花费的时间也多。这样就造成对于比较小的对象数据在malloc时入不敷出。
    • 结论就是:对于较小数据,适用于内存池来进行内存管理。
  • 对于客户端申请较小空间(128bytes)的具体处理流程
    1. 每次配置一大块内存,并维护对应的自由链表(free-list)
    2. 下次如果再有相同大小的内存需求,直接从free-list中取出若干小块。
    3. 如果客户端释放小块内存,就由配置器回收到free-list中。所以配置器除了分配空间,还负责回收空间。
  • 为了方便管理,SGI第二级配置器会主动按任何小块内存的内存需求量计算成8的倍数。(比如客户端要求30bytes,则计算为32bytes。
  • 共维护16个free-lists,各自管理大小分别为8、16、24、32、40、48、56、64、72、80、88、96、104、112、120、128的小额区块。
  • free-lists的单结点结构如下:
1
2
3
4
5
union obj
{
union obj * free_list_link;
char client_data[1]; //the client sees this
}

总览

1
2
3
enum { __ALIGN = 8};
enum { __MAX_BYTES = 128};
enum { __NFREELISTS = __MAX_BYTES / __ALIGN}; // 128 / 8 = 16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template<bool threads, int inst>	//参数1:是否考虑多线程,参数2:标识
class __default_alloc_template
{
private:
union obj /* 只是类型定义 */
{
union obj * free_list_link; //next
char client_data[1];
};
private:
static obj * volatile free_list[__NFREELISTS];//volatile修饰obj*,即free_list数组中对于的数据内容。
static char* start_free;//每次配置的一大块'内存' 的内存来源的剩余部分的起始
static char* end_free; //每次配置的一大块'内存' 的内存来源的剩余部分的末尾
static size_t heap_size;//上面提到的都是内存来源,而这个内存来源都是从堆区申请的,堆区申请的总空间数要记录在案
static size_t ROUNT_UP(size_t bytes);
static size_t FREELIST_INDEX(size_t bytes); //hash
static char* chunk_alloc(size_t size, int & nobjs);//size - 对象的大小,nobjs对象的个数
static void* refill(size_t size); //重新填充
public: /* 用户调用 */
static void* allocate(size_t size);
static void deallocate(void* p, size_t n);
static void* reallocate(void* p, size_t old_sz, size_t new_sz);
};

成员初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
// 对free_list[__NFREELISTS]进行初始化。
template<bool threads, int inst> // 1: __default_alloc_template是模板类,需要声明
typename __default_alloc_template<threads, inst>::obj * volatile // 2: obj是__default_alloc_template<threads, inst>类内的类,需要加上“哪个类”,并且要加上"typename"以示"obj"是个类名,而非成员。
__default_alloc_template<threads, inst>::free_list[__NFREELISTS] = {}; // 3: free_list[__NFREELISTS]是__default_alloc_template<threads, inst>类内的成员,需要加上“哪个类”,但无需加"typename"。
// 对start_free进行初始化
template<bool threads, int inst>
char* __default_alloc_template<threads, inst>::start_free = nullptr;
// 对end_free进行初始化
template<bool threads, int inst>
char* __default_alloc_template<threads, inst>::end_free = nullptr;
// 对heap_size进行初始化
template<bool threads, int inst>
size_t __default_alloc_template<threads, inst>::heap_size = 0;

函数实现

准备工作

1
using malloc_alloc = __malloc_alloc_template<0>;

allocate

相当于单链表的头删法,返回删了的节点的地址给用户用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void * allocate(size_t size)
{
if(size > (size_t)__MAX_BYTES)
{
return malloc_alloc::allocate(size); //转给一级配置器。
}
obj * result = nullptr;
obj * volatile * my_free_list = nullptr; //是二级指针,要指向obj*
my_free_list = free_list + FREELIST_INDEX(size);
result = *my_free_list;
if(nullptr == result) //说明此下标处无内存块了,需要申请。
{
void * r = refill(ROUND_UP(size));
return r;
}
*my_free_list = result->free_list_link; //把free_list此下标的指针更新到下一个节点。相当于链表的头删法。
return result;
}

refill

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static void * refill(size_t size)
{
int nobjs = 20;
char * chunk = chunk_alloc(size, nobjs);
//chunk_alloc运行之后,nobjs值可能发生改变。
if(1 == nobjs)
{
//如果nobjs是1,则说明内存块正好用完,不用再处理后续内存区域的链接、移动
return chunk;
}

//接下来,把内存块分崩离析,依次串联。
obj * volatile * my_free_list = NULL;
obj * result = (obj*)chunk;
obj * current_obj = NULL, * next_obj = NULL;
my_free_list = free_list + FREELIST_INDEX(size);
*my_free_list = next_obj = (obj*)(chunk+size);
for(int i = 1; ; ++i)
{
current_obj = next_obj;
next_obj = (obj*)((char*)next_obj + size);
if(i == nobjs-1)//最后一块
{
current_obj->free_list_link = NULL;
break;
}
current_obj->free_list_link = next_obj;
}
return result;
}

chunk_alloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
static char* chunk_alloc(size_t size, int & nobjs)
{
char * result = NULL;
size_t total_bytes = size * nobjs;
size_t bytes_left = end_free - start_free;
if(bytes_left >= total_bytes)
{
result = start_free;
start_free = start_free + total_bytes;
return result;
}
else if(bytes_left >= size)
{
nobjs = bytes_left / size;
total = size * nobjs;

result = start_free;
start_free = start_free + total_bytes;
return result;
}
else //内存不足以分配1个对象,把剩余的插入到其他下标(剩8字节插到0, 16字节插到1, ...),然后再另外申请一个大的
{
size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
if(bytes_left > 0)
{
obj * volatile * my_free_list = free_list + FREELIST_INDEX(bytes_left);
//头插
((obj*)start_free)->free_list_link = *my_free_list;
*my_free_list = (obj*)start_free;
}
//剩余的残存空间 处理完毕,接下来申请大块内存
start_free = (char*)malloc(bytes_to_get);
if(NULL == start_free) //堆区居然没有资源了!无奈之计,只能向比他之后的链表找有无剩余块。
{
obj * volatile * my_free_list = NULL;
for(int i = size; i <= __MAX_BYTES; i += __ALIGN) //注意,i从size开始,而不一定从最小的块大小8开始,因为找比你小的没有用处。得找大的。
{
my_free_list = free_list + FREELIST_INDEX(i);
obj * p = NULL;
if(NULL != p) //找到了比他大的的剩余的了!
{
*my_free_list = (*my_free_list)->free_list_link;
start_free = (char*)p;
end_free = start_free + i;
return chunk_alloc(size, nobjs);
}
}
//如果上面的for循环没能使得其找到合适的内存块,则说明真的是“山穷水尽疑无路”了,只能转1级配置器,抛出异常或者通过handler处理。
start_free = malloc_alloc::allocate(bytes_to_get);
}
end_free = start_free + bytes_to_get;
heap_size += bytes_to_get;
return chunk_alloc(size, nobjs); //递归此函数,相当于重新来一遍流程
}
}

ROUND_UP & INDEX

1
2
3
4
5
6
7
8
9
10
static size_t ROUNT_UP(size_t bytes)
{
// 0 -> 0, 1~8 -> 8, 9~16 - > 16, 17~24 -> 24, ...
return (bytes+ __ALIGN-1) & ~(__ALIGN - 1);
}
static size_t FREELIST_INDEX(size_t bytes)
{
// 1~8 -> 0, 9~16 -> 1, 17~24 -> 2, ...
return ((bytes+ __ALIGN-1) / __ALIGN) - 1;
}

测试allocate

1
2
3
4
5
6
#ifdef __USE_MALLOC
typedef __malloc_alloc_template<0> malloc_alloc;
typedef malloc_alloc alloc;
#else
typedef __default_alloc_template<0, 0> alloc;
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template<class T, class Alloc>
class simple_alloc
{
public:
static T* allocate(size_t n) //申请n个T对象
{
return Alloc::allocate(sizeof(T)*n);
}
static T* allocate()
{
return Alloc::allocate(sizeof(T));
}
void deallocate(T *p, size_t n)
{
if(NULL == p)return;
Alloc::deallocate(p, sizeof(T)*n);
}
void deallocate(T* p)
{
if(NULL == p)return;
Alloc::deallocate(p, sizeof(T));
}
};

引入list进行测试

1
2
3
4
5
6
7
8
9
10
11
12
#include"my_list.h"
using namespace std;
int main()
{
xcg::my_list<char> mylist;

for(int i = 0; i < 23; ++i)
{
mylist.push_back(i + 'a');
}
return 0;
}

list构建流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
template<class _Ty, class _A = xcg::alloc>
class my_list
{
public:
typedef _Ty value_type;
typedef _Ty& reference;
typedef _Ty* pointer;
typedef const _Ty& const_reference;
typedef const _Ty* const_pointer;

typedef _A allocator_type;
typedef xcg::simple_alloc<_Node, _A> data_allocate; //simple_alloc中使用的是第二个参数的allocate。在这里即使用_A::allocate,默认参数是xcg::alloc,而根据开关语句,若是使用malloc_alloc则为一级配置器,若为default_alloc则用二级配置器。我们默认使用的是__default_alloc_template<0, 0>。

public:
my_list() : _Head(_Buynode()), _Size(0) {}
protected:
_Nodeptr _Buynode(_Nodeptr _parg = NULL, _Nodeptr _narg = NULL)
{
_Nodeptr _S = data_allocate::allocate();
_Acc::_Prev(_S) = _parg == NULL ? _S : _parg;
_Acc::_Next(_S) = _narg == NULL ? _S : _narg;
return _S;
}
private:
_Nodeptr _Head;
size_t _Size;
};

deallocate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void deallocate(void * p, size_t size)	// n 表示空间大小
{
if(size > (size_t)__MAX_BYTES)
{
malloc_alloc::deallocate(p, size);
return;
}
//归还给配置器
obj * q = (obj*)p;
obj * volatile * my_free_list = free_list + FREELIST_INDEX(size);
//头插
q->free_list_link = *my_free_list;
*my_free_list = q;
return;
}

测试deallocate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
iterator erase(iterator _P)
{
_Nodeptr _S = _P++._Mynode();
_Acc::_Next(_Acc::_Prev(_S)) = _Acc::_Next(_S);
_Acc::_Prev(_Acc::_Next(_S)) = _Acc::_Prev(_S);
destroy(&_Acc::_Value(_S)); //不删除节点,而是释放value域。
_Freenode(_);

return _P;
}
void _Freenode(_Nodeptr _P)
{
data_allocate::deallocate(_P);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Object
{
private:
int value;
public:
Object(int x = 0) : value(x)
{
cout << "create object" << this << endl;
}
~Object()
{
cout << "destroy object" << this << endl;
}
};
int main()
{
xcg::my_list<Object> objlist;
for(int i = 0; i < 10; ++i)
{
objlist.push_back(Object(i));
}
objlist.pop_back();
return 0;
}

reallocate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void* reallocate(void * p, size_t old_sz, size_t new_sz)
{
if(old_sz > (size_t)__MAX_BYTES && new_sz > (size_t)__MAX_BYTES)
{
return malloc_alloc::reallocate(p, old_sz, new_sz);
}
// old_sz == 20, new_sz == 22 -->24, 24
if(ROUND_UP(old_sz) == ROUND_UP(new_sz))
{
return p;
}
// old_sz > 128, new_sz < 128
// old_sz < 128, new_sz < 128
// old_sz < 128, new_sz > 128
size_t sz = old_sz < new_sz ? old_sz : new_sz;
void * s = allocate(new_sz);
memmove(s, p ,sz);
deallocate(p, old, sz);
return s;
}