Skip to content

Commit 9d70e5f

Browse files
committed
Don't dump pages which only contain zero bytes
Introduces a new command line option '--skip-zero-bytes' which detects pages which only contain zero bytes and prohibits that they get dumped in the processes image file. It is a potentially expensive operation because it checks for every single process page if it contains only zeros, but it can significantly decrease the image size and improve the startup-time if many such pages exist. It effectively replaces such pages which the kernel's zero-page on restore. Signed-off-by: Volker Simonis <[email protected]>
1 parent 50190ae commit 9d70e5f

File tree

18 files changed

+346
-7
lines changed

18 files changed

+346
-7
lines changed

Documentation/criu.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,14 @@ mount -t cgroup -o devices,freezer none devices,freezer
369369
Deduplicate "old" data in pages images of previous *dump*. This option
370370
implies incremental *dump* mode (see the *pre-dump* command).
371371

372+
*--skip-zero-pages*::
373+
Don't dump pages containing only zero bytes. This is a
374+
potentially expensive operation because it checks for
375+
every single process page if it contains only zeros, but
376+
it can significantly decrease the image size and improve the
377+
startup-time if many such pages exist. It effectively
378+
replaces such pages which the kernel's zero-page on restore.
379+
372380
*-l*, *--file-locks*::
373381
Dump file locks. It is necessary to make sure that all file lock users
374382
are taken into dump, so it is only safe to use this for enclosed containers

criu/config.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,7 @@ int parse_options(int argc, char **argv, bool *usage_error, bool *has_exec_cmd,
650650
{ "ms", no_argument, 0, 1054 },
651651
BOOL_OPT("track-mem", &opts.track_mem),
652652
BOOL_OPT("auto-dedup", &opts.auto_dedup),
653+
BOOL_OPT("skip-zero-pages", &opts.skip_zero_pages),
653654
{ "libdir", required_argument, 0, 'L' },
654655
{ "cpu-cap", optional_argument, 0, 1057 },
655656
BOOL_OPT("force-irmap", &opts.force_irmap),

criu/cr-service.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,9 @@ static int setup_opts_from_req(int sk, CriuOpts *req)
541541
if (req->has_auto_dedup)
542542
opts.auto_dedup = req->auto_dedup;
543543

544+
if (req->has_skip_zero_pages)
545+
opts.skip_zero_pages = req->skip_zero_pages;
546+
544547
if (req->has_force_irmap)
545548
opts.force_irmap = req->force_irmap;
546549

criu/crtools.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,7 @@ int main(int argc, char *argv[], char *envp[])
541541
" pages images of previous dump\n"
542542
" when used on restore, as soon as page is restored, it\n"
543543
" will be punched from the image\n"
544+
" --skip-zero-pages don't dump pages containing only zero bytes.\n"
544545
" --pre-dump-mode splice - parasite based pre-dumping (default)\n"
545546
" read - process_vm_readv syscall based pre-dumping\n"
546547
"\n"

criu/include/cr_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ struct cr_options {
157157
int track_mem;
158158
char *img_parent;
159159
int auto_dedup;
160+
int skip_zero_pages;
160161
unsigned int cpu_cap;
161162
int force_irmap;
162163
char **exec_cmd;

criu/include/stats.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ enum {
3333
CNT_SHPAGES_SKIPPED_PARENT,
3434
CNT_SHPAGES_WRITTEN,
3535

36+
CNT_SKIPPED_ZERO_PAGES,
37+
3638
DUMP_CNT_NR_STATS,
3739
};
3840

criu/mem.c

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
#include <sys/mman.h>
44
#include <errno.h>
55
#include <fcntl.h>
6+
#include <string.h>
67
#include <sys/syscall.h>
78
#include <sys/prctl.h>
9+
#include <sys/uio.h>
810

911
#include "types.h"
1012
#include "cr_options.h"
@@ -31,6 +33,7 @@
3133
#include "prctl.h"
3234
#include "compel/infect-util.h"
3335
#include "pidfd-store.h"
36+
#include "xmalloc.h"
3437

3538
#include "protobuf.h"
3639
#include "images/pagemap.pb-c.h"
@@ -191,11 +194,33 @@ static int generate_iovs(struct pstree_item *item, struct vma_area *vma, struct
191194
bool has_parent)
192195
{
193196
unsigned long nr_scanned;
194-
unsigned long pages[3] = {};
197+
/* Counters for PAGES_SKIPPED_PARENT, PAGES_LAZY, PAGES_WRITTEN and SKIPPED_ZERO_PAGES */
198+
unsigned long pages[4] = {};
195199
unsigned long vaddr;
196200
bool dump_all_pages;
197201
int ret = 0;
198202

203+
static char *zero_page = NULL;
204+
static char *remote_page = NULL;
205+
int zero = 0;
206+
struct iovec local[2];
207+
struct iovec remote[1];
208+
int nread = 0;
209+
if (opts.skip_zero_pages && zero_page == NULL) {
210+
zero_page = xmalloc(PAGE_SIZE);
211+
remote_page = xmalloc(PAGE_SIZE);
212+
if (zero_page == NULL || remote_page == NULL) {
213+
pr_warn("Can't allocate memory - disabling --skip-zero-pages\n");
214+
opts.skip_zero_pages = 0;
215+
} else {
216+
memzero(zero_page, PAGE_SIZE);
217+
local[0].iov_base = remote_page;
218+
local[0].iov_len = PAGE_SIZE;
219+
remote[0].iov_base = (void *)0x0;
220+
remote[0].iov_len = PAGE_SIZE;
221+
}
222+
}
223+
199224
dump_all_pages = should_dump_entire_vma(vma->e);
200225

201226
nr_scanned = 0;
@@ -207,9 +232,25 @@ static int generate_iovs(struct pstree_item *item, struct vma_area *vma, struct
207232

208233
/* If dump_all_pages is true, should_dump_page is called to get pme. */
209234
next = should_dump_page(pmc, vma->e, vaddr, &softdirty);
210-
if (!dump_all_pages && next != vaddr) {
211-
vaddr = next - PAGE_SIZE;
212-
continue;
235+
if (!dump_all_pages) {
236+
if (next != vaddr) {
237+
vaddr = next - PAGE_SIZE;
238+
continue;
239+
} else if (opts.skip_zero_pages) {
240+
remote[0].iov_base = (void *)vaddr;
241+
nread = process_vm_readv(item->pid->real, local, 1, remote, 1, 0);
242+
if (nread == PAGE_SIZE) {
243+
zero = memcmp(zero_page, remote_page, PAGE_SIZE);
244+
/*
245+
* If the page contains just zeros we can treat it like the zero page and skip it.
246+
* At restore it will be replaced by a reference to the zero page and COWed if accessed.
247+
*/
248+
if (zero == 0) {
249+
pages[3]++;
250+
continue;
251+
}
252+
}
253+
}
213254
}
214255

215256
if (vma_entry_can_be_lazy(vma->e) && !is_stack(item, vaddr))
@@ -247,8 +288,10 @@ static int generate_iovs(struct pstree_item *item, struct vma_area *vma, struct
247288
cnt_add(CNT_PAGES_SKIPPED_PARENT, pages[0]);
248289
cnt_add(CNT_PAGES_LAZY, pages[1]);
249290
cnt_add(CNT_PAGES_WRITTEN, pages[2]);
291+
cnt_add(CNT_SKIPPED_ZERO_PAGES, pages[3]);
250292

251-
pr_info("Pagemap generated: %lu pages (%lu lazy) %lu holes\n", pages[2] + pages[1], pages[1], pages[0]);
293+
pr_info("Pagemap generated: %lu pages (%lu lazy) %lu holes %lu skipped zero\n",
294+
pages[2] + pages[1], pages[1], pages[0], pages[3]);
252295
return ret;
253296
}
254297

criu/stats.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ static void display_stats(int what, StatsEntry *stats)
134134
stats->dump->pages_skipped_parent, stats->dump->pages_skipped_parent);
135135
pr_msg("Memory pages written: %" PRIu64 " (0x%" PRIx64 ")\n", stats->dump->pages_written,
136136
stats->dump->pages_written);
137+
if (stats->dump->has_skipped_zero_pages)
138+
pr_msg("Memory pages skipped because zero: %" PRIu64 " (0x%" PRIx64 ")\n",
139+
stats->dump->skipped_zero_pages, stats->dump->skipped_zero_pages);
137140
pr_msg("Lazy memory pages: %" PRIu64 " (0x%" PRIx64 ")\n", stats->dump->pages_lazy,
138141
stats->dump->pages_lazy);
139142
} else if (what == RESTORE_STATS) {
@@ -178,6 +181,10 @@ void write_stats(int what)
178181
ds_entry.has_page_pipes = true;
179182
ds_entry.page_pipe_bufs = dstats->counts[CNT_PAGE_PIPE_BUFS];
180183
ds_entry.has_page_pipe_bufs = true;
184+
if (opts.skip_zero_pages) {
185+
ds_entry.has_skipped_zero_pages = true;
186+
ds_entry.skipped_zero_pages = dstats->counts[CNT_SKIPPED_ZERO_PAGES];
187+
}
181188

182189
ds_entry.shpages_scanned = dstats->counts[CNT_SHPAGES_SCANNED];
183190
ds_entry.has_shpages_scanned = true;

images/rpc.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ message criu_opts {
145145
optional bool leave_stopped = 69;
146146
optional bool display_stats = 70;
147147
optional bool log_to_stderr = 71;
148+
optional bool skip_zero_pages = 72;
148149
/* optional bool check_mounts = 128; */
149150
}
150151

images/stats.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ message dump_stats_entry {
2222
optional uint64 shpages_scanned = 12;
2323
optional uint64 shpages_skipped_parent = 13;
2424
optional uint64 shpages_written = 14;
25+
26+
optional uint64 skipped_zero_pages = 15;
2527
}
2628

2729
message restore_stats_entry {

lib/c/criu.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,17 @@ void criu_set_auto_dedup(bool auto_dedup)
387387
criu_local_set_auto_dedup(global_opts, auto_dedup);
388388
}
389389

390+
void criu_local_set_skip_zero_pages(criu_opts *opts, bool skip_zero_pages)
391+
{
392+
opts->rpc->has_skip_zero_pages = true;
393+
opts->rpc->skip_zero_pages = skip_zero_pages;
394+
}
395+
396+
void criu_set_skip_zero_pages(bool skip_zero_pages)
397+
{
398+
criu_local_set_skip_zero_pages(global_opts, skip_zero_pages);
399+
}
400+
390401
void criu_local_set_force_irmap(criu_opts *opts, bool force_irmap)
391402
{
392403
opts->rpc->has_force_irmap = true;

test/javaTests/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<!-- Suite testng xml file to consider for test execution -->
1919
<suiteXmlFiles>
2020
<suiteXmlFile>test.xml</suiteXmlFile>
21+
<suiteXmlFile>test-zero.xml</suiteXmlFile>
2122
</suiteXmlFiles>
2223
</configuration>
2324
</plugin>

test/javaTests/src/org/criu/java/tests/CheckpointRestore.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public void runtest(String testName, String checkpointOpt, String restoreOpt) th
112112
String pid;
113113
int exitCode;
114114

115-
System.out.println("======= Testing " + testName + " ========");
115+
System.out.println("======= Testing " + testName + " " + checkpointOpt + " ========");
116116

117117
testSetup(testName);
118118

test/javaTests/test-zero.xml

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?xml version = "1.0" encoding = "UTF-8"?>
2+
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
3+
4+
<suite name = "Suite2">
5+
<parameter name="checkpointOpt" value="--skip-zero-pages"/>
6+
<parameter name="restoreOpt" value=""/>
7+
8+
<test name = "test1-FileRead">
9+
<parameter name="testname" value="FileRead"/>
10+
<classes>
11+
<class name = "org.criu.java.tests.CheckpointRestore"/>
12+
</classes>
13+
</test>
14+
15+
<test name = "test2-ReadWrite">
16+
<parameter name="testname" value="ReadWrite"/>
17+
<classes>
18+
<class name = "org.criu.java.tests.CheckpointRestore"/>
19+
</classes>
20+
</test>
21+
22+
<test name = "test3-MemoryMappings">
23+
<parameter name="testname" value="MemoryMappings"/>
24+
<classes>
25+
<class name = "org.criu.java.tests.CheckpointRestore"/>
26+
</classes>
27+
</test>
28+
29+
<test name = "test4-MultipleFileRead">
30+
<parameter name="testname" value="MultipleFileRead"/>
31+
<classes>
32+
<class name = "org.criu.java.tests.CheckpointRestore"/>
33+
</classes>
34+
</test>
35+
36+
<test name = "test5-MultipleFileWrite">
37+
<parameter name="testname" value="MultipleFileWrite"/>
38+
<classes>
39+
<class name = "org.criu.java.tests.CheckpointRestore"/>
40+
</classes>
41+
</test>
42+
43+
<test name = "test6-Sockets">
44+
<parameter name="testname" value="Sockets"/>
45+
<parameter name="checkpointOpt" value="--tcp-established --skip-zero-pages"/>
46+
<parameter name="restoreOpt" value="--tcp-established"/>
47+
<classes>
48+
<class name = "org.criu.java.tests.CheckpointRestore"/>
49+
</classes>
50+
</test>
51+
52+
<test name = "test7-SocketsListen">
53+
<parameter name="testname" value="SocketsListen"/>
54+
<parameter name="checkpointOpt" value="--tcp-established --skip-zero-pages"/>
55+
<parameter name="restoreOpt" value="--tcp-established"/>
56+
<classes>
57+
<class name = "org.criu.java.tests.CheckpointRestore"/>
58+
</classes>
59+
</test>
60+
61+
<test name = "test8-SocketsConnect">
62+
<parameter name="testname" value="SocketsConnect"/>
63+
<parameter name="checkpointOpt" value="--tcp-established --skip-zero-pages"/>
64+
<parameter name="restoreOpt" value="--tcp-established"/>
65+
<classes>
66+
<class name = "org.criu.java.tests.CheckpointRestore"/>
67+
</classes>
68+
</test>
69+
70+
<test name = "test9-SocketsMultiple">
71+
<parameter name="testname" value="SocketsMultiple"/>
72+
<parameter name="checkpointOpt" value="--tcp-established --skip-zero-pages"/>
73+
<parameter name="restoreOpt" value="--tcp-established"/>
74+
<classes>
75+
<class name = "org.criu.java.tests.CheckpointRestore"/>
76+
</classes>
77+
</test>
78+
79+
<test name = "test10-SocketsData">
80+
<parameter name="testname" value="SocketsData"/>
81+
<parameter name="checkpointOpt" value="--tcp-established --skip-zero-pages"/>
82+
<parameter name="restoreOpt" value="--tcp-established"/>
83+
<classes>
84+
<class name = "org.criu.java.tests.CheckpointRestore"/>
85+
</classes>
86+
87+
</test>
88+
89+
</suite>

test/zdtm.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,7 @@ def __init__(self, opts):
10521052
self.__sat = bool(opts['sat'])
10531053
self.__dedup = bool(opts['dedup'])
10541054
self.__mdedup = bool(opts['noauto_dedup'])
1055+
self.__skip_zero_pages = bool(opts['skip_zero_pages'])
10551056
self.__user = bool(opts['user'])
10561057
self.__rootless = bool(opts['rootless'])
10571058
self.__leave_stopped = bool(opts['stop'])
@@ -1381,6 +1382,9 @@ def dump(self, action, opts=[]):
13811382
if self.__dedup:
13821383
a_opts += ["--auto-dedup"]
13831384

1385+
if self.__skip_zero_pages:
1386+
a_opts += ["--skip-zero-pages"]
1387+
13841388
a_opts += ["--timeout", "10"]
13851389

13861390
criu_dir = os.path.dirname(os.getcwd())
@@ -2083,7 +2087,7 @@ def run_test(self, name, desc, flavor):
20832087
'dedup', 'sbs', 'freezecg', 'user', 'dry_run', 'noauto_dedup',
20842088
'remote_lazy_pages', 'show_stats', 'lazy_migrate', 'stream',
20852089
'tls', 'criu_bin', 'crit_bin', 'pre_dump_mode', 'mntns_compat_mode',
2086-
'rootless')
2090+
'rootless', 'skip_zero_pages')
20872091
arg = repr((name, desc, flavor, {d: self.__opts[d] for d in nd}))
20882092

20892093
if self.__use_log:
@@ -2697,6 +2701,9 @@ def get_cli_args():
26972701
rp.add_argument("--noauto-dedup",
26982702
help="Manual deduplicate images on iterations",
26992703
action='store_true')
2704+
rp.add_argument("--skip-zero-pages",
2705+
help="Don't dump pages containing only zero bytes",
2706+
action='store_true')
27002707
rp.add_argument("--nocr",
27012708
help="Do not CR anything, just check test works",
27022709
action='store_true')

test/zdtm/static/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ TST_NOFILE := \
271271
sigtrap01 \
272272
change_mnt_context \
273273
fd_offset \
274+
zero_pages \
274275
# jobctl00 \
275276
276277
PKG_CONFIG ?= pkg-config

0 commit comments

Comments
 (0)