Android 进阶——Binder IPC之Native 服务的启动及代理对象的获取详解(六)

news2024/11/27 12:34:52

文章大纲

  • 引言
  • 一、Binder线程池的启动
    • 1、ProcessState#startThreadPool函数来启动线程池
    • 2、IPCThreadState#joinThreadPool 将当前线程进入到线程池中去等待和处理IPC请求
  • 二、Service 代理对象的获取
    • 1、获取Service Manager 代理对象BpServiceManager
    • 2、调用BpServiceManager#getService
      • 2.1、Service Manager 处理CHECK_SERVICE_TRANSACTION
      • 2.2、Binder 驱动为Client进程创建对应的Service组件的Binder引用对象
      • 2.3、Binder库为Client进程创建Binder代理对象
    • 3、asInterface "转换"

引言

我们都知道Binder IPC可以支持并发访问和响应,但是你知道是为什么么?

一、Binder线程池的启动

Binder线程池的启动主要就是依赖以下两个函数。一个进程通过调用其内部的ProcessState对象的成员startThreadPool函数来启动线程池,通过IPCThreadState对象的成员函数joinThreadPool,将启动的线程加入到Binder线程池并注册成为一个Binder线程。当线程启动之后,会不断与binder驱动进行通信,读取本线程和本进程中待处理的事务,如果接受到Binder驱动的请求,则处理之。而当进程繁忙时, Binder驱动会向目标进程发送一个BR_SPAWN_LOOPER命令,申请一个新的进程。

 ProcessState::self()->startThreadPool();
 IPCThreadState::self()->joinThreadPool();

当Binder驱动接到Server发来的IPC请求时,就会回复一个BR_SPAWN_LOOPER通知创建Binder线程用于处理这个请求,当threadLoop函数返回false时则会被回收相应的对象。

1、ProcessState#startThreadPool函数来启动线程池

ProcessState在同一进程内是唯一的,主要用于初始化Binder设备,而IPCThreadState 用于与Binder驱动通信。

通常Service组件注册完毕之后对应的进程就会自动开启一个Binder线程池来处理Client进程发送过来的IPC请求。通常进程是通过调用其内部的ProcessState对象的startThreadPool函数来启动的。

\frameworks\native\libs\binder\ProcessState.cpp

void ProcessState::startThreadPool()
{
    AutoMutex _l(mLock);
    if (!mThreadPoolStarted) {//防止重复启动线程池
        mThreadPoolStarted = true;
        spawnPooledThread(true);
    }
}

从上面我们可以得知一个进程中有且只有一个Binder线程池且只启动一次,接着是真正通过ProcessState#spawnPooledThread来启动线程池的。

isMain为true表示是线程是进程主动创建并加入到它Binder线程池的,对应的是BC_ENTER_LOOPER协议,而Binder驱动请求进程创建的线程则isMain为false,对应的是BC_REGISTER_LOOPER。

void ProcessState::spawnPooledThread(bool isMain)
{
    if (mThreadPoolStarted) {
        String8 name = makeBinderThreadName();//Binder线程的名称
        sp<Thread> t = new PoolThread(isMain);
        t->run(name.string());
    }
}

String8 ProcessState::makeBinderThreadName() {
    int32_t s = android_atomic_add(1, &mThreadPoolSeq);
    pid_t pid = getpid();
    String8 name;
    name.appendFormat("Binder:%d_%X", pid, s);
    return name;
} 

主要就是创建PoolThread对象并调用其run函数启动一个新线程,而PoolThread 继承Thread并重写了线程入口函数threadLoop

class PoolThread : public Thread
{
public:
    PoolThread(bool isMain)
        : mIsMain(isMain)
    {
    }
    
protected:
    virtual bool threadLoop()
    {
        IPCThreadState::self()->joinThreadPool(mIsMain);
        return false;
    }
    
    const bool mIsMain;
};

接着IPCThreadState#joinThreadPool 将当前线程进入到线程池中去等待和处理IPC请求。

Binder驱动接到IPC 请求的时候就会发一个 BR_SPAWN_LOOPE ,然后Server 端就创建一个线程来处理,但是一个fd 最多绑定15线程。

2、IPCThreadState#joinThreadPool 将当前线程进入到线程池中去等待和处理IPC请求

IPCThreadState#joinThreadPool 函数将当前线程注册到Binder驱动中,成为一个Binder线程,以便Binder驱动可以分发IPC请求给它处理,在进入Binder驱动前talkWithDriver会自动检测IPCThreadState内部的协议输出缓冲区是否还有协议,有则发给BInder驱动处理,处理完成返回后Binder驱动回复返回协议保存至IPCThreadState内部的协议输入缓冲区中,函数getAndExecuteCommand将会去处理。

void IPCThreadState::joinThreadPool(bool isMain)
{
    //将协议写入到IPCThreadState的输出缓冲区
    mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
    set_sched_policy(mMyThreadId, SP_FOREGROUND);
        
    status_t result;
    do {
        processPendingDerefs();
        // now get the next command to be processed, waiting if necessary
        result = getAndExecuteCommand();
        if (result < NO_ERROR && result != TIMED_OUT && result != -ECONNREFUSED && result != -EBADF) {
            abort();
        }
        if(result == TIMED_OUT && !isMain) {
            break;
        }
    } while (result != -ECONNREFUSED && result != -EBADF);
    LOG_THREADPOOL("**** THREAD %p (PID %d) IS LEAVING THE THREAD POOL err=%p\n",
        (void*)pthread_self(), getpid(), (void*)result);
    mOut.writeInt32(BC_EXIT_LOOPER);
    talkWithDriver(false);
}

若talkWithDriver函数长期没有等到IPC请求或者getAndExecuteCommand函数执行超时,且isMain为false时就跳出循环,并向Binder驱动发送BC_EXIT_LOOPER协议告知Binder驱动,前面创建的这个线程它要退出Binder线程池了。

二、Service 代理对象的获取

Server进程和Client进程的通信要依靠Binder驱动来进行。

在这里插入图片描述

Service组件在启动时,会将自己注册到ServiceManager组件中,以便Client组件通过ServiceManager来找到这个Service组件。
在这里插入图片描述
在Binder IPC机制中Client要与Server通信,Client组件必须要先获取Service组件的代理对象,使用的时候很方便直接通过Service Manager代理对象的getService接口就可以获取,前面分析了ServiceManager 自身代理对象的获取,普通Service 组件代理对象获取的流程也大同小异,虽然说要与Binder驱动沟通,但是对于我们开发者来说这一部分的工作被Service Manager 帮忙承担了。

sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder = sm->getService(String16(DETECT_SERVICE_NAME));
sp<IDetectService> bpDetectService = interface_cast <IDetectService>(binder);

总结起来在Client进程(本例中名称为DetectClient)获取一个普通Service 代理对象三部曲为:

1、获取Service Manager 代理对象BpServiceManager

主要就是通过IServiceManager#defaultServiceManager函数获取,预知详情参见前文。

2、调用BpServiceManager#getService

BpServiceManager继承自BpInterface,而BpInterface是一个继承BpRefsBase的模板类,在getService函数中最多会尝试100次来尝试获取名称对应的Service组件代理对象

class BpServiceManager : public BpInterface<IServiceManager>
{
public:
    BpServiceManager(const sp<IBinder>& impl)
        : BpInterface<IServiceManager>(impl)
    {
    }

    virtual sp<IBinder> getService(const String16& name) const
    {
        unsigned n;
        for (n = 0; n < 100; n++){
            if (n > 0) {
                ALOGI("Waiting for service %s...", String8(name).string());
                usleep(50000);
            }
            sp<IBinder> svc = checkService(name);
            if (svc != NULL) return svc;
        }
        return NULL;
    }
	...
};

在checkService函数来真正去尝试获取Service组件代理对象,对比前文addService的核心流程大同小异(通过Service Manager代理对象请求Service Manager进程执行ADD_SERVICE_TRANSACTION),把协议换为CHECK_SERVICE_TRANSACTION协议即可。

    virtual sp<IBinder> checkService( const String16& name) const
    {
        Parcel data, reply;
        //执行writeInterfaceToken函数,拼装Binder协议头
        data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
        data.writeString16(name);
        //remote()即0号引用
        remote()->transact(CHECK_SERVICE_TRANSACTION, data, &reply);
        return reply.readStrongBinder();
    }

换言之,getService 的实现就一个标准的Binder IPC 五部曲:

步骤说明
1DetectClient将DetectService代理对象对应的名称封装为Parcel对象并传递到Binder驱动。
2DetectClient向Binder驱动发送BC_TRANSACTION_COMPLETE协议,Binder驱动根据协议内容找到目标Service Manager进程后回复一个BR_TRANSACTION_COMPLETE告知DetectClient其通信请求已被接受,Client接到BR_TRANSACTION_COMPLETE并处理后,会再次进入到Binder驱动程序中等待Server Manager进程的返回它想要的代理对象的句柄
3Binder驱动在给DetectClient回复一个BR_TRANSACTION_COMPLETE的同时,向Service Manager进程发送一个BR_TRANSACTION返回协议,请求目标Server Manager 进程执行CHECK_SERVICE_TRANSACTION指令。
4Service Manager进程接收BR_TRANSACTION并处理CHECK_SERVICE_TRANSACTION后,给驱动回复一个BC_REPLY协议(包含了DetectClient申请Service组件的信息),驱动根据协议内容为DetectClient进程创建一个对应的Binder引用对象,给Service Manager进程发送一个BR_TRANSACTION_COMPLETE返回协议,告知Service Manager它返回的目标Service 组件信息已经收到了,已接到返回的相应结果,Service Manager接到并处理了BR_TRANSACTION_COMPLETE协议后,一次IPC流程就结束了,接着会重新进入到驱动程序中等待下一次IPC请求。
5Binder驱动在给Service Manager进程发送BR_TRANSACTION_COMPLETE的同时,也会向DetectClient进程发送一个BR_REPLY返回协议(内容包含了前面所创建的Binder引用对象的举止值),DetectClient进程就可以拿着这个句柄来创建一个目标Binder代理对象,同时也代表Service Manager进程已经处理完成IPC请求了并将结果返回给Client

通过这样子就可以拿到了Service 代理对象,简而言之,Client进程通过Service Manager进程拿到目标Service代理对象的引用对象的句柄值,再根据这个句柄值创建对应的Service 代理对象

2.1、Service Manager 处理CHECK_SERVICE_TRANSACTION

前面说过Service Manager是在svcmgr_handle函数中统一处理Client进程的IPC请求。

\frameworks\native\cmds\servicemanager\service_manager.c

int svcmgr_handler(struct binder_state *bs,
                   struct binder_transaction_data *txn,
                   struct binder_io *msg,
                   struct binder_io *reply)
{
    struct svcinfo *si;
    uint16_t *s;
    size_t len;
    uint32_t handle;
    uint32_t strict_policy;
    int allow_isolated;
	...
    switch(txn->code) {
    case SVC_MGR_GET_SERVICE:
    case SVC_MGR_CHECK_SERVICE:
        //从binder_io结构体中的msg数据缓冲区得到代理对象的名称
        s = bio_get_string16(msg, &len);
        if (s == NULL) {
            return -1;
        }
            //获取目标Binder引用对象的句柄
        handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);
        if (!handle)
            break;
        //传入目标引用对象的句柄到Binder驱动,并封装为一个对应的binder_object结构体并下入reply中,Binder驱动拿到的返回值就是reply
        bio_put_ref(reply, handle);
        return 0;

    case SVC_MGR_ADD_SERVICE:
    	...
        break;

    case SVC_MGR_LIST_SERVICES: {
    	...
    }
    bio_put_uint32(reply, 0);
    return 0;
}

从binder_io结构体中的msg数据缓冲区还原出代理对象的名称后,通过do_find_service函数从已注册的Service组件列表svclist中查找与之对应的一个svcinfo结构体,并返回Service 组件的句柄。

在Service Manager中每一个被注册了的Service组件对应一个svcinfo 结构并保存在一个全局队列svclist中,其中svcinfo的成员next指向下一个svcinfo结构体、prt是一个Service组件Binder引用对象的句柄值、name是Service组件名称,death指向一个用于描述死亡接收通知的结构体binder_death。

uint32_t do_find_service(const uint16_t *s, size_t len, uid_t uid, pid_t spid)
{
	// 查找与字符串s对应一个svcinfo结构体
    struct svcinfo *si = find_svc(s, len);
    ...
    if (!svc_can_find(s, len, spid, uid)) {
        return 0;
    }
    return si->handle;
}

2.2、Binder 驱动为Client进程创建对应的Service组件的Binder引用对象

找到Service组件结构体后就得到了其他Binder引用对象的句柄,返回到svcmgr_handler函数中,Service Manager接着通过bio_put_ref函数把句柄传给Binder驱动,Binder驱动就根据它找到对应的Binder引用对象,进而找到该引用对象所指向(引用)的Binder实体对象,最后Binder驱动就在请求该Service组件的代理对象时创建另一个Binder引用对象了。

\frameworks\native\cmds\servicemanager\binder.c

void bio_put_ref(struct binder_io *bio, uint32_t handle)
{
    struct flat_binder_object *obj;
    if (handle)//非0位true
        obj = bio_alloc_obj(bio);
    else
        obj = bio_alloc(bio, sizeof(*obj));

    if (!obj)
        return;
    obj->flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
    obj->type = BINDER_TYPE_HANDLE;
    obj->handle = handle;
    obj->cookie = 0;
}

bio_alloc函数在binder_io结构体的数据缓冲区分配一个未初始化的binder_object结构体并赋值给obj,保存了位置后返回到bio_put_ref函数,继续对创建的结构体进行初始化后

static struct flat_binder_object *bio_alloc_obj(struct binder_io *bio)
{
    struct flat_binder_object *obj;
    obj = bio_alloc(bio, sizeof(*obj));//在binder_io结构体的数据缓冲区分配一个未初始化的binder_object结构体
    //在binder_io的bio偏移数组中分配一个元素来保存binder_object结构体在数据缓冲区的位置,方便Binder驱动获知Service Manager给它返回的IPC结果中包含了一个Binder对象
    if (obj && bio->offs_avail) {
        bio->offs_avail--;
        *bio->offs++ = ((char*) obj) - ((char*) bio->data0);
        return obj;
    }
    bio->flags |= BIO_F_OVERFLOW;
    return NULL;
}

再回到svcmgr_handler函数中将Binder驱动IPC结果保存到binder_io类型结构体reply中了,接着回到binder_parse中最后调用binder_send_reply函数将reply的内容返回给Binder驱动(即向Binder驱动发送BC_REPLY协议)。

Binder驱动是在binder_transaction函数中处理Service Manager 发来的协议

Service Manager进程返回给Binder驱动的IPC结果中包含了BINDER_TYPE_WEAK_HANDLE类型的binder_object结构体(即flat_binder_object 结构体),Binder驱动就对这个结构体进行解包并从其中找到Binder引用对象ref(即指向的是运行在Server进程中的DetectService组件),然后查找是否存在对应的Binder引用对象,存在则直接返回给调用者,不存在则为Client进程创建一个新的Binder引用对象再返回,再经过Binder驱动一系列的处理之后

static void binder_transaction(struct binder_proc *proc,
			       struct binder_thread *thread,
			       struct binder_transaction_data *tr, int reply,
			       binder_size_t extra_buffers_size)
{
	int ret;
	struct binder_transaction *t;
	struct binder_work *tcomplete;
	struct binder_proc *target_proc = NULL;
	struct binder_thread *target_thread = NULL;
	struct binder_node *target_node = NULL;
	struct binder_transaction *in_reply_to = NULL;
	struct binder_buffer_object *last_fixup_obj = NULL;
	struct binder_context *context = proc->context;
	...
	/* TODO: reuse incoming transaction for reply */
	t = kzalloc(sizeof(*t), GFP_KERNEL);

	binder_stats_created(BINDER_STAT_TRANSACTION_COMPLETE);
	...
	for (; offp < off_end; offp++) {
		struct binder_object_header *hdr;
		switch (hdr->type) {
      case BINDER_TYPE_BINDER:
		case BINDER_TYPE_WEAK_BINDER: {
            ...
       }break;
		case BINDER_TYPE_HANDLE:
		case BINDER_TYPE_WEAK_HANDLE: {
			struct flat_binder_object *fp;
			...
		} break;
	}
}

2.3、Binder库为Client进程创建Binder代理对象

最终目标线程target_thread返回到用户空间之后,再次进去到IPCThreadState#waitForResponse函数处理从Binder驱动读取回来的BR_REPLY协议,将Binder 驱动IPC结果封装为Parcel的对象reply中,再次返回到Service Manager代理对象的成员函数checkService中,最后通过Parcel的readStrongBinder函数得到Binder的代理对象。

status_t Parcel::readStrongBinder(sp<IBinder>* val) const
{
    return unflatten_binder(ProcessState::self(), *this, val);
}


status_t unflatten_binder(const sp<ProcessState>& proc,
    const Parcel& in, sp<IBinder>* out)
{
    const flat_binder_object* flat = in.readObject(false);

    if (flat) {
        switch (flat->type) {
            case BINDER_TYPE_BINDER:
                *out = reinterpret_cast<IBinder*>(flat->cookie);
                return finish_unflatten_binder(NULL, *flat, in);
            case BINDER_TYPE_HANDLE:
                *out = proc->getStrongProxyForHandle(flat->handle);
                return finish_unflatten_binder(
                    static_cast<BpBinder*>(out->get()), *flat, in);
        }
    }
    return BAD_TYPE;
}

本质上还是通过ProcessState#getStrongProxyForHandle函数从其内部的Binder代理对象列表中查找是否存在一个与flat_binder_object结构体句柄值handle对应的Binder代理对象,存在则返回,不存在则根据handle创建相应的BInder代理对象再返回,而且flat_binder_object的handle同时是指向了Service组件引用对象,因此ProcessState#getStrongProxyForHandle函数返回的Binder代理对象也指向了Service组件的引用对象。

3、asInterface “转换”

当BpServiceManager#checkService 函数执行完成之后,就将一个Binder代理对象的IBinder接口返回到DetectClient进程的入口函数main,接着最差最后一步——"转换"为具体的Binder代理对象。

interface_cast并不是指针转换,而是利用BpBinder指针,构建出一个新的BpServiceManager对象。宏函数实现的。

android::sp<IDetectService> IDetectService::asInterface(               
            const android::sp<android::IBinder>& obj)                  
    {                                                                  
        android::sp<IDetectService> intr;                                
        if (obj != NULL) {                                             
            intr = static_cast<IDetectService*>(                         
                obj->queryLocalInterface(IDetectService::descriptor).get());              
            if (intr == NULL) {                                        
                intr = new BpIDetectService(obj);                         
            }                                                          
        }                                                              
        return intr;                                                   
    }                     

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

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

相关文章

【算法数据结构体系篇class16】:图 拓扑排序

一、图1&#xff09;由点的集合和边的集合构成2&#xff09;虽然存在有向图和无向图的概念&#xff0c;但实际上都可以用有向图来表达3&#xff09;边上可能带有权值二、图结构的表达1&#xff09;邻接表法 类似哈希表, key就是当前节点。value就是对应有指向的邻接节点2&…

LeetCode——1590. 使数组和能被 P 整除

一、题目 给你一个正整数数组 nums&#xff0c;请你移除 最短 子数组&#xff08;可以为 空&#xff09;&#xff0c;使得剩余元素的 和 能被 p 整除。 不允许 将整个数组都移除。 请你返回你需要移除的最短子数组的长度&#xff0c;如果无法满足题目要求&#xff0c;返回 -1…

PostgreSQL 数据库大小写规则

PostgreSQL 数据库对大小写的处理规则如下&#xff1a; 严格区分大小写默认把所有 SQL 语句都转换成小写再执行加双引号的 SQL 语句除外 如果想要成功执行名称中带有大写字母的对象&#xff0c;则需要把对象名称加上双引号。 验证如下&#xff1a; 想要创建数据库 IZone&…

Windows WSL配置ubuntu环境并登录

一、Windows WSL配置ubuntu环境1、管理员运行cmd&#xff0c;执行以下命令启用“适用于 Linux 的 Windows 子系统”dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart2、管理员运行cmd&#xff0c;执行以下命令启用“虚拟机功…

浅谈ChatGPT

ChatGPT概述 ChatGPT是一种自然语言处理模型&#xff0c;ChatGPT全称Chat Generative Pre-trained Transformer&#xff0c;由OpenAI开发。它使用了基于Transformer的神经网络架构&#xff0c;可以理解和生成自然语言文本。ChatGPT是当前最强大和最先进的预训练语言模型之一&a…

windows应用(vc++2022)MFC基础到实战(3)-基础(3)

目录框架调用代码MFC 对象之间的关系访问其他对象CWinApp&#xff1a;应用程序类initInstance 成员函数运行成员函数OnIdle 成员函数ExitInstance 成员函数CWinApp 和 MFC 应用程序向导特殊 CWinApp 服务Shell 注册文件管理器拖放CWinAppEx 类用于创建 OLE 应用程序的操作顺序用…

【算法题目】【Python】一文刷遍贪心算法题目

文章目录介绍分配饼干K 次取反后最大化的数组和柠檬水找零摆动序列单调递增的数字介绍 贪心算法是一种基于贪心思想的算法&#xff0c;它每次选择当前最优的解决方案&#xff0c;从而得到全局最优解。具体来说&#xff0c;贪心算法在每一步都做出局部最优选择&#xff0c;希望…

Flutter——Isolate主线机制

简述 在DartFlutter应用程序启动时&#xff0c;会启动一个主线程其实也就是Root Isolate,在Root Isolate内部运行一个EventLoop事件循环。所以所有的Dart代码都是运行在Isolate之中的&#xff0c;它就像是机器上的一个小空间&#xff0c;具有自己的私有内存块和一个运行事件循…

Linux下LED灯驱动模板详解

一、地址映射我们先了解MMU&#xff0c;全称是Memory Manage Unit。在老版本的Linux中要求处理器必须有MMU&#xff0c;但是现在Linux内核已经支持五MMU。MMU主要完成的功能如下&#xff1a;1、完成虚拟空间到物理空间的映射2、内存保护&#xff0c;设置存储器的访问权限&#…

【Linux学习笔记】mmap-共享内存进程通信 vs 有名信号量和无名信号量

mmap和信号量实现进程间通信相关mmap1. mmap 使用的注意事项2. mmap的两种映射3. mmap调用接口以及参数4. 使用存储映射区实现父子进程间通信&#xff08;有名&#xff09;父子进程通信的三种方式unlink5. 创建匿名存储映射区6. 通过存储映射区实现非血缘关系进程间的通信信号量…

SiteSucker for macOS + CRACK

SiteSucker for macOS CRACK SiteSucker是一个简单的macOS应用程序&#xff0c;允许您下载网站。它还可以将网站、网页、背景图片、视频和许多其他文件复制到Mac的硬盘上。 SiteSucker是一个Macintosh应用程序&#xff0c;可以自动下载Internet上的网页。它通过将网站的页面、…

遥感影像道路提取算法——SGCN

论文介绍 Split Depth-wise Separable Graph Convolution Network for Road Extraction in Complex Environment from High-resolution Remote Sensing Imagery&#xff08;TGRS&#xff09; 用于从高分辨率遥感图像&#xff08;TGRS&#xff09;中提取复杂环境中道路的分割深…

java对象的创建与内存分配机制

文章目录对象的创建与内存分配机制对象的创建类加载检查分配内存初始化零值设置对象头指向init方法其他&#xff1a;指针压缩对象内存分配对象在栈上分配对象在Eden区中分配大对象直接分配到老年代长期存活的对象进入老年代对象动态年龄判断老年代空间分配担保机制对象的内存回…

Spring的核心模块:Bean的生命周期(内含依赖循环+业务场景)。

Bean的生命周期前言为什么要学习Bean的生命周期前置知识Spring Post-processor&#xff08;后置处理器&#xff09;Aware接口简单介绍Bean的实例化过程为什么会有bean的实例化&#xff1f;过程Bean的初始化阶段为什么会有Bean的初始化&#xff1f;Bean的初始化目的是什么&#…

线性和非线性最小二乘问题的常见解法总结

线性和非线性最小二乘问题的各种解法 先看这篇博客&#xff0c;非常好&#xff1a;线性和非线性最小二乘问题的各种解法 1. 线性最小二乘问题有最优解 但是面对大型稀疏矩阵的时候使用迭代法效率更好。 迭代法 有Jacobi迭代法、 Seidel迭代法及Sor法 【数值分析】Jacobi、Se…

[ubuntu][GCC]gcc源码编译

1.下载gcc安装包 https://ftp.gnu.org/gnu/gcc/ 选择一个需要的gcc版本&#xff0c;下载。 2.下载依赖包 查看下载的gcc安装包中contrib文件夹下的download_prerequisites文件&#xff0c;查看需要的依赖包版本。 根据download_prerequisites中红框位置的信息&#xff0c;在下…

JSON.stringify()的5种使用场景

JSON.stringify() 方法将一个JavaScript对象或值转换为JSON字符串&#xff0c;如果指定了一个replacer函数&#xff0c;则可以选择性地替换值&#xff0c;或者指定的replacer是数组&#xff0c;则可选择性地仅包含数组指定的属性。 语法如下&#xff1a; JSON.stringify(value…

电子技术课程设计基于FPGA的音乐硬件演奏电路的设计与实现

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;乐曲电路 免费获取完整无水印论文报告&#xff08;包含电路图&#xff09; 文章目录一、设计任务要求二、总体框图三、选择器件四、功能模块五、总体设计电路图六、结束语一、设计任务要求 1、课程设计题目 设计一个乐曲演…

【Flutter从入门到入坑之三】Flutter 是如何工作的

【Flutter从入门到入坑之一】Flutter 介绍及安装使用 【Flutter从入门到入坑之二】Dart语言基础概述 【Flutter从入门到入坑之三】Flutter 是如何工作的 本文章主要以界面渲染过程为例&#xff0c;介绍一下 Flutter 是如何工作的。 页面中的各界面元素&#xff08;Widget&…

使数组和能被P整除[同余定理+同余定理变形]

同余定理同余定理变形前言一、使数组和能被P整除二、同余定理变形总结参考资料前言 同余定理非常经典&#xff0c;采用前缀和 map&#xff0c;当两个余数前缀和为一个值时&#xff0c;则中间一段子数组刚好对P整除。但是能否找到前面是否有一段子数组和可以对P整除呐&#xf…