【android10】【binder】【2.servicemanager启动——全源码分析】
系列文章目录
可跳转到下面链接查看下表所有内容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 hwservicemanagerstart vndservicemanager
2.2 servicemanager.rc
1.init进程启动后,会从指定路径加载进程的rc文件,然后启动servicemanager服务。
service servicemanager /system/bin/servicemanagerclass core animation //服务的类为core和animationuser system //在启动服务前将用户切换为systemgroup system readproc //在启动前将用户组切换为system和readproccritical //表明这个服务是至关重要的服务,如果它在四分钟内退出超过四次,则设备将重启进入恢复模式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 VENDORSERVICEMANAGERALOGW("failed to open binder driver %s\n", driver);while (true) {sleep(UINT_MAX);}
#elseALOGE("failed to open binder driver %s\n", driver);
#endifreturn -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 VENDORSERVICEMANAGERcb.func_log = selinux_vendor_log_callback;
#elsecb.func_log = selinux_log_callback;
#endifselinux_set_callback(SELINUX_CB_LOG, cb);#ifdef VENDORSERVICEMANAGERsehandle = selinux_android_vendor_service_context_handle();
#elsesehandle = selinux_android_service_context_handle();
#endifselinux_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;//128kbs->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进程的tskINIT_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对象创建+1hlist_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找到此procbinder_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_threadif (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树),关键字 ptrstruct rb_root refs_by_desc;//记录binder引用, 便于快速查找,binder_ref红黑树的根节点(以handle为key),它是Client在Binder驱动中的体现。//Binder引用对象,关键字 descstruct rb_root refs_by_node;//记录binder引用, 便于快速查找,binder_ref红黑树的根节点(以ptr为key),它是Client在Binder驱动中的体现,//Binder引用对象,关键字 nodestruct list_head waiting_threads; int pid;//进程组pidstruct 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以及namespinlock_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)//保证映射内存大小不超过4Mvma->vm_end = vma->vm_start + SZ_4M;if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {//判断是否禁止mmapret = -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;elsemm = 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_areaif (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]);//映射物理页地址到虚拟用户地址空间vmaif (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 methodif (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=0if (ret)goto err_unlocked;binder_lock(__func__);thread = binder_get_thread(proc);//获取binder_threadif (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;//则设置为当前进程的用户idbinder_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;elsereturn 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;//此处是nullnode->cookie = cookie;//此处是nullnode->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_LOOPERfor (;;) {//死循环,从驱动中读取消息bwr.read_size = sizeof(readbuf);//此时是BC_ENTER_LOOPER的大小,32字节bwr.read_consumed = 0;//bwr.read_buffer = (uintptr_t) readbuf;//数据是BC_ENTER_LOOPERres = 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;//是bwrtrace_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_threadif (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的值写回给ubufbwr.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_LOOPERreturn -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_threadif (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_LOOPERif (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线程加1binder_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;}*/elseret = wait_event_freezable_exclusive(proc->wait, binder_has_proc_work(proc, thread));//当进程todo队列没有数据,则进入休眠等待状态,//待直到其所属的进程有新的未处理工作项为止.}
}
相关文章:
【android10】【binder】【2.servicemanager启动——全源码分析】
系列文章目录 可跳转到下面链接查看下表所有内容https://blog.csdn.net/handsomethefirst/article/details/138226266?spm1001.2014.3001.5501文章浏览阅读2次。系列文章大全https://blog.csdn.net/handsomethefirst/article/details/138226266?spm1001.2014.3001.5501 目录 …...
Java实现简易计算器功能(idea)
目的:写一个计算器,要求实现加减乘除功能,并且能够循环接收新的数据,通过用户交互实现。 思路: (1)写4个方法:加减乘除 (2)利用循环switch进行用户交互 &…...
Parsec问题解决方案
Parsec目前就是被墙了,有解决方案但治标不治本,如果想稳定串流建议是更换稳定的串流软件,以下是一些解决方案 方案一:在%appdata%/Parsec/config.txt中,添加代理 app_proxy_address 127.0.0.1 app_proxy_scheme http…...
Swift 创建扩展(Extension)
类别(Category) 和 扩展(Extension) 的 用法很多. 常用的 扩展(Extension) 有分离代码和封装模块的功能,例如登陆页面有注册功能,有登陆功能,有找回密码功能,都写在一个页面就太冗余了,可以考虑使用 扩展(Extension) 登陆页面的方法来分离代码 本文介绍Swift 如何创建扩展(Ex…...
九月五日(k8s配置)
一、安装环境 环境准备:(有阿里云) k8s-master 192.168.1.11 k8s-node1 192.168.1.22 k8s-node2 192.168.1.33 二、前期准备 在k8s-master主机 [rootk8s-master ~]# vim /etc/hosts …...
某极验4.0 -消消乐验证
⚠️前言⚠️ 本文仅用于学术交流。 学习探讨逆向知识,欢迎私信共享学习心得。 如有侵权,联系博主删除。 请勿商用,否则后果自负。 网址 aHR0cHM6Ly93d3cyLmdlZXRlc3QuY29tL2FkYXB0aXZlLWNhcHRjaGE 1. 浅聊一下 验证码样式 验证成功 - …...
洛谷 P10798 「CZOI-R1」消除威胁
题目来源于:洛谷 题目本质:贪心,st表,单调栈 解题思路:由于昨天联练习了平衡树,我就用平衡树STL打了个暴力,超时得了30分 这是暴力代码: #include<bits/stdc.h> using name…...
Pow(x, n)
题目 实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,xn)。 示例 1: 输入:x 2.00000, n 10 输出:1024.00000示例 2: 输入:x 2.10000, n 3 输出:9.26100示…...
一文带你学会使用滑动窗口
🔥个人主页:guoguoqiang. 🔥专栏:leetcode刷题 209.长度最小的子数组 求最短长度之和等于目标值。 方法一: 暴力枚举(会超时) 从头开始遍历直到之和等于target然后更新结果。这…...
如何从0到1本地搭建whisper语音识别模型
文章目录 环境准备1. 系统要求2. 安装依赖项1:安装 Python 和虚拟环境2:安装 Whisper3:下载 Whisper 模型4:进行语音识别5:提高效率和精度6:开发和集成Whisper 是 OpenAI 发布的一个强大的语音识别模型,它可以将语音转换为文本,支持多语言输入,并且可以处理各种音频类…...
PyTorch 创建数据集
图片数据和标签数据准备 1.本文所用图片数据在同级文件夹中 ,文件路径为train/’ 2.标签数据在同级文件,文件路径为train.csv 3。将标签数据提取 train_csvpd.read_csv(train.csv)创建继承类 第一步,首先创建数据类对象 此时可以想象为单个数据单元的…...
[Java]SpringBoot登录认证流程详解
登录认证 登录接口 1.查看原型 2.查看接口 3.思路分析 登录核心就是根据用户名和密码查询用户信息,存在则登录成功, 不存在则登录失败 4.Controller Slf4j RestController public class LoginController {Autowiredprivate EmpService empService;/*** 登录的方法** param …...
【Day08】
目录 MySQL-多表查询-概述 MySQL-多表查询-内连接 MySQL-多表查询-外连接 MySQL-多表查询-[标量、列]子查询 MySQL-多表查询-[行、表]子查询 MySQL-多表查询-案例 MySQL-事务-介绍与操作 MySQL-事务-四大特性 MySQL-索引-介绍 MySQL-索引-结构 MySQL-索引-操作语法 …...
mongodb在Java中条件分组聚合查询并且分页(时间戳,按日期分组,年月日...)
废话不多说,先看效果图: SQL查询结果示例: 多种查询结果示例: 原SQL: db.getCollection("hbdd_order").aggregate([{// 把时间戳格式化$addFields: {orderDate: {"$dateToString": {"for…...
怎么样处理浮毛快捷又高效?霍尼韦尔、希喂、米家宠物空气净化器实测对比
掉毛多?掉毛快?猫毛满天飞对身体有危害吗?多猫家庭经验分享篇: 一个很有趣的现象,很多人在养猫、养狗后耐心都变得更好了。养狗每天得遛,养猫出门前得除毛,日复一日的重复磨练了极好的耐心。我家…...
什么是WebGL技术?有什么特点?应用领域有哪些?
WebGL(Web Graphics Library)技术是一种在Web浏览器中渲染交互式3D和2D图形的JavaScript API。以下是对WebGL技术的详细解析: 一、定义与起源 定义: WebGL全称Web Graphics Library,即网络图形库,它允许…...
500W逆变器(一)
EG8015_24V_500W 这款逆变器是基于 EG8015 SPWM 专用芯片而设计的方案。其额定的输出功率为 500 瓦, 最大输出功率为 600 瓦,输出电压为 220V10%,输出频率为 50Hz0.1Hz,额定输出电流为 2.3 安培。 穿越机降落的时候不要垂直降落,要…...
ubuntu 22.04 编译安装新内核
1、普通用户登录系统 查看当前内核版本 $ uname -r 5.15.0-118-generic 2、下载内核源码 www.kernel.org 用户home目录新建子目录linux,下载并解压 linux-5.15.165.tar.xz 3、创建起始的配置文件.config Configuration targets (见linux kernel i…...
Linux 文件权限与属性管理
概述 Linux 系统是一种典型的多用户系统,不同的用户处于不同的地位,拥有不同的权限。为了保护系统的安全性,Linux 对不同用户访问同一文件(包括目录文件)的权限做了详细的规定。 文件属性查看 在 Linux 中࿰…...
Django学习实战篇三(适合略有基础的新手小白学习)(从0开发项目)
前言: 在上一章中,我们对Django的Model层有了比较全面的认识,本章就来配置Django自带的admin。这里需要认识到,Django的Model层是很重要的一环,无论是对于框架本身还是对于基于Django框架开发的大多数系统而言。因为一…...
网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...
国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...
