• S4 image save:save_image_lzo函数分析


    save_image_lzo函数分析

    save_image_lzo在S4中运行,运于保存image数据,函数的调用逻辑如下:

    hibernate()->swsusp_write()->save_image_lzo()
    
    • 1

    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;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 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");
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
    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++;
           	}
      ..............................
       }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
    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);
       }
      
      • 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
    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;
      }
      
      
      • 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
      • 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;
          	}
      }	
      
      
      • 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
      • 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;
      }
      
      • 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

      再来欣赏一下唤醒过程:

       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);
       }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
    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;
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
  • 相关阅读:
    不黑箱,不抽卡,分分钟带你拿捏SD中的色彩控制 | 京东云技术团队
    55、美国德克萨斯大学奥斯汀分校、钱德拉家族电气与计算机工程系:通过迁移学习解决BCI个体差异性[不得不说,看技术还得是老美]
    全向移动机器人运动参数校准
    代码随想录刷题 Day28
    Springboot+druid
    Docker-如何获取docker官网x86、ARM、AMD等不同架构下的镜像资源
    Unity之ShaderGraph如何实现冰冻效果
    跳表和散列表
    拓扑排序及其衍生
    git命令的操作
  • 原文地址:https://blog.csdn.net/LiebeZQ/article/details/128022656