save_image_lzo在S4中运行,运于保存image数据,函数的调用逻辑如下:
hibernate()->swsusp_write()->save_image_lzo()
该函数实现了S4中内核image压缩的功能,大致流程如下图所示:
首先,我们分析一下该函数创建的变量:
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;
创建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;