Linux设备模型(九) - bus/device/device_driver/class

news2024/10/24 2:25:10

一,设备驱动模型

1,概述

在前面写的驱动中,我们发现编写驱动有个固定的模式只有往里面套代码就可以了,它们之间的大致流程可以总结如下:

  • 实现入口函数xxx_init()和卸载函数xxx_exit()

  • 申请设备号 register_chrdev_region()

  • 初始化字符设备,cdev_init函数、cdev_add函数

  • 硬件初始化,如时钟寄存器配置使能,GPIO设置为输入输出模式等。

  • 构建file_operation结构体内容,实现硬件各个相关的操作

  • 在终端上使用mknod根据设备号来进行创建设备文件(节点) (也可以在驱动使用class_create创建设备类、在类的下面device_create创建设备节点)

因此,在Linux开发驱动,只要能够掌握了这些“套路”,开发一个驱动便不是难事。但是,如果我们将硬件的信息都写进了驱动里了, 根据某个硬件编写的驱动只要修改了一下引脚接口,这个驱动代码就得重新修改才能使用,这显然是不合理的。

那有没有合适的解决方案呢?答案是肯定的:

Linux引入了设备驱动模型分层的概念, 将我们编写的驱动代码分成:设备与驱动。

  • 设备负责提供硬件资源

  • 驱动代码负责去使用这些设备提供的硬件资源

  • 总线将它们联系起来

这样子就构成以下图形中的关系:

在实际操作上:

  • 每次有新设备device添加时,bus就会去匹配合适的驱动

  • 每次有新驱动driver添加时,bus就会去匹配合适的设备

  • 这样device代表的硬件设置就和driver代表的软件设计解耦了

当然实际中,同一总线下的设备有很多,驱动也有很多,在总线上管理着两个链表,分别管理着设备和驱动,当我们向系统注册一个驱动时,便会向驱动的管理链表插入我们的新驱动, 同样当我们向系统注册一个设备时,便会向设备的管理链表插入我们的新设备。

在插入的同时总线会执行一个匹配方法对新插入的设备/驱动进行匹配,在匹配成功的时候会调用驱动中的初始化方法,在移除设备或驱动时会调用注销方法。

以上只是设备驱动模型的机制 。

2,设备驱动模型初始化

从内核启动到driver_init()的流程:

start_kernel(void) // msm_kernel\init\main.c
    arch_call_rest_init();
        rest_init();
            kernel_thread(kernel_init, NULL, CLONE_FS);
                kernel_init_freeable();
                    do_basic_setup();
                        driver_init();

driver_init()调用流程:

msm_kernel\drivers\base\init.c
/**
* driver_init - initialize driver model.
*
* Call the driver model init functions to initialize their
* subsystems. Called early from init/main.c.
*/
void __init driver_init(void)
{
    /* These are the core pieces */
    devtmpfs_init();
    devices_init(); //初始化 devices_kset, /sys/devices/
    buses_init(); //初始化bus_kset, /sys/bus/
    classes_init(); //初始化class_kset, /sys/class 
    firmware_init();
    hypervisor_init();

    /* These are also core pieces, but must come after the
     * core core pieces.
     */
    of_core_init(); //初始化of_kset
    platform_bus_init(); //注册platform_bus device和platform_bus_type bus
    cpu_dev_init();
    memory_dev_init();
    container_dev_init();
}

二,总线的注册

在Linux设备模型中,Bus(总线)是一类特殊的设备,它是连接处理器和其它设备之间的通道(channel)。为了方便设备模型的实现,内核规定,系统中的每个设备都要连接在一个Bus上,这个Bus可以是一个内部Bus、虚拟Bus或者Platform Bus。内核通过struct bus_type结构,抽象Bus。

1,struct bus_type结构体

/**
* struct bus_type - The bus type of the device
*
* @name:    The name of the bus.
* @dev_name:    Used for subsystems to enumerate devices like ("foo%u", dev->id).
* @dev_root:    Default device to use as the parent.
* @bus_groups:    Default attributes of the bus.
* @dev_groups:    Default attributes of the devices on the bus.
* @drv_groups: Default attributes of the device drivers on the bus.
* @match:    Called, perhaps multiple times, whenever a new device or driver
*        is added for this bus. It should return a positive value if the
*        given device can be handled by the given driver and zero
*        otherwise. It may also return error code if determining that
*        the driver supports the device is not possible. In case of
*        -EPROBE_DEFER it will queue the device for deferred probing.
* @uevent:    Called when a device is added, removed, or a few other things
*        that generate uevents to add the environment variables.
* @probe:    Called when a new device or driver add to this bus, and callback
*        the specific driver's probe to initial the matched device.
* @sync_state:    Called to sync device state to software state after all the
*        state tracking consumers linked to this device (present at
*        the time of late_initcall) have successfully bound to a
*        driver. If the device has no consumers, this function will
*        be called at late_initcall_sync level. If the device has
*        consumers that are never bound to a driver, this function
*        will never get called until they do.
* @remove:    Called when a device removed from this bus.
* @shutdown:    Called at shut-down time to quiesce the device.
*
* @online:    Called to put the device back online (after offlining it).
* @offline:    Called to put the device offline for hot-removal. May fail.
*
* @suspend:    Called when a device on this bus wants to go to sleep mode.
* @resume:    Called to bring a device on this bus out of sleep mode.
* @num_vf:    Called to find out how many virtual functions a device on this
*        bus supports.
* @dma_configure:    Called to setup DMA configuration on a device on
*            this bus.
* @pm:        Power management operations of this bus, callback the specific
*        device driver's pm-ops.
* @iommu_ops:  IOMMU specific operations for this bus, used to attach IOMMU
*              driver implementations to a bus and allow the driver to do
*              bus-specific setup
* @p:        The private data of the driver core, only the driver core can
*        touch this.
* @lock_key:    Lock class key for use by the lock validator
* @need_parent_lock:    When probing or removing a device on this bus, the
*            device core should lock the device's parent.
*
* A bus is a channel between the processor and one or more devices. For the
* purposes of the device model, all devices are connected via a bus, even if
* it is an internal, virtual, "platform" bus. Buses can plug into each other.
* A USB controller is usually a PCI device, for example. The device model
* represents the actual connections between buses and the devices they control.
* A bus is represented by the bus_type structure. It contains the name, the
* default attributes, the bus' methods, PM operations, and the driver core's
* private data.
*/
struct bus_type {
    const char        *name; //总线的名字,在syfs中以目录的形式存在例如,/sys/bus/i2c
    const char        *dev_name; //子系统枚举设备时的名字
    struct device        *dev_root;
    const struct attribute_group **bus_groups; //bus属性集
    const struct attribute_group **dev_groups; //device属性集
    const struct attribute_group **drv_groups; //driver属性集

    int (*match)(struct device *dev, struct device_driver *drv); /* bus提供的match函数, 一个由具体的bus driver实现的回调函数。当任何属于该Bus的device或者device_driver添加到内核时,内核都会调用该接口 */
    int (*uevent)(struct device *dev, struct kobj_uevent_env *env); /* 一个由具体的bus driver实现的回调函数。当任何属于该Bus的device,发生添加、移除或者其它动作时,Bus模块的核心逻辑就会调用该接口,以便bus driver能够修改环境变量 */
    int (*probe)(struct device *dev); /* bus提供的probe函数, 如果需要probe(其实就是初始化)指定的device话,需要保证该device所在的bus是被初始化过、确保能正确工作的。这就要就在执行device_driver的probe前,先执行它的bus的probe */
    void (*sync_state)(struct device *dev);
    int (*remove)(struct device *dev);
    void (*shutdown)(struct device *dev);

    int (*online)(struct device *dev);
    int (*offline)(struct device *dev);

    int (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);

    int (*num_vf)(struct device *dev);

    int (*dma_configure)(struct device *dev);

    const struct dev_pm_ops *pm;

    const struct iommu_ops *iommu_ops;

    struct subsys_private *p; //bus的私有成员
    struct lock_class_key lock_key;

    bool need_parent_lock;

    ANDROID_KABI_RESERVE(1);
    ANDROID_KABI_RESERVE(2);
    ANDROID_KABI_RESERVE(3);
    ANDROID_KABI_RESERVE(4);
};

bus私有成员结构体,这个结构就是集合了一些bus模块需要使用的私有数据:

/**
* struct subsys_private - structure to hold the private to the driver core portions of the bus_type/class structure.
*
* @subsys - the struct kset that defines this subsystem
* @devices_kset - the subsystem's 'devices' directory
* @interfaces - list of subsystem interfaces associated
* @mutex - protect the devices, and interfaces lists.
*
* @drivers_kset - the list of drivers associated
* @klist_devices - the klist to iterate over the @devices_kset
* @klist_drivers - the klist to iterate over the @drivers_kset
* @bus_notifier - the bus notifier list for anything that cares about things
*                 on this bus.
* @bus - pointer back to the struct bus_type that this structure is associated
*        with.
*
* @glue_dirs - "glue" directory to put in-between the parent device to
*              avoid namespace conflicts
* @class - pointer back to the struct class that this structure is associated
*          with.
*
* This structure is the one that is the actual kobject allowing struct
* bus_type/class to be statically allocated safely.  Nothing outside of the
* driver core should ever touch these fields.
*/
struct subsys_private {
    struct kset subsys; //bus内嵌的kset,代表其自身 eg: /sys/bus/i2c,kset是一个特殊的kobject,用来集合相似的kobject,它在sysfs中也会以目录的形式体现
    struct kset *devices_kset; //属于subsys kset, eg: /sys/bus/i2c/devices
    struct list_head interfaces;
    struct mutex mutex;

    struct kset *drivers_kset; //属于subsys kset, eg: /sys/bus/i2c/drivers
    struct klist klist_devices; //本bus包含的所有设备
    struct klist klist_drivers; //本bus包含的所有driver
    struct blocking_notifier_head bus_notifier;
    unsigned int drivers_autoprobe:1; //device与driver是否自动probe
    struct bus_type *bus; //回指到bus_type

    struct kset glue_dirs;
    struct class *class;
};

2,bus_register()流程

3,关键代码流程分析

int bus_register(struct bus_type *bus)
{
    int retval;
    struct subsys_private *priv;
    struct lock_class_key *key = &bus->lock_key;

    priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL); //为struct subsys_private分配空间
    if (!priv)
        return -ENOMEM;

    priv->bus = bus; //私有成员的bus回指该bus
    bus->p = priv; //初始化bus->p,即其私有属性

    BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);

    retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name); //设置该bus的名字,bus是kset的封装
    if (retval)
        goto out;

    priv->subsys.kobj.kset = bus_kset; //bus_ket即为所有bus的总起始端点,本bus从属于bus_ket,/sys/bus/
    priv->subsys.kobj.ktype = &bus_ktype; //属性操作级别统一为bus_ktype
    priv->drivers_autoprobe = 1; //device与driver自动进行probe

    retval = kset_register(&priv->subsys); //注册kset,创建目录结构以及层级关系 /sys/bus/i2c/
    if (retval)
        goto out;

    retval = bus_create_file(bus, &bus_attr_uevent); //当前bus下生成uevent attribute文件 /sys/bus/i2c/uevent
    if (retval)
        goto bus_uevent_fail;

    priv->devices_kset = kset_create_and_add("devices", NULL, //初始化bus目录下的devices目录,里面级联了该bus下的设备,仍然以ket为原型
                         &priv->subsys.kobj); // /sys/bus/i2c/devices
    if (!priv->devices_kset) {
        retval = -ENOMEM;
        goto bus_devices_fail;
    }

    priv->drivers_kset = kset_create_and_add("drivers", NULL, //初始化bus目录下的drivers目录,里面级联了该bus下设备的driver
                         &priv->subsys.kobj); // /sys/bus/i2c/drivers
    if (!priv->drivers_kset) {
        retval = -ENOMEM;
        goto bus_drivers_fail;
    }

    INIT_LIST_HEAD(&priv->interfaces);
    __mutex_init(&priv->mutex, "subsys mutex", key);
    klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); //初始化klist_devices里的操作函数成员 
    klist_init(&priv->klist_drivers, NULL, NULL); //klist_drivers里的操作函数置空

    retval = add_probe_files(bus); /* 增加drivers_autoprobe和drivers_probe属性文件,/sys/bus/i2c/drivers_probe, /sys/bus/i2c/drivers_autoprobe */
    if (retval)
        goto bus_probe_files_fail;

    retval = bus_add_groups(bus, bus->bus_groups); //增加bus默认的属性文件, const struct attribute_group **bus_groups;
    if (retval)
        goto bus_groups_fail;

    pr_debug("bus: '%s': registered\n", bus->name);
    return 0;

bus_groups_fail:
    remove_probe_files(bus);
bus_probe_files_fail:
    kset_unregister(bus->p->drivers_kset);
bus_drivers_fail:
    kset_unregister(bus->p->devices_kset);
bus_devices_fail:
    bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
    kset_unregister(&bus->p->subsys);
out:
    kfree(bus->p);
    bus->p = NULL;
    return retval;
}

由此可见,bus又是kset的封装,bus_register主要完成了其私有成员priv的初始化,并初始化了其下的两个目录devices和drivers,及其属性文件,bus有个自己的根目录也就是bus有个起始端点,是bus_kset,经过此番的注册,bus目录下将会出现我们注册的bus,并且其下会有device和driver两个子目录,代表它下面的driver和device链表。

三,设备的注册

1,struct device 结构体

/**
* 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.
* @lockdep_mutex: An optional debug lock that a subsystem can use as a
*         peer lock to gain localized lockdep coverage of the device_lock.
* @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.
* @links:    Links to suppliers and consumers of this device.
* @power:    For device power management.
*        See Documentation/driver-api/pm/devices.rst 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.
* @em_pd:    device's energy model performance domain
* @pins:    For device pin management.
*        See Documentation/driver-api/pinctl.rst 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_ops:    DMA mapping operations for this device.
* @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.
* @bus_dma_limit: Limit of an upstream bridge or bus which imposes a smaller
*        DMA limit than the device itself supports.
* @dma_range_map: map for DMA memory ranges relative to that 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:    Per device generic IOMMU runtime data
*
* @offline_disabled: If set, the device is permanently online.
* @offline:    Set after successful invocation of bus type's .offline().
* @of_node_reused: Set if the device-tree node is shared with an ancestor
*              device.
* @state_synced: The hardware state of this device has been synced to match
*          the software state of this device by calling the driver/bus
*          sync_state() callback.
* @dma_coherent: this particular device is dma coherent, even if the
*        architecture supports non-coherent devices.
* @dma_ops_bypass: If set to %true then the dma_ops are bypassed for the
*        streaming DMA operations (->map_* / ->unmap_* / ->sync_*),
*        and optionall (if the coherent mask is large enough) also
*        for dma allocations.  This flag is managed by the dma ops
*        instance from ->dma_supported.
*
* 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 kobject kobj; //该数据结构对应的struct kobject,代表自身
    struct device        *parent; //该设备的父设备,一般是该设备所从属的bus、controller等设备

    struct device_private    *p; //一个用于struct device的私有数据结构指针

    const char        *init_name; /* initial name of the device */ /* 任何注册到内核中的设备,都必须有一个合法的名称,可以在初始化时给出,也可以由内核根据“bus name + device ID”的方式创造 */
    const struct device_type *type;

    struct bus_type    *bus;        /* type of bus device is on */
    struct device_driver *driver;    /* which driver has allocated this
                       device */
    void        *platform_data;    /* Platform specific data, device
                       core doesn't touch it */
    void        *driver_data;    /* Driver data, set and get with
                       dev_set_drvdata/dev_get_drvdata */
#ifdef CONFIG_PROVE_LOCKING
    struct mutex        lockdep_mutex;
#endif
    struct mutex        mutex;    /* mutex to synchronize calls to
                     * its driver.
                     */

    struct dev_links_info    links;
    struct dev_pm_info    power;
    struct dev_pm_domain    *pm_domain;

#ifdef CONFIG_ENERGY_MODEL
    struct em_perf_domain    *em_pd;
#endif

#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
    struct irq_domain    *msi_domain;
#endif
#ifdef CONFIG_PINCTRL
    struct dev_pin_info    *pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
    struct list_head    msi_list;
#endif
#ifdef CONFIG_DMA_OPS
    const struct dma_map_ops *dma_ops;
#endif
    u64        *dma_mask;    /* dma mask (if dma'able device) */
    u64        coherent_dma_mask;/* Like dma_mask, but for
                         alloc_coherent mappings as
                         not all hardware supports
                         64 bit addresses for consistent
                         allocations such descriptors. */
    u64        bus_dma_limit;    /* upstream dma constraint */
    const struct bus_dma_region *dma_range_map;

    struct device_dma_parameters *dma_parms;

    struct list_head    dma_pools;    /* dma pools (if dma'ble) */

#ifdef CONFIG_DMA_DECLARE_COHERENT
    struct dma_coherent_mem    *dma_mem; /* internal for coherent mem
                         override */
#endif
#ifdef CONFIG_DMA_CMA
    struct cma *cma_area;        /* contiguous memory area for dma
                       allocations */
#endif
    /* arch specific additions */
    struct dev_archdata    archdata;

    struct device_node    *of_node; /* associated device tree node */
    struct fwnode_handle    *fwnode; /* firmware device node */

#ifdef CONFIG_NUMA
    int        numa_node;    /* NUMA node this device is close to */
#endif
    dev_t            devt;    /* dev_t, creates the sysfs "dev" */ /* 该变量主要用于在sys文件系统中,为每个具有设备号的device,创建/sys/dev/* 下的对应目录 */
    u32            id;    /* device instance */

    spinlock_t        devres_lock;
    struct list_head    devres_head;

    struct class        *class; //该设备属于哪个class
    const struct attribute_group **groups;    /* optional groups */ /* 该设备的默认attribute集合。将会在设备注册时自动在sysfs中创建对应的文件 */

    void    (*release)(struct device *dev);
    struct iommu_group    *iommu_group;
    struct dev_iommu    *iommu;

    bool            offline_disabled:1;
    bool            offline:1;
    bool            of_node_reused:1;
    bool            state_synced:1;
#if defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_DEVICE) || \
    defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU) || \
    defined(CONFIG_ARCH_HAS_SYNC_DMA_FOR_CPU_ALL)
    bool            dma_coherent:1;
#endif
#ifdef CONFIG_DMA_OPS_BYPASS
    bool            dma_ops_bypass : 1;
#endif
    ANDROID_KABI_RESERVE(1);
    ANDROID_KABI_RESERVE(2);
    ANDROID_KABI_RESERVE(3);
    ANDROID_KABI_RESERVE(4);
    ANDROID_KABI_RESERVE(5);
    ANDROID_KABI_RESERVE(6);
    ANDROID_KABI_RESERVE(7);
    ANDROID_KABI_RESERVE(8);
};

设备的私有属性结构:

/**
* struct device_private - structure to hold the private to the driver core portions of the device structure.
*
* @klist_children - klist containing all children of this device
* @knode_parent - node in sibling list
* @knode_driver - node in driver list
* @knode_bus - node in bus list
* @knode_class - node in class list
* @deferred_probe - entry in deferred_probe_list which is used to retry the
*    binding of drivers which were unable to get all the resources needed by
*    the device; typically because it depends on another driver getting
*    probed first.
* @async_driver - pointer to device driver awaiting probe via async_probe
* @device - pointer back to the struct device that this structure is
* associated with.
* @dead - This device is currently either in the process of or has been
*    removed from the system. Any asynchronous events scheduled for this
*    device should exit without taking any action.
*
* Nothing outside of the driver core should ever touch these fields.
*/
struct device_private {
    struct klist klist_children; //包含的子设备
    struct klist_node knode_parent; //父级挂接点
    struct klist_node knode_driver; //driver list挂接点
    struct klist_node knode_bus; //bus list挂接点
    struct klist_node knode_class; //class list挂接点
    struct list_head deferred_probe;
    struct device_driver *async_driver;
    char *deferred_probe_reason;
    struct device *device; //回指向该设备
    u8 dead:1;
};

2,device_register()流程

清晰分解图:

3,关键代码流程分析

/* device_initialize */

void device_initialize(struct device *dev)
{
    dev->kobj.kset = devices_kset; //device kobject属于devices_kset
    kobject_init(&dev->kobj, &device_ktype); //初始化这个kobj并建立层次关系以及属性文件,此时是放到了总的device文件目录下面
    INIT_LIST_HEAD(&dev->dma_pools);
    mutex_init(&dev->mutex);
#ifdef CONFIG_PROVE_LOCKING
    mutex_init(&dev->lockdep_mutex);
#endif
    lockdep_set_novalidate_class(&dev->mutex);
    spin_lock_init(&dev->devres_lock);
    INIT_LIST_HEAD(&dev->devres_head);
    device_pm_init(dev);
    set_dev_node(dev, -1);
#ifdef CONFIG_GENERIC_MSI_IRQ
    INIT_LIST_HEAD(&dev->msi_list);
#endif
    INIT_LIST_HEAD(&dev->links.consumers);
    INIT_LIST_HEAD(&dev->links.suppliers);
    INIT_LIST_HEAD(&dev->links.defer_sync);
    dev->links.status = DL_DEV_NO_DRIVER;
}

/* device_add */

int device_add(struct device *dev)
{
    struct device *parent;
    struct kobject *kobj;
    struct class_interface *class_intf;
    int error = -EINVAL;
    struct kobject *glue_dir = NULL;

    dev = get_device(dev);
    if (!dev)
        goto done;

    if (!dev->p) {
        error = device_private_init(dev); //初始化dev的私有成员及其链表操作函数
        if (error)
            goto done;
    }

    /*
     * for statically allocated devices, which should all be converted
     * some day, we need to initialize the name. We prevent reading back
     * the name, and force the use of dev_name()
     */
    if (dev->init_name) {
        dev_set_name(dev, "%s", dev->init_name); //设置kobject的名字,kobj->name = s
        dev->init_name = NULL;
    }

    /* subsystems can specify simple device enumeration */
    if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
        dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id); //如果kobj的名字为空,则使用总线名字+设备的ID组合当做设备名字

    if (!dev_name(dev)) {
        error = -EINVAL;
        goto name_error; //如果dev的名字还是空则退出,必须有正确的设备名字才能被注册
    }

    pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

    parent = get_device(dev->parent);
    kobj = get_device_parent(dev, parent); //获取父亲节点
    if (IS_ERR(kobj)) {
        error = PTR_ERR(kobj);
        goto parent_error;
    }
    if (kobj)
        dev->kobj.parent = kobj;

    /* use parent numa_node */
    if (parent && (dev_to_node(dev) == NUMA_NO_NODE))
        set_dev_node(dev, dev_to_node(parent));

    /* first, register with generic layer. */
    /* we require the name to be set before, and pass NULL */
    error = kobject_add(&dev->kobj, dev->kobj.parent, NULL); //初始化kobj与其父亲节点的连接,在父亲dev的目录下会创建该device的sysfs path,并设置kobj->state_in_sysfs为1,表示该设备已被注册
    if (error) {
        glue_dir = get_glue_dir(dev);
        goto Error;
    }

    /* notify platform of device entry */
    error = device_platform_notify(dev, KOBJ_ADD);
    if (error)
        goto platform_error;

    error = device_create_file(dev, &dev_attr_uevent); //产生uevent属性文件,/sys/devices/llcc-pmu/uevent
    if (error)
        goto attrError;

    error = device_add_class_symlinks(dev); //在dev的path创建一些符号链接,具体链接上述流程图中有示例
    if (error)
        goto SymlinkError;
    error = device_add_attrs(dev); //在dev的path添加一些属性文件
    if (error)
        goto AttrsError;
    error = bus_add_device(dev); //把设备添加到bus
    if (error)
        goto BusError;
    error = dpm_sysfs_add(dev);
    if (error)
        goto DPMError;
    device_pm_add(dev);

    if (MAJOR(dev->devt)) {
        error = device_create_file(dev, &dev_attr_dev);
        if (error)
            goto DevAttrError;

        error = device_create_sys_dev_entry(dev);
        if (error)
            goto SysEntryError;

        devtmpfs_create_node(dev);
    }

    /* Notify clients of device addition.  This call must come
     * after dpm_sysfs_add() and before kobject_uevent().
     */
    if (dev->bus)
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                         BUS_NOTIFY_ADD_DEVICE, dev);

    kobject_uevent(&dev->kobj, KOBJ_ADD); //发送该设备被ADD的uevent消息 KOBJ_ADD

    /*
     * Check if any of the other devices (consumers) have been waiting for
     * this device (supplier) to be added so that they can create a device
     * link to it.
     *
     * This needs to happen after device_pm_add() because device_link_add()
     * requires the supplier be registered before it's called.
     *
     * But this also needs to happen before bus_probe_device() to make sure
     * waiting consumers can link to it before the driver is bound to the
     * device and the driver sync_state callback is called for this device.
     */
    if (dev->fwnode && !dev->fwnode->dev) {
        dev->fwnode->dev = dev;
        fw_devlink_link_device(dev);
    }

    bus_probe_device(dev); //为一个新的device探测driver
    if (parent)
        klist_add_tail(&dev->p->knode_parent,
                   &parent->p->klist_children); //如果该设备有关联的父设备,将该设备挂接到父设备的children list

    if (dev->class) {
        mutex_lock(&dev->class->p->mutex);
        /* tie the class to the device */
        klist_add_tail(&dev->p->knode_class,
                   &dev->class->p->klist_devices); //如果设备跟class关联,将设备挂接到class list

        /* notify any interfaces that the device is here */
        list_for_each_entry(class_intf,
                    &dev->class->p->interfaces, node)
            if (class_intf->add_dev)
                class_intf->add_dev(dev, class_intf);
        mutex_unlock(&dev->class->p->mutex);
    }
done:
    put_device(dev); //减少device的引用计数
    return error;
SysEntryError:
    if (MAJOR(dev->devt))
        device_remove_file(dev, &dev_attr_dev);
DevAttrError:
    device_pm_remove(dev);
    dpm_sysfs_remove(dev);
DPMError:
    bus_remove_device(dev);
BusError:
    device_remove_attrs(dev);
AttrsError:
    device_remove_class_symlinks(dev);
SymlinkError:
    device_remove_file(dev, &dev_attr_uevent);
attrError:
    device_platform_notify(dev, KOBJ_REMOVE);
platform_error:
    kobject_uevent(&dev->kobj, KOBJ_REMOVE);
    glue_dir = get_glue_dir(dev);
    kobject_del(&dev->kobj);
Error:
    cleanup_glue_dir(dev, glue_dir);
parent_error:
    put_device(parent);
name_error:
    kfree(dev->p);
    dev->p = NULL;
    goto done;
}

/* bus_add_device */

int bus_add_device(struct device *dev)
{
    struct bus_type *bus = bus_get(dev->bus);
    int error = 0;

    if (bus) {
        pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev));
        error = device_add_groups(dev, bus->dev_groups); //在该设备的sysfs目录中创建bus上默认的属性文件
        if (error)
            goto out_put;
        error = sysfs_create_link(&bus->p->devices_kset->kobj,
                        &dev->kobj, dev_name(dev)); /* 调用sysfs_create_link接口,将该device在sysfs中的目录,链接到该bus的device目录下,eg: /sys/bus/platform/devices/soc:gpio_keys -> ../../../devices/platform/soc/soc:gpio_keys */
        if (error)
            goto out_groups;
        error = sysfs_create_link(&dev->kobj,
                &dev->bus->p->subsys.kobj, "subsystem"); /* 调用sysfs_create_link接口,在该device的sysfs中创建一个指向该设备所在bus目录的链接,取名为subsystem eg: /sys/devices/platform/soc/soc:gpio_keys/subsystem -> ../../../../bus/platform */
        if (error)
            goto out_subsys;
        klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices); //将该设备挂接到bus的device list链表
    }
    return 0;

out_subsys:
    sysfs_remove_link(&bus->p->devices_kset->kobj, dev_name(dev));
out_groups:
    device_remove_groups(dev, bus->dev_groups);
out_put:
    bus_put(dev->bus);
    return error;
}

/*__device_attach */

static int __device_attach(struct device *dev, bool allow_async)
{
    int ret = 0;
    bool async = false;

    device_lock(dev);
    if (dev->p->dead) {
        goto out_unlock; //如果设备已经dead直接退出
    } else if (dev->driver) { //默认指定了driver就直接绑定
        if (device_is_bound(dev)) { //判断该设备是否已经driver绑定,这一点很重要,通过它,可以使同一个Driver,驱动相同名称的多个设备
            ret = 1;
            goto out_unlock; //如果设备已经绑定过驱动直接退出
        }
        ret = device_bind_driver(dev); //将一个driver与一个device绑定
        if (ret == 0)
            ret = 1;
        else {
            dev->driver = NULL;
            ret = 0;
        }
    } else {
        struct device_attach_data data = {
            .dev = dev,
            .check_async = allow_async,
            .want_async = false,
        };

        if (dev->parent)
            pm_runtime_get_sync(dev->parent);

        ret = bus_for_each_drv(dev->bus, NULL, &data,
                    __device_attach_driver); //没有给设备指定driver就进行遍历匹配
        if (!ret && allow_async && data.have_async) {
            /*
             * If we could not find appropriate driver
             * synchronously and we are allowed to do
             * async probes and there are drivers that
             * want to probe asynchronously, we'll
             * try them.
             */
            dev_dbg(dev, "scheduling asynchronous probe\n");
            get_device(dev);
            async = true;
        } else {
            pm_request_idle(dev);
        }

        if (dev->parent)
            pm_runtime_put(dev->parent);
    }
out_unlock:
    device_unlock(dev);
    if (async)
        async_schedule_dev(__device_attach_async_helper, dev);
    return ret;
}

/* bus_for_each_drv */

int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
             void *data, int (*fn)(struct device_driver *, void *))
{
    struct klist_iter i;
    struct device_driver *drv;
    int error = 0;

    if (!bus)
        return -EINVAL;

    klist_iter_init_node(&bus->p->klist_drivers, &i,
                 start ? &start->p->knode_bus : NULL); /* 从头开始遍历bus的driver链表,发现一个driver就调用fn即__device_attach_driver进行匹配 */
    while ((drv = next_driver(&i)) && !error)
        error = fn(drv, data);
    klist_iter_exit(&i);
    return error;
}

/* __device_attach_driver */

static int __device_attach_driver(struct device_driver *drv, void *_data)
{
    struct device_attach_data *data = _data;
    struct device *dev = data->dev;
    bool async_allowed;
    int ret;

    ret = driver_match_device(drv, dev); /* drv->bus->match(dev, drv)调用bus的match函数,检查device与driver是否匹配,device和device_driver必须具备相同的名称,内核才能完成匹配操作,进而调用device_driver中的相应接口。这里的同名,作用范围是同一个bus下的所有device和device_driver */
    if (ret == 0) {
        /* no match */
        return 0;
    } else if (ret == -EPROBE_DEFER) {
        dev_dbg(dev, "Device match requests probe deferral\n");
        driver_deferred_probe_add(dev);
        /*
         * Device can't match with a driver right now, so don't attempt
         * to match or bind with other drivers on the bus.
         */
        return ret;
    } else if (ret < 0) {
        dev_dbg(dev, "Bus failed to match device: %d\n", ret);
        return ret;
    } /* ret > 0 means positive match */

    async_allowed = driver_allows_async_probing(drv);

    if (async_allowed)
        data->have_async = true;

    if (data->check_async && async_allowed != data->want_async)
        return 0;

    return driver_probe_device(drv, dev); //device与driver匹配成功,将device与driver绑定
}

/* really_probe */

static int really_probe(struct device *dev, struct device_driver *drv)
{
    int ret = -EPROBE_DEFER;
    int local_trigger_count = atomic_read(&deferred_trigger_count);
    bool test_remove = IS_ENABLED(CONFIG_DEBUG_TEST_DRIVER_REMOVE) &&
               !drv->suppress_bind_attrs;

    ret = device_links_check_suppliers(dev);
    if (ret == -EPROBE_DEFER)
        driver_deferred_probe_add_trigger(dev, local_trigger_count);
    if (ret)
        return ret;

    atomic_inc(&probe_count);
    pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
         drv->bus->name, __func__, drv->name, dev_name(dev));
    if (!list_empty(&dev->devres_head)) {
        dev_crit(dev, "Resources present before probing\n");
        ret = -EBUSY;
        goto done;
    }

re_probe:
    dev->driver = drv; //将绑定的driver赋值给dev->driver

    /* If using pinctrl, bind pins now before probing */
    ret = pinctrl_bind_pins(dev);
    if (ret)
        goto pinctrl_bind_failed;

    if (dev->bus->dma_configure) {
        ret = dev->bus->dma_configure(dev);
        if (ret)
            goto probe_failed;
    }

    ret = driver_sysfs_add(dev); //创建driver与device之间的符号链接
    if (ret) {
        pr_err("%s: driver_sysfs_add(%s) failed\n",
               __func__, dev_name(dev));
        goto probe_failed;
    }

    if (dev->pm_domain && dev->pm_domain->activate) {
        ret = dev->pm_domain->activate(dev);
        if (ret)
            goto probe_failed;
    }

    if (dev->bus->probe) {         //如果bus有probe函数优先执行bus的probe函数,如果没有则执行驱动的probe函数
        ret = dev->bus->probe(dev);
        if (ret)
            goto probe_failed;
    } else if (drv->probe) {
        ret = drv->probe(dev);
        if (ret)
            goto probe_failed;
    }

    ret = device_add_groups(dev, drv->dev_groups); //创建属性文件,const struct attribute_group **dev_groups
    if (ret) {
        dev_err(dev, "device_add_groups() failed\n");
        goto dev_groups_failed;
    }

    if (dev_has_sync_state(dev)) {
        ret = device_create_file(dev, &dev_attr_state_synced);
        if (ret) {
            dev_err(dev, "state_synced sysfs add failed\n");
            goto dev_sysfs_state_synced_failed;
        }
    }

    pinctrl_init_done(dev);

    if (dev->pm_domain && dev->pm_domain->sync)
        dev->pm_domain->sync(dev);

    driver_bound(dev); //将该设备挂接到driver的device list,一个driver可以对应于几个设备,因此driver同样有其设备链表,用于挂接可以操作的设备
    ret = 1;
    pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
         drv->bus->name, __func__, dev_name(dev), drv->name);
    goto done;

dev_sysfs_state_synced_failed:
    device_remove_groups(dev, drv->dev_groups);
dev_groups_failed:
    if (dev->bus->remove)
        dev->bus->remove(dev);
    else if (drv->remove)
        drv->remove(dev);
probe_failed:
    if (dev->bus)
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                         BUS_NOTIFY_DRIVER_NOT_BOUND, dev);
pinctrl_bind_failed:
    device_links_no_driver(dev);
    devres_release_all(dev);
    arch_teardown_dma_ops(dev);
    kfree(dev->dma_range_map);
    dev->dma_range_map = NULL;
    driver_sysfs_remove(dev);
    dev->driver = NULL;
    dev_set_drvdata(dev, NULL);
    if (dev->pm_domain && dev->pm_domain->dismiss)
        dev->pm_domain->dismiss(dev);
    pm_runtime_reinit(dev);
    dev_pm_set_driver_flags(dev, 0);

    switch (ret) {
    case -EPROBE_DEFER:
        /* Driver requested deferred probing */
        dev_dbg(dev, "Driver %s requests probe deferral\n", drv->name);
        driver_deferred_probe_add_trigger(dev, local_trigger_count);
        break;
    case -ENODEV:
    case -ENXIO:
        pr_debug("%s: probe of %s rejects match %d\n",
             drv->name, dev_name(dev), ret);
        break;
    default:
        /* driver matched but the probe failed */
        pr_warn("%s: probe of %s failed with error %d\n",
            drv->name, dev_name(dev), ret);
    }
    /*
     * Ignore errors returned by ->probe so that the next driver can try
     * its luck.
     */
    ret = 0;
done:
    atomic_dec(&probe_count);
    wake_up_all(&probe_waitqueue);
    return ret;
}

/* driver_sysfs_add */

static int driver_sysfs_add(struct device *dev)
{
    int ret;

    if (dev->bus)
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                         BUS_NOTIFY_BIND_DRIVER, dev);

    ret = sysfs_create_link(&dev->driver->p->kobj, &dev->kobj,
                kobject_name(&dev->kobj)); /* 在该设备绑定的bus下的driver目录中创建一个指向该device在sysfs下的目录,名字为设备的名字 eg: /sys/bus/platform/drivers/gpio-keys/soc:gpio_keys -> ../../../../devices/platform/soc/soc:gpio_keys */
    if (ret)
        goto fail;

    ret = sysfs_create_link(&dev->kobj, &dev->driver->p->kobj,
                "driver"); /* 在该device的sysfs目录下创建一个指向该设备绑定的bus下的driver目录的链接,名字为"driver", eg: /sys/devices/platform/soc/soc:gpio_keys/driver -> ../../../../bus/platform/drivers/gpio-keys  */
    if (ret)
        goto rm_dev;

    if (!IS_ENABLED(CONFIG_DEV_COREDUMP) || !dev->driver->coredump ||
        !device_create_file(dev, &dev_attr_coredump))
        return 0;

    sysfs_remove_link(&dev->kobj, "driver");

rm_dev:
    sysfs_remove_link(&dev->driver->p->kobj,
              kobject_name(&dev->kobj));

fail:
    return ret;
}

/* driver_bound */

static void driver_bound(struct device *dev)
{
    if (device_is_bound(dev)) {
        pr_warn("%s: device %s already bound\n",
            __func__, kobject_name(&dev->kobj));
        return;
    }

    pr_debug("driver: '%s': %s: bound to device '%s'\n", dev->driver->name,
         __func__, dev_name(dev));

    klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices); //将该设备挂接到driver的device list
    device_links_driver_bound(dev);

    device_pm_check_callbacks(dev);

    /*
     * Make sure the device is no longer in one of the deferred lists and
     * kick off retrying all pending devices
     */
    driver_deferred_probe_del(dev);
    driver_deferred_probe_trigger();

    if (dev->bus)
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                         BUS_NOTIFY_BOUND_DRIVER, dev);

    kobject_uevent(&dev->kobj, KOBJ_BIND); //device与driver已经绑定,发出该设备的KOBJ_BIND uevent消息
}

device注册主要是在devices_kset的目录中创建了属于该设备的目录结构,并将该设备挂接在bus的device list中,然后遍历bus的driver list,为该设备查找匹配的驱动,当匹配成功之后将device与driver绑定,一个device只能与一个driver对应,一个driver能驱动多个device,整个过程中还伴随着一些属性文件的创建,通过这些属性文件可以直接读写设备的信息,以及一些符号链接的创建,通过这些符号链接class/bus/device/driver可以关联在一起,bus下的device会指向实际的sysfs下的device,bus下的driver也会指向实际的sysfs下的device,sysfs下的device也会有名字为driver指向bus下driver的软链接,class下也有指向实际的sysfs下的device的链接。

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

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

相关文章

首发:鸿蒙面试真题分享【独此一份】

最早在23年华为秋季发布会中&#xff0c;就已经宣布了“纯血鸿蒙”。而目前鸿蒙处于星河版中&#xff0c;加速了各大互联网厂商的合作。目前已经有200参与鸿蒙的原生应用开发当中。对此各大招聘网站上的鸿蒙开发需求&#xff0c;每日都在增长中。 2024大厂面试真题 目前的鸿蒙…

OpenHarmony教程指南—ArkUI中组件、通用、动画、全局方法的集合

介绍 本示例为ArkUI中组件、通用、动画、全局方法的集合。 本示例使用 Tabs容器组件搭建整体应用框架&#xff0c;每个 TabContent内容视图 使用 div容器组件 嵌套布局&#xff0c;在每个 div 中使用 循环渲染 加载此分类下分类导航数据&#xff0c;底部导航菜单使用 TabCont…

LeetCode 2917.找出数组中的 K-or 值:基础位运算

【LetMeFly】2917.找出数组中的 K-or 值&#xff1a;基础位运算 力扣题目链接&#xff1a;https://leetcode.cn/problems/find-the-k-or-of-an-array/ 给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。 nums 中的 K-or 是一个满足以下条件的非负整数&#xff1a; 只有…

如何合理布局子图--确定MATLAB的subplot子图位置参数

确定MATLAB的subplot子图位置参数 目录 确定MATLAB的subplot子图位置参数摘要1. 问题描述2. 计算过程2.1 确定子图的大小和间距2.2 计算合适的figure大小2.3 计算每个子图的position数据 3. MATLAB代码实现3.1 MATLAB代码3.2 绘图结果 4. 总结 摘要 在MATLAB中&#xff0c;使用…

网络编程套接字(1)—网络编程基础

目录 一、为什么需要网络编程? 二、什么是网络编程 三、网络编程中的基本概念 1、发送端和接收端 2、请求和响应 3、客户端和服务端 四、常见的客户端服务端模型 1、一问一答模型 2、一问多答模型 3、多问一答模型 4、多问多答模型 一、为什么需要网络编程? 为什么…

(二十二)从零开始搭建k8s集群——高可用kubernates集群搭建上篇

前言 本节内容分为上、中、下三篇&#xff0c;上篇主要是关于搭建k8s的基础环境&#xff0c;包括服务器基本环境的配置&#xff08;网络、端口、主机名、防火墙、交换分区、文件句柄数等&#xff09;、docker环境部署安装配置、镜像源配置等。中篇会介绍k8s的核心组件安装、k8…

rk3568 恢复出厂设置横屏

author daisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 daisy.skye_嵌入式,Linux,Qt-CSDN博客daisy.skye擅长嵌入式,Linux,Qt,等方面的知识https://blog.csdn.net/qq_40715266?typeblog 在使用rk3568开发过程&#xff0c;虽然显示的方向已经改成了横屏&#xff0c;但是恢…

4.1k star,官方出品的redis桌面管理工具——redislnsight

导航 令人抓狂的大key加载RedisInsight 简介RedisInsight的亮点GitHub 地址安装和使用RedisInsight 下载安装 使用RedisInsight redis数据库可视化直观的CLI&#xff08;Command-Line Interface&#xff09;日志分析和命令分析 结语参考 令人抓狂的大key加载 工欲善其事必先利…

JavaScript基础4之原型的原型继承、原型链和理解对象的数据属性、访问器属性

JavaScript基础 原型原型继承问题解决 原型链isPrototypeOf()Object.getPrototypeOf() 理解对象数据属性访问器属性 原型 原型继承 继承是面向对象编程的另一个特征&#xff0c;通过继承进一步提升代码封装的程度&#xff0c;JavaScript中大多是借助原型对象实现继承的特性。…

sudo command not found

文章目录 一句话Intro其他操作 一句话 sudo 某命令 改成 sudo -i 某命令 试试。 -i 会把当前用户的环境变量带过去&#xff0c;这样在sudo的时候&#xff0c;有更高的权限&#xff0c;有本用户的环境变量(下的程序命令)。 -i, --login run login shell as the target user; a …

软件测试相关概念和bug的相关总结

文章目录 什么是测试什么是需求测试用例(CASE)什么是BUG软件的生命周期开发模型瀑布模型螺旋模型增量模型和迭代模型 敏捷测试模型v模型W模型(双V模型) 软件测试的生命周期如何描述一个bugbug的级别bug的生命周期.产生争执怎么办 什么是测试 测试是测试人员用来检验软件的实际运…

全自动玻璃切割机控制系统设计

目 录 摘 要 I Abstract II 引 言 1 1 玻璃切割机控制系统设计 4 1.1系统方案选择 4 1.2玻璃切割机的工作原理 4 1.3工艺过程 5 1.4玻璃切割机的控制要求 6 2硬件设计 8 2.1控制部分设计 8 2.2驱动部分设计 8 2.2.1步进电机及驱动器的选型 8 2.2.2步进电机驱动器接口电路设计 …

VM 虚拟机 ubuntu 解决无法连接网络问题

添加网卡法 就是在虚拟机的设置那里多增加一个网卡

每日OJ题_链表②_力扣24. 两两交换链表中的节点

目录 力扣24. 两两交换链表中的节点 解析代码 力扣24. 两两交换链表中的节点 24. 两两交换链表中的节点 难度 中等 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&…

顺序表以及单链表

目录 1顺序表&#xff08;规范&#xff09; 2单链表&#xff08;规范&#xff09; 3总结 1顺序表&#xff08;规范&#xff09; #include<iostream> using namespace std; #define MAXSIZE 100 #define ok -1 #define error -2 typedef int Status; typedef int…

支小蜜校园防欺凌系统如何有效应对学生霸凌?

学生霸凌不仅直接伤害到被霸凌者的身心健康&#xff0c;也对整个校园的和谐氛围构成了威胁。为了应对这一问题&#xff0c;校园防欺凌系统应运而生&#xff0c;成为维护校园安全、保护学生权益的重要工具。那么当校园防欺凌系统面对学生霸凌时&#xff0c;该如何有效应对呢&…

K8S之实现业务的蓝绿部署

如何实现蓝绿部署 什么是蓝绿部署&#xff1f;蓝绿部署的优势和缺点优点缺点 通过k8s实现线上业务的蓝绿部署 什么是蓝绿部署&#xff1f; 部署两套系统&#xff1a;一套是正在提供服务系统&#xff0c;标记为 “绿色” &#xff1b;另一套是准备发布的系统&#xff0c;标记为…

JS函数

目录 1.Function声明 2.匿名函数 3.函数表达式 4.箭头函数 5.构造函数 个人版JS函数使用&#xff1a; 函数的声明&#xff1a;函数如果有return则返回的是 return 后面的值&#xff0c;如果函数没有有return 声明方式一&#xff1a; 声明方式二&#xff1a;变量名声明…

0x04_数组_指针_字符串

数组 数组的定义与使用 数组是具有一定顺序关系的若干相同类型变量的集合体&#xff0c;组成数组的变量称为该数组的元素。 给出下面程序的输出&#xff1a; #include <iostream> using namespace std; int main() {int a[10], b[10];for(int i 0; i < 10; i) {a[…

【Web前端入门学习】—CSS

目录 CSS简介CSS语法CSS三种导入方式CSS选择器元素选择器&#xff08;标签选择器&#xff09;类选择器ID选择器通用选择器子元素选择器后代选择器&#xff08;包含选择器&#xff09;并集选择器&#xff08;兄弟选择器&#xff09;伪类选择器伪元素选择器 CSS常用属性盒子模型网…