aboutsummaryrefslogtreecommitdiffstats
path: root/docs/misc/tmem-internals.html
blob: ccc34c374f8c04e09ade946060a23c817ee80147 (plain)
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
<h1>Transcendent Memory Internals in Xen</h1>
<P>
by Dan Magenheimer, Oracle Corp.</p>
<P>
Draft 0.1 -- Updated: 20100324
<h2>Overview</h2>
<P>
This document focuses on the internal implementation of
Transcendent Memory (tmem) on Xen.  It assumes
that the reader has a basic knowledge of the terminology, objectives, and
functionality of tmem and also has access to the Xen source code.
It corresponds to the Xen 4.0 release, with
patch added to support page deduplication (V2).
<P>
The primary responsibilities of the tmem implementation are to:
<ul>
<li>manage a potentially huge and extremely dynamic
number of memory pages from a potentially large number of clients (domains)
with low memory overhead and proper isolation
<li>provide quick and efficient access to these
pages with as much concurrency as possible
<li>enable efficient reclamation and <i>eviction</i> of pages (e.g. when
memory is fully utilized)
<li>optionally, increase page density through compression and/or
deduplication
<li>where necessary, properly assign and account for
memory belonging to guests to avoid malicious and/or accidental unfairness
and/or denial-of-service
<li>record utilization statistics and make them available to management tools
</ul>
<h2>Source Code Organization</h2>

<P>
The source code in Xen that provides the tmem functionality
is divided up into four files: tmem.c, tmem.h, tmem_xen.c, and tmem_xen.h.
The files tmem.c and tmem.h are intended to
be implementation- (and hypervisor-) independent and the other two files
provide the Xen-specific code.  This
division is intended to make it easier to port tmem functionality to other
hypervisors, though at this time porting to other hypervisors has not been
attempted.  Together, these four files
total less than 4000 lines of C code.
<P>
Even ignoring the implementation-specific functionality, the
implementation-independent part of tmem has several dependencies on
library functionality (Xen source filenames in parentheses):
<ul>
<li>
a good fast general-purpose dynamic memory
allocator with bounded response time and efficient use of memory for a very
large number of sub-page allocations.  To
achieve this in Xen, the bad old memory allocator was replaced with a
slightly-modified version of TLSF (xmalloc_tlsf.c), first ported to Linux by
Nitin Gupta for compcache.
<li>
good tree data structure libraries, specifically
<i>red-black</i> trees (rbtree.c) and <i>radix</i> trees (radix-tree.c).
Code for these was borrowed for Linux and adapted for tmem and Xen.
<li>
good locking and list code.  Both of these existed in Xen and required
little or no change.
<li>
optionally, a good fast lossless compression
library.  The Xen implementation added to
support tmem uses LZO1X (lzo.c), also ported for Linux by Nitin Gupta.
</ul>
<P>
More information about the specific functionality of these
libraries can easily be found through a search engine, via wikipedia, or in the
Xen or Linux source logs so we will not elaborate further here.

<h2>Prefixes/Abbreviations/Glossary</h2>

<P>
The tmem code uses several prefixes and abbreviations.
Knowledge of these will improve code readability:
<ul>
<li>
<i>tmh</i> ==
transcendent memory host.  Functions or
data structures that are defined by the implementation-specific code, i.e. the
Xen host code
<li>
<i>tmemc</i>
== transcendent memory control.
Functions or data structures that provide management tool functionality,
rather than core tmem operations.
<li>
<i>cli </i>or
<i>client</i> == client.
The tmem generic term for a domain or a guest OS.
</ul>
<P>
When used in prose, common tmem operations are indicated
with a different font, such as <big><kbd>put</kbd></big>
and <big><kbd>get</kbd></big>.

<h2>Key Data Structures</h2>

<P>
To manage a huge number of pages, efficient data structures
must be carefully selected.
<P>
Recall that a tmem-enabled guest OS may create one or more
pools with different attributes.  It then
<kbd>put</kbd></big>s and <kbd>get</kbd></big>s
pages to/from this pool, identifying the page
with a <i>handle</i> that consists of a <i>pool_id</i>, an <i>
object_id</i>, and a <i>page_id </i>(sometimes
called an <i>index</i>).
This suggests a few obvious core data
structures:
<ul>
<li>
When a guest OS first calls tmem, a <i>client_t</i> is created to contain
and track all uses of tmem by that guest OS.  Among
other things, a <i>client_t</i> keeps pointers
to a fixed number of pools (16 in the current Xen implementation).
<li>
When a guest OS requests a new pool, a <i>pool_t</i> is created.
Some pools are shared and are kept in a
sharelist (<i>sharelist_t</i>) which points
to all the clients that are sharing the pool.
Since an <i>object_id</i> is 64-bits,
a <i>pool_t</i> must be able to keep track
of a potentially very large number of objects.
To do so, it maintains a number of parallel trees (256 in the current
Xen implementation) and a hash algorithm is applied to the <i>object_id</i>
to select the correct tree.
Each tree element points to an object.
Because an <i>object_id</i> usually represents an <i>inode</i>
(a unique file number identifier), and <i>inode</i> numbers
are fairly random, though often &quot;clumpy&quot;, a <i>red-black tree</i>
is used.
<li>
When a guest first
<kbd>put</kbd></big>s a page to a pool with an as-yet-unused <i>object_id,</i> an
<i>obj_t</i> is created.  Since a <i
>page_id</i> is usually an index into a file,
it is often a small number, but may sometimes be very large (up to
32-bits).  A <i>radix tree</i> is a good data structure to contain items
with this kind of index distribution.
<li>
When a page is
<kbd>put</kbd></big>, a page descriptor, or <i>pgp_t</i>, is created, which
among other things will point to the storage location where the data is kept.
In the normal case the pointer is to a <i>pfp_t</i>, which is an
implementation-specific datatype representing a physical pageframe in memory
(which in Xen is a &quot;struct page_info&quot;).
When deduplication is enabled, it points to
yet another data structure, a <i>pcd_</i>t
(see below).  When compression is enabled
(and deduplication is not), the pointer points directly to the compressed data.
For reasons we will see shortly, each <i>pgp_t</i> that represents
an <i>ephemeral</i> page (that is, a page placed
in an <i>ephemeral</i> pool) is also placed
into two doubly-linked linked lists, one containing all ephemeral pages
<kbd>put</kbd></big> by the same client and one
containing all ephemeral pages across all clients (&quot;global&quot;).
<li>
When deduplication is enabled, multiple <i>pgp_</i>t's may need to point to
the same data, so another data structure (and level of indirection) is used
called a page content descriptor, or <i>pcd_t</i>.
Multiple page descriptors (<i>pgp_t</i>'s) may point to the same <i>pcd_t</i>.
The <i>pcd_t</i>, in turn, points to either a <i>pfp_t</i>
(if a full page of data), directly to a
location in memory (if the page has been compressed or trailing zeroes have
been eliminated), or even a NULL pointer (if the page contained all zeroes and
trailing zero elimination is enabled).
</ul>
<P>
The most apparent usage of this multi-layer web of data structures
is &quot;top-down&quot; because, in normal operation, the vast majority of tmem
operations invoked by a client are
<kbd>put</kbd></big>s and <kbd>get</kbd></big>s, which require the various
data structures to be walked starting with the <i>client_t</i>, then
a <i>pool_t</i>, then an <i>obj_t</i>, then a <i>pgd_t</i>.
However, there is another highly frequent tmem operation that is not
visible from a client: memory reclamation.
Since tmem attempts to use all spare memory in the system, it must
frequently free up, or <i>evict</i>,
pages.  The eviction algorithm will be
explained in more detail later but, in brief, to free memory, ephemeral pages
are removed from the tail of one of the doubly-linked lists, which means that
all of the data structures associated with that page-to-be-removed must be
updated or eliminated and freed.  As a
result, each data structure also contains a <i>back-pointer</i>
to its parent, for example every <i>obj_t</i>
contains a pointer to its containing <i>pool_t</i>.
<P>
This complex web of interconnected data structures is updated constantly and
thus extremely sensitive to careless code changes which, for example, may
result in unexpected hypervisor crashes or non-obvious memory leaks.
On the other hand, the code is fairly well
modularized so, once understood, it is possible to relatively easily switch out
one kind of data structure for another.
To catch problems as quickly as possible when debug is enabled, most of
the data structures are equipped with <i>sentinels</i>and many inter-function
assumptions are documented and tested dynamically
with <i>assertions</i>.
While these clutter and lengthen the tmem
code substantially, their presence has proven invaluable on many occasions.
<P>
For completeness, we should also describe a key data structure in the Xen
implementation-dependent code: the <i>tmh_page_list</i>. For security and
performance reasons, pages that are freed due to tmem operations (such
as <kbd>get</kbd></big>) are not immediately put back into Xen's pool
of free memory (aka the Xen <i>heap</i>).
Tmem pages may contain guest-private data that must be <i>scrubbed</i> before
those memory pages are released for the use of other guests.
But if a page is immediately re-used inside of tmem itself, the entire
page is overwritten with new data, so need not be scrubbed.
Since tmem is usually the most frequent
customer of the Xen heap allocation code, it would be a waste of time to scrub
a page, release it to the Xen heap, and then immediately re-allocate it
again.  So, instead, tmem maintains
currently-unused pages of memory on its own free list, <i>tmh_page_list</i>,
and returns the pages to Xen only when non-tmem Xen
heap allocation requests would otherwise fail.

<h2>Scalablility/Concurrency</h2>

<P>Tmem has been designed to be highly scalable.
Since tmem access is invoked similarly in
many ways to asynchronous disk access, a &quot;big SMP&quot; tmem-aware guest
OS can, and often will, invoke tmem hypercalls simultaneously on many different
physical CPUs.  And, of course, multiple
tmem-aware guests may independently and simultaneously invoke tmem
hypercalls.  While the normal frequency
of tmem invocations is rarely extremely high, some tmem operations such as data
compression or lookups in a very large tree may take tens of thousands of
cycles or more to complete.  Measurements
have shown that normal workloads spend no more than about 0.2% (2% with
compression enabled) of CPU time executing tmem operations.
But those familiar with OS scalability issues
recognize that even this limited execution time can create concurrency problems
in large systems and result in poorly-scalable performance.
<P>
A good locking strategy is critical to concurrency, but also
must be designed carefully to avoid deadlock and <i>livelock</i> problems.  For
debugging purposes, tmem supports a &quot;big kernel lock&quot; which disables
concurrency altogether (enabled in Xen with &quot;tmem_lock&quot;, but note
that this functionality is rarely tested and likely has bit-rotted). Infrequent
but invasive tmem hypercalls, such as pool creation or the control operations,
are serialized on a single <i>read-write lock</i>, called tmem_rwlock,
which must be held for writing.  All other tmem operations must hold this lock
for reading, so frequent operations such as
<kbd>put</kbd></big> and <kbd>get</kbd></big> <kbd>flush</kbd></big> can execute simultaneously
as long as no invasive operations are occurring.
<P>
Once a pool has been selected, there is a per-pool
read-write lock (<i>pool_rwlock</i>) which
must be held for writing if any transformative operations might occur within
that pool, such as when an<i> obj_t</i> is
created or destroyed.  For the highly
frequent operation of finding an<i> obj_t</i>
within a pool, pool_rwlock must be held for reading.
<P>
Once an object has been selected, there is a per-object
spinlock (<i>obj_spinlock)</i>.
This is a spinlock rather than a read-write
lock because nearly all of the most frequent tmem operations (e.g.
<kbd>put</kbd></big> and <kbd>get</kbd></big> <kbd>flush</kbd></big>)
are transformative, in
that they add or remove a page within the object.
This lock is generally taken whenever an
object lookup occurs and released when the tmem operation is complete.
<P>
Next, the per-client and global ephemeral lists are
protected by a single global spinlock (<i>eph_lists_</i>spinlock)
and the per-client persistent lists are also protected by a single global
spinlock (<i>pers_list_spinlock</i>).
And to complete the description of
implementation-independent locks, if page deduplication is enabled, all pages
for which the first byte match are contained in one of 256 trees that are
protected by one of 256 corresponding read-write locks
(<i>pcd_tree_rwlocks</i>).
<P>
In the Xen-specific code (tmem_xen.c), page frames (e.g.  struct page_info)
that have been released are kept in a list (<i>tmh_page_list</i>) that
is protected by a spinlock (<i>tmh_page_list_lock</i>).
There is also an &quot;implied&quot; lock
associated with compression, which is likely the most time-consuming operation
in all of tmem (of course, only when compression is enabled): A compression
buffer is allocated one-per-physical-cpu early in Xen boot and a pointer to
this buffer is returned to implementation-independent code and used without a
lock.
<P>
The proper method to avoid deadlocks is to take and release
locks in a very specific predetermined order.
Unfortunately, since tmem data structures must simultaneously be
accessed &quot;top-down&quot; (
<kbd>put</kbd></big> and <kbd>get</kbd></big>)
and &quot;bottoms-up&quot;
(memory reclamation), more complex methods must be employed:
A <i>trylock</i>mechanism is used (c.f. <i>tmem_try_to_evict_pgp()</i>),
which takes the lock if it is available but returns immediately (rather than
spinning and waiting) if the lock is not available.
When walking the ephemeral list to identify
pages to free, any page that belongs to an object that is locked is simply
skipped.  Further, if the page is the
last page belonging to an object, and the pool read-write lock for the pool the
object belongs to is not available (for writing), that object is skipped.
These constraints modify the LRU algorithm
somewhat, but avoid the potential for deadlock.
<P>
Unfortunately, a livelock was still discovered in this approach:
When memory is scarce and each client is
<kbd>put</kbd></big>ting a large number of pages
for exactly one object (and thus holding the object spinlock for that object),
memory reclamation takes a very long time to determine that it is unable to
free any pages, and so the time to do a
<kbd>put</kbd></big> (which eventually fails) becomes linear to the
number of pages in the object!  To avoid
this situation, a workaround was added to always ensure a minimum amount of
memory (1MB) is available before any object lock is taken for the client
invoking tmem (see <i>tmem_ensure_avail_pages()</i>).
Other such livelocks (and perhaps deadlocks)
may be lurking.
<P>
A last issue related to concurrency is atomicity of counters.
Tmem gathers a large number of
statistics.  Some of these counters are
informational only, while some are critical to tmem operation and must be
incremented and decremented atomically to ensure, for example, that the number
of pages in a tree never goes negative if two concurrent tmem operations access
the counter exactly simultaneously.  Some
of the atomic counters are used for debugging (in assertions) and perhaps need
not be atomic; fixing these may increase performance slightly by reducing
cache-coherency traffic.  Similarly, some
of the non-atomic counters may yield strange results to management tools, such
as showing the total number of successful
<kbd>put</kbd></big>s as being higher than the number of
<kbd>put</kbd></big>s attempted.
These are left as exercises for future tmem implementors.

<h2>Control and Manageability</h2>

<P>
Tmem has a control interface to, for example, set various
parameters and obtain statistics.  All
tmem control operations funnel through <i>do_tmem_control()</i>
and other functions supporting tmem control operations are prefixed
with <i>tmemc_</i>.

<P>
During normal operation, even if only one tmem-aware guest
is running, tmem may absorb nearly all free memory in the system for its own
use.  Then if a management tool wishes to
create a new guest (or migrate a guest from another system to this one), it may
notice that there is insufficient &quot;free&quot; memory and fail the creation
(or migration).  For this reason, tmem
introduces a new tool-visible class of memory -- <i>freeable</i> memory --
and provides a control interface to access
it.  All ephemeral memory and all pages on the <i>tmh_page_list</i>
are freeable. To properly access freeable
memory, a management tool must follow a sequence of steps:
<ul>
<li>
<i>freeze</i>
tmem:When tmem is frozen, all 
<kbd>put</kbd></big>s fail, which ensures that no
additional memory may be absorbed by tmem.
(See <i>tmemc_freeze_pools()</i>, and
note that individual clients may be frozen, though this functionality may be
used only rarely.)
<li>
<i>query freeable MB: </i>If all freeable memory were released to the Xen
heap, this is the amount of memory (in MB) that would be freed.
See <i>tmh_freeable_pages()</i>.
<li>
<i>flush</i>:
Tmem may be requested to flush, or relinquish, a certain amount of memory, e.g.
back to the Xen heap.  This amount is
specified in KB.  See <i
>tmemc_flush_mem()</i> and <i
>tmem_relinquish_npages()</i>.
<li>
At this point the management tool may allocate
the memory, e.g. using Xen's published interfaces.
<li>
<i>thaw</i>
tmem: This terminates the freeze, allowing tmem to accept 
<kbd>put</kbd></big>s again.
</ul>
<P>
Extensive tmem statistics are available through tmem's
control interface (see <i>tmemc_list </i>and
the separate source for the &quot;xm tmem-list&quot; command and the
xen-tmem-list-parse tool).  To maximize
forward/backward compatibility with future tmem and tools versions, statistical
information is passed via an ASCII interface where each individual counter is
identified by an easily parseable two-letter ASCII sequence.

<h2>Save/Restore/Migrate</h2>

<P>
Another piece of functionality that has a major impact on
the tmem code is support for save/restore of a tmem client and, highly related,
live migration of a tmem client.
Ephemeral pages, by definition, do not need to be saved or
live-migrated, but persistent pages are part of the state of a running VM and
so must be properly preserved.
<P>
When a save (or live-migrate) of a tmem-enabled VM is initiated, the first step
is for the tmem client to be frozen (see the manageability section).
Next, tmem API version information is
recorded (to avoid possible incompatibility issues as the tmem spec evolves in
the future).  Then, certain high-level
tmem structural information specific to the client is recorded, including
information about the existing pools.
Finally, the contents of all persistent pages are recorded.
<P>
For live-migration, the process is somewhat more complicated.
Ignoring tmem for a moment, recall that in
live migration, the vast majority of the VM's memory is transferred while the
VM is still fully operational.  During
each phase, memory pages belonging to the VM that are changed are marked and
then retransmitted during a later phase.
Eventually only a small amount of memory remains, the VM is paused, the
remaining memory is transmitted, and the VM is unpaused on the target machine.
<P>
The number of persistent tmem pages may be quite large,
possibly even larger than all the other memory used by the VM; so it is
unacceptable to transmit persistent tmem pages during the &quot;paused&quot;
phase of live migration.  But if the VM
is still operational, it may be making calls to tmem:
A frozen tmem client will reject any 
<big><kbd>put</kbd></big> operations, but tmem must
still correctly process <big><kbd>flush</kbd></big>es
(page and object), including implicit flushes due to duplicate 
<big><kbd>put</kbd></big>s.
Fortunately, these operations can only
invalidate tmem pages, not overwrite tmem pages or create new pages.
So, when a live-migrate has been initiated,
the client is frozen.  Then during the
&quot;live&quot; phase, tmem transmits all persistent pages, but also records
the handle of all persistent pages that are invalidated.
Then, during the &quot;paused&quot; phase,
only the handles of invalidated persistent pages are transmitted, resulting in
the invalidation on the target machine of any matching pages that were
previously transmitted during the &quot;live&quot; phase.
<P>
For restore (and on the target machine of a live migration),
tmem must be capable of reconstructing the internal state of the client from
the saved/migrated data.  However, it is
not the client itself that is <big><kbd>put</kbd></big>'ing
the pages but the management tools conducting the restore/migration.
This slightly complicates tmem by requiring
new API calls and new functions in the implementation, but the code is
structured so that duplication is minimized.
Once all tmem data structures for the client are reconstructed, all
persistent pages are recreated and, in the case of live-migration, all
invalidations have been processed and the client has been thawed, the restored
client can be resumed.
<P>
Finally, tmem's data structures must be cluttered a bit to
support save/restore/migration.  Notably,
a per-pool list of persistent pages must be maintained and, during live
migration, a per-client list of invalidated pages must be logged.
A reader of the code will note that these
lists are overlaid into space-sensitive data structures as a union, which may
be more error-prone but eliminates significant space waste.

<h2>Miscellaneous Tmem Topics</h2>

<P>
<i><b>Duplicate <big><kbd>puts</kbd></big></b></i>.
One interesting corner case that
significantly complicates the tmem source code is the possibility
of a <i>duplicate</i>
<big><kbd>put</kbd></big>,
which occurs when two
<big><kbd>put</kbd></big>s
are requested with the same handle but with possibly different data.
The tmem API addresses
<i>
<big><kbd>put</kbd></big>-<big><kbd>put</kbd></big>-<big><kbd>get</kbd></big>
coherence</i> explicitly: When a duplicate
<big><kbd>put</kbd></big> occurs, tmem may react one of two ways: (1) The 
<big><kbd>put</kbd></big> may succeed with the old
data overwritten by the new data, or (2) the
<big><kbd>put</kbd></big> may be failed with the original data flushed and
neither the old nor the new data accessible.
Tmem may <i>not</i> fail the 
<big><kbd>put</kbd></big> and leave the old data accessible.
<P>
When tmem has been actively working for an extended period,
system memory may be in short supply and it is possible for a memory allocation
for a page (or even a data structure such as a <i>pgd_t</i>) to fail. Thus,
for a duplicate 
<big><kbd>put</kbd></big>, it may be impossible for tmem to temporarily
simultaneously maintain data structures and data for both the original 
<big><kbd>put</kbd></big> and the duplicate 
<big><kbd>put</kbd></big>.
When the space required for the data is
identical, tmem may be able to overwrite <i>in place </i>the old data with
the new data (option 1).  But in some circumstances, such as when data
is being compressed, overwriting is not always possible and option 2 must be
performed.
<P>
<i><b>Page deduplication and trailing-zero elimination.</b></i>
When page deduplication is enabled
(&quot;tmem_dedup&quot; option to Xen), ephemeral pages for which the contents
are identical -- whether the pages belong
to the same client or different clients -- utilize the same pageframe of
memory.  In Xen environments where
multiple domains have a highly similar workload, this can save a substantial
amount of memory, allowing a much larger number of ephemeral pages to be
used.  Tmem page deduplication uses
methods similar to the KSM implementation in Linux [ref], but differences between
the two are sufficiently great that tmem does not directly leverage the
code.  In particular, ephemeral pages in
tmem are never dirtied, so need never be <i>copied-on-write</i>.
Like KSM, however, tmem avoids hashing,
instead employing <i>red-black trees</i>
that use the entire page contents as the <i>lookup
key</i>.  There may be better ways to implement this.
<P>
Dedup'ed pages may optionally be compressed
(&quot;tmem_compress&quot; and &quot;tmem_dedup&quot; Xen options specified),
to save even more space, at the cost of more time.
Additionally, <i>trailing zero elimination (tze)</i> may be applied to dedup'ed
pages.  With tze, pages that contain a
significant number of zeroes at the end of the page are saved without the trailing
zeroes; an all-zero page requires no data to be saved at all.
In certain workloads that utilize a large number
of small files (and for which the last partial page of a file is padded with
zeroes), a significant space savings can be realized without the high cost of
compression/decompression.
<P>
Both compression and tze significantly complicate memory
allocation.  This will be discussed more below.
<P>
<b><i>Memory accounting</i>.</b>
Accounting is boring, but poor accounting may
result in some interesting problems.  In
the implementation-independent code of tmem, most data structures, page frames,
and partial pages (e.g. for compresssion) are <i>billed</i> to a pool,
and thus to a client.  Some <i>infrastructure</i> data structures, such as
pools and clients, are allocated with <i>tmh_alloc_infra()</i>, which does not
require a pool to be specified.  Two other
exceptions are page content descriptors (<i>pcd_t</i>)
and sharelists (<i>sharelist_t</i>) which
are explicitly not associated with a pool/client by specifying NULL instead of
a <i>pool_t</i>.
(Note to self:
These should probably just use the <i>tmh_alloc_infra()</i> interface too.)
As we shall see, persistent pool pages and
data structures may need to be handled a bit differently, so the
implementation-independent layer calls a different allocation/free routine for
persistent pages (e.g. <i>tmh_alloc_page_thispool()</i>)
than for ephemeral pages (e.g. <i>tmh_alloc_page()</i>).
<P>
In the Xen-specific layer, we
disregard the <i>pool_t</i> for ephemeral
pages, as we use the generic Xen heap for all ephemeral pages and data
structures.(Denial-of-service attacks
can be handled in the implementation-independent layer because ephemeral pages
are kept in per-client queues each with a counted length.
See the discussion on weights and caps below.)
However we explicitly bill persistent pages
and data structures against the client/domain that is using them.
(See the calls to the Xen routine <i>alloc_domheap_pages() </i>in tmem_xen.h; of
the first argument is a domain, the pages allocated are billed by Xen to that
domain.)This means that a Xen domain
cannot allocate even a single tmem persistent page when it is currently utilizing
its maximum assigned memory allocation!
This is reasonable for persistent pages because, even though the data is
not directly accessible by the domain, the data is permanently saved until
either the domain flushes it or the domain dies.
<P>
Note that proper accounting requires (even for ephemeral pools) that the same
pool is referenced when memory is freed as when it was allocated, even if the
ownership of a pool has been moved from one client to another (c.f. <i
>shared_pool_reassign()</i>).
The underlying Xen-specific information may
not always enforce this for ephemeral pools, but incorrect alloc/free matching
can cause some difficult-to-find memory leaks and bent pointers.
<P>
Page deduplication is not possible for persistent pools for
accounting reasons: Imagine a page that is created by persistent pool A, which
belongs to a domain that is currently well under its maximum allocation.
Then the <i>pcd_t</i>is matched by persistent pool B, which is
currently at its maximum.
Then the domain owning pool A is destroyed.
Is B beyond its maximum?
(There may be a clever way around this
problem.  Exercise for the reader!)
<P>
<b><i>Memory allocation.</i></b> The implementation-independent layer assumes
there is a good fast general-purpose dynamic memory allocator with bounded
response time and efficient use of memory for a very large number of sub-page
allocations.  The old xmalloc memory
allocator in Xen was not a good match for this purpose, so was replaced by the
TLSF allocator.  Note that the TLSF
allocator is used only for allocations smaller than a page (and, more
precisely, no larger than <i>tmem_subpage_maxsize()</i>);
full pages are allocated by Xen's normal heap allocator.
<P>
After the TLSF allocator was integrated into Xen, more work
was required so that each client could allocate memory from a separate
independent pool. (See the call to <i>xmem_pool_create()</i>in
<i>tmh_client_init()</i>.) 
This allows the data structures allocated for the
purpose of supporting persistent pages to be billed to the same client as the
pages themselves.  It also allows partial
(e.g. compressed) pages to be properly billed.
Further, when partial page allocations cause internal fragmentation,
this fragmentation can be isolated per-client.
And, when a domain dies, full pages can be freed, rather than only
partial pages. One other change was
required in the TLSF allocator: In the original version, when a TLSF memory
pool was allocated, the first page of memory was also allocated.
Since, for a persistent pool, this page would
be billed to the client, the allocation of the first page failed if the domain
was started at its maximum memory, and this resulted in a failure to create the
memory pool.  To avoid this, the code was
changed to delay the allocation of the first page until first use of the memory
pool.
<P>
<b><i>Memory allocation interdependency.</i></b>
As previously described,
pages of memory must be moveable back and forth between the Xen heap and the
tmem ephemeral lists (and page lists).
When tmem needs a page but doesn't have one, it requests one from the
Xen heap (either indirectly via xmalloc, or directly via Xen's <i
>alloc_domheap_pages()</i>).
And when Xen needs a page but doesn't have
one, it requests one from tmem (via a call to <i
>tmem_relinquish_pages()</i> in Xen's <i
>alloc_heap_pages() </i>in page_alloc.c).
This leads to a potential infinite loop!
To break this loop, a new memory flag (<i>MEMF_tmem</i>) was added to Xen
to flag and disallow the loop.
See <i>tmh_called_from_tmem()</i>
in <i>tmem_relinquish_pages()</i>.
Note that the <i
>tmem_relinquish_pages()</i> interface allows for memory requests of
order &gt; 0 (multiple contiguous pages), but the tmem implementation disallows
any requests larger than a single page.
<P>
<b><i>LRU page reclamation</i></b>.
Ephemeral pages generally <i>age </i>in
a queue, and the space associated with the oldest -- or <i
>least-recently-used -- </i>page is reclaimed when tmem needs more
memory.  But there are a few exceptions
to strict LRU queuing.  First is when
removal from a queue is constrained by locks, as previously described above.
Second, when an ephemeral pool is <i>shared,</i> unlike a private ephemeral
pool, a
<big><kbd>get</kbd></big>
does not imply a
<big><kbd>flush</kbd></big>
Instead, in a shared pool, a 
results in the page being promoted to the front of the queue.
Third, when a page that is deduplicated (i.e.
is referenced by more than one <i>pgp_</i>t)
reaches the end of the LRU queue, it is marked as <i
>eviction attempted</i> and promoted to the front of the queue; if it
reaches the end of the queue a second time, eviction occurs.
Note that only the <i
>pgp_</i>t is evicted; the actual data is only reclaimed if there is no
other <i>pgp_t </i>pointing to the data.
<P>
All of these modified- LRU algorithms deserve to be studied
carefully against a broad range of workloads.
<P>
<b><i>Internal fragmentation</i>.</b>
When
compression or tze is enabled, allocations between a half-page and a full-page
in size are very common and this places a great deal of pressure on even the
best memory allocator.  Additionally,
problems may be caused for memory reclamation: When one tmem ephemeral page is
evicted, only a fragment of a physical page of memory might be reclaimed.
As a result, when compression or tze is
enabled, it may take a very large number of eviction attempts to free up a full
contiguous page of memory and so, to avoid near-infinite loops and livelocks, eviction
must be assumed to be able to fail.
While all memory allocation paths in tmem are resilient to failure, very
complex corner cases may eventually occur.
As a result, compression and tze are disabled by default and should be
used with caution until they have been tested with a much broader set of
workloads.(Note to self: The 
code needs work.)
<P>
<b><i>Weights and caps</i>.</b>
Because
of the just-discussed LRU-based eviction algorithms, a client that uses tmem at
a very high frequency can quickly swamp tmem so that it provides little benefit
to a client that uses it less frequently.
To reduce the possibility of this denial-of-service, limits can be
specified via management tools that are enforced internally by tmem.
On Xen, the &quot;xm tmem-set&quot; command
can specify &quot;weight=&lt;weight&gt;&quot; or &quot;cap=&lt;cap&gt;&quot;
for any client.  If weight is non-zero
for a client and the current percentage of ephemeral pages in use by the client
exceeds its share (as measured by the sum of weights of all clients), the next
page chosen for eviction is selected from the requesting client's ephemeral
queue, instead of the global ephemeral queue that contains pages from all
clients.(See <i>client_over_quota().</i>)
Setting a cap for a client is currently a no-op.
<P>
<b><i>Shared pools and authentication.</i></b>
When tmem was first proposed to the linux kernel mailing list
(LKML), there was concern expressed about security of shared ephemeral
pools.  The initial tmem implementation only
required a client to provide a 128-bit UUID to identify a shared pool, and the
linux-side tmem implementation obtained this UUID from the superblock of the
shared filesystem (in ocfs2).  It was
pointed out on LKML that the UUID was essentially a security key and any
malicious domain that guessed it would have access to any data from the shared
filesystem that found its way into tmem.
Ocfs2 has only very limited security; it is assumed that anyone who can
access the filesystem bits on the shared disk can mount the filesystem and use
it.  But in a virtualized data center,
higher isolation requirements may apply.
As a result, a Xen boot option -- &quot;tmem_shared_auth&quot; -- was
added.  The option defaults to disabled,
but when it is enabled, management tools must explicitly authenticate (or may
explicitly deny) shared pool access to any client.
On Xen, this is done with the &quot;xm
tmem-shared-auth&quot; command.
<P>
<b><i>32-bit implementation</i>.</b>
There was some effort put into getting tmem working on a 32-bit Xen.
However, the Xen heap is limited in size on
32-bit Xen so tmem did not work very well.
There are still 32-bit ifdefs in some places in the code, but things may
have bit-rotted so using tmem on a 32-bit Xen is not recommended.
<P>
<b><i>IA-64 implementation. </i></b>
The vast majority of the tmem
implementation is architecture-independent.
For tmem to run on Xen/ia64, it is believed that only one or two
routines needs to be written.(See the
#ifdef __ia64__ at <i>cli_mfn_to_va()</i>.)

<h2>Known Issues</h2>

<p><b><i>Fragmentation.</i></b>When tmem
is active, all physically memory becomes <i>fragmented</i>
into individual pages.  However, the Xen
memory allocator allows memory to be requested in multi-page contiguous
quantities, called order&gt;0 allocations.
(e.g. 2<sup>order</sup> so
order==4 is sixteen contiguous pages.)
In some cases, a request for a larger order will fail gracefully if no
matching contiguous allocation is available from Xen.
As of Xen 4.0, however, there are several
critical order&gt;0 allocation requests that do not fail gracefully.
Notably, when a domain is created, and
order==4 structure is required or the domain creation will fail.
And shadow paging requires many order==2
allocations; if these fail, a PV live-migration may fail.
There are likely other such issues.
<P>
But, fragmentation can occur even without tmem if any domU does
any extensive ballooning; tmem just accelerates the fragmentation.
So the fragmentation problem must be solved
anyway.  The best solution is to disallow
order&gt;0 allocations altogether in Xen -- or at least ensure that any attempt
to allocate order&gt;0 can fail gracefully, e.g. by falling back to a sequence
of single page allocations. However this restriction may require a major rewrite
in some of Xen's most sensitive code.
(Note that order&gt;0 allocations during Xen boot and early in domain0
launch are safe and, if dom0 does not enable tmem, any order&gt;0 allocation by
dom0 is safe, until the first domU is created.)
<P>
Until Xen can be rewritten to be <i>fragmentation-safe</i>, a small hack
was added in the Xen page
allocator.(See the comment &quot;
memory is scarce&quot; in <i>alloc_heap_pages()</i>.)
Briefly, a portion of memory is pre-reserved
for allocations where order&gt;0 and order&lt;9.
(Domain creation uses 2MB pages, but fails
gracefully, and there are no other known order==9 allocations or order&gt;9
allocations currently in Xen.)
<P>
<b><i>NUMA</i></b>.  Tmem assumes that
all memory pages are equal and any RAM page can store a page of data for any
client.  This has potential performance
consequences in any NUMA machine where access to <i
>far memory</i> is significantly slower than access to <i
>near memory</i>.
On nearly all of today's servers, however,
access times to <i>far memory</i> is still
much faster than access to disk or network-based storage, and tmem's primary performance
advantage comes from the fact that paging and swapping are reduced.
So, the current tmem implementation ignores
NUMA-ness; future tmem design for NUMA machines is an exercise left for the
reader.

<h2>Bibliography</h2>

<P>
(needs work)<b style='mso-bidi-font-weight:>
<P><a href="http://oss.oracle.com/projects/tmem">http://oss.oracle.com/projects/tmem</a>