bus中设备驱动的probe触发逻辑和device、driver的添加逻辑

注:以下的代码皆摘自于linux 4.9.88版本的内核源码,不同版本可能有所出入。

往期内容:

  1. 驱动中的device和device_driver结构体
  2. bus总线的相关结构体和注册逻辑

1. driver的probe触发方式

在 Linux 设备模型中,probe() 函数是驱动与设备绑定的核心机制。其触发时机与设备(device)和设备驱动(device_driver)的注册紧密相关。当内核发现设备与驱动匹配时,调用驱动的 probe() 函数来初始化设备,通常包括硬件初始化、资源分配、设备文件的注册等操作。以下是设备驱动 probe() 触发的几种时机,分为自动触发和手动触发:

自动触发probe()

  1. 设备(device)注册到内核时触发
    • 当通过 device_register() 或者其他相关接口(例如 device_add()device_create())将 struct device 类型的设备对象注册到内核时,内核会查找与该设备名称匹配的驱动程序。如果找到了匹配的 device_driver,并且设备未绑定其他驱动,内核会自动调用该驱动的 probe() 函数来进行初始化。
    • 例如:
struct device my_device;
device_register(&my_device);

在这个过程中,内核会尝试在总线(bus)下查找与 my_device 名称匹配的驱动程序。

  1. 驱动(device_driver)注册到内核时触发
    • 同样地,当通过 driver_register() 接口将一个新的 device_driver 驱动对象注册到内核时,内核会遍历当前总线下已注册的所有设备,检查是否有与该驱动匹配的设备。如果存在匹配设备,并且设备未绑定其他驱动,内核将自动调用该驱动的 probe() 函数。
    • 例如:
struct device_driver my_driver;
driver_register(&my_driver);

在这个过程中,内核会为总线下所有与 my_driver 匹配的设备执行 probe() 操作。

手动触发probe()

  1. 手动调用 device_attach()
    • device_attach() 是一个手动触发设备驱动配对的函数。它会在总线下遍历所有已注册的驱动程序,寻找与指定设备匹配的驱动。如果找到匹配的驱动,且该设备尚未绑定任何驱动,则调用该驱动的 probe()
    • 这种方式的使用场景通常是在设备已经存在但驱动程序尚未加载的情况下,通过手动调用 device_attach(),使设备与驱动程序绑定并执行 probe()
device_attach(&my_device);
  1. 手动调用 driver_attach()
    • driver_attach() 是另一个手动触发配对的接口,它的功能是遍历总线下的所有设备,查找与指定驱动匹配的设备。如果找到匹配的设备,且设备尚未绑定驱动,则执行该驱动的 probe() 操作。
    • 使用场景通常是在驱动已经加载但设备尚未注册的情况下,通过手动调用 driver_attach() 来触发设备绑定:
driver_attach(&my_driver);
- **<font style="color:#DF2A3F;">说白了,driver_register自动触发驱动的probe函数,其实研究一下它的内部就可以发现它调用到了driver_attach</font>**
driver_register -> bus_add_driver(下面会讲) -> 
int bus_add_driver(struct device_driver *drv)
{//......if (drv->bus->p->drivers_autoprobe) {if (driver_allows_async_probing(drv)) {pr_debug("bus: '%s': probing driver %s asynchronously\n",drv->bus->name, drv->name);async_schedule(driver_attach_async, drv);} else {error = driver_attach(drv);  //这不就体现到了,调用了driver_attach,内部会去调用设备驱动probeif (error)goto out_unregister;}}//......	
}
  1. 手动调用 device_bind_driver()
    • 通过 device_bind_driver(),可以手动将驱动绑定到指定设备上,并调用该驱动的 probe() 函数进行初始化。这种方式比上述手动触发的方法更为直接,因为它不进行遍历,而是显式地将驱动和设备关联。
    • 在该接口中,用户可以自己指定设备和驱动的配对,而无需总线来查找匹配项:
device_bind_driver(&my_device);

2. bus中device和device_driver的添加

在 Linux 内核中,设备和驱动程序的管理由 bus 模块处理,提供了一种通用的机制来注册和管理设备(device)和设备驱动(device_driver)。具体来说,bus_add_devicebus_add_driver 是处理设备和驱动程序注册的核心逻辑。

bus_add_device 处理逻辑

/*** bus_add_device - add device to bus* @dev: device being added** - Add device's bus attributes.* - Create links to device's bus.* - Add the device to its bus's list of devices.*/
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_attrs(bus, dev);if (error)goto out_put;error = device_add_groups(dev, bus->dev_groups);if (error)goto out_id;error = sysfs_create_link(&bus->p->devices_kset->kobj,&dev->kobj, dev_name(dev));if (error)goto out_groups;error = sysfs_create_link(&dev->kobj,&dev->bus->p->subsys.kobj, "subsystem");if (error)goto out_subsys;klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);}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_id:device_remove_attrs(bus, dev);
out_put:bus_put(dev->bus);return error;
}

bus_add_device 的主要目的是将设备添加到总线,并进行相关的初始化和管理操作。其处理逻辑如下:

  1. 添加默认属性到内核
    • 调用 device_add_attrs 函数,该函数根据 bus->dev_attrs 指针定义的默认属性,将这些属性添加到内核中。这些属性会显示在设备的 sysfs 目录下,例如 /sys/devices/xxx/xxx_device/
  2. 创建符号链接到总线的设备目录
    • 使用 sysfs_create_link 创建一个符号链接,将设备的 sysfs 目录链接到该设备所在总线的 devices 目录下。这一操作提供了方便的路径,以便用户空间程序能够轻松访问设备的信息。例如:
ls -l /sys/bus/spi/devices/spi1.0
lrwxrwxrwx root root 2014-04-11 10:46 spi1.0 -> ../../../devices/platform/s3c64xx-spi.1/spi_master/spi1/spi1.0
  1. 创建指向总线目录的链接
    • 再次调用 sysfs_create_link,在设备的 sysfs 目录(如 /sys/devices/platform/alarm/)中,创建一个名为 subsystem 的符号链接,指向该设备所在总线的目录。这种设计便于在设备目录中快速找到其所在的总线信息。
ls -l /sys/devices/platform/alarm/subsystem
lrwxrwxrwx root root 2014-04-11 10:28 subsystem -> ../../../bus/platform
  1. 将设备指针保存到总线的设备列表
    • 最后,将设备指针保存在 bus->priv->klist_devices 链表中,以便后续操作中可以方便地查找和管理该设备。

其实也就是调用了device_register内部会调用bus_add_device:

  1. device_register** 函数**:
    • device_register 是用于注册一个新的设备的主要接口。当你调用这个函数时,它会处理设备的创建和初始化。
  2. **内部调用 **bus_add_device:
    • device_register 中,它会执行一系列初始化操作,然后调用 bus_add_device 函数。这个调用的主要目的是将设备添加到相应的总线(bus)中,并执行必要的注册操作。
  3. bus_add_device** 的作用**:
    • bus_add_device 负责将设备的信息添加到总线的数据结构中,设置设备的默认属性,创建符号链接等,使得设备能够在 sysfs 中正确显示,并允许后续的设备管理和操作。

以下是一个简化的示例流程:

int device_register(struct device *dev) {// 一些设备初始化逻辑...// 调用 bus_add_device 将设备添加到总线int ret = bus_add_device(dev);if (ret) {// 错误处理...}// 其他初始化逻辑...return 0;
}
  • device_register 的具体实现可以在 drivers/base/core.c 中找到。
  • bus_add_device 的实现如前所述,在 drivers/base/bus.c 中。

bus_add_driver 处理逻辑

Linux-4.9.88\drivers\base\bus.c:
/*** bus_add_driver - Add a driver to the bus.* @drv: 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);if (!bus)return -EINVAL;pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);priv = kzalloc(sizeof(*priv), GFP_KERNEL);if (!priv) {error = -ENOMEM;goto out_put_bus;}klist_init(&priv->klist_devices, NULL, NULL);priv->driver = drv;drv->p = priv;priv->kobj.kset = bus->p->drivers_kset;error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,"%s", drv->name);if (error)goto out_unregister;klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);if (drv->bus->p->drivers_autoprobe) {if (driver_allows_async_probing(drv)) {pr_debug("bus: '%s': probing driver %s asynchronously\n",drv->bus->name, drv->name);async_schedule(driver_attach_async, drv);} else {error = driver_attach(drv);if (error)goto out_unregister;}}module_add_driver(drv->owner, drv);error = driver_create_file(drv, &driver_attr_uevent);if (error) {printk(KERN_ERR "%s: uevent attr (%s) failed\n",__func__, drv->name);}error = driver_add_groups(drv, bus->drv_groups);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);if (error) {/* Ditto */printk(KERN_ERR "%s: add_bind_files(%s) failed\n",__func__, drv->name);}}return 0;out_unregister:kobject_put(&priv->kobj);/* drv->p is freed in driver_release()  */drv->p = NULL;
out_put_bus:bus_put(bus);return error;
}

bus_add_driver 的目的是将设备驱动注册到相应的总线。其处理逻辑如下:

  1. 分配并初始化struct driver_private
    • 为该驱动的 struct driver_private 指针(priv)分配内存,并初始化其内部字段,包括 priv->klist_devicespriv->driverpriv->kobj.kset 等。同时,将该指针保存在 device_driver 结构体的 p 字段中。
  2. 设置驱动的 kset
    • 将驱动的 kset(priv->kobj.kset)设置为总线的驱动 kset(bus->p->drivers_kset),这意味着所有驱动的 kobject 都位于该总线的驱动目录下(例如 /sys/bus/xxx/drivers)。
  3. 注册驱动的 kobject
    • 以驱动的名字为参数,调用 kobject_init_and_add 函数,在 sysfs 中注册驱动的 kobject,并体现在 /sys/bus/xxx/drivers/ 目录下,例如 /sys/bus/spi/drivers/spidev
  4. 将驱动保存在总线的驱动列表中
    • 将该驱动保存在 bus->priv->klist_drivers 链表中,以便后续管理。
  5. **根据 drivers_autoprobe 值决定是否调用 **driver_attach
    • 根据 drivers_autoprobe 的值来选择是否调用 driver_attach 函数。如果该值为 1,则会自动尝试将匹配的设备与驱动进行配对并调用 probe() 函数。
  6. 创建驱动的 uevent 属性
    • 调用 driver_create_file,在 sysfs 的该驱动目录下创建 uevent 属性,允许用户空间程序获取和响应设备事件。
  7. 添加驱动的默认属性
    • 调用 driver_add_attrs 函数,在该驱动的 sysfs 目录下创建由 bus->drv_attrs 指针定义的默认属性。
  8. 创建 bindunbind 属性
    • 根据 suppress_bind_attrs 标志的值,决定是否在 sysfs 的该驱动目录下创建 bindunbind 属性,这些属性允许用户空间手动绑定和解绑驱动与设备。

通过 bus_add_devicebus_add_driver 接口,Linux 内核提供了一个统一的机制来注册和管理设备和驱动。前者处理设备的注册、属性的创建和符号链接的设置,后者则负责驱动的注册、kobject 的初始化和自动 probe 的控制。这些操作不仅简化了设备与驱动之间的管理流程,也为用户空间提供了便利的接口来监控和管理设备驱动。

3. Bus中设备的probe触发逻辑

这里是通过内部代码对上面第1点的进一步讲解

  • 在 Linux 设备模型中,设备device和驱动device_driver的匹配和 probe() 调用实际是由总线(bus)模块负责的。每个设备和驱动都是通过总线连接的,因此总线模块知道如何为设备和驱动进行配对。
  • 当设备或驱动注册到总线时,总线会负责调用 bus_probe_device()driver_attach()(内部会调用bus_probe_device),以检查当前设备和驱动的匹配情况,并决定是否的调用device_driver中 probe()

来看一下内核代码,以设备调用了driver_register后,自动触发调用设备驱动的probe为例:

\Linux-4.9.88\Linux-4.9.88\drivers\base\driver.c:
int driver_register(struct device_driver *drv)
{//.......ret = bus_add_driver(drv); //继续进入看if (ret)return ret;//.......
}
//---------------分割--------------------
int bus_add_driver(struct device_driver *drv)
{//.......if (drv->bus->p->drivers_autoprobe) { //drivers_autoprobe的影响,这个看下面小点介绍//-----------------------------------------------------------------(1)if (driver_allows_async_probing(drv)) {pr_debug("bus: '%s': probing driver %s asynchronously\n",drv->bus->name, drv->name);async_schedule(driver_attach_async, drv);} else {error = driver_attach(drv); //这里,内部必定会去匹配然后调用设备驱动的probe//继续进入内部查看if (error)goto out_unregister;}}module_add_driver(drv->owner, drv);//.......
}//---------------分割--------------------
int driver_attach(struct device_driver *drv)
{return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); //继续进入//留意一下参4:__driver_attach函数,调用设备驱动的probe函数就在里面
}
//---------------分割--------------------
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));while ((dev = next_device(&i)) && !error)error = fn(dev, data); //这里,调用了传进来的参四,也就是__driver_attach函数,来查看一下这个函数klist_iter_exit(&i);return error;
}
//---------------分割--------------------
static int __driver_attach(struct device *dev, void *data)
{struct device_driver *drv = data;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); //这里是去查看是否和device匹配,一共有四种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);} else if (ret < 0) {dev_dbg(dev, "Bus failed to match device: %d", ret);return ret;} /* ret > 0 means positive match */if (dev->parent)	/* Needed for USB */device_lock(dev->parent);device_lock(dev);if (!dev->driver)driver_probe_device(drv, dev);//--------------(2)篇幅有点长了,在下面(2)处在贴内核源码吧device_unlock(dev);if (dev->parent)device_unlock(dev->parent);return 0;
}

其中__driver_attach的ret = driver_match_device(drv, dev); 就是去查看driver和device是否匹配,一共有四种匹配方式,具体的留到platform bus的讲解时再说

(1) drivers_autoprobe 的影响

  • 每个总线对象中有一个 drivers_autoprobe 变量,控制是否在设备或驱动程序注册时自动触发 probe() 操作。
    • drivers_autoprobe 设为 1(默认值),设备或驱动程序注册时会自动进行配对并调用 probe()
    • drivers_autoprobe 设为 0,则需要手动调用上面所说的手动触发的接口来进行配对和 probe()

drivers_autoprobe 是通过 sysfs 暴露给用户空间的,路径为 /sys/bus/<bus_name>/drivers_autoprobe,因此可以通过用户空间的操作修改 drivers_autoprobe 的值,控制自动 probe 行为。

(2)总线的 probe 操作: bus_probe_device() driver_attach()

  • bus_probe_device()
    • 当设备被添加到总线时(比如调用了device_register()),会调用 bus_probe_device()。该函数遍历该总线下的所有驱动,检查是否有与该设备匹配的驱动程序。如果找到匹配的驱动,并且设备没有绑定驱动,则调用该驱动的 probe() 函数。可以继续看内部代码:
\Linux-4.9.88\drivers\base\dd.c:
int driver_probe_device(struct device_driver *drv, struct device *dev)
{int ret = 0;if (!device_is_registered(dev))return -ENODEV;pr_debug("bus: '%s': %s: matched device %s with driver %s\n",drv->bus->name, __func__, dev_name(dev), drv->name);if (dev->parent)pm_runtime_get_sync(dev->parent);pm_runtime_barrier(dev);ret = really_probe(dev, drv);  //这里,内部就会去调用driver的probe函数pm_request_idle(dev);if (dev->parent)pm_runtime_put(dev->parent);return ret;
}
//---------------分割--------------------
static int really_probe(struct device *dev, struct device_driver *drv)
{//,,,,,,,,if (dev->bus->probe) {ret = dev->bus->probe(dev);if (ret)goto probe_failed;} else if (drv->probe) {ret = drv->probe(dev);  //这,不就调用到了driver的probe函数if (ret)goto probe_failed;}//,,,,,,,,
}
  • driver_attach()
    • driver_attach() 是在驱动注册时被调用的,它会遍历总线下的所有设备,寻找与该驱动匹配的设备。如果找到匹配的设备,且设备没有绑定其他驱动,则调用该驱动的 probe()。它内部还是会调用到bus_probe_device,这个在上面代码分析中已经指出来了

设备驱动 probe() 的调用机制贯穿整个 Linux 设备模型,通过设备和驱动的注册,手动或自动进行设备和驱动的匹配。在大多数情况下,系统会自动触发 probe(),但在某些特殊情况下(如设备和驱动的异步加载),用户也可以手动触发 probe() 来绑定设备和驱动。这一切的背后,是由总线模块负责管理设备与驱动的匹配和绑定逻辑。

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

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

相关文章

ThreeJS入门(091):THREE.PositionalAudio 知识详解,示例代码

作者&#xff1a; 还是大剑师兰特 &#xff0c;曾为美国某知名大学计算机专业研究生&#xff0c;现为国内GIS领域高级前端工程师&#xff0c;CSDN知名博主&#xff0c;深耕openlayers、leaflet、mapbox、cesium&#xff0c;webgl&#xff0c;ThreeJS&#xff0c;canvas&#xf…

边缘人工智能(Edge Intelligence)

边缘人工智能&#xff08;Edge AI&#xff09;是指在边缘设备上直接运行人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;算法的技术。机器学习是一个广泛的领域&#xff0c;近年来取得了巨大的进步。它所基于的原则是&#xff0c;计算机可以通过从数据…

免杀对抗—javaASMMSF源码特征修改汇编调用CS内联C

前言 今天讲最后的两个语言java和汇编&#xff0c;那么基本所有语言就讲了一个遍了。java在后门免杀这一块呢其实是有点鸡肋的&#xff0c;其它语言编译成的是exe&#xff0c;而java编译成的是jar包&#xff0c;而jar包又得有java环境才能运行&#xff0c;不像exe是个电脑都行…

数据分析案例-机器学习工程师薪资数据可视化分析

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

揭秘AI写作工具:如何改变内容创作新格局

小伙伴们&#xff0c;今儿咱们来聊聊那些个让人眼前一亮、脑洞大开的AI写作神器——笔灵AI写作、宙语AI写作、博思白板AI写作&#xff0c;还有讯飞星火&#xff0c;它们啊&#xff0c;简直就是文案人儿的超级辅助&#xff0c;让咱们写东西的时候&#xff0c;灵感嗖嗖地往外冒&a…

利士策分享,彩礼能否临时增加?

利士策分享&#xff0c;彩礼能否临时增加&#xff1f; 在中国的传统婚俗中&#xff0c;彩礼作为男方家庭向女方家庭表达诚意与尊重的一种方式&#xff0c;承载着丰富的文化内涵。 然而&#xff0c;在现代社会&#xff0c;彩礼的多少、是否临时增加等问题&#xff0c;却常常成为…

LLM大模型企业应用实战-“消灭”LLM幻觉的利器

大模型一定程度改变了我们生活工作的思考方式&#xff0c;越来越多的个人和企业在思考如何将大模型应用到更加实际的生产生活。 1 LLM的问题 1.1 幻觉 LLM因为是一个预训练模型&#xff0c;它已有一些知识储备&#xff0c;我们提的问题跟他的知识储备不相符时&#xff0c;会…

小目标检测利器:YOLOv8+SAHI使用教程

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

MySQL连接查询:自连接

先看我的表结构 emp表 自连接也就是把一个表看作是两个作用的表就好&#xff0c;也就是说我把emp看作员工表&#xff0c;也看做领导表 自连接 基本语法 select 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件;例子1&#xff1a;查询员工 及其 所属领导的名字 select a.n…

《从零开始大模型开发与微调》真的把大模型说透了!零基础入门一定要看!

2022年底&#xff0c;ChatGPT震撼上线&#xff0c;大语言模型技术迅速“席卷”了整个社会&#xff0c;人工智能技术因此迎来了一次重要进展。与大语言模型相关的研发岗薪资更是水涨船高&#xff0c;基本都是5w月薪起。很多程序员也想跟上ChatGPT脚步&#xff0c;今天给大家带来…

【C++指南】类和对象(二):类的默认成员函数——全面剖析 :构造函数

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《C指南》 期待您的关注 ​ 阅读本篇文章之前&#xff0c;你需要具备的前置知识&#xff1a;类和对象的基础 点击下方链接 【C指南…

顶会论文复现:PROVING TEST SET CONTAMINATION IN BLACK BOX LANGUAGE MODELS

文章目录 1 资料2 我的总结3 复现源码首先你需要有gpt的api接口安装&#xff1a;数据集执行指令源码 4 结果 1 资料 我复现的源码:https://github.com/Whiffe/test_set_contamination 官网源码&#xff1a;https://github.com/tatsu-lab/test_set_contamination 论文&#x…

Java实体对象转换利器MapStruct详解

概述 现在的JAVA项目多数采用分层结构&#xff0c;参考《阿里巴巴JAVA开发手册》。 分层之后&#xff0c;每一层都有自己的领域模型&#xff0c;即不同类型的 Bean&#xff1a;  DO &#xff08; Data Object &#xff09; &#xff1a;与数据库表结构一一对应&#xff0c;…

游戏盾是如何解决游戏行业攻击问题

随着游戏行业的迅猛发展&#xff0c;其高额的利润和激烈的市场竞争吸引了众多企业和创业者的目光。然而&#xff0c;这一行业也面临着前所未有的业务和安全挑战&#xff0c;尤其是DDoS&#xff08;分布式拒绝服务&#xff09;攻击&#xff0c;已经成为游戏行业的一大威胁。今天…

C语言基础(10)之指针(2)

在上一篇文章中我们谈到了指针&#xff0c;并给老铁们讲解了什么是指针、指针类型、野指针以及指针运算等知识。在这篇文章中小编将继续带大家了解指针的相关知识点。 1. 指针和数组 指针和数组之间又能有什么联系呢&#xff1f;在谈这个之前&#xff0c;我们先来讲讲指针和数…

Android15车载音频之Virtualbox中QACT实时调试(八十八)

简介: CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布:《Android系统多媒体进阶实战》🚀 优质专栏: Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏: 多媒体系统工程师系列【原创干货持续更新中……】🚀 优质视频课程:AAOS车载系统+…

微信小程序开发-调试及配置文件介绍

一&#xff0c;隐藏控制台系统日志 在小程序开发中&#xff0c;如果你想要隐藏控制台中的系统日志&#xff0c;可以通过以下步骤进行操作&#xff1a; 打开小程序的开发工具。在开发工具的控制台(Console)中&#xff0c;找到你想要隐藏的系统日志。右键点击该系统日志条目。在…

MySQL连接查询:外连接

先看我的表结构 dept表 emp表 外连接分为 1.左外连接 2.右外连接 1.左外连接 基本语法 select 字段列表 FORM 表1 LEFT [OUTER] JOIN 表2 ON 条件;例子&#xff1a;查询emp表的所有数据&#xff0c;和对应部门的员工信息&#xff08;左外连接&#xff09; select e.*, d.n…

利士策分享,旅游是否要舟车劳顿才能尽兴?

利士策分享&#xff0c;旅游是否要舟车劳顿才能尽兴&#xff1f; 国庆假期&#xff0c;当夜幕降临&#xff0c;城市灯火阑珊&#xff0c;一场关于美食与等待的较量悄然上演。 李女士在北京天坛公园附近餐厅的等位经历——前方1053桌的壮观景象&#xff0c;不仅让人咋舌&#xf…

信息学奥赛复赛复习14-CSP-J2021-03网络连接-字符串处理、数据类型溢出、数据结构Map、find函数、substr函数

PDF文档回复:20241007 1 P7911 [CSP-J 2021] 网络连接 [题目描述] TCP/IP 协议是网络通信领域的一项重要协议。今天你的任务&#xff0c;就是尝试利用这个协议&#xff0c;还原一个简化后的网络连接场景。 在本问题中&#xff0c;计算机分为两大类&#xff1a;服务机&#x…