save_image_lzo函数分析
save_image_lzo在S4中运行,运于保存image数据,函数的调用逻辑如下:
hibernate()->swsusp_write()->save_image_lzo()
1. 基本流程
该函数实现了S4中内核image压缩的功能,大致流程如下图所示:
- 首先内核准备了三个image_compress线程用于压缩数据,每个image_compress负责一块LZO_UNC_SIZE大小的数据。
- LZO_UNC_SIZE大小的数据是从哪里来的呢,是通过mencpy将snapshot里的数据一页一页的复制成一个大小为LZO_UNC_SIZE内存块。
- 等第一个线程复制完一块大小为LZO_UNC_SIZE内存块后,将ready设为1,代表准备就绪,然后唤醒第一个image_compress线程,交给它进行数据压缩,压缩完后唤醒done等待队列,把压缩后的数据复制到磁盘中。
- 之前的循环继续运行,准备LZO_UNC_SIZE大小的内存块,交给第二个image_compress线程压缩,压缩完了进行复制。直到三个线程工作结束,开始唤醒image_crc32线程开始数据校验。
- 最后把磁盘中的数据,放到image_crc32中进行数据校验,到此为止,image save完毕。
2. 函数中的变量含义
首先,我们分析一下该函数创建的变量:
unsigned int m;
int ret = 0;
int nr_pages;
int err2;
struct hib_bio_batch hb;
ktime_t start;
ktime_t stop;
size_t off;
unsigned thr, run_threads, nr_threads;
unsigned char *page = NULL;
// cmp_data压缩LZO数据所用的数据结构
struct cmp_data *data = NULL;
// crc_data结构用于crc32校验
struct crc_data *crc = NULL;
- m:定义该变量用来显示image saving压缩完成的进度;
- nr_pages:用来记录image压缩的页数;
- ktime_t start、ktime_t stop:记录函数运行的时间;
- off用来记录位置偏移量;
- thr, run_threads, nr_threads:代表线程数;
- page:image页;
- struct cmp_data:压缩LZO数据所用的数据结构;
- struct crc_data:用于crc32校验的数据结构;
3. 详细分析过程
-
创建nr_threads个image_compress和一个image_crc32线程。image_compress为压缩线程,它的处理函数为lzo_compress_threadfn,image_crc32为校验线程,它的处理函数为crc32_threadfn,我们来看一下它的代码:
/* * Start the image_compress thread. */ for (thr = 0; thr < nr_threads; thr++) { // 初始化等待队列,为每个线程创建一个go和done等待事件 init_waitqueue_head(&data[thr].go); init_waitqueue_head(&data[thr].done); //开始运行线程,image_compress/1、2、3, 处理函数为lzo_compress_threadfn,直到signal_pending打断 data[thr].thr = kthread_run(lzo_compress_threadfn, &data[thr], "image_compress/%u", thr); } /* * Start the CRC32 thread. */ init_waitqueue_head(&crc->go); init_waitqueue_head(&crc->done); // crc32_threadfn函数在自己的线程中运行的 CRC32 更新函数。 crc->thr = kthread_run(crc32_threadfn, crc, "image_crc32");
-
将image pages分成10份,用于压缩进度提示:
//将pages分成十份 m = nr_to_write / 10; // 如果pages = 0,则令m=1; if (!m) m = 1; // nr_pages 表示已经处理的页面 nr_pages = 0; ....................... for (thr = 0; thr < nr_threads; thr++) { for (off = 0; off < LZO_UNC_SIZE; off += PAGE_SIZE) { ........................... if (!(nr_pages % m)) pr_info("Image saving progress: %3d%%\n", nr_pages / m * 10); nr_pages++; } .............................. }
-
nr_threads个线程分别压缩一块大约LZO_UNC_SIZE大小的内存区域,如下图所示,内部for循环,将复制一个
nr_pages*page_size
大小的snapshot内存块到要压缩的地址中,直到nr_pages*page_siz >= LZO_UNC_SIZE
退出循环。此时,将&data[thr].ready设置为1,意为准备工作已做好,利用wake_up(&data[thr].go)
通知线程可以工作了。
for (thr = 0; thr < nr_threads; thr++) { // 一轮压缩LZO_UNC_SIZE大小的data,一个nr_page占地大小是PAGE_SIZEi,这里的意思是一次压缩一个LZO_UNC_SIZE的量 for (off = 0; off < LZO_UNC_SIZE; off += PAGE_SIZE) { // 获取从中读取下一个图像页面的地址 ret = snapshot_read_next(snapshot); // 如果为负数,就出错 if (ret < 0) goto out_finish; // 如果为0,代表image pages已经全部读取完了,退出此次循环 if (!ret) break; memcpy(data[thr].unc + off, data_of(*snapshot), PAGE_SIZE); if (!(nr_pages % m)) pr_info("Image saving progress: %3d%%\n", nr_pages / m * 10); nr_pages++; } //off为0,说明此次无数据可压,退出线程 if (!off) break; // 需要压缩的长度为off,此时off = nr_pages * PAGE_SIZE data[thr].unc_len = off; mb(); // 准备就绪,开始唤醒线程 atomic_set(&data[thr].ready, 1); //唤醒压缩等待队列,跳转到wait_event(&data[thr].go)的地方去执行,继续接下来循环。 wake_up(&data[thr].go); }
-
wake_up(&data[thr].go)
通知准备工作已经做好了,lzo_compress_threadfn可以开始工作了,我们看一下它的代码:static int lzo_compress_threadfn(void *data) { struct cmp_data *d = data; while (1) { wait_event(d->go, atomic_read(&d->ready) || kthread_should_stop()); // 如果线程停止了,说明所有的image pages已经全部压缩完毕,break跳出循环 if (kthread_should_stop()) { d->thr = NULL; d->ret = -1; atomic_set(&d->stop, 1); wake_up(&d->done); break; } atomic_set(&d->ready, 0); // 压缩数据 d->ret = lzo1x_1_compress(d->unc, d->unc_len, d->cmp + LZO_HEADER, &d->cmp_len, d->wrk); atomic_set(&d->stop, 1); wake_up(&d->done); } return 0; }
atomic_set(&d->ready, 0)
将ready重新设置为0,代表下一次的内存块还没有准备好。lzo1x_1_compress
压缩函数,暂未分析。atomic_set(&d->stop, 1)
:设置top为1,代表压缩工作结束,可以唤醒结束后的工作;wake_up(&d->done)
:通知压缩工作结束,进行一下步操作。
-
接下来,我们看一下压缩结束后,下一步的代码是什么:
for (run_threads = thr, thr = 0; thr < run_threads; thr++) { wait_event(data[thr].done, atomic_read(&data[thr].stop)); atomic_set(&data[thr].stop, 0); // 同上,为了顺序执行代码 mb(); ret = data[thr].ret; if (ret < 0) { pr_err("LZO compression failed\n"); goto out_finish; } if (unlikely(!data[thr].cmp_len || data[thr].cmp_len > lzo1x_worst_compress(data[thr].unc_len))) { pr_err("Invalid LZO compressed length\n"); ret = -1; goto out_finish; } *(size_t *)data[thr].cmp = data[thr].cmp_len; /* * Given we are writing one page at a time to disk, we * copy that much from the buffer, although the last * bit will likely be smaller than full page. This is * OK - we saved the length of the compressed data, so * any garbage at the end will be discarded when we * read it. * */ // 将压缩后的数据,一页一页的存到磁盘中 for (off = 0; off < LZO_HEADER + data[thr].cmp_len; off += PAGE_SIZE) { memcpy(page, data[thr].cmp + off, PAGE_SIZE); ret = swap_write_page(handle, page, &hb); if (ret) goto out_finish; } }
-
lzo_compress_threadfn
函数把数据压缩完了之后,通过wake_up(&d->done)唤醒data[thr].done
事件,data[thr].done
事件开始工作的条件是&data[thr].stop为1,我们可以看到lzo_compress_threadfn()
中lzo1x_1_compress()
函数完成后,利用atomic_set()
将&data[thr].stop
设置为1,所以满足了开始工作的条件。 -
swap_write_page()
:将输入写入磁盘
-
-
接第3点,当nr_threads个线程工作结束后 ,唤醒image_crc32线程,开始数据校验,我们来看一下这个线程的工作内容:
static int crc32_threadfn(void *data) { struct crc_data *d = data; unsigned i; while (1) { wait_event(d->go, atomic_read(&d->ready) || kthread_should_stop()); if (kthread_should_stop()) { d->thr = NULL; atomic_set(&d->stop, 1); wake_up(&d->done); break; } atomic_set(&d->ready, 0); for (i = 0; i < d->run_threads; i++) *d->crc32 = crc32_le(*d->crc32, d->unc[i], *d->unc_len[i]); // 数据校验工作结束 atomic_set(&d->stop, 1); //开始唤醒结束的等待事件 wake_up(&d->done); } return 0; }
再来欣赏一下唤醒过程:
for (;;) { for (thr = 0; thr < nr_threads; thr++) { // 一轮压缩LZO_UNC_SIZE大小的data,一个nr_page占地大小是PAGE_SIZEi,这里的意思 是一次压缩一个LZO_UNC_SIZE的量 for (off = 0; off < LZO_UNC_SIZE; off += PAGE_SIZE) { ................... } } // 当nr_threads个线程工作结束后 ,唤醒image_crc32线程,开始数据校验, crc->run_threads = thr; atomic_set(&crc->ready, 1); wake_up(&crc->go); for (run_threads = thr, thr = 0; thr < run_threads; thr++) { ................ } // crc->done等待被唤醒 wait_event(crc->done, atomic_read(&crc->stop)); atomic_set(&crc->stop, 0); }
-
3-7小点分析了nr_threads个进程压缩image pages的过程。循环这个过程,直到所有的page页被存完,下面是循环的结束条件。
/* 代表ret = 0,代表全部的image page已经读完了, * 退出 for (off = 0; off < LZO_UNC_SIZE; off += PAGE_SIZE)循环 */ if (!ret) break; /* off等于0代表无需数据可压缩 * 退出for (thr = 0; thr < nr_threads; thr++)循环 */ if (!off) break; /* 在前两层的基础上,才会触发第三层,代表所有的page页被存完 * 退出死循环 */ if (!thr) break;