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

四,驱动的注册

1,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.
* @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 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.
* @dev_groups:    Additional attributes attached to device instance once the
*        it is bound to the driver.
* @pm:        Power management operations of the device which matched
*        this driver.
* @coredump:    Called when sysfs entry is written to. The device driver
*        is expected to call the dev_coredump API resulting in a
*        uevent.
* @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; //该driver的名称
    struct bus_type        *bus; //该driver所驱动设备的总线

    struct module        *owner;
    const char        *mod_name;    /* used for built-in modules */

    bool suppress_bind_attrs;    /* disables bind/unbind via sysfs */ /* 在kernel中,bind/unbind是从用户空间手动的为driver绑定/解绑定指定的设备的机制 */
    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); /* probe、remove,这两个接口函数用于实现driver逻辑的开始和结束。Driver是一段软件code,因此会有开始和结束两个代码逻辑,就像PC程序,会有一个main函数,main函数的开始就是开始,return的地方就是结束。而内核driver却有其特殊性:在设备模型的结构下,只有driver和device同时存在时,才需要开始执行driver的代码逻辑。这也是probe和remove两个接口名称的由来:检测到了设备和移除了设备(就是为热拔插起的!) */
    void (*sync_state)(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; /* 默认的attribute,在driver注册到内核时,内核设备模型部分的代码(driver/base/driver.c)会自动将这些attribute添加到sysfs中 */
    const struct attribute_group **dev_groups;

    const struct dev_pm_ops *pm;
    void (*coredump) (struct device *dev);

    struct driver_private *p; //driver core的私有数据指针,其它模块不能访问

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

driver的私有数据driver_private:

struct driver_private {

    struct kobject kobj; //该数据结构对应的struct kobject,代表自身

    struct klist klist_devices; //该driver驱动的所有设备的链表

    struct klist_node knode_bus; //用来挂接到bus的driver list

    struct module_kobject *mkobj;

    struct device_driver *driver; //回指到本结构体

};

2,driver_register()流程

清晰分解图:

  

3,关键代码流程分析

/* driver_register */

int driver_register(struct device_driver *drv)
{
    int ret;
    struct device_driver *other;

    if (!drv->bus->p) {
        pr_err("Driver '%s' was unable to register with bus_type '%s' because the bus was not initialized.\n",
               drv->name, drv->bus->name);
        return -EINVAL; //如果bus未被初始化退出注册driver
    }

    if ((drv->bus->probe && drv->probe) ||
        (drv->bus->remove && drv->remove) ||
        (drv->bus->shutdown && drv->shutdown)) //driver和bus的同名操作函数如果同时存在,会出现警告,并且会优先选用bus的
        pr_warn("Driver '%s' needs updating - please use "
            "bus_type methods\n", drv->name);

    other = driver_find(drv->name, drv->bus); //进入bus的driver链表,确认该driver是否已经注册
    if (other) {
        pr_err("Error: Driver '%s' is already registered, "
            "aborting...\n", drv->name); //该driver已经被注册,退出
        return -EBUSY;
    }

    ret = bus_add_driver(drv); //如果没有被注册,就把该driver加入所在bus
    if (ret)
        return ret;
    ret = driver_add_groups(drv, drv->groups); //创建attribute文件
    if (ret) {
        bus_remove_driver(drv);
        return ret;
    }
    kobject_uevent(&drv->p->kobj, KOBJ_ADD); //该driver已被成功注册,发出KOBJ_ADD uevent

    return ret;
}

/* driver_find */

struct device_driver *driver_find(const char *name, struct bus_type *bus)
{
    struct kobject *k = kset_find_obj(bus->p->drivers_kset, name); /* bus->p->drivers_kset代表bus下的driver目录,此处会遍历kset->list,该list中包含了所有的挂接到该bus上的driver的kobject,然后通过比较driver内嵌的kobj名字,就是通过kset与kobj之间的关系来查找 */
    struct driver_private *priv;

    if (k) {
        /* Drop reference added by kset_find_obj() */
        kobject_put(k); //减少kobject的引用计数
        priv = to_driver(k);
        return priv->driver; //如果找到同名的kobj就返回该driver
    }
    return NULL;
}

/* kset_find_obj */

struct kobject *kset_find_obj(struct kset *kset, const char *name)
{
    struct kobject *k;
    struct kobject *ret = NULL;

    spin_lock(&kset->list_lock);

    list_for_each_entry(k, &kset->list, entry) { //遍历kset->list链表,取出其中的每一个kobj
        if (kobject_name(k) && !strcmp(kobject_name(k), name)) { //比较取出的kobj的名字跟指定的kobj的名字是否一致
            ret = kobject_get_unless_zero(k); //找到匹配的kobj把该driver的kobj引用计数+1并返回
            break;
        }
    }

    spin_unlock(&kset->list_lock);
    return ret;
}

/* bus_add_driver */

int bus_add_driver(struct device_driver *drv)
{
    struct bus_type *bus;
    struct driver_private *priv;
    int error = 0;

    bus = bus_get(drv->bus); //取得其所在bus的指针
    if (!bus)
        return -EINVAL;

    pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);

    priv = kzalloc(sizeof(*priv), GFP_KERNEL); //开始初始化这个driver的私有成员
    if (!priv) {
        error = -ENOMEM;
        goto out_put_bus;
    }
    klist_init(&priv->klist_devices, NULL, NULL);
    priv->driver = drv; //回指到本结构体
    drv->p = priv; //给driver的私有数据赋值
    priv->kobj.kset = bus->p->drivers_kset; //本driver的kobject属于bus的drivers_kset
    error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
                     "%s", drv->name); //初始化driver的kobject并在sysfs中创建目录结构
    if (error)
        goto out_unregister;

    klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); //将该driver挂接在bus的drivers list
    if (drv->bus->p->drivers_autoprobe) { 
        error = driver_attach(drv); //如果device与driver自动probe的flag为真,那么到bus的devices上去匹配设备
        if (error)
            goto out_del_list;
    }
    module_add_driver(drv->owner, drv);

    error = driver_create_file(drv, &driver_attr_uevent); //创建uevent attribute, /sys/bus/platform/drivers/gpio-keys/uevent
    if (error) {
        printk(KERN_ERR "%s: uevent attr (%s) failed\n",
            __func__, drv->name);
    }
    error = driver_add_groups(drv, bus->drv_groups); //创建默认的drv_groups attributes
    if (error) {
        /* How the hell do we get out of this pickle? Give up */
        printk(KERN_ERR "%s: driver_create_groups(%s) failed\n",
            __func__, drv->name);
    }

    if (!drv->suppress_bind_attrs) {
        error = add_bind_files(drv); /* 创建手动device与driver的bind/unbind的文件节点, /sys/bus/platform/drivers/gpio-keys/bind, /sys/bus/platform/drivers/gpio-keys/unbind */
        if (error) {
            /* Ditto */
            printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
                __func__, drv->name);
        }
    }

    return 0;

out_del_list:
    klist_del(&priv->knode_bus);
out_unregister:
    kobject_put(&priv->kobj);
    /* drv->p is freed in driver_release()  */
    drv->p = NULL;
out_put_bus:
    bus_put(bus);
    return error;
}

/* driver_attach */

int driver_attach(struct device_driver *drv)
{
    return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); /* 遍历bus的设备链表找到合适的设备就调用__driver_attach,NULL表示从头开始遍历 */
}

/* bus_for_each_dev */

int bus_for_each_dev(struct bus_type *bus, struct device *start,
             void *data, int (*fn)(struct device *, void *))
{
    struct klist_iter i;
    struct device *dev;
    int error = 0;

    if (!bus || !bus->p)
        return -EINVAL;

    klist_iter_init_node(&bus->p->klist_devices, &i,
                 (start ? &start->p->knode_bus : NULL)); //进入bus的devices链表
    while (!error && (dev = next_device(&i))) //设备存在则调用fn即__driver_attach进行匹配
        error = fn(dev, data);
    klist_iter_exit(&i);
    return error;
}

/* __driver_attach */

static int __driver_attach(struct device *dev, void *data)
{
    struct device_driver *drv = data;
    bool async = false;
    int ret;

    /*
     * Lock device and try to bind to it. We drop the error
     * here and always return 0, because we need to keep trying
     * to bind to devices and some drivers will return an error
     * simply if it didn't support the device.
     *
     * driver_probe_device() will spit a warning if there
     * is an error.
     */

    ret = driver_match_device(drv, dev); /* bus的match存在就用bus的,否则就直接匹配成功,match通常实现为首先扫描driver支持的id设备表,如果为NULL就用名字进行匹配 */
    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);
        /*
         * Driver could not match with device, but may match with
         * another device on the bus.
         */
        return 0;
    } else if (ret < 0) {
        dev_dbg(dev, "Bus failed to match device: %d\n", ret);
        return ret;
    } /* ret > 0 means positive match */

    if (driver_allows_async_probing(drv)) {
        /*
         * Instead of probing the device synchronously we will
         * probe it asynchronously to allow for more parallelism.
         *
         * We only take the device lock here in order to guarantee
         * that the dev->driver and async_driver fields are protected
         */
        dev_dbg(dev, "probing driver %s asynchronously\n", drv->name);
        device_lock(dev);
        if (!dev->driver) {
            get_device(dev);
            dev->p->async_driver = drv;
            async = true;
        }
        device_unlock(dev);
        if (async)
            async_schedule_dev(__driver_attach_async_helper, dev);
        return 0;
    }

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

    return 0;
}

/* device_driver_attach */

int device_driver_attach(struct device_driver *drv, struct device *dev)
{
    int ret = 0;

    __device_driver_lock(dev, dev->parent);

    /*
     * If device has been removed or someone has already successfully
     * bound a driver before us just skip the driver probe call.
     */
    if (!dev->p->dead && !dev->driver)
        ret = driver_probe_device(drv, dev); //driver与device绑定操作跟之前device注册流程中的一致,后面不再分析

    __device_driver_unlock(dev, dev->parent);

    return ret;
}

总结一下,driver的注册,主要涉及将自身挂接到bus的driver链表,并将匹配到的设备加入自己的device链表,并且将匹配到的 device的driver成员初始化为该driver,私有属性的driver节点也挂到driver的设备链表下,其中匹配函数是利用利用bus的 match函数,该函数通常判断如果driver有id表,就查表匹配,如果没有就用driver和device名字匹配。bus的probe优先级始终高于driver的。另外注意一点driver是没有总的起始端点的,driver不是 可具体描述的事物。

五,class

1,class概述

在设备模型中,Bus、Device、Device driver等等,都比较好理解,因为它们对应了实实在在的东西,所有的逻辑都是围绕着这些实体展开的。而本文所要描述的Class就有些不同了,因为它是虚拟出来的,只是为了抽象设备的共性。

举个例子,一些年龄相仿、需要获取的知识相似的人,聚在一起学习,就构成了一个班级(Class)。这个班级可以有自己的名称(如295),但如果离开构成它的学生(device),它就没有任何存在意义。另外,班级存在的最大意义是什么呢?是由老师讲授的每一个课程!因为老师只需要讲一遍,一个班的学生都可以听到。不然的话(例如每个学生都在家学习),就要为每人请一个老师,讲授一遍。而讲的内容,大多是一样的,这就是极大的浪费。

设备模型中的Class所提供的功能也一样了,例如一些相似的device(学生),需要向用户空间提供相似的接口(课程),如果每个设备的驱动都实现一遍的话,就会导致内核有大量的冗余代码,这就是极大的浪费。所以,Class说了,我帮你们实现吧,你们会用就行了。

2,struct class结构体

msm_kernel\include\linux\device\class.h
/**
* struct class - device classes
* @name:    Name of the class.
* @owner:    The module owner.
* @class_groups: Default attributes of this class.
* @dev_groups:    Default attributes of the devices that belong to the class.
* @dev_kobj:    The kobject that represents this class and links it into the hierarchy.
* @dev_uevent:    Called when a device is added, removed from this class, or a
*        few other things that generate uevents to add the environment
*        variables.
* @devnode:    Callback to provide the devtmpfs.
* @class_release: Called to release this class.
* @dev_release: Called to release the device.
* @shutdown_pre: Called at shut-down time before driver shutdown.
* @ns_type:    Callbacks so sysfs can detemine namespaces.
* @namespace:    Namespace of the device belongs to this class.
* @get_ownership: Allows class to specify uid/gid of the sysfs directories
*        for the devices belonging to the class. Usually tied to
*        device's namespace.
* @pm:        The default device power management operations of this class.
* @p:        The private data of the driver core, no one other than the
*        driver core can touch this.
*
* A class is a higher-level view of a device that abstracts out low-level
* implementation details. Drivers may see a SCSI disk or an ATA disk, but,
* at the class level, they are all simply disks. Classes allow user space
* to work with devices based on what they do, rather than how they are
* connected or how they work.
*/
struct class {
    const char        *name; //class的名称,会在“/sys/class/”目录下体现
    struct module        *owner; //class所属的模块,虽然class是涉及一类设备,但也是由相应的模块注册的。比如usb类就是由usb模块注册的

    const struct attribute_group    **class_groups; /* 该class的默认attribute,会在class注册到内核时,自动在“/sys/class/xxx_class”下创建对应的attribute文件 */
    const struct attribute_group    **dev_groups; /* 该class下每个设备的attribute,会在设备注册到内核时,自动在该设备的sysfs目录下创建对应的attribute文件 */
    struct kobject            *dev_kobj; /* 表示该class下的设备在/sys/dev/下的目录,现在一般有char和block两个,如果dev_kobj为NULL,则默认选择char */

    int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env); /* 当该class下有设备发生变化时,会调用class的uevent回调函数 */
    char *(*devnode)(struct device *dev, umode_t *mode);

    void (*class_release)(struct class *class); //用于release自身的回调函数
    void (*dev_release)(struct device *dev); /* 用于release class内设备的回调函数。在device_release接口中,会依次检查Device、Device Type以及Device所在的class,是否注册release接口,如果有则调用相应的release接口release设备指针 */

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

    const struct kobj_ns_type_operations *ns_type;
    const void *(*namespace)(struct device *dev);

    void (*get_ownership)(struct device *dev, kuid_t *uid, kgid_t *gid);

    const struct dev_pm_ops *pm;

    struct subsys_private *p; //私有数据,和struct bus_type中的subsys_private一致

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

struct class_interface:

struct class_interface是这样的一个结构:它允许class driver在class下有设备添加或移除的时候,调用预先设置好的回调函数(add_dev和remove_dev)。那调用它们做什么呢?想做什么都行(例如修改设备的名称),由具体的class driver实现。

该结构的定义如下:

struct class_interface {
    struct list_head    node;
    struct class        *class;

    int (*add_dev)        (struct device *, struct class_interface *);
    void (*remove_dev)    (struct device *, struct class_interface *);
};

3,class创建流程

4,device注册时,和class有关的动作

4.1 调用device_add_class_symlinks接口,创建device与class之间的各种符号链接
static int device_add_class_symlinks(struct device *dev)
{
    struct device_node *of_node = dev_of_node(dev);
    int error;

    if (of_node) {
        error = sysfs_create_link(&dev->kobj, of_node_kobj(of_node), "of_node");
        if (error)
            dev_warn(dev, "Error %d creating of_node link\n",error);
        /* An error here doesn't warrant bringing down the device */
    }

    if (!dev->class)
        return 0;

    error = sysfs_create_link(&dev->kobj,
                  &dev->class->p->subsys.kobj,
                  "subsystem"); /* 在该设备所在的sysfs目录创建到该设备所在的class目录的链接,链接名字为subsystem eg: /sys/devices/platform/soc/994000.qcom,qup_uart/tty/ttyMSM0/sussystem -> ../../../../../../class/tty ; /sys/devices/platform/soc/99c000.qcom,qup_uart/tty/ttyHS0/subsystem -> ../../../../../../class/tty */
    if (error)
        goto out_devnode;

    if (dev->parent && device_is_not_partition(dev)) {
        error = sysfs_create_link(&dev->kobj, &dev->parent->kobj,
                      "device");
        if (error)
            goto out_subsys;
    }

#ifdef CONFIG_BLOCK
    /* /sys/block has directories and does not need symlinks */
    if (sysfs_deprecated && dev->class == &block_class)
        return 0;
#endif

    /* link in the class directory pointing to the device */
    error = sysfs_create_link(&dev->class->p->subsys.kobj,
                  &dev->kobj, dev_name(dev)); /* 在该设备所在的class目录创建到该设备所在目录的链接,链接名字为设备的名字,eg: /sys/class/tty/ttyMSM0 -> ../../devices/platform/soc/994000.qcom,qup_uart/tty/ttyMSM0 ; /sys/class/tty/ttyHS0 -> ../../devices/platform/soc/99c000.qcom,qup_uart/tty/ttyHS0 */
    if (error)
        goto out_device;

    return 0;

out_device:
    sysfs_remove_link(&dev->kobj, "device");

out_subsys:
    sysfs_remove_link(&dev->kobj, "subsystem");
out_devnode:
    sysfs_remove_link(&dev->kobj, "of_node");
    return error;
}
4.2 调用device_add_attrs,添加由class指定的attributes(class->dev_attrs)
static int device_add_attrs(struct device *dev)
->
    struct class *class = dev->class;
        if (class) {
        error = device_add_groups(dev, class->dev_groups);
        if (error)
            return error;
    }
4.3 如果存在对应该class的add_dev回调函数,调用该回调函数
    struct class_interface *class_intf;

    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);


        /* 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);
    }

5,应用示例

5.1 使用class_create()创建一个class,并在class中创建一个设备
struct class *sec_class;
struct device *factory_ts_dev;

sec_class = class_create(THIS_MODULE, "tsp");

factory_ts_dev = device_create(sec_class, NULL, 0, info, "tsp");
if (unlikely(!factory_ts_dev)) {
        dev_err(&info->client->dev, "Failed to create factory dev\n");
        ret = -ENODEV;
        goto err_create_device;
}

ret = sysfs_create_group(&factory_ts_dev->kobj, &ts_attr_group);
if (unlikely(ret)) {
    dev_err(&info->client->dev, "Failed to create ts sysfs group\n");
    goto err_create_sysfs;
}
5.2 使用device_register()在已存在的class目录创建一个设备
info->dev.class = sec_class;
info->dev.parent = &info->client->dev;
info->dev.release = bt541_registered_release;

dev_set_name(&info->dev, "tsp_%d", 1);
dev_set_drvdata(&info->dev, info);

ret = device_register(&info->dev);
if (ret) {
    dev_err(&info->client->dev,
        "%s: Failed to register device\n",
        __func__);
}

ret = device_create_bin_file(&info->dev, &attr_data);
if (ret < 0) {
    dev_err(&info->client->dev,
            "%s: Failed to create sysfs bin file\n",
            __func__);
}

show result info:

lynkco:/sys/class/tsp # ls -l //链接到实际的device

total 0

lrwxrwxrwx 1 root root 0 2023-09-11 15:00 tsp -> ../../devices/virtual/tsp/tsp

lrwxrwxrwx 1 root root 0 2023-09-11 15:00 tsp_1 -> ../../devices/platform/soc/984000.i2c/i2c-2/2-0020/tsp/tsp_1

lynkco:/sys/class/tsp/tsp # ls -l

total 0

-rw-rw-r-- 1 root        root   4096 2023-09-11 15:40 bigobject_off

--w--w---- 1 vendor_tcmd system 4096 2023-09-11 13:09 cmd

-r--r----- 1 vendor_tcmd system 4096 2023-09-11 13:09 cmd_result

-r--r--r-- 1 root        root   4096 2023-09-11 15:40 cmd_status

-rw-rw-r-- 1 root        root   4096 2023-09-11 15:40 easy_wakeup_gesture

lrwxrwxrwx 1 root        root      0 2023-09-11 15:40 input -> ../../../platform/soc/984000.i2c/i2c-2/2-0020/input/input12

drwxr-xr-x 2 root        root      0 2023-09-11 15:40 power

-r--r----- 1 vendor_tcmd system 4096 2023-09-11 13:09 raw_data

-r--r----- 1 vendor_tcmd system 4096 2023-09-11 13:09 short_data

lrwxrwxrwx 1 root        root      0 2023-09-11 15:40 subsystem -> ../../../../class/tsp

-rw-r--r-- 1 root        root   4096 2023-09-11 15:40 uevent

 

 

lynkco:/sys/class/tsp/tsp_1 # ls -l

total 0

-rw-rw-r-- 1 root root    0 2023-09-11 15:40 data

lrwxrwxrwx 1 root root    0 2023-09-11 15:40 device -> ../../../2-0020

drwxr-xr-x 2 root root    0 2023-09-11 15:40 power

lrwxrwxrwx 1 root root    0 2023-09-11 15:40 subsystem -> ../../../../../../../../class/tsp

-rw-r--r-- 1 root root 4096 2023-09-11 15:40 uevent

 

六,通过实例来看bus/device/device_driver/class在sysfs中的关系

1,device,/sys/devices/platform/soc/soc:gpio_keys

虚线表示软连接。

2,device,/sys/devices/platform/soc/994000.qcom,qup_uart

device,/sys/devices/platform/soc/99c000.qcom,qup_uart

参考链接:

Linux设备模型(5)_device和device driver

【Linux内核|驱动模型】bus/class/device/driver - 知乎

Linux的设备驱动模型 - 知乎

https://www.cnblogs.com/bcfx/articles/2915120.html

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

selenuim【1】$x(‘xpath’)、WebDriverWait()、try/assert

文章目录 1、执行driver webdriver.Chrome()后很久才打开浏览器2、浏览器多元素定位 $x(‘xpath语法’)3、打开浏览器driver.get("网址")执行了很久才开始定位元素&#xff1a;等待&#xff08;1&#xff09;driver.set_page_load_timeout(t)&#xff08;2&#xff…

VMwareWorkstation17.0虚拟机搭建WindowsXP虚拟机(完整安装步骤详细图文教程)

VMwareWorkstation17.0虚拟机搭建WindowsXP虚拟机&#xff08;完整安装步骤详细图文教程&#xff09; 一、Windows XP1.Windows XP简介2.Windows XP 的下载地址 二、配置 Windows XP 虚拟机运行环境1.新建虚拟机2.选择类型配置3.插入WinXP光盘映像文件(ISO)4.选择操作系统5.命名…

阿里工作10年,失业4个月,每天都在崩溃边缘,有些事现在才明白

在近年的互联网行业风波中&#xff0c;不少大型企业开始缩减人员&#xff0c;而众多小企业则面对生死的考验。在这股裁员潮中&#xff0c;许多人失去了工作&#xff0c;长时间找不到新的就业机会&#xff0c;心中满是失望和绝望。 一位拥有十年工作经验的前阿里巴巴员工在网络上…

浅谈WPF之Binding数据校验和类型转换

在WPF开发中&#xff0c;Binding实现了数据在Source和Target之间的传递和流通&#xff0c;就像现实生活中的一条条道路&#xff0c;建立起了城镇与城镇之间的衔接&#xff0c;而数据校验和类型转换&#xff0c;就像高速公路之间的收费站和安检站。那在WPF开发中&#xff0c;如何…

【一起学习Arcade】(6):属性规则实例_约束规则和验证规则

一、约束规则 约束规则用于指定要素上允许的属性配置和一般关系。 与计算规则不同&#xff0c;约束规则不用于填充属性&#xff0c;而是用于确保要素满足特定条件。 简单理解&#xff0c;约束规则就是约束你的编辑操作在什么情况下可执行。 如果出现不符合规则的操作&#…

MySQL表分区技术介绍

目录 1. 分区概述 1.1 表分区 1.2 表分区与分表的区别 1.3 表分区的好处 1.4 分区表的限制因素 2. 如何判断当前MySQL是否支持分区&#xff1f; 3. 分区类型详解 3.1 MySQL支持分区类型 3.2 RANGE分区 3.2.1 根据数值范围分区 3.2.2 根据TIMESTAMP范围分区 3.2.3 根…

Vue - 调用接口获取文件数据流并根据类型预览

Vue - 调用接口获取文件数据流并根据类型预览 一、接口返回的数据流格式二. 方法实现1. image 图片类型2. txt 文件类型3. pdf 文件类型 一、接口返回的数据流格式 二. 方法实现 1. image 图片类型 <img :src"imageUrl" alt"" srcset"" /&g…

LeetCode每日一题【c++版】

20240227-困难2867-统计树中的合法路径数目 一、概述 由于比较难打&#xff0c;直接截图贴过来了&#xff01; 二、思路 预处理得到 [1,n]中的所有质数&#xff0c;其中 prime[i]是否为质数。根据二维整数整数构建图 g&#xff0c;其中 g[i]表示节点i的所有邻居节点。如果一条…

【二叉树的最近公共祖先】【后序遍历】Leetcode 236. 二叉树的最近公共祖先

【二叉树的最近公共祖先】【后序遍历】Leetcode 236. 二叉树的最近公共祖先 解法1 涉及到结果向上返回就要用后序遍历解法2 自己写的方法 后序遍历 ---------------&#x1f388;&#x1f388;236. 二叉树的最近公共祖先 题目链接&#x1f388;&#x1f388;-----------------…

外包干了6个月,技术退步明显

先说一下自己的情况&#xff0c;本科生&#xff0c;19年通过校招进入广州某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

群晖Synology Drive服务搭建结合内网穿透实现云同步Obsidian笔记文件夹

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-ebec69DBjtGk7apF {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

Rust学习笔记:深度解析内存管理(二)

在这个信息爆炸的时代&#xff0c;学习一门新的编程语言不仅仅是为了找到一份好工作&#xff0c;更是为了打开思维的新窗口。Rust&#xff0c;作为一门注重安全、速度和并发的系统编程语言&#xff0c;正吸引着越来越多的年轻开发者的目光。今天&#xff0c;我们将一起深入探讨…

线程的同步互斥机制3月4日

题目&#xff1a; 代码&#xff1a; #include <stdio.h> #include <pthread.h> #include <string.h> #include <semaphore.h> #include <unistd.h>sem_t sem1,sem2;void* callback1(void*arg) {while(1){if(sem_wait(&sem1)<0) //等待…

python二级常见题目

一.常见语法 jieba—第三方中文分词函数库 jieba—第三方中文分词函数库_jieba库函数-CSDN博客 Python基础——format格式化 Python基础——format格式化_python format-CSDN博客 format()方法的使用超全_format方法-CSDN博客 Python中random函数用法整理 Python中random…

判断回文字符串

判断回文字符串 题目描述&#xff1a;解法思路&#xff1a;解法代码&#xff1a;运行结果&#xff1a; 题目描述&#xff1a; 输入⼀个字符串&#xff0c;判断这个字符串是否是回文字符串&#xff08;字符串的长度小于等于30&#xff0c;字符串不包含空格&#xff09;&#xf…

聊一聊日常开发中如何优雅的避免那无处不在的空指针异常

在Java编程语言中&#xff0c;NullPointerException&#xff08;简称NPE&#xff09;是一种常见的运行时异常&#xff0c;当程序试图访问或操作一个还未初始化&#xff08;即值为null&#xff09;的对象引用时&#xff0c;Java虚拟机就会抛出NullPointerException。如果我们在日…

DataWorks(ODPS)性能优化技巧指南

使用阿里云DataWorks进行数据处理的时候&#xff0c;有时候会遇到一个sql或pyodps&#xff08;本质上还是转化为sql&#xff09;执行很长的情况&#xff0c;这个时候有必要对代码进行性能优化。 一、打开ODPS运行评估报告 一个sql脚本执行完毕后&#xff0c;在运维中心的周期…

java中Timer和Timertask的关系

一 time和timertask的关系 1.1 timer和timertask关系 1.Timer来讲就是一个调度器,而TimerTask呢只是一个实现了run方法的一个类&#xff1b; 2.Timer和TimerTask成对出现&#xff0c;Timer是定时器&#xff0c;TimerTask是定时任务。换句话说&#xff0c;定时任务TimerTask是…

程序员必备的linux常用的26条命令

04 穿越功耗墙&#xff0c;我们该从哪些方面提升“性能”&#xff1f; 上一讲&#xff0c;在讲 CPU 的性能时&#xff0c;我们提到了这样一个公式&#xff1a; 程序的 CPU 执行时间 指令数CPIClock Cycle Time 这么来看&#xff0c;如果要提升计算机的性能&#xff0c;我们可以…

鸿蒙实战开发:【SIM卡管理】

概述 本示例展示了电话服务中SIM卡相关功能&#xff0c;包含SIM卡的服务提供商、ISO国家码、归属PLMN号信息&#xff0c;以及默认语音卡功能。 样例展示 基础信息 介绍 本示例使用sim相关接口&#xff0c;展示了电话服务中SIM卡相关功能&#xff0c;包含SIM卡的服务提供商、…