1. 前言

device和device driver是Linux驱动开发的基本概念。Linux kernel的思路很简单:驱动开发,就是要开发指定的软件(driver)以驱动指定的设备(device),所以kernel就为设备和驱动它的driver定义了两个数据结构,分别是device和device_driver。因此本文将会围绕这两个数据结构,介绍Linux设备模型的核心逻辑,包括:

设备及设备驱动在kernel中的抽象、使用和维护;

设备及设备驱动的注册、加载、初始化原理;

设备模型在实际驱动开发过程中的使用方法。

注:在介绍device和device_driver的过程中,会遇到很多额外的知识点,如Class、Bus、DMA、电源管理等等,这些知识点都很复杂,任何一个都可以作为一个单独的专题区阐述,因此本文不会深入解析它们,而会在后续的文章中专门描述。

2. struct device和struct device_driver

在阅读Linux内核源代码时,通过核心数据结构,即可理解某个模块60%以上的逻辑,设备模型部分尤为明显。

在include/linux/device.h中,Linux内核定义了设备模型中最重要的两个数据结构,struct device和struct device_driver。


/*** struct device - The basic device structure* @parent:   The device's "parent" device, the device to which it is attached.*      In most cases, a parent device is some sort of bus or host*         controller. If parent is NULL, the device, is a top-level device,*      which is not usually what you want.* @p:        Holds the private data of the driver core portions of the device.*      See the comment of the struct device_private for detail.* @kobj:    A top-level, abstract class from which other classes are derived.* @init_name:  Initial name of the device.* @type: The type of device.*        This identifies the device type and carries type-specific*      information.* @mutex:   Mutex to synchronize calls to its driver.* @bus:    Type of bus device is on.* @driver: Which driver has allocated this* @platform_data: Platform data specific to the device.*         Example: For devices on custom boards, as typical of embedded*      and SOC based hardware, Linux often uses platform_data to point*        to board-specific structures describing devices and how they*       are wired.  That can include what ports are available, chip*        variants, which GPIO pins act in what additional roles, and so*         on.  This shrinks the "Board Support Packages" (BSPs) and*      minimizes board-specific #ifdefs in drivers.* @driver_data: Private pointer for driver specific info.* @power:  For device power management.*       See Documentation/power/devices.txt for details.* @pm_domain:   Provide callbacks that are executed during system suspend,*         hibernation, system resume and during runtime PM transitions*       along with subsystem-level and driver-level callbacks.* @pins:  For device pin management.*     See Documentation/pinctrl.txt for details.* @msi_list:  Hosts MSI descriptors* @msi_domain: The generic MSI domain this device is using.* @numa_node:   NUMA node this device is close to.* @dma_mask:  Dma mask (if dma'ble device).* @coherent_dma_mask: Like dma_mask, but for alloc_coherent mapping as not all*        hardware supports 64-bit addresses for consistent allocations*      such descriptors.* @dma_pfn_offset: offset of DMA memory range relatively of RAM* @dma_parms:   A low level driver may set these to teach IOMMU code about*         segment limitations.* @dma_pools:   Dma pools (if dma'ble device).* @dma_mem:   Internal for coherent mem override.* @cma_area: Contiguous memory area for dma allocations* @archdata:  For arch-specific additions.* @of_node: Associated device tree node.* @fwnode:  Associated device node supplied by platform firmware.* @devt:   For creating the sysfs "dev".* @id:     device instance* @devres_lock: Spinlock to protect the resource of the device.* @devres_head: The resources list of the device.* @knode_class: The node used to add the device to the class list.* @class:  The class of the device.* @groups:  Optional attribute groups.* @release:   Callback to free the device after all references have*      gone away. This should be set by the allocator of the*      device (i.e. the bus driver that discovered the device).* @iommu_group: IOMMU group the device belongs to.* @iommu_fwspec: IOMMU-specific properties supplied by firmware.** @offline_disabled: If set, the device is permanently online.* @offline:    Set after successful invocation of bus type's .offline().** At the lowest level, every device in a Linux system is represented by an* instance of struct device. The device structure contains the information* that the device model core needs to model the system. Most subsystems,* however, track additional information about the devices they host. As a* result, it is rare for devices to be represented by bare device structures;* instead, that structure, like kobject structures, is usually embedded within* a higher-level representation of the device.*/
struct device {struct device        *parent;struct device_private   *p;struct kobject kobj;const char       *init_name; /* initial name of the device */const struct device_type *type;struct mutex     mutex;  /* mutex to synchronize calls to* its driver.*/struct bus_type  *bus;       /* type of bus device is on */struct device_driver *driver; /* which driver has allocated thisdevice */void     *platform_data; /* Platform specific data, devicecore doesn't touch it */void       *driver_data;   /* Driver data, set and get withdev_set/get_drvdata */struct dev_pm_info    power;struct dev_pm_domain  *pm_domain;#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAINstruct irq_domain    *msi_domain;
#endif
#ifdef CONFIG_PINCTRLstruct dev_pin_info    *pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQstruct list_head   msi_list;
#endif#ifdef CONFIG_NUMAint     numa_node;  /* NUMA node this device is close to */
#endifu64       *dma_mask;  /* dma mask (if dma'able device) */u64      coherent_dma_mask;/* Like dma_mask, but foralloc_coherent mappings asnot all hardware supports64 bit addresses for consistentallocations such descriptors. */unsigned long  dma_pfn_offset;struct device_dma_parameters *dma_parms;struct list_head dma_pools;  /* dma pools (if dma'ble) */struct dma_coherent_mem *dma_mem; /* internal for coherent memoverride */
#ifdef CONFIG_DMA_CMAstruct cma *cma_area;      /* contiguous memory area for dmaallocations */
#endif/* arch specific additions */struct dev_archdata  archdata;struct device_node *of_node; /* associated device tree node */struct fwnode_handle *fwnode; /* firmware device node */dev_t            devt;   /* dev_t, creates the sysfs "dev" */u32         id; /* device instance */spinlock_t     devres_lock;struct list_head    devres_head;struct klist_node   knode_class;struct class        *class;const struct attribute_group **groups;   /* optional groups */void   (*release)(struct device *dev);struct iommu_group   *iommu_group;struct iommu_fwspec    *iommu_fwspec;bool          offline_disabled:1;bool         offline:1;
};

device结构很复杂(不过linux内核的开发人员素质是很高的,该接口的注释写的非常详细,感兴趣的同学可以参考内核源代码),这里将会选一些对理解设备模型非常关键的字段进行说明。

parent,该设备的父设备,一般是该设备所从属的bus、controller等设备。

p,一个用于struct device的私有数据结构指针,该指针中会保存子设备链表、用于添加到bus/driver/prent等设备中的链表头等等,具体可查看源代码。

kobj,该数据结构对应的struct kobject。

init_name,该设备的名称。

注1:在设备模型中,名称是一个非常重要的变量,任何注册到内核中的设备,都必须有一个合法的名称,可以在初始化时给出,也可以由内核根据“bus name + device ID”的方式创造。

type,struct device_type结构是新版本内核新引入的一个结构,它和struct device关系,非常类似stuct kobj_type和struct kobject之间的关系,后续会再详细说明。

bus,该device属于哪个总线(后续会详细描述)。

driver,该device对应的device driver。

platform_data,一个指针,用于保存具体的平台相关的数据。具体的driver模块,可以将一些私有的数据,暂存在这里,需要使用的时候,再拿出来,因此设备模型并不关心该指针得实际含义。

power、pm_domain,电源管理相关的逻辑,后续会由电源管理专题讲解。

pins,"PINCTRL”功能,暂不描述。

numa_node,"NUMA”功能,暂不描述。

dma_mask~archdata,DMA相关的功能,暂不描述。

devt,dev_t是一个32位的整数,它由两个部分(Major和Minor)组成,在需要以设备节点的形式(字符设备和块设备)向用户空间提供接口的设备中,当作设备号使用。在这里,该变量主要用于在sys文件系统中,为每个具有设备号的device,创建/sys/dev/* 下的对应目录,如下:

1|root@android:/storage/sdcard0 #ls /sys/dev/char/1\:                                                                     
1:1/  1:11/ 1:13/ 1:14/ 1:2/  1:3/  1:5/  1:7/  1:8/  1:9/ 
1|root@android:/storage/sdcard0 #ls /sys/dev/char/1:1                                                                    
1:1/  1:11/ 1:13/ 1:14/
1|root@android:/storage/sdcard0 # ls /sys/dev/char/1\:1 
/sys/dev/char/1:1

class,该设备属于哪个class。

groups,该设备的默认attribute集合。将会在设备注册时自动在sysfs中创建对应的文件。

iommu_fwspec,固件提供的IOMMU特定属性

offline_disabled,如果设置,则设备永久在线

offline,成功调用总线类型的.offline()后设置

在最低级别,Linux系统中的每个设备都由一个结构设备的实例。 设备结构包含信息 设备模型核心需要为系统建模。 ‘

大多数子系统, 但是,请跟踪有关其托管设备的其他信息。 作为一个  结果,设备很少用裸设备结构表示;相反,该

结构,如kobject结构,通常嵌入其中 设备的更高级别表示。

  • struct device_driver

/*** struct device_driver - The basic device driver structure* @name:   Name of the device driver.* @bus:   The bus which the device of this driver belongs to.* @owner:    The module owner.* @mod_name:   Used for built-in modules.* @suppress_bind_attrs: Disables bind/unbind via sysfs.* @probe_type: Type of the probe (synchronous or asynchronous) to use.* @of_match_table: The open firmware table.* @acpi_match_table: The ACPI match table.* @probe:   Called to query the existence of a specific device,*        whether this driver can work with it, and bind the driver*      to a specific device.* @remove: Called when the device is removed from the system to*       unbind a device from this driver.* @shutdown:   Called at shut-down time to quiesce the device.* @suspend:  Called to put the device to sleep mode. Usually to a*       low power state.* @resume:  Called to bring a device from sleep mode.* @groups: Default attributes that get created by the driver core*     automatically.* @pm:        Power management operations of the device which matched*        this driver.* @p:       Driver core's private data, no one other than the driver*       core can touch this.** The device driver-model tracks all of the drivers known to the system.* The main reason for this tracking is to enable the driver core to match* up drivers with new devices. Once drivers are known objects within the* system, however, a number of other things become possible. Device drivers* can export information and configuration variables that are independent* of any specific device.*/
struct device_driver {const char        *name;struct bus_type       *bus;struct module      *owner;const char       *mod_name;  /* used for built-in modules */bool suppress_bind_attrs;    /* disables bind/unbind via sysfs */enum probe_type probe_type;const struct of_device_id    *of_match_table;const struct acpi_device_id *acpi_match_table;int (*probe) (struct device *dev);int (*remove) (struct device *dev);void (*shutdown) (struct device *dev);int (*suspend) (struct device *dev, pm_message_t state);int (*resume) (struct device *dev);const struct attribute_group **groups;const struct dev_pm_ops *pm;struct driver_private *p;
};

device_driver就简单多了(在早期的内核版本中driver的数据结构为"struct driver”,不知道从哪个版本开始,就改成device_driver了):

name,该driver的名称。和device结构一样,该名称非常重要,后面会再详细说明。

bus,该driver所驱动设备的总线设备。为什么driver需要记录总线设备的指针呢?因为内核要保证在driver运行前,设备所依赖的总线能够正确初始化。

owner、mod_name,內核module相关的变量,暂不描述。

suppress_bind_attrs,是不在sysfs中启用bind和unbind attribute,如下:root@android:/storage/sdcard0 # ls /sys/bus/platform/drivers/switch-gpio/                                                  
bind   uevent unbind
在kernel中,bind/unbind是从用户空间手动的为driver绑定/解绑定指定的设备的机制。这种机制是在bus.c中完成的,后面会详细解释。

probe、remove,这两个接口函数用于实现driver逻辑的开始和结束。Driver是一段软件code,因此会有开始和结束两个代码逻辑,就像PC程序,会有一个main函数,main函数的开始就是开始,return的地方就是结束。而内核driver却有其特殊性:在设备模型的结构下,只有driver和device同时存在时,才需要开始执行driver的代码逻辑。这也是probe和remove两个接口名称的由来:检测到了设备和移除了设备(就是为热拔插起的!)。

shutdown、suspend、resume、pm,电源管理相关的内容,会在电源管理专题中详细说明。

groups,和struct device结构中的同名变量类似,driver也可以定义一些默认attribute,这样在将driver注册到内核中时,内核设备模型部分的代码(driver/base/driver.c)会自动将这些attribute添加到sysfs中。

p,driver core的私有数据指针,其它模块不能访问。

3. 设备模型框架下驱动开发的基本步骤

在设备模型框架下,设备驱动的开发是一件很简单的事情,主要包括2个步骤:

步骤1:分配一个struct device类型的变量,填充必要的信息后,把它注册到内核中。

步骤2:分配一个struct device_driver类型的变量,填充必要的信息后,把它注册到内核中。

这两步完成后,内核会在合适的时机(后面会讲),调用struct device_driver变量中的probe、remove、suspend、resume等回调函数,从而触发或者终结设备驱动的执行。而所有的驱动程序逻辑,都会由这些回调函数实现,此时,驱动开发者眼中便不再有“设备模型”,转而只关心驱动本身的实现。

以上两个步骤的补充说明:

1. 一般情况下,Linux驱动开发很少直接使用device和device_driver,因为内核在它们之上又封装了一层,如soc device、platform device等等,而这些层次提供的接口更为简单、易用(也正是因为这个原因,本文并不会过多涉及device、device_driver等模块的实现细节)。

2. 内核提供很多struct device结构的操作接口(具体可以参考include/linux/device.h和drivers/base/core.c的代码),主要包括初始化(device_initialize)、注册到内核(device_register)、分配存储空间+初始化+注册到内核(device_create)等等,可以根据需要使用。

3. device和device_driver必须具备相同的名称,内核才能完成匹配操作,进而调用device_driver中的相应接口。这里的同名,作用范围是同一个bus下的所有device和device_driver。

4. device和device_driver必须挂载在一个bus之下,该bus可以是实际存在的,也可以是虚拟的。

5. driver开发者可以在struct device变量中,保存描述设备特征的信息,如寻址空间、依赖的GPIOs等,因为device指针会在执行probe等接口时传入,这时driver就可以根据这些信息,执行相应的逻辑操作了。

device部分的实现细节:

https://blog.csdn.net/qq_16777851/article/details/81437352

device_driver部分的实现细节

https://mp.csdn.net/postedit/81459931

4. 设备驱动probe的时机

所谓的"probe”,是指在Linux内核中,如果存在相同名称的device和device_driver(注:还存在其它方式,我们先不关注了),内核就会执行device_driver中的probe回调函数,而该函数就是所有driver的入口,可以执行诸如硬件设备初始化、字符设备注册、设备文件操作ops注册等动作("remove”是它的反操作,发生在device或者device_driver任何一方从内核注销时,其原理类似,就不再单独说明了)。

设备驱动prove的时机有如下几种(分为自动触发和手动触发):

  • 将struct device类型的变量注册到内核中时自动触发(device_register,device_add,device_create_vargs,device_create)
  • 将struct device_driver类型的变量注册到内核中时自动触发(driver_register)
  • 手动查找同一bus下的所有device_driver,如果有和指定device同名的driver,执行probe操作(device_attach)
  • 手动查找同一bus下的所有device,如果有和指定driver同名的device,执行probe操作(driver_attach)
  • 自行调用driver的probe接口,并在该接口中将该driver绑定到某个device结构中----即设置dev->driver(device_bind_driver)

注2:probe动作实际是由bus模块(会在下一篇文章讲解)实现的,这不难理解:device和device_driver都是挂载在bus这根线上,因此只有bus最清楚应该为哪些device、哪些driver配对。

注3:每个bus都有一个drivers_autoprobe变量,用于控制是否在device或者driver注册时,自动probe。该变量默认为1(即自动probe),bus模块将它开放到sysfs中了,因而可在用户空间修改,进而控制probe行为。

注4:上面的手动触发和自动触发以及probe机制都在设备模型六和七有分析,要看细节请看那两篇博客。

5. 其它杂项

5.1 device_attribute和driver_attribute

在Linux设备模型四(attribure)中,我们有讲到,大多数时候,attribute文件的读写数据流为:vfs---->sysfs---->kobject---->attibute---->kobj_type---->sysfs_ops---->xxx_attribute,其中kobj_type、sysfs_ops和xxx_attribute都是由包含kobject的上层数据结构实现。

Linux内核中关于该内容的例证到处都是,device也不无例外的提供了这种例子,如下


#define to_dev_attr(_attr) container_of(_attr, struct device_attribute, attr)static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr,char *buf)
{struct device_attribute *dev_attr = to_dev_attr(attr);struct device *dev = kobj_to_dev(kobj);ssize_t ret = -EIO;if (dev_attr->show)ret = dev_attr->show(dev, dev_attr, buf);if (ret >= (ssize_t)PAGE_SIZE) {print_symbol("dev_attr_show: %s returned bad count\n",(unsigned long)dev_attr->show);}return ret;
}static ssize_t dev_attr_store(struct kobject *kobj, struct attribute *attr,const char *buf, size_t count)
{struct device_attribute *dev_attr = to_dev_attr(attr);struct device *dev = kobj_to_dev(kobj);ssize_t ret = -EIO;if (dev_attr->store)ret = dev_attr->store(dev, dev_attr, buf, count);return ret;
}static const struct sysfs_ops dev_sysfs_ops = {.show   = dev_attr_show,.store  = dev_attr_store,
};static struct kobj_type device_ktype = {.release  = device_release,.sysfs_ops = &dev_sysfs_ops,.namespace = device_namespace,
};struct sysfs_ops {ssize_t (*show)(struct kobject *, struct attribute *,char *);ssize_t    (*store)(struct kobject *,struct attribute *,const char *, size_t);
};/* interface for exporting device attributes */
struct device_attribute {struct attribute   attr;ssize_t (*show)(struct device *dev, struct device_attribute *attr,char *buf);ssize_t (*store)(struct device *dev, struct device_attribute *attr,const char *buf, size_t count);
};

至于driver的attribute,则要简单的多,其数据流为:vfs---->sysfs---->kobject---->attribute---->driver_attribute,如下:


/* sysfs interface for exporting driver attributes */struct driver_attribute {struct attribute attr;ssize_t (*show)(struct device_driver *driver, char *buf);ssize_t (*store)(struct device_driver *driver, const char *buf,size_t count);
};#define DRIVER_ATTR(_name, _mode, _show, _store) \struct driver_attribute driver_attr_##_name = __ATTR(_name, _mode, _show, _store)

5.2 device_type

device_type是内嵌在struct device结构中的一个数据结构,用于指明设备的类型,并提供一些额外的辅助功能。它的的形式如下:

/** The type of device, "struct device" is embedded in. A class* or bus can contain devices of different types* like "partitions" and "disks", "mouse" and "event".* This identifies the device type and carries type-specific* information, equivalent to the kobj_type of a kobject.* If "name" is specified, the uevent will contain it in* the DEVTYPE variable.*/
struct device_type {const char *name;const struct attribute_group **groups;int (*uevent)(struct device *dev, struct kobj_uevent_env *env);char *(*devnode)(struct device *dev, umode_t *mode,kuid_t *uid, kgid_t *gid);void (*release)(struct device *dev);const struct dev_pm_ops *pm;
};

device_type的功能包括:

  • name表示该类型的名称,当该类型的设备添加到内核时,内核会发出"DEVTYPE=‘name’”类型的uevent,告知用户空间某个类型的设备available了
  • groups,该类型设备的公共attribute集合。设备注册时,会同时注册这些attribute。这就是面向对象中“继承”的概念
  • uevent,同理,所有相同类型的设备,会有一些共有的uevent需要发送,由该接口实现
  • devnode,devtmpfs有关的内容,暂不说明
  • release,如果device结构没有提供release接口,就要查询它所属的type是否提供。用于释放device变量所占的空间

5.3 root device

在sysfs中有这样一个目录:/sys/devices,系统中所有的设备,都归集在该目录下。有些设备,是通过device_register注册到Kernel并体现在/sys/devices/xxx/下。但有时候我们仅仅需要在/sys/devices/下注册一个目录,该目录不代表任何的实体设备,这时可以使用下面的接口:

/** Root device objects for grouping under /sys/devices*/
extern struct device *__root_device_register(const char *name,struct module *owner);/* This is a macro to avoid include problems with THIS_MODULE */
#define root_device_register(name) \__root_device_register(name, THIS_MODULE)extern void root_device_unregister(struct device *root);

该接口会调用device_register函数,向内核中注册一个设备,但是(你也想到了),没必要注册与之对应的driver(顺便提一下,内核中有很多不需要driver的设备,这是之一)。

参考文章:

http://www.wowotech.net/device_model/device_and_driver.html

linux设备模型五(device和device_driver)相关推荐

  1. linux设备模型bus,device,driver,(kobject、ktype、kset,bus_type、device、device_driver)

    1.1Linux设备驱动模型简介 1.什么是设备驱动模型 (1)类class.总线bus(负责将设备和驱动挂接起来).设备devices.驱动driver(可以看到在驱动源码中,不管是什么样的驱动,都 ...

  2. LINUX设备模型BUS,DEVICE,DRIVER

    虽然看了上面一篇转载的<使用/sys/访问系统>对总线,驱动,设备都讲得比较细但还是没有太多的感觉.在此就先把自己今天所学回忆一下. 为了满足新的要求,linux2.6提供了新的设备模型: ...

  3. Linux设备模型、平台设备驱动、设备树(device tree)、GPIO子系统以及pinctrl子系统介绍

    文章目录 一.Linux设备模型介绍 (1)设备驱动模型总体介绍 (2)设备驱动模型文件表现 (3)设备驱动模型工作原理 [1]总线 [2]设备 [3]驱动 [4]注册流程 二.平台设备驱动介绍 (1 ...

  4. Linux设备模型之platform设备

    Linux设备模型之platform设备 1. Platform模块的软件架构 2. Platform设备 2.1 platform_device原型 2.2 注册添加device 2.2.1 pla ...

  5. Linux设备模型(9)_device resource management ---devm申请空间【转】

    转自:http://www.wowotech.net/linux_kenrel/device_resource_management.html 1. 前言蜗蜗建议,每一个Linux驱动工程师,都能瞄一 ...

  6. linux设备驱动目录,Linux设备模型(5)_device和device driver

    Linux设备模型(5)_device和device driver 作者:wowo 发布于:2014-4-2 19:28 分类:统一设备模型 1. 前言 device和device driver是Li ...

  7. linux设备模型深探

    ------------------------------------------ 本文系本站原创,欢迎转载! 转载请注明出处:http://ericxiao.cublog.cn/ -------- ...

  8. 《linux设备驱动程序》——Linux设备模型

    一.概论 1.2.6版内核对系统结构的一般性抽象描述.现在内核使用了该抽象支持了多种不同的任务,其中包括: 1).电源管理和系统关机. 2).与用户控件通信. 3).热插拔设备. 4).设备类型. 5 ...

  9. Linux通常把设备对象抽象为,linux 设备模型(1)

    设备模型(一) 一.概述 从2.6内核引入了sysfs文件系统,与proc, devfs, devpty同类别,属于虚拟的文件系统.目的是展示设备驱动模型中各组件的层次关系,第一层目录:block, ...

  10. Linux设备模型组件-类设备-设备类及subsystem

    Linux设备模型   一.sysfs文件系统: sysfs文件系统是Linux2.6内核引入的,它被看成是与proc.devfs和devpty等同类别的文件系统,sysfs文件系统也是一个虚拟文件系 ...

最新文章

  1. 无刷新提交表单(非Ajax实现)
  2. 自己看着视频的理解:设计模式之abstractfactory模式(2)
  3. 3D打印机分类与速度
  4. Gentoo 安装日记 11 (配置内核 :Module, block layer 和 CPU)
  5. sql 查看某用户的连接数 以及 如何删除该用户的会话
  6. tornado.httpclient.HTTPClient()的用法
  7. 网站升级到新服务器,第一次折腾站点升级HTTPS 虽胜尤败
  8. gitlab中的CI
  9. mybatis学习(2):基本设置和核心配置
  10. PowerDesigner 表名、字段大小写转换
  11. HTTP/2 in GO(一)
  12. 微软符号服务器opencv的符号,Opencv Mat类详解和用法1
  13. 不要根据自己的喜好创业
  14. np.dot和np.matmul的区别与联系
  15. JBOSS EAP6.2.0的下载安装、环境变量配置以及部署
  16. 4 EDA实用技术与教程【基本语句1】
  17. mysql 嵌套查询多表_MySql嵌套查询+关联查询+多表查询+对应案例 超详细,一看就会!!!...
  18. 交换机生成树相关实验
  19. excessive cpu 优化杀进程解决方案 android P
  20. 销售crm系统排行?2022年终十大销售管理系统软件推荐

热门文章

  1. hive分隔符_Hive表字段、行、map默认分隔符
  2. 前端组件库自定义主题切换探索-02-webpack-theme-color-replacer webpack 的实现逻辑和原理-01
  3. [强烈推荐]ring0下文件解锁强制删除工具
  4. RN系列:Android原生与RN如何交互通信
  5. 网页QQ客服聊天功能
  6. action、gitter
  7. vue动态绑定背景图片
  8. Java毕设项目超市进销存管理系统计算机(附源码+系统+数据库+LW)
  9. pythonRuntimeError: Cannot re-initialize CUDA in forked subprocess. To use CUDA with multiprocessing
  10. 自适应QP(Adaptive QP)