S4 image save:save_image_lzo函数分析

1 篇文章 0 订阅
1 篇文章 0 订阅

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. 详细分析过程

  1. 创建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");
    
  2. 将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++;
         	}
    ..............................
     }
    
  3. 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);
     }
    
  4. 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):通知压缩工作结束,进行一下步操作。
  5. 接下来,我们看一下压缩结束后,下一步的代码是什么:

    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():将输入写入磁盘

  6. 接第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);
     }
    
    
  7. 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;
    
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值