返回顶部
首页 > 资讯 > 操作系统 >如何解析Linux 驱动架构
  • 675
分享到

如何解析Linux 驱动架构

2023-06-28 17:06:06 675人浏览 独家记忆
摘要

今天就跟大家聊聊有关如何解析linux 驱动架构,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。首先,需要熟悉操作系统的设计与实现,推荐大家看 MINIX作者的那部书,同时把MINIX

今天就跟大家聊聊有关如何解析linux 驱动架构,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

  1. 首先,需要熟悉操作系统的设计与实现,推荐大家看 MINIX作者的那部书,同时把MINIX的kernel代码研读一下。 不然,你不知道操作系统都有哪些模块, 不知道操作系统要做些什么事情,提供什么功能。简单地说,操作系统首先要驱动 CPU,然后提供那几大管理(进程,内存,文件),实现一两百个系统呼叫,提供驱动接口, 用户态与内核之间进行切换。

  2. 去intel的官网,找一下 ‘Intel® 64 and IA-32 Architectures Software Developer’s Manual’ , 了解一下 CPU的架构,工作模式,底层编码。否则, 你不知道 gdt,ldt,page table,实地址,保护模式,定时器中断都是什么东西,为什么操作系统要这样来设置寄存器。这块基本上全汇编语言,对CPU的初始化,寄存器设置,手册上面都有严格的时需要求。 哪些操作需要屏蔽中断,哪些需要在一个指令周期完成等等。有了上面的基础后,大概知道一个操作系统大概要做些什么事情, 如何驱动底层的 CPU,这个时候阅读 linux的kernel代码,事半功倍。

kernel分为两个模块:一个是core: cpu, 中断,进程,内存几大管理, 提供系统呼叫。另一个是driver。 linux的driver 都是有架构的,不需要从底层做起。各类架构称为‘子系统’:如,block子系统,net子系统,usb子系统等。 别看操作系统的代码量大,其实,driver占了估计 80%的代码量, 这些都是不需要去看的。驱动是否放在内核,就是微内核与宏内核的区别。

阅读源码过程中,观其大略即可,主要了解整个结构,以及程序的流程。如:系统呼叫的调用,追一个就可以了—— 看看操作系统如何捕捉软中断, 根据中断号,dispatch到相应的服务程序,如何保存现场, 完成后,又如何回到用户态。 系统呼叫调用,核心就是 dispatch的流程。 追完一支系统呼叫,其它的大概就知道怎么回事了。driver 也就一样的, 找个简单的驱动看看, 从驱动层一直到驱动的架构,流程清楚就可以了。 如字符设备驱动, 追一下注册后, 驱动框架如何把该设备放入 list,当有用户请求的时候,它又如何查找到相应的设备,调用相应的操作函数, 一路追下来,流程大概就清楚了。

不建议一开始就阅读“linux内核源码分析” 之类的书, 会让读者一头雾水。 正确的方法应该是, 先了解相应的背景知识后,再来阅读源码。Driver 框架的源码的位于 drivers/base/, 它是整个驱动模式的基础框架,相当于OO语言的里面的Object对象。 其实,Linux 的驱动框架就是一个OO的结构, core模块定义数据结构,函数接口,实现各种通用的功能——相当于OO里面的基类。各模块的设备驱动程序则只需要实现 core模块里面定义的接口即可。

bus, driver, device 框架

linux的外围设备驱动,都是通过 bus + driver + device来管理的,其实也好理解 ,外设都是通过总线来与cpu通讯的。kernel会实现各种总线的规范以及设备管理(设备检测,驱动绑定等),驱动程序只需要注册自己的驱动,实现对设备的读写控制即可。

这类驱动通常是2个层次:总线子系统 + 驱动模块,它的流程大概是:

  1. bus_register(xx)

kernel里面的各bus子系统(如:serio, usb, pci, …)会使用该函数来注册自己。

  1. driver_register(xx)

驱动模块使用它来向总线系统注册自己,这样驱动模块只需要关注相应driver接口的实现。通常,bus子系统会对 driver_reGISter来进行封装,如:

  • serio 提供serio_register_driver()
  • usb 提供usb_register_driver()
  • pci提供 pci_register_driver()
  1. registe_device(xx)

各总线除了管理driver外,还管理device,通常会提供一支api来添加设备,如: input_register_device, serio_add_port.实现上都是通过一个链表对设备进行管理,通常是在初始化或者probe的时候, 添加设备。

设备(device)指的是具体实现总线协议的物理设备,如对serio总线而言,i8042就是它的一个设备,而该总线连接的设备(鼠标,键盘)则是一个serio driver。

注册

bus.c 和 driver.c 分别对 bus,driver和device进行管理,提供注册bus, driver和查找 device 功能。

bus_register(*bus) 这个函数会生成两个list,用来保存设备和驱动。

INIT_LIST_HEAD(&priv->interfaces);
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);

* priv是 struct subsys_private定义在 driver/base/base.h

driver_register(*drv) 实际上就是调用 bus_add_driver(*drv) 把 drv 添加到 klist_drivers:

klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);

同理注册device,也是通过 bus_add_device(*dev),添加到 klist_devices:

klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);

以 hid_bus_type为例,执行 bus_register(&hid_bus_type) 后, hid_bus_type->p->klist_devices 和 hid_bus_type->p->klist_klist_drivers 这两个list 会被初始化,为后面的 driver和 device 注册做准备,driver数据结构如下:

       static struct hid_driver tpkbd_driver = {
        .name = "lenovo_tpkbd",
        .id_table = tpkbd_devices,
        .input_mapping = tpkbd_input_mapping,
        .probe = tpkbd_probe,
        .remove = tpkbd_remove,
    };

注册driver时,它先经过 __hid_register_driver(&tpkbd_driver),设置一些基本参数。

hdrv->driver.bus = &hid_bus_type;
.....    
driver_register(&hdrv->driver);

设置’driver.bus’字段后,driver和bus的对应关系就建立起来了。 然后, 经过 driver_register 后,hid_bus_type->p->list_drivers 保存了 tpkbd_driver.

Q: driver模块是不知道 hid_driver 这个数据结构的,它如何能把它的指针放到list里面呢?

答案是”不能”, list_drivers 是不能保存 hid_driver 指针的。driver模块提供了一个接口: ‘struct device_driver’ , hid_driver 这个结构里面需要包含该结构。

      struct hid_driver {
              const struct hid_device_id *id_table;
                  
                 struct device_driver driver;
      }

注册的时候,取的是 driver 字段的地址,也就是 hid_driver.driver 的指针, driver_register(&hdrv->driver); 当从 driver模块 callback 到 hid-core模块的时候, 如

 static int hid_bus_match(struct device *dev, struct device_driver *drv)
 {
         struct hid_driver *hdrv = container_of(drv, struct hid_driver, driver);
         struct hid_device *hdev = container_of(dev, struct hid_device, dev);

         return hid_match_device(hdev, hdrv) != NULL;
 }

使用 container_of 就把 hid_driver.driver 的指针转换成了hid_driver 的指针--这个方法类似 OO编程里面使用基类指针指向派生类对象。Linux普通使用这个方法,来构建框架。

device和driver绑定

当增加新device的时候,bus 会轮循它的驱动列表来找到一个匹配的驱动,它们是通过device id和 driver的id_table来进行 ”匹配”的,主要是在 driver_match_device()[drivers/base/base.h] 通过 bus->match() 这个callback来让驱动判断是否支持该设备,一旦匹配成功,device的driver字段会被设置成相应的driver指针 :

really_probe()
{
    dev->driver = drv;
    if (dev->bus->probe) {
        ret = dev->bus->probe(dev);
        ...
    } else if (drv->probe) {
        ret = drv->probe(dev);
        ...
    }
}

然后 callback 该 driver 的 probe 或者 connect 函数,进行一些初始化操作。

同理,当增加新的driver时,bus也会执行相同的动作,为驱动查找设备。因此,绑定发生在两个阶段:

驱动找设备,发生在driver向bus系统注册自己时候,函数调用链是:

driver_register –> bus_add_driver –> driver_attach() [dd.c] -- 将轮循device链表,查找匹配的device。

设备查找驱动,发生在设备增加到总线的的时候,函数调用链是:

device_add –> bus_probe_device –> device_initial_probe –> device_attach -- 将轮循driver链表,查找匹配的driver。

匹配成功后,系统继续调用 driver_probe_device() 来 callback ‘drv->probe(dev)’ 或者 ‘bus->probe(dev) –>drv->connect(),在probe或者connect函数里面,驱动开始实际的初始化操作。因此,probe() 或者 connect() 是真正的驱动’入口’。

对驱动开发者而言,最基本是两个步骤:

  • 定义device id table.
  • probe()或connect()开始具体的初始化工作。
如何解析Linux 驱动架构

(driver和device注册流程图)

实例分析:atkbd键盘驱动

Serio Bus 主要是支持 PS/2,串口等串行设备协议,物理上可以通过i8042控制芯片来连接PS/2的鼠标或键盘,它的架构是:

  • serio.c 实现总线框架。
  • serio_register_port 注册底层读写设备-- port就是 serio的底层通讯设备, 它执行serio总线的底层读写。
  • serio_register_driver 注册驱动,与port进行绑定,利用port进行底层的读写通讯。

atkbd 驱动注册的时侯,需指定它支持的port类型。

serio->id.type        = SERIO_8042; //表明驱动需要8042的支持。

serio_register_driver()     // 注册自己, 根据设备id绑定相应的 port(本例中是8042)。 

作为serio的port, i8042 通过 serio_register_port 来注册,生成serio对象,这样驱动程序就可以通过 serio->wirte/read 来调用i8042进行底层的通讯。 数据流程框图如下:

如何解析Linux 驱动架构

driver 通过 bus 匹配 port,通过port与外设通讯。

我们可以在sys接口 [/sys/bus/serio/] 目录下找到设备和驱动的相关信息,里面的内容可以通过 DEVICE_ATTR_XX 系列宏定义来添加。 port 的命令规则是serio0, serio1, serioN 是自动增加的。

dev_set_name(&serio->dev, "serio%ld", (long)atomic_inc_return(&serio_no) - 1);

atkbd注册serio驱动后,还需要注册input设备,它需要实现input子系统的接口,作为一个input设备工作。input.c 定义了callback 和公用接口,子模块实现相应接口。

keybord:  drivers/input/keyboard/atkbd.c

注册设备
atkbd.c: atkbd_connect  → input_register_device()

Input子系统通过event 字符设备来与应用程序进行通讯。

evdev.c: evdev_init → input_register_handler → ....
cdev_init(xx, fops) // 处理  /dev/input/eventX的读写操作。

atkdb通过注册Serio Bus 驱动和Input device来打通从应用程序到外设通讯链路:

application –> /dev/input/eventX –> Input 子系统 –> atkdb驱动 –> serio bus –> i8042 port –> 物理键盘。

我们可以看到字符设备主要是用来提供应用层接口,Bus框架则用来管理外设驱动。

Input提供了proc文件接口,可以查看相应的信息。

cat /proc/bus/input/devices : 可以得到某个设备的event number.
cat /proc/bus/input/handlers

通过cat eventX可以得到按键产生input_event,查看 event里面的原始数据

sudo cat /dev/input/eventXX | hexdump
XX: event number.

USB

以usb serial的代码为例来说明一下usb总线驱动的基本工作流程。

注册

    struct usb_driver *udriver = kzalloc(sizeof(*udriver), GFP_KERNEL);
    udriver->name = "usb_ftdi";
    udriver->probe = usb_ftdi_probe;
    rc = usb_register(udriver);
    udriver->id_table = id_table;
    rc = driver_attach(&udriver->drvwrap.driver);

先创建一个udriver --

  • probe 函数用来做硬件初始化
  • id_table用来标识驱动支持的芯片类型,用于匹配外设。
  • usb_register 注册到usb总线
  • driver_attach 插入驱动。

插入驱动后,将会与usb的底层设备(hub)进行绑定。hub在初始化的时侯,会开启一个任务来检测端口的变化,并使用缺省的”endpoint”来枚举外设,得到它的“接口描述符”,与驱动绑定后,会把该外设的信息通过 probe函数回传给驱动,通过id比对后,找到对应的外设驱动,在_usb_ftdi_probe_ 函数进行具体的驱动初始化。

通讯

USB设备框架是 device –> interface –> endpoint。如某个设备拥有音频和存储两个功能,那么它有”音频接口“和“存储接口”,每个接口又包含几个通讯“端点”,用来同主机通讯。其中“endpoint 0” 是缺省的通讯端点,主机通过它来读取设备的各种信息--在usb的规范里称为“描述符”,如:设备描述符,接口描述符,端点描述符,每个设备都要提供一套usb的“标志描述符”供主机来枚举它。

_usb_ftdi_probe_首先分析“接口描述符”,得到它的端口信息--

for (i = 0; i desc.bNumEndpoints; ++i) {
    epd = &iface_desc->endpoint[i].desc;
    if (usb_endpoint_is_bulk_in(epd)) {
            ...
    } else if (usb_endpoint_is_bulk_out(epd)) {
            ...
    } else if (usb_endpoint_is_int_in(epd)) {
            ...
    } else if (usb_endpoint_is_int_out(epd)) {
            ...
    }
}

usb共有4类“端点”--

  1. bulk : 用于大量的数据传输,如:U盘。
  2. Control : 控制信息传输。
  3. Interrupt : 低频低延时数据传输。
  4. Isochronal: 周期性数据传输。

驱动需要在probe函数分析“端点描述符”来创建相应的端点,每个“端点”都有IN/OUT两个方向--

  • IN : Host 读取 设备数据。
  • OUT : Host 写数据到设备。

控制管道

endpoint 0是usb协议的标准控制端点,主机通过这个端点来枚举设备,读取它的信息。usb_control_msg 这个函数通过”控制管道”来读写外设的’寄存器’,核心参数是 “Request”, “Request Type”,”wWalue”和”wIndex”。具体参数值需要向设备制造查询,如果是通用设备可以直接查询相应的规范。

以FTDI_SIO_GET_MODEM_STATUS 为例:


    usb_control_msg(dev,  usb_rcvctrlpipe(dev, 0),
                    5, 0xC0, 0, priv->port,
                    buf, 1, WDR_TIMEOUT);

根据设备的定义,我们得到的参数如下:

  • request -- 6
  • requesttype -- 0xC0
  • wValue -- 0
  • wIndex -- Port (在设备的描述符里找回)

usb_rcvctrlpipe(dev, 0),创建一个读的“管道”,使用的是默认的控制端点0。

下面是一个设置”波特率”的例子:

#define FTDI_SIO_SET_BAUDRATE_REQUEST_TYPE 0x40
#define FTDI_SIO_SET_BAUDRATE_REQUEST 3


具体调用如下:

    usb_control_msg(port->serial->dev,
                usb_sndctrlpipe(port->serial->dev, 0),
                FTDI_SIO_SET_BAUDRATE_REQUEST,
                FTDI_SIO_SET_BAUDRATE_REQUEST_TYPE,
                1, port,
                NULL, 0, WDR_SHORT_TIMEOUT);

usb_sndctrlpipe(dev, 0),创建默认(端点0)的写控制“管道”,由于只是改命令无 附加数据,buf参数是’NULL’,长度是’0’,其它的核心参数,根据协议规范写入即可。

Bulk 管道

bulk 用于大量的数据传输,数据封装在urb里,urb 全称为”USB Request Block” ,类似于网络的IP包,它由Host调度,通过层次的hub传递到设备。USB2是主机对从机的单向通讯,读写数据都由主机来发动。

Host写数据到设备

usb_fill_bulk_urb(port->write_urbs[i], udev,
            usb_sndbulkpipe(udev, epd->bEndpointAddress),
            port->bulk_out_buffers[i], buffer_size,
            write_bulk_callback, port);

usb_submit_urb(urb, mem_flags);  // 启动

void  write_bulk_callback(struct urb *urb) {
    
    count = port->serial->type->prepare_write_buffer(
        port, urb->transfer_buffer, port->bulk_out_size);
    urb->transfer_buffer_length = count;

    result = usb_submit_urb(urb, mem_flags);
}

如上图的示例代码,它是一个连续传输数据的流程,

首先,usb_fill_bulk_urb --

  • usb_sndbulkpipe 定义一个主机输出的管道。
  • port->bulk_out_buffers[i]和buffer_size 提供数据缓冲和大小。
  • write_bulk_callback, urb传输完成后的callback。

其次, usb_submit_urb -- 启动”写数据”,提交urb到主机去调度。

最后,定义 write_bulk_callback 函数 --

  • 更新数据缓冲区。
  • 再次usb_submit_urb ,轮循调用这个callback,直到数据传输完毕,如果数据可以一次性传输完成的,就不需要定义这个 callback了。

Host 从设备读取数据

usb_fill_bulk_urb(port->read_urbs[i], udev,
            usb_rcvbulkpipe(udev, epd->bEndpointAddress),
            port->bulk_in_buffers[i], buffer_size,
            type->read_bulk_callback, port);

usb_submit_urb(port->read_urbs[index], mem_flags); // 启动


static void read_bulk_callback(struct urb *urb) {
    for (i = 0; i actual_length; i++) 
        printk(KERN_CONT " 0x%x", urb->transfer_buffer[i]);
     usb_submit_urb(port->read_urbs[urbinx], GFP_ATOMIC);
}

上面的代码则是一个连续读取数据的流程,

首先,usb_fill_bulk_urb --

  • usb_rcvbulkpipe 定义一个主机输出的管道。
  • port->bulk_in_buffers[i]和buffer_size 提供数据缓冲和大小。
  • read_bulk_callback, urb传输完成后的callback。

其次, usb_submit_urb -- 启动”读数据”,提交urb到主机去调度。

最后,定义 read_bulk_callback 函数 --

  • 提取缓冲区的数据。
  • 再次usb_submit_urb ,轮循调用这个callback,直到数据传输完毕,如果不需要再读取数据,就不要再提交 urb。

从上面例子可以看出,bulk的读写流程都是先创建urb,然后使用 _usb_submit_urb_来启动读写,在callback里面处理数据,如果需要继续读写,则在callback函数里,再次调用 usb_submit_urb

Interrupt 管道

通讯流程同bulk,只是 usb_fill_int_urb 多了一个 ‘interval’ 参数,用来设置它的调度时间,可以进行一些时序控制。

Isochronal 管道

用来传输一定速率的数据,如:音视频流。速率通过 ‘interval’ 参数来设置,驱动通过循环检测 URB的状态,来持续写入/读取数据。

块设备

块设备指的是存储设备,块设备驱动就是存储驱动如:HD,SSD。它不同于外设驱动,Linux 用 Block 子系统对它们进行管理,把应用层的IO读写请求,转变为Request ,传给相应的块设备驱动。

那么文件系统与Block子系统是什么关系呢?

Block子系统主要是提供最底层的数据读写,也就是raw io,文件系统使用它进行IO操作,文件系统只专注于文件系统格式,以读取分区为例: 文件系统在初始化阶段,会call blkdev_get, 如果块设备还没有初始化,blkdev_get 则 调用块设备的 rescan_partitions, 去扫描分区,初始化设备。

如何解析Linux 驱动架构

注册

#define FR_MAJOR  310
#define DEVICE_NAME "fooram"
注册块设备(主设备号)
register_blkdev(FR_MAJOR, DEVICE_NAME);
unregister_blkdev(FR_MAJOR,DEVICE_NAME);

注册设备(MAJOR, MINOR)
blk_register_region(MKDEV(FR_MAJOR, 0),1, ....);
blk_unregister_region(MKDEV(FR_MAJOR, 0),1, ...);

添加磁盘

dev->gd = alloc_disk(1);
dev->queue =blk_MQ_init_queue(&dev→tag_set);  //初始化请求队列
dev->gd->major = FR_MAJOR;
dev->gd->first_minor = 0;
dev->gd->fops = &fr_fops;
dev->gd->queue = dev->queue;
dev->gd->private_data = dev;
sprintf (dev->gd->disk_name, "frd0");
set_capacity(dev->gd, size);
add_disk(dev->gd);

初始化请求队列

struct blk_mq_tag_set  tag_set;

dev->tag_set.ops = &fr_mq_ops;
dev->tag_set.nr_hw_queues = 1;
dev->tag_set.queue_depth = 16;
dev->tag_set.numa_node = NUMA_NO_NODE;
dev->tag_set.flags = BLK_MQ_F_SHOULD_MERGE;
dev->tag_set.driver_data = dev;
err = blk_mq_alloc_tag_set(&dev->tag_set);
dev->queue = blk_mq_init_queue(&dev→tag_set);

static struct blk_mq_ops fr_mq_ops = {
    .queue_rq       =  fr_queue_rq,
    .map_queue   =  blk_mq_map_queue,
};

初始化请求队列是添加磁盘的核心操作,块设备驱动的核心就是围绕着“请求队列”来展开,它的核心任务就是优化这个队列读写请求。当_add_disk_把磁盘加入系统后,请求队列就随时可以调度了。

处理设备请求

*BIO 数据结构*

如何解析Linux 驱动架构

*Request Queue*

如何解析Linux 驱动架构

请求队列的每个”请求”(struct request)都包含着一个bio队列,bio结构里面就是设备的寻址(扇区,长度)和对应的内存空间。 “读”请求就是把设备对应的扇区写到相应的内存空间,“写”请求则刚好相反,把内存的数据写入相应的扇区。最简单的驱动就是用一个循环,依次处理各个请求,进行底层的IO读写,如:

*Loop处理请求 (fr_queue_rq)*

rq_for_each_segment (bvec, rq, iter) {
       char *buffer = kmap_atomic(bvec.bv_page) + bvec.bv_offset;
        unsigned nsecs = bvec.bv_len >> 9;
        fr_transfer(dev, sector, nsecs, buffer, write);
        sector += nsecs;
        kunmap_atomic(buffer);
 }

设备读写数据(fr_transfer)

这个函数就是具体存储设备的底层读写操作,PCI设备可以用_PCI_WRITE来写数据,USB设备也可以使用 BULK管道来传输数据。下面的示例代码没有物理设备是一个ram disk,简单地用memcpy_ 来传输数据。

    loff_t  pos = sector     loff_t  len = nsecs 
    //底层IO 如,PCI_WRITE(IOBase, register add,);
    if (write)
        memcpy(dev->data + pos, buffer, len);
    else
        memcpy(buffer, dev->data + pos, len);

看完上述内容,你们对如何解析Linux 驱动架构有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注编程网操作系统频道,感谢大家的支持。

--结束END--

本文标题: 如何解析Linux 驱动架构

本文链接: https://lsjlt.com/news/320855.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 如何解析Linux 驱动架构
    今天就跟大家聊聊有关如何解析Linux 驱动架构,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。首先,需要熟悉操作系统的设计与实现,推荐大家看 MINIX作者的那部书,同时把MINIX...
    99+
    2023-06-28
  • 如何解析Linux驱动中的platform总线
    如何解析Linux驱动中的platform总线,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。1、platform 总线简介1.1、Linux 驱动的分离和分层思...
    99+
    2023-06-22
  • 如何解析Linux系统架构中的内核
    如何解析Linux系统架构中的内核,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。概述Linux系统一般有4个主要部分组成,内核、shell、文件系统和应用程序。内核、shell...
    99+
    2023-06-16
  • Linux 网络驱动-MAC、PHY层驱动框架(三)
    I.MX6ULL 网络外设设备树    I.MX6ULL 有两个 10/100M 的网络 MAC 外设,因此 I.MX6ULL 网络驱动主要就是这两个网络 MAC 外设的驱动。这两个外设的驱动都是一样的,我们分析其 中一个就行了,首先肯定是...
    99+
    2023-09-02
    linux 运维 服务器
  • 如何解析.NET三层架构
    如何解析.NET三层架构,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。所谓三层架构,是在客户端与数据库之间加入了一个“中间层”,也叫组件层。这里所说的三层体系,...
    99+
    2023-06-17
  • Linux的tty架构及UART驱动知识点有哪些
    这篇文章主要讲解了“Linux的tty架构及UART驱动知识点有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Linux的tty架构及UART驱动知识点有哪些”吧!一、模块硬件学习1.1...
    99+
    2023-06-15
  • 如何让Linux驱动11-Linux设备驱动统一模型
    这期内容当中小编将会给大家带来有关如何让Linux驱动11-Linux设备驱动统一模型,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。1. 设备树概念1.1.设备树感性认识设备树(Device Tree),...
    99+
    2023-06-15
  • 汽车架构解析:python解析Autosar架构的ARXML
    文章目录 前言一、Container-I-PDU概念引入二、以文本形式读取ARXML文件三、解析Frame的基本参数四、解析Frame中的PDU五、解析PDU中的Signals六、解析Sign...
    99+
    2023-09-01
    汽车 python 开发语言
  • 如何解析Linux系统结构
    这期内容当中小编将会给大家带来有关如何解析Linux系统结构,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Linux系统主要由内核、shell、文件系统和应用程序组成,它们使得用户可以运行程序、管理文件并...
    99+
    2023-06-28
  • Hadoop架构解析
    Hadoop是一个开源的分布式存储和处理大数据的框架,它的架构主要包括以下几个核心组件: Hadoop Distributed ...
    99+
    2024-02-29
    Hadoop
  • 详解linux 驱动编写(sd卡驱动)
    随着sd卡的流行,sd卡在嵌入式设备上使用的场景也越来越多。那下面我们可以看一下,linux驱动框架上是怎么处理sd卡驱动的? 1、代码目录地址 drivers/mmc 2、基本结构 从mmc的代码结构可以看得出...
    99+
    2022-06-04
    linux sd卡驱动 linux驱动编写 linux sd卡
  • Facebook移动架构:Android Flux架构详解
      要为Android应用找到一个好的架构不是一件容易的事情。谷歌似乎不太在乎这个事情,因此在设计模式上,除了Activity 生命周期管理之外,再也没有官方的推荐。  ...
    99+
    2022-06-06
    flux Android 架构
  • Linux下如何进行SPI驱动
    Linux下如何进行SPI驱动,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。1. SPI总线1.1. SPI总线概述SPI,是英语Serial Peripheral &nb...
    99+
    2023-06-15
  • Linux如何安装显卡驱动
    小编给大家分享一下Linux如何安装显卡驱动,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!Linux安装显卡驱动五部曲:***步:下载一个for Linux版的显...
    99+
    2023-06-17
  • 如何安装Linux网卡驱动
    小编给大家分享一下如何安装Linux网卡驱动,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!Linux下网卡驱动安装及问题:首先需要确认网卡是否安装。PCI详细信息查看#lspci\\如果有Ethernet相关信息说明网卡硬...
    99+
    2023-06-16
  • Linux如何编译声卡驱动
    Linux如何编译声卡驱动,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。关于Linux编译声卡驱动:惠普笔记本刚买来时,安装Debian 5,通常会遇到两个问题。一个是无线...
    99+
    2023-06-16
  • linux系统如何更新驱动
    在Linux系统中,可以通过以下步骤更新驱动:1. 打开终端,使用管理员权限登录。2. 使用命令`sudo apt update`更...
    99+
    2023-09-06
    linux
  • linux如何查看串口驱动
    在Linux中,可以使用以下命令来查看串口驱动:1. 使用`dmesg | grep tty`命令来查看系统启动时检测到的串口设备信...
    99+
    2023-09-06
    linux
  • 如何理解Linux中Xen的奇异架构
    如何理解Linux中Xen的奇异架构,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。  这一篇我要体验的虚拟机系统是Xen。在虚拟机领域,Xen具有非常高的知名度,其名字经常...
    99+
    2023-06-13
  • 解析如何在android中增加gsensor驱动(MMA7660)
    系统原来用的是BOSCH_BMA222的gsensor, 现在要求换成使用MMA7660,我们来看一下怎样增加驱动和调试过程。 1. 修改Makefie首先,修改放置驱动的目录...
    99+
    2022-06-06
    Android
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作