Android 13 - Media框架(26)- OMXNodeInstance(三)

news2024/9/28 17:24:22

上一节我们了解了OMXNodeInstance中的端口定义,这一节我们一起来学习ACodec、OMXNode、OMX 组件使用的 buffer 到底是怎么分配出来的,以及如何关联起来的。(我们只会去了解 graphic buffer的创建、input bytebuffer的创建、secure buffer的创建)

1、ACodec::allocateOutputMetadataBuffers

我们先一起来回忆一下,ACodec在使用surface的情况下给ouput port分配buffer使用的是allocateOutputMetadataBuffers方法,这时候真正的graphic buffer还未分配出来,BufferInfo的状态还是OWNED_BY_NATIVE_WINDOW(意为 buffer 还在 native 中)。

    for (OMX_U32 i = 0; i < bufferCount; i++) {
        BufferInfo info;
        info.mStatus = BufferInfo::OWNED_BY_NATIVE_WINDOW;
        info.mFenceFd = -1;
        info.mRenderInfo = NULL;
        info.mGraphicBuffer = NULL;
        info.mNewGraphicBuffer = false;
        info.mDequeuedAt = mDequeueCounter;
		// 创建 meta data
        info.mData = new MediaCodecBuffer(mOutputFormat, new ABuffer(bufferSize));

        // Initialize fence fd to -1 to avoid warning in freeBuffer().
        ((VideoNativeMetadata *)info.mData->base())->nFenceFd = -1;

        info.mCodecData = info.mData;

        err = mOMXNode->useBuffer(kPortIndexOutput, OMXBuffer::sPreset, &info.mBufferID);
        mBuffers[kPortIndexOutput].push(info);

        ALOGV("[%s] allocated meta buffer with ID %u",
                mComponentName.c_str(), info.mBufferID);
    }

这里BufferInfo是作为分配出来的buffer的索引,分配的 mCodecData (Meta data) 没有任何作用,仅仅是起着占位的作用。

ACodec 需要给这个 BufferInfo 打上索引,调用useBuffer时,传入的OMXBuffer类型是kBufferTypePreset,这个内容可以到 OMXBuffer.cpp 中查询。

1.1、useBuffer

进入到 OMXNodeInstance 中,首先就会检查 port mode,我们可以看到,如果port mode不是dynamic的类型,会直接报错。

status_t OMXNodeInstance::useBuffer(
        OMX_U32 portIndex, const OMXBuffer &omxBuffer, IOMX::buffer_id *buffer) {
    switch (omxBuffer.mBufferType) {
        case OMXBuffer::kBufferTypePreset: {
            if (mPortMode[portIndex] != IOMX::kPortModeDynamicANWBuffer
                    && mPortMode[portIndex] != IOMX::kPortModeDynamicNativeHandle) {
                break;
            }
            return useBuffer_l(portIndex, NULL, NULL, buffer);
        }
    }

kBufferTypePreset 在这里应该表示的是占位的意思。

我们不要被 useBuffer 的参数 buffer 迷惑了,它其实是一个 int 类型。判断完成后就会进入到 useBuffer_l 中。

1.2、useBuffer_l

进入下面的内容之前我们首先要了解的是,之所以方法名为 use buffer,指的是 OMX 组件端口使用的 buffer 是在其他地方分配的,OMX 组件仅仅只是使用。

useBuffer_l 的设计思路其实和之前的一篇文章中的 enableNativeBuffers_l 类似,它是把几个不同的设定写到一个函数体当中,用入参来判断当前走的是什么设定。useBuffer_l 有两个参数,但是它其实有三种可能的设定,一种是IMemory,另一种是 IHidlMemory,最后一个是两个参数都为 NULL,表示是一个占位

status_t OMXNodeInstance::useBuffer_l(
        OMX_U32 portIndex, const sp<IMemory> &params,
        const sp<IHidlMemory> &hParams, IOMX::buffer_id *buffer) {
    BufferMeta *buffer_meta;
    OMX_BUFFERHEADERTYPE *header;
    OMX_ERRORTYPE err = OMX_ErrorNone;
    // 判断是否使用 meta data
    bool isMetadata = mMetadataType[portIndex] != kMetadataBufferTypeInvalid;
	// 如果使用graphic buffer但是不用meta data则直接报错
    if (!isMetadata && mGraphicBufferEnabled[portIndex]) {
        ALOGE("b/62948670");
        android_errorWriteLog(0x534e4554, "62948670");
        return INVALID_OPERATION;
    }
	// 检查参数,不能同时设定两个参数
    size_t paramsSize;
    void* paramsPointer;
    if (params != NULL && hParams != NULL) {
        return BAD_VALUE;
    }
    // 解析传递的buffer的指针以及buffer的大小,如果什么都没有传,那么指针为NULL
    if (params != NULL) {
        // TODO: Using unsecurePointer() has some associated security pitfalls
        //       (see declaration for details).
        //       Either document why it is safe in this case or address the
        //       issue (e.g. by copying).
        paramsPointer = params->unsecurePointer();
        paramsSize = params->size();
    } else if (hParams != NULL) {
        paramsPointer = hParams->getPointer();
        paramsSize = hParams->getSize();
    } else {
        paramsPointer = nullptr;
    }
	// 使用的buffer的大小
    OMX_U32 allottedSize;
    // metadata mode下,设定的是占位符,OMXNode 与 OMX 组件之间通过 metadata交流,metadata buffer由 OMXNode分配
    // 这里需要根据不同的类型分配不同类型的meta data,确定metadata size
    if (isMetadata) {
        if (mMetadataType[portIndex] == kMetadataBufferTypeGrallocSource) {
            allottedSize = sizeof(VideoGrallocMetadata);
        } else if (mMetadataType[portIndex] == kMetadataBufferTypeANWBuffer) {
        	// 
            allottedSize = sizeof(VideoNativeMetadata);
        } else if (mMetadataType[portIndex] == kMetadataBufferTypeNativeHandleSource) {
            allottedSize = sizeof(VideoNativeHandleMetadata);
        } else {
            return BAD_VALUE;
        }
    } else {
    	// NULL 只允许出现在 meta mode 当中
    	// 如果不使用meta data 并且 没有传 buffer下来,那么直接报错
        // NULL params is allowed only in metadata mode.
        if (paramsPointer == nullptr) {
            ALOGE("b/25884056");
            return BAD_VALUE;
        }
        allottedSize = paramsSize;
    }
	// 是否是与graphic搭配使用的metadata
    bool isOutputGraphicMetadata = (portIndex == kPortIndexOutput) &&
            (mMetadataType[portIndex] == kMetadataBufferTypeGrallocSource ||
                    mMetadataType[portIndex] == kMetadataBufferTypeANWBuffer);
	// 是否需要分配buffer
    uint32_t requiresAllocateBufferBit =
        (portIndex == kPortIndexInput)
            ? kRequiresAllocateBufferOnInputPorts
            : kRequiresAllocateBufferOnOutputPorts;

	// quirks 模式下,如果不是使用的 meta data mode,则需要让 OMX 组件分配buffer
    // we use useBuffer for output metadata regardless of quirks
    if (!isOutputGraphicMetadata && (mQuirks & requiresAllocateBufferBit)) {
        // metadata buffers are not connected cross process; only copy if not meta.
        // quirks 模式下,将 OMX 组件分配的buffer 和 上层 buffer相关绑定,进行数据拷贝
        // 这应该是很没有效率的一种行为
        buffer_meta = new BufferMeta(
                    params, hParams, portIndex, !isMetadata /* copy */, NULL /* data */);

        err = OMX_AllocateBuffer(
                mHandle, &header, portIndex, buffer_meta, allottedSize);

        if (err != OMX_ErrorNone) {
            CLOG_ERROR(allocateBuffer, err,
                    SIMPLE_BUFFER(portIndex, (size_t)allottedSize,
                            paramsPointer));
        }
    } else {
        OMX_U8 *data = NULL;
		
        // metadata buffers are not connected cross process
        // use a backup buffer instead of the actual buffer
        if (isMetadata) {
        	// 为 meta data buffer 分配空间
            data = new (std::nothrow) OMX_U8[allottedSize];
            if (data == NULL) {
                return NO_MEMORY;
            }
            memset(data, 0, allottedSize);
			// 将 meta data buffer 封装到 BufferMeta 当中
            buffer_meta = new BufferMeta(
                    params, hParams, portIndex, false /* copy */, data);
        } else {
        	// 如果不是 meta data mode,直接使用buffer指针
            data = static_cast<OMX_U8 *>(paramsPointer);
			// 将 buffer 封装到 BufferMeta 当中
            buffer_meta = new BufferMeta(
                    params, hParams, portIndex, false /* copy */, NULL);
        }
		// 直接把创建的 BufferMeta 传递给 OMX 组件使用,不需要进行buffer拷贝
        err = OMX_UseBuffer(
                mHandle, &header, portIndex, buffer_meta,
                allottedSize, data);

        if (err != OMX_ErrorNone) {
            CLOG_ERROR(useBuffer, err, SIMPLE_BUFFER(
                    portIndex, (size_t)allottedSize, data));
        }
    }

    if (err != OMX_ErrorNone) {
        delete buffer_meta;
        buffer_meta = NULL;

        *buffer = 0;

        return StatusFromOMXError(err);
    }
	// 检查创建的buffer header中的上层数据是否和 创建的 buffer meta 相同
    CHECK_EQ(header->pAppPrivate, buffer_meta);
	// 为 buffer header 创建 index
    *buffer = makeBufferID(header);

    addActiveBuffer(portIndex, *buffer);

    sp<IOMXBufferSource> bufferSource(getBufferSource());
    if (bufferSource != NULL && portIndex == kPortIndexInput) {
        bufferSource->onInputBufferAdded(*buffer);
    }

    CLOG_BUFFER(useBuffer, NEW_BUFFER_FMT(
            *buffer, portIndex, "%u(%zu)@%p", allottedSize, paramsSize, paramsPointer));
    return OK;
}

useBuffer 的内容很长,里面有很多判断,很多同学可能读不太明白里面到底想干什么,这里我们就来解析一下:

  1. 首先,它会判断configure阶段配置的一些内容, 比如说metadata mode需要和graphic buffer共同使用;IMemory 参数和 IHidlMemory 参数不能同时设定;

  2. 接下来会分为两种情况,

    • metadata mode,也就是我们上面讨论的传下来的buffer类型是kBufferTypePreset的情况(占位),这种情况上层没有真正传buffer下来,OMX组件和上层通过metadata进行数据传递,所以usebuffer中会根据configure的配置获取需要传递的metadata类型,计算metadata所需的size;
    • preset byte buffer,这种就是普通的non secure buffer,可以通过指针读写buffer中的数据,use buffer需要获取buffer的地址以及大小;
  3. 判断是否用quirks,这个东西我们在创建OMXNodeInstance时有看见过,它是在xml中配置的,一旦有了这个配置,那么只要端口不是metadata mode,则需要让OMX组件分配buffer,上层传下来的buffer会与OMX分配的buffer做绑定,创建一个BufferMeta,上层传的数据会在这里做拷贝,写入到OMX组件分配的buffer当中。(这里是调用OMX_AllocateBuffer的地方之一)
    请添加图片描述

  4. 如果不使用quirks,那么数据就不需要在OMXNodeInstance层做中转了。

    • metadata mode,分配meta data大小的buffer,创建出BufferMeta;
    • preset byte buffer,直接用上层传的buffer创建BufferMeta;
    • 调用OMX_UseBuffer,将 BufferMeta 和 metadata/buffer 注册到 OMX 组件当中,并且创建出 OMX_BUFFERHEADERTYPE
  5. 为创建出的 OMX_BUFFERHEADERTYPE 分配 id;

到这里,useBuffer_l 的分析就完成了,之所以要在 OMXNodeInstance 这一层创建 BufferMeta,可能就是为了记录上层传下来的所有buffer,或者是做数据中转用的。

quirks模式会在 OMXNodeInstance 层多做一次数据拷贝,现在已经被弃用了。

再放上两张示意图:

一张是 preset byte buffer:
请添加图片描述

另一张是meta data,可以看到 meta data 模式下,output buffer 不会有任何东西回传给上层。
请添加图片描述
OMXNodeInstance 收到返回的 OMX_BUFFERHEADERTYPE 后会为其分配 id:

IOMX::buffer_id OMXNodeInstance::makeBufferID(OMX_BUFFERHEADERTYPE *bufferHeader) {
    if (bufferHeader == NULL) {
        return 0;
    }
    Mutex::Autolock autoLock(mBufferIDLock);
    IOMX::buffer_id buffer;
    do { // handle the very unlikely case of ID overflow
        if (++mBufferIDCount == 0) {
            ++mBufferIDCount;
        }
        buffer = (IOMX::buffer_id)mBufferIDCount;
    } while (mBufferIDToBufferHeader.indexOfKey(buffer) >= 0);
    mBufferIDToBufferHeader.add(buffer, bufferHeader);
    mBufferHeaderToBufferID.add(bufferHeader, buffer);
    return buffer;
}

可以看到一个 OMXNodeInstance 的buffer id用一个mBufferIDCount 成员来管理,所以input buffer和ouput buffer id 是连续的。

makeBufferID 还为 id 和 bufferheader 建立起一个映射,便于快速完成查找。

1.2、allocateSecureBuffer

allocate buffer 是让 OMX 组件分配需要使用的 buffer,在ACodec中,只有在使用 secure buffer时,会让omx组件分配buffer handle。secure buffer是一块受保护的buffer,我们无法读取到内部的资料,内存拷贝需要通过操作 handle 来完成,这里我们要看 handle 是如何创建的:

                BufferInfo info;
                info.mStatus = BufferInfo::OWNED_BY_US;
                info.mFenceFd = -1;
                info.mRenderInfo = NULL;
                info.mGraphicBuffer = NULL;
                info.mNewGraphicBuffer = false;

                if (mode == IOMX::kPortModePresetSecureBuffer) {
                    void *ptr = NULL;
                    sp<NativeHandle> native_handle;
                    err = mOMXNode->allocateSecureBuffer(
                            portIndex, bufSize, &info.mBufferID,
                            &ptr, &native_handle);

                    info.mData = (native_handle == NULL)
                            ? new SecureBuffer(format, ptr, bufSize)
                            : new SecureBuffer(format, native_handle, bufSize);
                    info.mCodecData = info.mData;
                }

可以看到,直接就调用 OMXNode 的 allocateSecureBuffer 方法了,这块buffer将由native层创建,所以 allocateSecureBuffer 将会调用组件的OMX_AllocateBuffer方法:

status_t OMXNodeInstance::allocateSecureBuffer(
        OMX_U32 portIndex, size_t size, IOMX::buffer_id *buffer,
        void **buffer_data, sp<NativeHandle> *native_handle) {
    if (buffer == NULL || buffer_data == NULL || native_handle == NULL) {
        ALOGE("b/25884056");
        return BAD_VALUE;
    }

    if (portIndex >= NELEM(mSecureBufferType)) {
        ALOGE("b/31385713, portIndex(%u)", portIndex);
        android_errorWriteLog(0x534e4554, "31385713");
        return BAD_VALUE;
    }

    Mutex::Autolock autoLock(mLock);
    if (mHandle == NULL) {
        return DEAD_OBJECT;
    }

    if (!mSailed) {
        ALOGE("b/35467458");
        android_errorWriteLog(0x534e4554, "35467458");
        return BAD_VALUE;
    }
    // 检查 port mode
    if (mPortMode[portIndex] != IOMX::kPortModePresetSecureBuffer) {
        ALOGE("b/77486542");
        android_errorWriteLog(0x534e4554, "77486542");
        return INVALID_OPERATION;
    }
    // 创建 BufferMeta,这里的 bufferMeta没有起实质性作用
    BufferMeta *buffer_meta = new BufferMeta(portIndex);

    OMX_BUFFERHEADERTYPE *header;
	// 调用 OMX 组件分配 secure buffer
    OMX_ERRORTYPE err = OMX_AllocateBuffer(
            mHandle, &header, portIndex, buffer_meta, size);

    if (err != OMX_ErrorNone) {
        CLOG_ERROR(allocateBuffer, err, BUFFER_FMT(portIndex, "%zu@", size));
        delete buffer_meta;
        buffer_meta = NULL;

        *buffer = 0;

        return StatusFromOMXError(err);
    }

    CHECK_EQ(header->pAppPrivate, buffer_meta);
	// 为 bufferheader 分配id
    *buffer = makeBufferID(header);
    // OMX 组件创建的 secure buffer handle 将存储在 buffer header 的 pBuffer 中
    if (mSecureBufferType[portIndex] == kSecureBufferTypeNativeHandle) {
        *buffer_data = NULL;
        // 将handle封装为 native handle
        *native_handle = NativeHandle::create(
                (native_handle_t *)header->pBuffer, false /* ownsHandle */);
    } else {
        *buffer_data = header->pBuffer;
        *native_handle = NULL;
    }

    addActiveBuffer(portIndex, *buffer);

    sp<IOMXBufferSource> bufferSource(getBufferSource());
    if (bufferSource != NULL && portIndex == kPortIndexInput) {
        bufferSource->onInputBufferAdded(*buffer);
    }
    CLOG_BUFFER(allocateSecureBuffer, NEW_BUFFER_FMT(
            *buffer, portIndex, "%zu@%p:%p", size, *buffer_data,
            *native_handle == NULL ? NULL : (*native_handle)->handle()));

    return OK;
}

allocateSecureBuffer 和 useBuffer 的内容大同小异,只是调用 OMX_AllocateBuffer 回传的 buffer header 中存储有 OMX 组件分配的 secure buffer header,这个buffer handle将会被封装成为 NativeHandle 回传给上层。OMXNodeInstance 中的 BufferMeta 将不会存储任何相关的资讯。
请添加图片描述

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

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

相关文章

技术阅读周刊第十一期

技术阅读周刊&#xff0c;每周更新。 历史更新 20231124&#xff1a;第七期20231201&#xff1a;第八期20231215&#xff1a;第十‍期 A Comprehensive guide to Spring Boot 3.2 with Java 21, Virtual Threads, Spring Security, PostgreSQL, Flyway, Caching, Micrometer, O…

61.SVN版本控制系统

SVN&#xff08;Subversion&#xff09;是一种集中式版本控制系统&#xff0c;它有一个中央仓库用于存储代码库的完整历史记录。相对于分布式版本控制系统&#xff08;例如 Git&#xff09;&#xff0c;SVN 不支持本地仓库。 一、SVN 安装。 &#xff08;1&#xff09;在windo…

Sharding JDBC 学习了解 - 总览和概念

第一部分&#xff1a;概述 ShardingSphere是一个由一套分布式数据库中间件解决方案组成的开源生态圈&#xff0c;包括Sharding-JDBC、Sharding-Proxy和Sharding-Proxy 3个独立产品。它们都提供了数据分片、分布式事务、数据库编排等功能&#xff0c;适用于Java同构、异构语言、…

【音视频】remb twcc原理

目录 twcc简介 WebRTC REMB 参考文档 twcc简介 TWCC全称是Transport wide Congestion Control&#xff0c;是webrtc的最新的拥塞控制算法。其原理是在接收端保存数据包状态&#xff0c;然后构造RTCP包反馈给发送端&#xff0c;反馈信息包括包到达时间、丢包状态等&#xff…

开源路由工具NextTrace Web

什么是 NextTrace &#xff1f; NextTrace 是一个由 Golang 语言开发的开源可视路由工具。它不仅支持 IPv4 和 IPv6 协议&#xff0c;而且在轻量级的同时&#xff0c;提供了快速、准确的路由信息。不论您是网络管理员、开发者还是普通用户&#xff0c;NextTrace 都是您网络问题…

【English】水果单词小小汇总~~

废物研究生&#xff0c;只要不搞科研干啥都是开心的&#xff0c;啊啊啊啊啊科研要命。作为一个水果怪&#xff08;每天不吃水果就要命的那种哈哈哈哈&#xff09;突然发现竟然就知道什么apple、banana、orange&#xff01;惭愧惭愧&#xff0c;正好兴致正浓&#xff0c;来整理一…

编写第一个APP自动化脚本 appium_helloworld ,将脚本跑起来

一、前置说明 我们把学习 Appium 的第一个脚本称为 appium_helloworld&#xff0c;它用于展示 Appium 的基本用法&#xff0c;验证配置和环境是否正确。 Appium 自动化操作 APP 的基本流程&#xff08;Android平台&#xff09;&#xff1a; 启动 Appium Serveradb 连接设备&…

分类预测 | Matlab实现MTF-CNN-Mutilhead-Attention基于马尔可夫转移场-卷积神经网络融合多头注意力多特征数据分类预测

分类预测 | Matlab实现MTF-CNN-Mutilhead-Attention基于马尔可夫转移场-卷积神经网络融合多头注意力多特征数据分类预测 目录 分类预测 | Matlab实现MTF-CNN-Mutilhead-Attention基于马尔可夫转移场-卷积神经网络融合多头注意力多特征数据分类预测分类效果基本描述程序设计参考…

Python的基本数据类型和数据类型的转换

TOC 数据类型 类型查看 type 可以使用type内置函数查看变量所指的对象类型 a1 b1.0 c"1" d1, e[1] f{1:1} g{1}print(type(a)) print(type(b)) print(type(c)) print(type(d)) print(type(e)) print(type(f)) print(type(g))isinstance **如字面意思,isinstance()…

Flask+Mysql项目docker-compose部署(Pythondocker-compose详细步骤)

一、前言 环境&#xff1a; Linux、docker、docker-compose、python(Flask)、Mysql 简介&#xff1a; 简单使用Flask框架写的查询Mysql数据接口&#xff0c;使用docker部署&#xff0c;shell脚本启动 优势&#xff1a; 采用docker方式部署更加便于维护&#xff0c;更加简单快…

多维时序 | MATLAB实CNN-Mutilhead-Attention卷积神经网络融合多头注意力机制多变量时间序列预测

多维时序 | MATLAB实CNN-Mutilhead-Attention卷积神经网络融合多头注意力机制多变量时间序列预测 目录 多维时序 | MATLAB实CNN-Mutilhead-Attention卷积神经网络融合多头注意力机制多变量时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 多维时序 | …

CUMT--Java复习--文件及IO流

目录 一、文件 1、文件系统和路径 2、File类 3、FilenameFilter接口 二、IO流 1、流的分类 2、流的体系结构 三、字节流 1、InputStream 2、OutputStream 四、字符流 1、Reader 2、Writer 五、过滤流和转换流 1、过滤流 2、转换流 六、序列化 1、对象序列化…

继承易错总结

1.继承会将所有的成员继承下来&#xff0c;但是继承方式限定的是继承下来成员的可见类型(如果是private继承&#xff0c;那么他不论哪里都是不可见的&#xff1b;如果是protected继承在类中是可见的&#xff0c;在类外是不可见的&#xff1b;如果是public继承&#xff0c;在任何…

[机器人-2]:开源MIT Min cheetah机械狗设计(二):机械结构设计

目录 1、四肢朝向的选择 2、电机布局形式的选择 3、电机的选型及测试&#xff08;非常重要&#xff09; 4、结构优化 5、尺寸效应 6、其他 1、四肢朝向的选择 机械狗的结构设计&#xff0c;第一个摆在我们面前的就说四肢的朝向问题&#xff0c;如下图&#xff0c;我们是…

白龙地铁消费项目(地铁消费系统,包括用户端、管理端)

大一学的C#可视化项目文件&#xff0c;所有功能均可使用。可以直接下载 下方是演示照片

Vue 3 Composition API:让组件开发更高效、灵活(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

修改第三方npm包

文章目录 一、前言二、补丁方案2.1、patch-package2.2、pnpm patch 三、换日方案四、总结五、最后 一、前言 在开发过程中&#xff0c;发现某个npm包有Bug&#xff0c;应该怎么办&#xff1f;可以试试下面这2种方案&#xff1a; 代码量少&#xff0c;可以直接修改npm包代码的&…

【计算机四级(网络工程师)笔记】操作系统运行机制

目录 一、中央处理器&#xff08;CPU&#xff09; 1.1CPU的状态 1.2指令分类 二、寄存器 2.1寄存器分类 2.2程序状态字&#xff08;PSW&#xff09; 三、系统调用 3.1系统调用与一般过程调用的区别 3.2系统调用的分类 四、中断与异常 4.1中断 4.2异常 &#x1f308;嗨&#xff…

【LeetCode】每日一题 2023_12_25 不浪费原料的汉堡制作方案(数学,解二元一次方程)

文章目录 刷题前唠嗑题目&#xff1a;不浪费原料的汉堡制作方案题目描述代码与解题思路 结语 刷题前唠嗑 LeetCode&#xff1f;启动&#xff01;&#xff01;&#xff01; 题目&#xff1a;不浪费原料的汉堡制作方案 题目链接&#xff1a;1276. 不浪费原料的汉堡制作方案 题…

详解—数据结构—<常用排序>基本实现和代码分析

目录 一.排序的概念及其运用 1.1排序的概念 1.2排序运用​编辑 1.3 常见的排序算法​编辑 二.常见排序算法的实现 2.1 插入排序 2.1.1基本思想&#xff1a; 2.1.2直接插入排序&#xff1a; 2.1.3 希尔排序( 缩小增量排序 ) 2.2 选择排序 2.2.1基本思想&#xff1a; …