【android10】【binder】【2.servicemanager启动——全源码分析】

news2024/12/24 21:37:44

系列文章目录

 可跳转到下面链接查看下表所有内容https://blog.csdn.net/handsomethefirst/article/details/138226266?spm=1001.2014.3001.5501文章浏览阅读2次。系列文章大全https://blog.csdn.net/handsomethefirst/article/details/138226266?spm=1001.2014.3001.5501


目录

系列文章目录

目录

1.简介

1.1 流程介绍

1.2 时序图

2.源码分析

2.1 servicemanager的启动

2.2 servicemanager.rc

 2.3 main.cpp

2.4 binder_open

2.5 驱动的binder_open

2.6 获取版本号binder_ioctl

2.7 binder_mmap

 2.8 binder_update_page_range

2.9 binder_become_context_manager

2.10 binder_ioctl

2.11 binder_new_node

2.12 binder_loop

2.13 binder_write

2.14 binder_ioctl

2.15 binder_thread_write

2.16 binder_ioctl

2.17 binder_thread_read


1.简介

1.1 流程介绍

第一步:首先init进程启动后会读取各个进程的rc启动文件,然后会去启动servermanager服务。

第二步:在servermanager服务的main函数中,首先会打开/dev/binder驱动,并申请一块内存,通过mmap的进行内存映射,将servermanager服务的用户空间映射到驱动中,从而会减少数据的拷贝。

第三步:becomeContextManager通知驱动成为binder的管理者。

第四步:servermanager服务向binder驱动发出了BC_ENTER_LOOPER命令,告诉binder驱动"本线程要进入循环状态了",接着进入一个for循环不断调用ioctl()读取发来的数据,接着解析这些数据。当没有消息的时候会阻塞。等待消息的到来。

1.2 时序图

  (为了保证流程的完整性,较为模糊,可放大观看)


2.源码分析

2.1 servicemanager的启动

1.在init.rc中,当init进程启动后,会去启动servicemanager、hwservicemanager、vndservicemanager 三个binder的守护进程。

[/system/core/rootdir/init.rc]
on init
    ...
    start servicemanager //启动sericemanager服务
    start hwservicemanager
    start vndservicemanager

2.2 servicemanager.rc

1.init进程启动后,会从指定路径加载进程的rc文件,然后启动servicemanager服务。

service servicemanager /system/bin/servicemanager
    class core animation //服务的类为core和animation
    user system //在启动服务前将用户切换为system
    group system readproc //在启动前将用户组切换为system和readproc
    critical //表明这个服务是至关重要的服务,如果它在四分钟内退出超过四次,则设备将重启进入恢复模式
    onrestart restart apexd //当此服务重启后,重启apexd服务
    onrestart restart audioserver //当此服务重启后,重启audioserver服务
    onrestart restart gatekeeperd //当此服务重启后,重启gatekeeperd服务
    onrestart class_restart main //当此服务重启后,重启所有class为main的服务
    onrestart class_restart hal //当此服务重启后,重启所有class为 hal的服务
    onrestart class_restart early_hal //当此服务重启后,重启所有class为early_hal的服务
    writepid /dev/cpuset/system-background/tasks //写入当前servicemanager进程的pid到/dev/cpuset/system-background/tasks文件中
    shutdown critical //设置Service进程的关闭行为。在关机期间,当前服务在shutdown超时之前不会被关闭。

 2.3 main.cpp

主要作用为:

1.打开/dev/binder驱动,并完成mmap的内存映射。内核中会创建servicemanager对应的proc对象。

2.becomeContextManager通知驱动成为binder的管理者。

3.servermanager服务向binder驱动发出了BC_ENTER_LOOPER命令,告诉binder驱动"本线程要进入循环状态了",接着进入一个for循环不断调用ioctl()读取发来的数据,接着解析这些数据。当没有消息的时候会阻塞。等待消息的到来。

int main(int argc, char** argv)//servicemanager入口,与/dev/binder交互
    
{
    struct binder_state *bs;//存储binder的三个信息
	/*struct binder_state
	{
    int fd;//binder设备的文件描述符
    void *mapped;//binder设备文件映射到进程的用户地址空间(内核地址空间或物理地址)
    size_t mapsize;//内存映射后,系统分配的地址空间大小
	};*/
    union selinux_callback cb;
    char *driver;

    if (argc > 1) {
        driver = argv[1];
    } else {
        driver = "/dev/binder";//dev/binder
    }

    bs = binder_open(driver, 128*1024);//打开/dev/binder,下文有展开。
	
	/**
    if (!bs) {
#ifdef VENDORSERVICEMANAGER
        ALOGW("failed to open binder driver %s\n", driver);
        while (true) {
            sleep(UINT_MAX);
        }
#else
        ALOGE("failed to open binder driver %s\n", driver);
#endif
        return -1;
    }
	*/

    if (binder_become_context_manager(bs)) {//下文有展开。sm成为管理者,只能调用一次
        ALOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }
	/**selinux相关,不分析
    cb.func_audit = audit_callback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);
#ifdef VENDORSERVICEMANAGER
    cb.func_log = selinux_vendor_log_callback;
#else
    cb.func_log = selinux_log_callback;
#endif
    selinux_set_callback(SELINUX_CB_LOG, cb);

#ifdef VENDORSERVICEMANAGER
    sehandle = selinux_android_vendor_service_context_handle();
#else
    sehandle = selinux_android_service_context_handle();
#endif
    selinux_status_open(true);

    if (sehandle == NULL) {
        ALOGE("SELinux: Failed to acquire sehandle. Aborting.\n");
        abort();
    }

    if (getcon(&service_manager_context) != 0) {
        ALOGE("SELinux: Failed to acquire service_manager context. Aborting.\n");
        abort();
    }
	*/


    binder_loop(bs, svcmgr_handler);//下文有展开。binder_loop()会先向binder驱动发出了BC_ENTER_LOOPER命令,
	//告诉binder驱动"本线程要进入循环状态了",接着进入一个for循环不断调用ioctl()读取发来的数据,接着解析这些数据

    return 0;
}

2.4 binder_open

1.调用驱动的binder_open函数。

2.调用驱动的mmap函数,完成内存映射。

struct binder_state *binder_open(const char* driver, size_t mapsize)
{
    struct binder_state *bs;
    struct binder_version vers;

    bs = malloc(sizeof(*bs));//为bs变量分配空间
    if (!bs) {
        errno = ENOMEM;
        return NULL;
    }

    bs->fd = open(driver, O_RDWR | O_CLOEXEC);//下文有展开。driver值是/dev/binder,打开binder驱动,陷入内核,此
	//open对应_open,然后对应binder驱动层的binder_open,binder_open中主要是为/dev/binder这个驱动设备节点,创建
	//对应的sm进程的proc对象,然后通过fd返回。这样就可以和驱动进行通信。
	
	/**
    if (bs->fd < 0) {
        fprintf(stderr,"binder: cannot open %s (%s)\n",
                driver, strerror(errno));
        goto fail_open;
    }
	*/

    if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||
        (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {//下文有展开。获取binder驱动的版本信息,用vers保存,
		//查看内核版本和用户空间版本是否匹配
        fprintf(stderr,
                "binder: kernel driver version (%d) differs from user space version (%d)\n",
                vers.protocol_version, BINDER_CURRENT_PROTOCOL_VERSION);
        goto fail_open;
    }

    bs->mapsize = mapsize;//128k
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);//下文有展开。内存映射。128k的内存地址,
	//申请128k的内存地址用来接收事务,对应binder驱动的binder_mmap函数。
    if (bs->mapped == MAP_FAILED) {
        fprintf(stderr,"binder: cannot map device (%s)\n",
                strerror(errno));
        goto fail_map;
    }

    return bs;

fail_map:
    close(bs->fd);
fail_open:
    free(bs);
    return NULL;
}

2.5 驱动的binder_open

主要作用为:

1.为当前sm服务进程分配保存binder_proc的对象。binder_proc结构体保存的是sm服务的进程的信息。

2.对sm服务的binder_proc的对象进行初始化,如初始化todo队列,设置进程优先级等。

3.将sm服务的binder_proc添加到binder_procs队列中,驱动有一个全局的binder_procss的列表,用于存储所有进程的binder_proc对象。

//主要作用:binder驱动为用户进程创建了用户进程自己的binder——proc实体
static int binder_open(struct inode *nodp, struct file *filp)
{
	struct binder_proc *proc;//proc表示该进程(SM)的binder进程信息。


	proc = kzalloc(sizeof(*proc), GFP_KERNEL);//为binder_proc结构体在分配kernel内存空间
	if (proc == NULL)
		return -ENOMEM;
	//下面是对binder_proc进行初始化,binder_proc用于管理数据的记录体
	get_task_struct(current);
	proc->tsk = current;//current代表当前线程。当前线程的task保存在binder进程的tsk
	INIT_LIST_HEAD(&proc->todo);//初始化to消息列表
	init_waitqueue_head(&proc->wait);//初始化wait列表
	proc->default_priority = task_nice(current);//当前进程的nice值转化为进程优先级

	binder_lock(__func__);

	binder_stats_created(BINDER_STAT_PROC);//BINDER_PROC对象创建+1
	hlist_add_head(&proc->proc_node, &binder_procs);//将当前SM的binder_proc对象添加到binder_procs为表头的队列
	proc->pid = current->group_leader->pid;
	INIT_LIST_HEAD(&proc->delivered_death);
	filp->private_data = proc;//将 sm的proc 保存到filp中,此filp对应的就是fd,这样下次可以通过filp找到此proc

	binder_unlock(__func__);

	if (binder_debugfs_dir_entry_proc) {
		char strbuf[11];
		snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
		proc->debugfs_entry = debugfs_create_file(strbuf, S_IRUGO,
			binder_debugfs_dir_entry_proc, proc, &binder_proc_fops);
	}

	return 0;
}

2.6 获取版本号binder_ioctl

主要作用为:

1.此时用于获取版本号。

//获取binder版本号分析
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int ret;
	struct binder_proc *proc = filp->private_data;
	struct binder_thread *thread;//binder线程
	unsigned int size = _IOC_SIZE(cmd);
	void __user *ubuf = (void __user *)arg;

	trace_binder_ioctl(cmd, arg);

	ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);//binder_stop_on_user_error值是0,
	//binder_stop_on_user_error < 2为假时,休眠,故此处不休眠
	if (ret)
		goto err_unlocked;

	binder_lock(__func__);
	thread = binder_get_thread(proc);//获取当前sm的proc对象的binder_thread
	if (thread == NULL) {
		ret = -ENOMEM;
		goto err;
	}

	switch (cmd) {
	case BINDER_VERSION://获取版本号
		if (size != sizeof(struct binder_version)) {
			ret = -EINVAL;
			goto err;
		}
		if (put_user(BINDER_CURRENT_PROTOCOL_VERSION, &((struct binder_version *)ubuf)->protocol_version)) {
			//将驱动程序的BINDER_CURRENT_PROTOCOL_VERSION变量地址,拷贝sizeof(*ptr)的ubuf用户空间地址。
			//分析,此处是有一次拷贝的。
			ret = -EINVAL;
			goto err;
		}
		break;
	default:
		ret = -EINVAL;
		goto err;
	}
	ret = 0;
err:
	if (thread)
		thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN;
	binder_unlock(__func__);
	wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);//不休眠
	if (ret && ret != -ERESTARTSYS)
		printk(KERN_INFO "binder: %d:%d ioctl %x %lx returned %d\n", proc->pid, current->pid, cmd, arg, ret);
err_unlocked:
	trace_binder_ioctl_done(ret);
	return ret;
}
//进程相关的参数
struct binder_proc {
	struct hlist_node proc_node;//hlist_node表示哈希表中的一个节点。哈希表中的一个节点,用于标记该进程
	struct rb_root threads;// rb_root表示红黑树。Binder线程池每一个Binder进程都有一个线程池,
	//由Binder驱动来维护,Binder线程池中所有线程由一个红黑树来组织,RB树以线程ID为关键字  */
        //上述红黑树的根节点
	struct rb_root nodes;// 一系列Binder实体对象(binder_node)和Binder引用对象(binder_ref) */
    //在用户空间:运行在Server端称为Binder本地对象,运行在Client端称为Binder代理对象*/
    //在内核空间:Binder实体对象用来描述Binder本地对象,Binder引用对象来描述Binder代理对象 */
    //Binder实体对象列表(RB树),关键字 ptr
	struct rb_root refs_by_desc;//记录binder引用, 便于快速查找,binder_ref红黑树的根节点(以handle为key),它是Client在Binder驱动中的体现。
	//Binder引用对象,关键字  desc
	struct rb_root refs_by_node;//记录binder引用, 便于快速查找,binder_ref红黑树的根节点(以ptr为key),它是Client在Binder驱动中的体现,
	//Binder引用对象,关键字  node
	struct list_head waiting_threads; 
	int pid;//进程组pid
	struct task_struct *tsk;//任务控制模块
	const struct cred *cred;
	struct hlist_node deferred_work_node;
	int deferred_work;
	bool is_dead;

	struct list_head todo;//待处理队列,进程每接收到一个通信请求,Binder将其封装成一个工作项,保存在待处理队列to_do中  */

	struct binder_stats stats;
	struct list_head delivered_death;
	int max_threads;// Binder驱动程序最多可以请求进程注册线程的最大数量,
	//进程可以调用ioctl注册线程到Binder驱动程序中,当线程池中没有足够空闲线程来处理事务时,Binder驱动可以主动要求进程注册更多的线程到Binder线程池中 */
    // Binder驱动程序最多可以请求进程注册线程的最大数量
	int requested_threads;
	int requested_threads_started;
	int tmp_ref;
	long default_priority;
	struct dentry *debugfs_entry;
	struct binder_alloc alloc;
	struct binder_context *context;//存储binder_node和binder_context_mgr_uid以及name
	spinlock_t inner_lock;
	spinlock_t outer_lock;
	struct dentry *binderfs_entry;
};

2.7 binder_mmap

主要作用为:

1.get_vm_area分配一个连续的内核虚拟空间,与用户进程虚拟空间大小一致。

2.binder_update_page_range,分配物理页面,同时映射到内核空间和用户进程空间。

即,此时servermanager服务在其内部分配了一块地址映射进入了内核空间。减少了拷贝。

//filp对应fp,vma代表用户地址空间
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)//vma描述了应用程序使用的用户虚拟空间
{
	int ret;
	struct vm_struct *area;//内核虚拟空间
	struct binder_proc *proc = filp->private_data;//取出sm进程对应的binder_proc对象
	const char *failure_string;
	struct binder_buffer *buffer;//用于binder传输数据的缓冲区

	if ((vma->vm_end - vma->vm_start) > SZ_4M)//保证映射内存大小不超过4M
		vma->vm_end = vma->vm_start + SZ_4M;



	if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {//判断是否禁止mmap
		ret = -EPERM;
		failure_string = "bad vm_flags";
		goto err_bad_arg;
	}
	vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;

	mutex_lock(&binder_mmap_lock);
	if (proc->buffer) {//如果buffer不为空,则代表已经mmap过了
		ret = -EBUSY;
		failure_string = "already mapped";
		goto err_already_mapped;
	}

	area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);//分配一个连续的内核虚拟空间,与用户进程虚拟空间大小一致
	if (area == NULL) {
		ret = -ENOMEM;
		failure_string = "get_vm_area";
		goto err_get_vm_area_failed;
	}
	proc->buffer = area->addr;//binder_proc对象的buffer指向内核虚拟空间的地址
	proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;//计算偏移量,地址偏移量=用户空间地址-内核空间地址
	mutex_unlock(&binder_mmap_lock);

	proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);//分配
	//分配物理页的指针数组,大小等于用户虚拟内存/4K,此数组用于指示binder申请的物理页的状态
	if (proc->pages == NULL) {
		ret = -ENOMEM;
		failure_string = "alloc page array";
		goto err_alloc_pages_failed;
	}
	proc->buffer_size = vma->vm_end - vma->vm_start;//计算大小

	vma->vm_ops = &binder_vm_ops;
	vma->vm_private_data = proc;

	if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) {//分配物理页面,同时映射到内核空间和进程空间,
	//目前只分配1个page的物理页
		ret = -ENOMEM;
		failure_string = "alloc small buf";
		goto err_alloc_small_buf_failed;
	}
	buffer = proc->buffer;//binder_buffer对象,指向proc的buffer地址,proc的buffer地址又指向内核虚拟空间的地址
	INIT_LIST_HEAD(&proc->buffers);
	list_add(&buffer->entry, &proc->buffers);//将内核虚拟空间的地址加入到所属进程的buffer队列
	buffer->free = 1;
	binder_insert_free_buffer(proc, buffer);//将空闲的buffer放入proc->free_buffer中
	proc->free_async_space = proc->buffer_size / 2;
	barrier();
	proc->files = get_files_struct(proc->tsk);
	proc->vma = vma;//binder_proc对象保存了用户空间的地址
	proc->vma_vm_mm = vma->vm_mm;

	/*printk(KERN_INFO "binder_mmap: %d %lx-%lx maps %p\n",
		 proc->pid, vma->vm_start, vma->vm_end, proc->buffer);*/
	return 0;
错误跳转
err_alloc_small_buf_failed:
	kfree(proc->pages);
	proc->pages = NULL;
err_alloc_pages_failed:
	mutex_lock(&binder_mmap_lock);
	vfree(proc->buffer);
	proc->buffer = NULL;
err_get_vm_area_failed:
err_already_mapped:
	mutex_unlock(&binder_mmap_lock);
err_bad_arg:
	printk(KERN_ERR "binder_mmap: %d %lx-%lx %s failed %d\n",
	       proc->pid, vma->vm_start, vma->vm_end, failure_string, ret);
	return ret;
}

 2.8 binder_update_page_range

主要作用为:

1.分配物理内存空间。

2.将物理空间映射到内核空间。

3.将物理空间映射到用户进程空间。

当然binder_update_page_range 既可以分配物理页面,也可以释放物理页面。

//binder_update_page_range 主要完成工作:分配物理内存空间,将物理空间映射到内核空间,将物理空间映射到进程空间。
//当然binder_update_page_range 既可以分配物理页面,也可以释放物理页面
static int binder_update_page_range(struct binder_proc *proc, int allocate,
				    void *start, void *end,
				    struct vm_area_struct *vma)
					//参数说明:
					//proc对应的进程的proc对象
					//allocate表示是申请还是释放
					//start,表示内核虚拟空间起始地址
					//end,内核虚拟空间末尾地址
					//用户空间地址
{
	void *page_addr;
	unsigned long user_page_addr;
	struct vm_struct tmp_area;
	struct page **page;
	struct mm_struct *mm;


	if (end <= start)
		return 0;

	trace_binder_update_page_range(proc, allocate, start, end);

	if (vma)
		mm = NULL;
	else
		mm = get_task_mm(proc->tsk);

	if (mm) {
		down_write(&mm->mmap_sem);
		vma = proc->vma;
		if (vma && mm != proc->vma_vm_mm) {
			pr_err("binder: %d: vma mm and task mm mismatch\n",
				proc->pid);
			vma = NULL;
		}
	}

	if (allocate == 0)
		goto free_range;

	if (vma == NULL) {
		printk(KERN_ERR "binder: %d: binder_alloc_buf failed to "
		       "map pages in userspace, no vma\n", proc->pid);
		goto err_no_vma;
	}
	//前面都在判断参数是否合法
	//
	for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
		int ret;
		struct page **page_array_ptr;
		page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];

		BUG_ON(*page);
		//分配物理内存
		*page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
		if (*page == NULL) {
			printk(KERN_ERR "binder: %d: binder_alloc_buf failed "
			       "for page at %p\n", proc->pid, page_addr);
			goto err_alloc_page_failed;
		}
		tmp_area.addr = page_addr;//tmp_area是内核地址,此时指向物理地址
		tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;
		page_array_ptr = page;
		ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr);//映射物理页地址到内核地址tmp_area
		if (ret) {
			printk(KERN_ERR "binder: %d: binder_alloc_buf failed "
			       "to map page at %p in kernel\n",
			       proc->pid, page_addr);
			goto err_map_kernel_failed;
		}
		user_page_addr =
			(uintptr_t)page_addr + proc->user_buffer_offset;
		ret = vm_insert_page(vma, user_page_addr, page[0]);//映射物理页地址到虚拟用户地址空间vma
		if (ret) {
			printk(KERN_ERR "binder: %d: binder_alloc_buf failed "
			       "to map page at %lx in userspace\n",
			       proc->pid, user_page_addr);
			goto err_vm_insert_page_failed;
		}
		/* vm_insert_page does not seem to increment the refcount */
	}
	if (mm) {
		up_write(&mm->mmap_sem);
		mmput(mm);
	}
	return 0;

free_range:
	for (page_addr = end - PAGE_SIZE; page_addr >= start;
	     page_addr -= PAGE_SIZE) {
		page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
		if (vma)
			zap_page_range(vma, (uintptr_t)page_addr +
				proc->user_buffer_offset, PAGE_SIZE, NULL);
err_vm_insert_page_failed:
		unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE);
err_map_kernel_failed:
		__free_page(*page);
		*page = NULL;
err_alloc_page_failed:
		;
	}
err_no_vma:
	if (mm) {
		up_write(&mm->mmap_sem);
		mmput(mm);
	}
	return -ENOMEM;
}

2.9 binder_become_context_manager

主要作用是:

1.告诉驱动注册成为binder的管理者。

int binder_become_context_manager(struct binder_state *bs)
{
    struct flat_binder_object obj;//描述进程中通信过程中传递的Binder实体/引用对象,没用到
    memset(&obj, 0, sizeof(obj));
    obj.flags = FLAT_BINDER_FLAG_TXN_SECURITY_CTX;//flag是安全的上下文

    int result = ioctl(bs->fd, BINDER_SET_CONTEXT_MGR_EXT, &obj);//下文有展开。和驱动沟通,成为binder的管理者,安全的上下文

    // fallback to original method
    if (result != 0) {
        android_errorWriteLog(0x534e4554, "121035042");

        result = ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);//成为管理者
    }
    return result;
}

2.10 binder_ioctl

主要作用为:

1.取出SM进程对应的porc对象。

2.设置binder的context管理者,也就是servicemanager称为守护进程。

3.在驱动中创建一个驱动中的binder实体对象,并赋值给binder_context_mgr_node。

//设置成为binder管理者
//ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);//通知驱动,将ServiecManager设置成为管理者上下文
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
//参数分析:filp是/dev/binder的fd,cmd是BINDER_SET_CONTEXT_MGR,arg是0
{
	int ret;
	struct binder_proc *proc = filp->private_data;//取出SM进程对应的porc对象
	struct binder_thread *thread;//SM进程的binder线程
	unsigned int size = _IOC_SIZE(cmd);
	void __user *ubuf = (void __user *)arg;//__user表示用户空间的指针

	trace_binder_ioctl(cmd, arg);

	ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);//条件成立,不休眠,返回值是0
	//此时binder_stop_on_user_error=0
	if (ret)
		goto err_unlocked;

	binder_lock(__func__);
	thread = binder_get_thread(proc);//获取binder_thread
	if (thread == NULL) {
		ret = -ENOMEM;
		goto err;
	}

	switch (cmd) {
	case BINDER_SET_CONTEXT_MGR://设置binder的context管理者,也就是servicemanager称为守护进程
		if (binder_context_mgr_node != NULL) {//如果不为空,代表已经设置过了
			printk(KERN_ERR "binder: BINDER_SET_CONTEXT_MGR already set\n");
			ret = -EBUSY;
			goto err;
		}
		ret = security_binder_set_context_mgr(proc->tsk);
		if (ret < 0)
			goto err;
		if (binder_context_mgr_uid != -1) {//binder_context_mgr_uid不为-1,代表已经有进程注册过管理者了
			if (binder_context_mgr_uid != current->cred->euid) {//如果和当前进程的用户id不等,则错误
				printk(KERN_ERR "binder: BINDER_SET_"
				       "CONTEXT_MGR bad uid %d != %d\n",
				       current->cred->euid,
				       binder_context_mgr_uid);
				ret = -EPERM;
				goto err;
			}
		} 
		else//未被注册过
			binder_context_mgr_uid = current->cred->euid;//则设置为当前进程的用户id
		binder_context_mgr_node = binder_new_node(proc, NULL, NULL);//创建一个binder实体对象(sm的binder对象),并赋值给binder_context_mgr_node
		//static struct binder_node *binder_context_mgr_node;binder_node代表是一个binder实体
		if (binder_context_mgr_node == NULL) {
			ret = -ENOMEM;
			goto err;
		}
		binder_context_mgr_node->local_weak_refs++;
		binder_context_mgr_node->local_strong_refs++;
		binder_context_mgr_node->has_strong_ref = 1;
		binder_context_mgr_node->has_weak_ref = 1;
		break;
	}
	ret = 0;
err:
	if (thread)
		thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN;
	binder_unlock(__func__);
	wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);//此时不休眠
	if (ret && ret != -ERESTARTSYS)
		printk(KERN_INFO "binder: %d:%d ioctl %x %lx returned %d\n", proc->pid, current->pid, cmd, arg, ret);
err_unlocked:
	trace_binder_ioctl_done(ret);
	return ret;
}

2.11 binder_new_node

1.创建驱动中对应的sm的binder实体对象,并用红黑树保存,此时sm的binder对象作为红黑树的第一个节点。

static struct binder_node *binder_new_node(struct binder_proc *proc,
                       void __user *ptr,
                       void __user *cookie)
{
    struct rb_node **p = &proc->nodes.rb_node;//该进程(SM进程)的nodes会用红黑树保存一系列的binder实体对象(binder_node)和binder引用对象(binder_ref)。
    struct rb_node *parent = NULL;
    struct binder_node *node;//binder_node代表是binder实体

    while (*p) {//proc->nodes.rb_node,代表如果当前进程的红黑树节点不为空,则将当前节点插入红黑树的指定位置。那么我们知道sm刚启动肯定是空的,
        parent = *p;
        node = rb_entry(parent, struct binder_node, rb_node);//返回该节点对应的数据类型

        if (ptr < node->ptr)
            p = &(*p)->rb_left;
        else if (ptr > node->ptr)
            p = &(*p)->rb_right;
        else
            return NULL;
    }

    node = kzalloc(sizeof(*node), GFP_KERNEL);//为binder实体(SM的驱动的binder实体)对象分配内核空间
    if (node == NULL)
        return NULL;
    binder_stats_created(BINDER_STAT_NODE);
    rb_link_node(&node->rb_node, parent, p);
    rb_insert_color(&node->rb_node, &proc->nodes);//将当前binder对象插入红黑树
    node->debug_id = ++binder_last_id;
    node->proc = proc;//binder实体的节点中保存此binder实体对应的进程proc对象,即sm的驱动中的binder实体保存sm进程的proc对象。
    node->ptr = ptr;//此处是null
    node->cookie = cookie;//此处是null
    node->work.type = BINDER_WORK_NODE;
    INIT_LIST_HEAD(&node->work.entry);//创建该binder实体对象的work头节点
    INIT_LIST_HEAD(&node->async_todo);//创建该binder实体对象todo头节点
    binder_debug(BINDER_DEBUG_INTERNAL_REFS,
             "binder: %d:%d node %d u%p c%p created\n",
             proc->pid, current->pid, node->debug_id,
             node->ptr, node->cookie);
    return node;
}

2.12 binder_loop

主要作用为:

1.向binder驱动发送命令协议BC_ENTER_LOOPER,告诉binder驱动"本线程要进入循环状态了"。

2.然后进入死循环,从驱动中读取消息,如果无消息时,会阻塞在此处,等待有消息,然后调用binder_parse去解析信息。

//此函数会在无消息时候,阻塞。
void binder_loop(struct binder_state *bs, binder_handler func)
//参数分析:bs是存储了binder的三个信息。func是回调函数svcmgr_handler
{
    int res;
    struct binder_write_read bwr;//一个结构体
    uint32_t readbuf[32];

    bwr.write_size = 0;
    bwr.write_consumed = 0;
    bwr.write_buffer = 0;

    readbuf[0] = BC_ENTER_LOOPER;//向binder驱动发送命令协议BC_ENTER_LOOPER,告诉binder驱动"本线程要进入循环状态了"
    binder_write(bs, readbuf, sizeof(uint32_t));//下文有展开。只写入,即BC_ENTER_LOOPER

    for (;;) {//死循环,从驱动中读取消息
        bwr.read_size = sizeof(readbuf);//此时是BC_ENTER_LOOPER的大小,32字节
        bwr.read_consumed = 0;//
        bwr.read_buffer = (uintptr_t) readbuf;//数据是BC_ENTER_LOOPER

        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);//无消息时,会阻塞在此处,等待有消息,然后调用binder_parse去解析消息。

        if (res < 0) {
            ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
            break;
        }

        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
        if (res == 0) {
            ALOGE("binder_loop: unexpected reply?!\n");
            break;
        }
        if (res < 0) {
            ALOGE("binder_loop: io error %d %s\n", res, strerror(errno));
            break;
        }
    }
}

2.13 binder_write

主要作用为:

1.构建binder_write_read结构体,然后写入BC_ENTER_LOOPER指令。

int binder_write(struct binder_state *bs, void *data, size_t len)
//参数:bs为保存的信息,data是buffer地址,数据是BC_ENTER_LOOPER,len就是BC_ENTER_LOOPER的长度
{
    struct binder_write_read bwr;
    int res;

    bwr.write_size = len;//写buffer的大小
    bwr.write_consumed = 0;//驱动消费了多少
    bwr.write_buffer = (uintptr_t) data;//写数据
    bwr.read_size = 0;
    bwr.read_consumed = 0;
    bwr.read_buffer = 0;
    res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);//下文有展开。向驱动发送BINDER_WRITE_READ命令。
	//此函数执行完后,bwr的write_consumed的大小变为了4字节,也就是C_ENTER_LOOPER的大小
    if (res < 0) {
        fprintf(stderr,"binder_write: ioctl failed (%s)\n",
                strerror(errno));
    }
    return res;
}

2.14 binder_ioctl

主要作用为:

1.向驱动发送本线程进入循环的消息。

//ioctl(bs->fd, BINDER_WRITE_READ, &bwr);,此时bwr中的数据是BC_ENTER_LOOPER
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int ret;
	struct binder_proc *proc = filp->private_data;//取出此fd的proc对象
	struct binder_thread *thread;//此sm进程对应的binder线程
	unsigned int size = _IOC_SIZE(cmd);
	void __user *ubuf = (void __user *)arg;//是bwr

	trace_binder_ioctl(cmd, arg);

	ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);//不休眠
	if (ret)
		goto err_unlocked;

	binder_lock(__func__);
	thread = binder_get_thread(proc);//获取此proc的binder_thread
	if (thread == NULL) {
		ret = -ENOMEM;
		goto err;
	}

	switch (cmd) {
	case BINDER_WRITE_READ: {
		struct binder_write_read bwr;
		if (size != sizeof(struct binder_write_read)) {//查看大小是否正确
			ret = -EINVAL;
			goto err;
		}
		if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {//从用户空间复制数据到内核空间
		//第一个参数to是内核空间的数据目标地址指针,
		//第二个参数from是用户空间的数据源地址指针,
		//第三个参数n是数据的长度。
			ret = -EFAULT;
			goto err;
		}

		if (bwr.write_size > 0) {//当写缓存有数据的时候,执行写操作
			ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed);
			//参数分析:
			//proc代表sm对象的proc
			//thread为此sm进程的binder线程
			//bwr.write_buffer,内核数据的起始地址
			//write_size,数据大小
			//write_consumed,驱动程序已消费的数据大小
			trace_binder_write_done(ret);
			/**
			if (ret < 0) {//如果写失败,再将bwr的值写回给ubuf
				bwr.read_consumed = 0;
				if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
					//第一个参数是用户空间的指针,
					//第二个参数是内核空间指针,
					//n表示从内核空间向用户空间拷贝数据的字节数
					ret = -EFAULT;
				goto err;
			}
			*/
		}
		/**
		if (bwr.read_size > 0) {//当读缓存有数据的时候,执行读操作,此时读缓存无数据
			ret = binder_thread_read(proc, thread, (void __user *)bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK);
			trace_binder_read_done(ret);
			if (!list_empty(&proc->todo))
				wake_up_interruptible(&proc->wait);
			if (ret < 0) {
				if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
					ret = -EFAULT;
				goto err;
			}
		}
		*/
		if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {//将此内核空间数据,拷贝到ubuf中,此时是写的消费的大小write_consumed从变成了4字节。
			ret = -EFAULT;
			goto err;
		}
		break;
	}
	ret = 0;
err:
	if (thread)
		thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN;
	binder_unlock(__func__);
	wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);//不休眠
	if (ret && ret != -ERESTARTSYS)
		printk(KERN_INFO "binder: %d:%d ioctl %x %lx returned %d\n", proc->pid, current->pid, cmd, arg, ret);
err_unlocked:
	trace_binder_ioctl_done(ret);
	return ret;
}

2.15 binder_thread_write

主要作用为:

 1.取出消息是BC_ENTER_LOOPER,驱动层则设置当前线程BINDER_LOOPER_STATE_ENTERED。

int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread,
            void __user *buffer, int size, signed long *consumed)
            //参数分析:
            //proc代表sm对象的proc
            //thread为此sm进程的binder线程
            //bwr.write_buffer,内核数据的起始地址,数据是BC_ENTER_LOOPER
            //write_size,4字节,数据大小
            //consumed=0,驱动程序已消费的数据大小
{
    uint32_t cmd;
    void __user *ptr = buffer + *consumed;//首地址+0,即是写buffer首地址。
    void __user *end = buffer + size;//buffer的尾地址。

    while (ptr < end && thread->return_error == BR_OK) {
        if (get_user(cmd, (uint32_t __user *)ptr))//从写buffer中获取命令给cmd,即此时是BC_ENTER_LOOPER
            return -EFAULT;
        ptr += sizeof(uint32_t);//让buffer的地址跳过BC_ENTER_LOOPER,因为buffer中可能还有其他数据。此时是没数据了
        trace_binder_command(cmd);
        if (_IOC_NR(cmd) < ARRAY_SIZE(binder_stats.bc)) {//记录信息
            binder_stats.bc[_IOC_NR(cmd)]++;
            proc->stats.bc[_IOC_NR(cmd)]++;
            thread->stats.bc[_IOC_NR(cmd)]++;
        }
        switch (cmd) {
        case BC_ENTER_LOOPER:
        /**
            if (thread->looper & BINDER_LOOPER_STATE_REGISTERED) {//如果此looper已经注册过,则错误
                thread->looper |= BINDER_LOOPER_STATE_INVALID;
                binder_user_error("binder: %d:%d ERROR:"
                    " BC_ENTER_LOOPER called after "
                    "BC_REGISTER_LOOPER\n",
                    proc->pid, thread->pid);
            }
            */
            thread->looper |= BINDER_LOOPER_STATE_ENTERED;//设置为此binder线程已经注册过了。
            break;
        }
        *consumed = ptr - buffer;//已消费的字节大小,此时为4字节
    }
    return 0;
}

2.16 binder_ioctl

主要作用为:

从驱动中读取消息,如果没有消息,则会阻塞等待消息的到来。

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int ret;
	struct binder_proc *proc = filp->private_data;
	struct binder_thread *thread;//binder线程
	unsigned int size = _IOC_SIZE(cmd);
	void __user *ubuf = (void __user *)arg;

	trace_binder_ioctl(cmd, arg);

	ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);//不休眠
	if (ret)
		goto err_unlocked;

	binder_lock(__func__);
	thread = binder_get_thread(proc);//获取binder_thread
	if (thread == NULL) {
		ret = -ENOMEM;
		goto err;
	}

	switch (cmd) {
	case BINDER_WRITE_READ: {
		struct binder_write_read bwr;
		if (size != sizeof(struct binder_write_read)) {//检查大小是否正常
			ret = -EINVAL;
			goto err;
		}
		if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {//拷贝用户空间数据到内核空间
			ret = -EFAULT;
			goto err;
		}
		/**
		if (bwr.write_size > 0) {//此时为0,不执行
			ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed);
			trace_binder_write_done(ret);
			if (ret < 0) {
				bwr.read_consumed = 0;
				if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
					ret = -EFAULT;
				goto err;
			}
		}
		*/
		if (bwr.read_size > 0) {
			ret = binder_thread_read(proc, thread, (void __user *)bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK);
			//此时会阻塞,等待消息的到来。
			/**
			trace_binder_read_done(ret);
			if (!list_empty(&proc->todo))
				wake_up_interruptible(&proc->wait);
			if (ret < 0) {
				if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
					ret = -EFAULT;
				goto err;
			}
		}
		if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
			ret = -EFAULT;
			goto err;
		}
		break;
	}
	}
	ret = 0;
err:
	if (thread)
		thread->looper &= ~BINDER_LOOPER_STATE_NEED_RETURN;
	binder_unlock(__func__);
	wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
	if (ret && ret != -ERESTARTSYS)
		printk(KERN_INFO "binder: %d:%d ioctl %x %lx returned %d\n", proc->pid, current->pid, cmd, arg, ret);
err_unlocked:
	trace_binder_ioctl_done(ret);
	return ret;
	*/
}

2.17 binder_thread_read

主要作用为:

1.当驱动无消息的时候,进行休眠等待。

static int binder_thread_read(struct binder_proc *proc,
			      struct binder_thread *thread,
			      void  __user *buffer, int size,
			      signed long *consumed, int non_block)
				//参数分析:
				//sm对应的proc对象
				//binder线程对象
				//读buffer的首地址,
				//size是32字节
				//consumed是0,代表驱动消费读取的大小
{
	void __user *ptr = buffer + *consumed;//还是数据首地址
	void __user *end = buffer + size;//数据尾地址

	int ret = 0;
	int wait_for_proc_work;

	if (*consumed == 0) {//当消费数量为空时,将BR_NOOP放入ptr,即放入了read buffer,那么就是覆盖了BC_ENTER_LOOPER
		if (put_user(BR_NOOP, (uint32_t __user *)ptr))
			return -EFAULT;
		ptr += sizeof(uint32_t);//readbuffer往后移动跳过命令的位置。
	}

retry:
	wait_for_proc_work = thread->transaction_stack == NULL &&
				list_empty(&thread->todo);//此时todo为空,并且transaction_stack也为空,即wait_for_proc_work为true
				//如果一个线程的的事务堆栈 transaction_stack 不等于 NULL, 表示它正在等待其他线程完成另外一个事务.
				//如果一个线程的 todo 队列不等于 NULL, 表示该线程有未处理的工作项.
				//一个线程只有在其事务堆栈 transaction_stack 为 NULL, 并且 todo 队列为 NULL 时, 才可以去处理其所属进程todo 队列中的待处理工作项. 
				//否则就要处理其事务堆栈 transaction_stack 中的事物或者 todo 队列中的待处理工作项.
	/**
	if (thread->return_error != BR_OK && ptr < end) {//如果当前线程的状态是错误,并且readbuffer有空间,则写入错误信息。
		if (thread->return_error2 != BR_OK) {
			if (put_user(thread->return_error2, (uint32_t __user *)ptr))
				return -EFAULT;
			ptr += sizeof(uint32_t);
			binder_stat_br(proc, thread, thread->return_error2);
			if (ptr == end)
				goto done;
			thread->return_error2 = BR_OK;
		}
		if (put_user(thread->return_error, (uint32_t __user *)ptr))
			return -EFAULT;
		ptr += sizeof(uint32_t);
		binder_stat_br(proc, thread, thread->return_error);
		thread->return_error = BR_OK;
		goto done;
	}
	*/


	thread->looper |= BINDER_LOOPER_STATE_WAITING;//BINDER_LOOPER_STATE_WAITING 表示该线程正处于空闲状态
	if (wait_for_proc_work)//无任何任务处理
		proc->ready_threads++;//进程中空闲binder线程加1

	binder_unlock(__func__);

	trace_binder_wait_for_work(wait_for_proc_work,
				   !!thread->transaction_stack,
				   !list_empty(&thread->todo));
	if (wait_for_proc_work) {//当进程todo队列没有数据,则进入休眠等待状态
		/**
		if (!(thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
					BINDER_LOOPER_STATE_ENTERED))) {//如果当前线程还没有注册过,即还未发送BC_ENTER_LOOPER指令,而我们挂起了该线程,即为出错。
			binder_user_error("binder: %d:%d ERROR: Thread waiting "
				"for process work before calling BC_REGISTER_"
				"LOOPER or BC_ENTER_LOOPER (state %x)\n",
				proc->pid, thread->pid, thread->looper);
			wait_event_interruptible(binder_user_error_wait,
						 binder_stop_on_user_error < 2);//不休眠
		}
		*/
		binder_set_nice(proc->default_priority);
		/**
		if (non_block) {//如果是非阻塞的。但是binder通信一般是阻塞的
			if (!binder_has_proc_work(proc, thread))
				ret = -EAGAIN;
		}
		*/
		else
			ret = wait_event_freezable_exclusive(proc->wait, binder_has_proc_work(proc, thread));//当进程todo队列没有数据,则进入休眠等待状态,
		//待直到其所属的进程有新的未处理工作项为止.
	} 
}

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

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

相关文章

登山第九梯:稀疏点云实例分割——又快又准

文章&#xff1a;Fast Range Image-Based Segmentation of Sparse 3D Laser Scans for Online Operation 代码&#xff1a;https://github.com/PRBonn/depth_clustering 1&#xff09;摘要 从 3D 距离数据中分割对象是移动机器人领域的一个重要主题。在动态环境中导航的机器人需…

C51单片机-单按键输入识别,键盘消抖

【实验目的】 独立按键的识别方法、键盘消抖等。 【实验现象】 每按一次独立键盘的S2键&#xff0c;与P1口相连的八个发光二极管中点亮的一个往下移动一位。 【实验说明】 关于按键去抖动的解释&#xff0c;我们在手动按键的时候&#xff0c;由于机械抖动或是其它一些非人为的因…

NR PDSCH/PUSCH支持的maxMIMO layers

这里不考虑UE支持的具体MIMO能力&#xff0c;仅仅讨论协议上定的maxMIMO layers。 PDSCH 根据上面38.331中的结构&#xff0c;PDSCH max MIMO layers 为8 layers&#xff0c;进行8 layers传输时 要enable two codewords&#xff0c;因为 one codeword只能支持4 layers传输&…

【信创】Linux系统如何配置USB存储禁用及例外 _ 统信 _ 麒麟 _ 方德

原文链接&#xff1a;【信创】Linux系统如何配置USB存储禁用及例外 | 统信 | 麒麟 | 方德 Hello&#xff0c;大家好啊&#xff01;今天给大家带来一篇关于如何在Linux系统中配置USB存储禁用及例外的文章。禁用USB存储可以有效防止未经授权的人员从系统中复制数据或注入恶意软件…

CSS——盒子模型

首先CSS将所有的元素都看成一个盒子 盒子的组成&#xff1a; content —— 内容区域padding —— 内边距&#xff08;边框与内容间的距离&#xff09;border —— 边框线margin —— 外边距&#xff08;盒子盒子间的距离&#xff09; 这里着重说一下margin: 水平方向&#xff…

Kafka 基础与架构理解

目录 前言 Kafka 基础概念 消息队列简介&#xff1a;Kafka 与传统消息队列&#xff08;如 RabbitMQ、ActiveMQ&#xff09;的对比 Kafka 的组件 Kafka 的工作原理&#xff1a;消息的生产、分发、消费流程 Kafka 系统架构 Kafka 的分布式架构设计 Leader-Follower 机制与…

新品|瑞芯微RK3588工控机IPC8801适用AI算力、边缘计算、工业视觉

深圳触觉智能重磅推出旗舰级工控机IPC8801&#xff0c;搭载瑞芯微RK3588 ⼋核处理器&#xff1b;全铝紧凑机身、支持无风扇被动散热低噪音&#xff0c;确保设备在恶劣工业环境下稳定运行。 作为AI算力、边缘计算及工业视觉领域的高性能工控机&#xff0c;在国产化智能硬件与系统…

Axure PR 9 标签 设计交互

大家好&#xff0c;我是大明同学。 这期内容&#xff0c;我们将深入探讨Axure中可编辑标签元件设计与交互技巧。 可移除标签元件 创建可移除标签所需的元件 1.打开一个新的 RP 文件并在画布上打开 Page 1。 2.在元件库中拖出一个文本框元件。 3.选中文本框元件&#xff0c…

视频服务器:GB28181网络视频协议

一、前言 某项目中需要集成视频管理平台&#xff0c;实现分布在各省公司的摄像及接入&#xff0c;对视频进行统一管理。本项目中视频管理平台采用GB/T28181实现的监控设备接入管理平台&#xff0c;支持在开放互联网和局域网对监控设备进行远程接入、远程管理、远程调阅、录像回…

【文件包含】——日志文件注入

改变的确很难&#xff0c;但结果值得冒险 本文主要根据做题内容的总结&#xff0c;如有错误之处&#xff0c;还请各位师傅指正 一.伪协议的失效 当我们做到关于文件包含的题目时&#xff0c;常用思路其实就是使用伪协议&#xff08;php:filter,data,inpput等等&#xff09;执行…

【NOI-题解】1407. 图像相似度1330. 求最大梯形的面积1384. 靶心数1398. 奇偶统计

文章目录 一、前言二、问题问题&#xff1a;1407. 图像相似度问题&#xff1a;1330. 求最大梯形的面积问题&#xff1a;1384. 靶心数问题&#xff1a;1398. 奇偶统计 三、感谢 一、前言 欢迎关注本专栏《C从零基础到信奥赛入门级&#xff08;CSP-J&#xff09;》 本章节主要对…

优秀的安防视频监控平台应该具备怎样的视频编解码能力?

随着安防技术的飞速发展&#xff0c;监控平台作为保障公共安全、维护社会秩序的重要工具&#xff0c;其性能与效率日益成为行业关注的焦点。其中&#xff0c;监控平台的视频编码能力在视频监控系统中扮演着至关重要的角色&#xff0c;视频编码技术作为监控系统的核心组成部分&a…

200美元/月的ChatGPT Pro版上线?OpenAI草莓模型曝两周内发布,但模型表现要打个问号?

夕小瑶科技说 原创 作者 | 海野 现在看到“草莓”两个字&#xff0c;我已经条件反射了&#xff0c;已经不再是之前单纯的香香甜甜的草莓了。 一早醒来&#xff0c;又发生了两件“大事”&#xff1a; 一个是OpenAI的草莓&#xff08;strawberry&#xff09;被曝要提前了&#xf…

Xorbits Inference(Xinference):一款性能强大且功能全面的大模型部署与分布式推理框架

大模型部署与分布式推理框架Xinference Xinference的基本使用概述安装启动服务模型部署模型参数配置说明 API接口概述对话接口模型列表嵌入模型Rerank模型使用Xinference SDK使用OpenAI SDK 命令行工具概述启动模型引擎参数其他操作 集成LoRA启动时集成LoRA应用时集成LoRA 部署…

Stable Diffusion绘画 | ControlNet应用-Tile(分块)—tile_resample(分块-重采样)

要想使用 SD 生成高品质图片&#xff0c;放大增加分辨率是必不可少的环节。 tile_resample(分块-重采样) 主要是将图片切分成很多个分块&#xff0c;并识别每个分块的信息&#xff0c;最终通过特定算法把分块的信息重组起来。 能有效避免直接对一整张图片统一处理&#xff0c…

TensorFlow深度学习框架改进K-means聚类、SOM自组织映射算法及上海招生政策影响分析研究...

全文链接&#xff1a;https://tecdat.cn/?p37652 分析师&#xff1a;Chen Zhang 在教育政策研究领域&#xff0c;准确评估政策对不同区域和学生群体的影响至关重要。2021 年上海市出台的《上海市初中学业水平考试实施办法》对招生政策进行了调整&#xff0c;其中名额分配综合…

LSS可视化分析

1 完整 2 去掉plt.imshow(img_show) 3 去掉plt.axis(‘off’) 4 去掉plt.annotate(cams_text[img_id].replace(‘_’, ’ ), (0.01, 0.92), xycoords=‘axes fraction’)

3C电子胶黏剂在手机制造方面有哪些关键的应用

3C电子胶黏剂在手机制造方面有哪些关键的应用 3C电子胶黏剂在手机制造中扮演着至关重要的角色&#xff0c;其应用广泛且细致&#xff0c;覆盖了手机内部组件的多个层面&#xff0c;确保了设备的可靠性和性能。以下是电子胶在手机制造中的关键应用&#xff1a; 手机主板用胶&…

【蓝桥杯省赛真题52】Scratch猪八戒落地 蓝桥杯scratch图形化编程 中小学生蓝桥杯省赛真题讲解

scratch猪八戒落地 第十五届青少年蓝桥杯scratch编程省赛真题解析 一、题目要求 编程实现 1&#xff09;点击绿旗&#xff0c;猪八戒在空中踩着一朵云&#xff0c;如图所示; 2&#xff09;1秒后&#xff0c;猪八戒踩着云向地面移动&#xff0c;如图所示; 3&#xff09;猪八…

海外问卷调查有其他方式赚美金吗?

大家好&#xff0c;我是梦蝶问卷&#xff0c;专注于海外问卷调查项目。 大家应该都了解&#xff0c;做海外问卷项目填写问卷能赚美金&#xff0c;那除此之外&#xff0c;还有其它盈利模式吗&#xff1f;答案当然是肯定的。 有创业过的朋友应该都知道&#xff0c;一个项目的每个…