S4 image save:save_image_lzo函数分析

news2025/8/9 5:39:59

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;
    
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/34154.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

35+程序员该怎么办?有哪些破局方法?

在互联网行业一直有一种说法&#xff0c;如果35岁还没到管理岗&#xff0c;基本就是被裁的命运了。作为互联网行业的主力军&#xff0c;程序员的职业发展&#xff0c;也被人总结成&#xff0c;三年升高工、七年做架构、十年送外卖。虽然是个段子&#xff0c;也透露了大家对于年…

Python列表类型详解

今天继续给大家介绍Python相关知识&#xff0c;本文主要内容是Python列表类型 一、列表类型定义与创建 在Python中&#xff0c;列表类型是序列类型的一种&#xff0c;也是一种非常重要、非常常用的数据类型。列表使用方括号[]或list()函数创建&#xff0c;列表中的元素使用逗…

如何转变固定资产管理方式,让企业降本增效?

作为企业资产的重要组成部分&#xff0c;固定资产管理直接影响到企业的运营和核心的支出。固定资产的科学管理是企业管理者很关心问题。传统的纸质表格固定资产管理方式&#xff0c;通常是企业在成立之初采取的方式。因为成立之初企业需要节省成本&#xff0c;而且资产数量和种…

Android入门第33天-Android里的弹出式对话框

简介 Android Studio里在4.0前有一种ProgressDialog&#xff0c;这个已经淘汰了。我们完全可以使用ProgressBar来取代。但是还有一种Dialog叫PopWindow&#xff0c;它是一种“可阻塞式Dialog”。即弹出后除非你给它一个“动作”否则就一直显示在那。 今天我们就来看看这种Dia…

python面向对象(下)

python面向对象下封装封装的方法获取私有属性继承单一继承语法格式同名的情况私有属性的调用方法多重继承语法格式小例子同名的情况多态持续更新中~~~~~~~~~封装 封装是面向对象的核心思想。它是指将对象的属性和行为封装起来&#xff0c;其载体就是类&#xff0c;类通常对客户…

linux搭建git服务器,windows客户端配置git

Linux服务器配置之Git服务器搭建步骤&#xff1a; 一、配置环境 1、服务器&#xff1a;CentOS 8.2&#xff08;64位&#xff09; Git &#xff08;version 2.27.0&#xff09; 2、客户端&#xff1a;Windows 10 &#xff08;64位&#xff09; Git&#xff08;version 2.38.…

基于深度学习的AI绘画为何突然一下子火了?

CLIP &#xff5c;Midjourney | dreamstudio AIGC | Stable Diffusion | Imagen 随着Disco、Midjourney、dreamstudio 、AIGC、Stable Diffusion、Imagen、深度学习、高性能计算、数据分析、数据挖掘等技术的快速发展&#xff0c;AI绘画技术得到迅速发展。 即使今年年初的AI绘…

Redis String类型使用方法

String 类型 String 类型&#xff0c;也就是字符串类型&#xff0c;是Redis中最简单的存储类型。 其value是字符串&#xff0c;不过根据字符串的格式不同&#xff0c;又可以分为3类&#xff1a; string&#xff1a;普通字符串int&#xff1a;整数类型&#xff0c;可以做自增、…

国际站、速卖通、Lazada店铺运营技巧?如何提升销量?

现在国际站、速卖通、Lazada平台开店的卖家越来越多&#xff0c;如何让店铺稳定的使用下去&#xff0c;是每一个卖家都需要思考的问题。现在我们一起来看一看&#xff0c;有哪些运营上需要注意的事项。 1选择产品 不管什么平台都是一样&#xff0c;产品是根本&#xff0c;只有…

Handler 原理

线程的应用场景 Android是单线程模型&#xff0c;Activity、Service、Broadcast等组件的创建&#xff0c;都是在主线程完成的&#xff0c;即UI线程。但如果需要执行一些耗时的操作时&#xff0c;比如&#xff1a;I/O的读写、大文件的读写、数据库操作以及网络上传和下载等操作都…

Linux登陆配置虚拟机

启用虚拟机一、启动虚拟机1、登录虚拟机2、查看IP地址3、能否PING通外网二、配置静态IP地址1、修改网卡配置文件2、重启网络服务3、重启虚拟机4、查看修改后的IP地址5、测试虚拟机能否Ping通外网三、测试宿主机与虚拟机能否相互Ping通1、测试宿主机能否Ping通虚拟机2、测试虚拟…

信创产业多点开花,AntDB数据库积极参与行业标准研制,协同价值链伙伴共促新发展

11月&#xff0c;AntDB数据库积极参与多项数据库行业标准研讨会&#xff0c;助推行业规范建立&#xff1b;凭借领先的技术研发能力与企业创新能力&#xff0c;在今年9月入选了《2022爱分析数据智能厂商全景报告》&#xff0c;此次又凭借在信创市场的深入推广&#xff0c;入选《…

反转链表问题的递归解法

目录 一、反转整个链表 二、反转部分链表 一、反转整个链表 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 首先在方法中我们需要判断递归终止的情况&#xff0c;如果headnull或者head.nextnull&#xff0c;那么我们就可以直接返回hea…

【快速上手系列】保姆级Layuimini与SSM的联合使用教程(数据表格操作)

【快速上手系列】保姆级Layuimini与SSM的联合使用教程&#xff08;数据表格操作&#xff09; 使用步骤 导入layuimini 下载layuimini文件 这个并不是直接运行的&#xff0c;需要用HBuilder导入 layuimini的项目路径 我用的是iframe版&#xff0c;个人更喜欢这种的&#x1…

Git -- submoudule子模块使用

文章目录子模块的作用添加子模块拉取带子模块的项目修改子模块代码子模块的作用 通常情况下&#xff0c;我们做项目时会有几个业务功能区分比较明确的模块&#xff0c;比如简单来说&#xff0c;一个项目我们可以分为认证授权模块、工具类模块、常规业务模块。 而像认证…

【外卖项目实战开发一】

文章目录1、软件开发整体介绍2、外卖项目介绍3、环境搭建数据库环境搭建创建数据库执行SQL脚本数据表maven项目搭建添加依赖:application.yml配置application启动类4、后台登录功能开发需求分析代码开发5、后台退出功能开发1、软件开发整体介绍 软件开发流程 角色分工 软件…

基于JAVA的企业人力人事资源管理系统OA,部门、人员、薪资、招聘功能齐全的项目【数据库设计、源码、开题报告】

数据库脚本下载地址&#xff1a; https://download.csdn.net/download/itrjxxs_com/86427647 摘要 随着知识经济的崛起&#xff0c;掌握了高新技术和具有现代管理理念的人已成为经济发展的原动力。一个企业要在市场竞争中取得优势地位&#xff0c;依靠的主要不是物质资源&…

FCPX插件:Stupid Raisins Title Pop (78个动画标题+2个额外背景)

fcpx插件&#xff1a;Stupid RAIsins Title Pop for Mac激活版是兼容Final Cut Pro x的一款动画标题字幕插件。Stupid RAIsins Title Pop mac版包含78个fcpx动画标题2个额外背景&#xff0c;可以快速拖放编辑&#xff0c;使用基本&#xff0c;连续&#xff0c;充满活力&#xf…

电容笔做的比较好的品牌有哪些?高性价比电容笔测评

现在&#xff0c;高技术在推动数字产品的发展&#xff0c;同时也在增加。不管是工作还是学习&#xff0c;大屏幕可以使图像更加清晰。无论现在或将来&#xff0c;平板电脑都将成为我们日常生活中不可或缺的一部分。一款简单易用的电容笔&#xff0c;能够极大地提高我们的工作效…

7.9 用户接口

目录 一 序言 二 应用层实现 三 内核层实现 一 序言 这一部分&#xff0c;我们简单介绍一下用户接口。从本质上讲&#xff0c;操作系统就是对计算机硬件资源进行一个封装&#xff0c;从而方便用户来使用。前面几部分&#xff0c;我们重点介绍了操作系统自身的一些功能&#…