Linux网络设备驱动(一) _驱动模型

Linux素来以其强大的网络功能著名,同时, 设备‘>网络设备也作为三大设备之一, 成为Linux驱动学习中必不可少的设备类型, 此外, 由于历史原因, Linux并没有强制对设备‘>网络设备贯彻其”一切皆文件”的思想, 设备‘>网络设备不以/dev下的设备文件为接口,用户程序通过socket作为访问硬件的接口。本文以Linux3.14.0内核为例, 讨论Linux中的网络驱动模型

Linux的设备‘>网络设备并不使用文件作为用户程序访问设备‘>网络设备接口,所以/sys/dev下和/dev下并没有相应的设备‘>网络设备文件,在Linux中,用户程序最终使用套接字来访问设备‘>网络设备

框架

上图就是经典的OSI 7层模型,Linux的网卡驱动程序处于OSI模型中的数据链路层,他的职责就是将上上层的协议栈传过来的信息通过网卡发送出去,\

Linux的网络驱动模型采用4层结构:

  • 协议接口 向网络协议提供统一的数据包发送接口,上层任何形式的协议都通过dev_queue_xmit()发送,通过netif_rx()接收,都使用sk_buff作为数据的载体
  • 设备接口向协议接口层提供统一的用于描述具体设备‘>网络设备属性和操作的结构体net_device,这个结构从整体规划了具体操作硬件的设备驱动功能层的结构,是设备驱动功能层的各个函数的容器,开发网络驱动的主要工作就是编写驱动功能层的相关函数以填充net_device数据结构的内容并将net_device注册到内核
  • 驱动功能层的各个函数设备‘>网络设备接口层net_device数据结构的具体成员,是驱动 设备‘>网络设备硬件完成相应动作的程序,它通过ndo_start_xmit()函数启动发送动作,并通过设备‘>网络设备上的中断触发接收操作,通过中断或POLL机制接收
  • 设备与媒介层 是完成数据收发的物理实体,网卡被设备驱动层中的函数在物理上驱动,对于Linux系统而言,设备‘>网络设备和媒介都可以是虚拟的

第2 and 第3层是驱动开发主要关心的层次

核心类与方法简述

在分析核心对象与核心方法的时候, 找到在哪向上提供接口, 在哪向下操作硬件.????

核心类

  • sk_buff是网络驱动框架中信息的载体, 是网络分层模型中对数据进行层层打包以及层层解包的载体
  • net_device对象描述了一个设备‘>网络设备, **其中的struct net_device_ops *netdev_ops是操作方法集, 向上提供接口的同时也向下操作硬件
  • netdev_ops一个设备‘>网络设备的操作方法集
  • 私有数据 和其他模型框架一样, net_device对象也提供了承载私有数据的域, 不过不是使用void *, 参见下文alloc_ethdev

核心方法

核心类与方法详述

sk_buff

套接字缓冲区是数据在多层模型中传输的载体,其被处理的最终结果就是网络数据包, Linux巧妙的使用了移动head/tail指针的方式实现了网络模型中每一层对数据包的加工过程。sk_buff部分定义如下

 427 struct sk_buff {
 428         /* These two members must be first. */
 429         struct sk_buff          *next;
 430         struct sk_buff          *prev;
 432         ktime_t                 tstamp;
 434         struct sock             *sk;
 435         struct net_device       *dev;
 443         char                    cb[48] __aligned(8);
 445         unsigned long           _skb_refdst;
 449         unsigned int            len,
 450                                 data_len;
 451         __u16                   mac_len,
 452                                 hdr_len;
 473         __be16                  protocol;
 534         __u16                   inner_transport_header;
 535         __u16                   inner_network_header;
 536         __u16                   inner_mac_header;
 537         __u16                   transport_header;
 538         __u16                   network_header;
 539         __u16                   mac_header;
 540         /* These elements must be at the end, see alloc_skb() for details.  */
 541         sk_buff_data_t          tail;
 542         sk_buff_data_t          end;
 543         unsigned char           *head,
 544                                 *data;
 545         unsigned int            truesize;
 546         atomic_t                users;
 547 };

struct sk_buff

–435–>对应的net_device

–449–>len有效数据长度

–451–>mac_len表示mac头长度

–473–>protocol协议编号

–537–>transport_header指向传输层协议头

–538–>network_header指向IP头

–539–>mac_header指向以太网头

–541–>tail指向当前数据包的尾地址, 随着各个网络层的加工而变化

–542–>end 指向数据缓冲的内核尾地址, 不变

–543–>head指向数据缓冲(PackertData)的内核首地址, 不变

–544–>data指向当前数据包的首地址, 随着各个网路层的加工而变化

net_device

net_device是设备接口层的核心, 也是编写网络驱动核心的对象

1160 struct net_device {
1167         char                    name[IFNAMSIZ];
1179         unsigned long           mem_end;        /* shared mem end       */
1180         unsigned long           mem_start;      /* shared mem start     */
1181         unsigned long           base_addr;      /* device I/O address   */
1182         int                     irq;            /* device IRQ number    */
1189         unsigned long           state;
1190
1191         struct list_head        dev_list;
1192         struct list_head        napi_list;
1193         struct list_head        unreg_list;
1194         struct list_head        close_list;
1210         netdev_features_t       features;
1212         netdev_features_t       hw_features;
1214         netdev_features_t       wanted_features;
1243         const struct net_device_ops *netdev_ops;
1244         const struct ethtool_ops *ethtool_ops;
1245         const struct forwarding_accel_ops *fwd_ops;
1248         const struct header_ops *header_ops;
1250         unsigned int            flags;  /* interface flags (a la BSD)   */
1251         unsigned int            priv_flags; /* Like ‘flags‘ but invisible to userspace.
1252                                              * See if.h for definitions. */
1253         unsigned short          gflags;
1254         unsigned short          padded; /* How much padding added by alloc_netdev() */
1256         unsigned char           operstate; /* RFC2863 operstate */
1257         unsigned char           link_mode; /* mapping policy to operstate */
1259         unsigned char           if_port;        /* Selectable AUI, TP,..*/
1260         unsigned char           dma;            /* DMA channel          */
1262         unsigned int            mtu;    /* interface MTU value          */
1263         unsigned short          type;   /* interface hardware type      */
1264         unsigned short          hard_header_len;        /* hardware hdr length  */
1270         unsigned short          needed_headroom;
1271         unsigned short          needed_tailroom;
1274         unsigned char           perm_addr[MAX_ADDR_LEN]; /* permanent hw address */
1275         unsigned char           addr_assign_type; /* hw address assignment type */
1276         unsigned char           addr_len;       /* hardware address length      */
1289         struct kset             *queues_kset;
1386         int                     watchdog_timeo; /* used by dev_watchdog() */
1480 };

struct net_device

–1170–>name是设备‘>网络设备的名称, 设备‘>网络设备被载入后会出现在ifconfig中, 比如默认的eth0就是这个

–1179–>mem_start和mem_end存储了设备所使用的共享内存起始和结束地址

–1180–>base_addr表示设备‘>网络设备的IO基地址

–1182–>irq为设备使用的中断号

–1210–>用户层可以修改的特征

–1212–>用户层不能修改的特征

–1230–>网卡的统计信息

–1243–>netdev_ops即设备‘>网络设备的操作方法集

–1244–>ethtool的方法集

–1248–>header_ops表示协议头操作集

–1250–>用户层可以修改的标准

–1251–>用户层不能修改的标准

–1254–>alloc_netdev()时加入的pad的大小

–1259–>if_port指定多端口设备使用哪一个端口

–1260–>dma即分配给该设备的dma通道

–1264–>hard_header_len表示设备‘>网络设备的硬件头长度, 在以太网设备的初始化过程中, 该成员被赋值为ETH_HLEN, 即14

–1263–>type是硬件类型

–1262–>mtu即MAX TRANSFER UNIT

–1270–>needed_headroom表示数据包缓冲区中需要的head_room大小

–1271–>数据缓冲区中需要的tailroom的大小

–1274–>mac地址

–1275–>硬件地址类型

–1276–>硬件地址长度

–1289–>设备所属的kset

–1386–>计数值

下面是一些与net_device相关的内核API

分配/释放

//linux/etherdevice.h
/**
 * 分配及初始化net_device对象()
 * @sizeof_priv - 私有数据大小(单位:字节数)
 * 返回值:失败:NULL, 成功:net_device对象的首地址
 */
struct net_device *alloc_etherdev(int sizeof_priv);

//linux/netdevice.h
/**
 * 分配及初始化net_device对象
 * @int sizeof_priv - 私有数据大小(单位:字节数)
 * @const char *name - 物理接口名("名称%d")
 * @unsigned char name_assign_type - NET_NAME_UNKNOWN
 * @void (*setup)(struct net_device *) - 初始化函数
 * 返回值:失败:NULL成功:net_device对象的首地址
 */
struct net_device *alloc_netdev(int sizeof_priv, const char *name,unsigned char name_assign_type,void (*setup)(struct net_device *));

//释放
void free_netdev(struct net_device *dev);

通过形参的名字就可以看出, 这个函数其实不止分配了一个net_device对象的空间, 因为net_device中并没有一个存储私有数据的域(dev->platform_data除外), 关于net_device的私有数据的存储方式, 我们可以通过这个函数的定义中看出, 这也就是要使用内核API来分配一个net_device结构的原因

//include/linux/netdevice.h
2897 #define alloc_netdev(sizeof_priv, name, setup) 2898         alloc_netdev_mqs(sizeof_priv, name, setup, 1, 1)  
//net/core/dev.c
6308 struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
6309                 void (*setup)(struct net_device *),
6310                 unsigned int txqs, unsigned int rxqs)
6311{
6330         alloc_size = sizeof(struct net_device);
6331         if (sizeof_priv) {
6332                 /* ensure 32-byte alignment of private area */
6333                 alloc_size = ALIGN(alloc_size, NETDEV_ALIGN);
6334                 alloc_size += sizeof_priv;
6335         }
6336         /* ensure 32-byte alignment of whole construct */
6337         alloc_size += NETDEV_ALIGN - 1;
6339         p = kzalloc(alloc_size, GFP_KERNEL | __GFP_NOWARN | __GFP_REPEAT);
6340         if (!p)
6341                 p = vzalloc(alloc_size);
6342         if (!p)
6343                 return NULL;
6344
6345         dev = PTR_ALIGN(p, NETDEV_ALIGN);
6346         dev->padded = (char *)dev - (char *)p;

6406 }

alloc_net_dev_wqs

–6330–>用alloc_size保存一个net_device的大小

–6333–>如果有私有数据的要求, 对alloc_size重新赋值, 新的大小为net_device的32字节对齐大小+请求的私有数据大小

–6337–>确保分配的空间是32字节对齐, 与–6345–配合使用

–6339–>分配空间, 空间的大小=net_device大小+请求的私有数据空间大小+(NETDEV_ALIGN-1)大小 net_device的私有数据是预分配的, 就在net_device对象的下面.

–6345–>6337行的作用在这一行就体现出来了, PTR_ALIGN是向上对齐, 这样久保证了dev指向的空间是32字节对齐的, 之前分配的多余的部分作为padded被添加到net_device域中,所以, 考虑到net_device本身凑巧就是32字节对齐的,最后得到的内存布局是“net_device+priv_data+padded”的一块物理连续空间

初始化

//以太网的初始化
void ether_setup(struct net_device *dev);

这个函数也是一个重头, 在初始化一个以太网设备的时候应该被调用, 它的主要作用就是针对以太网标准对net_device对象进行初始化.

//net/ethernet/eth.c
359 void ether_setup(struct net_device *dev)
360 {
361         dev->header_ops         = &eth_header_ops;
362         dev->type               = ARPHRD_ETHER;
363         dev->hard_header_len    = ETH_HLEN;
364         dev->mtu                = ETH_DATA_LEN;
365         dev->addr_len           = ETH_ALEN;
366         dev->tx_queue_len       = 1000; /* Ethernet wants good queues */
367         dev->flags              = IFF_BROADCAST|IFF_MULTICAST;
368         dev->priv_flags         |= IFF_TX_SKB_SHARING;
369
370         memset(dev->broadcast, 0xFF, ETH_ALEN);
371
372 }

注册/注销

//注册
int register_netdev(struct net_device *dev);
//注销
void unregister_netdev(struct net_device *dev);

netdevice_ops

1002 struct net_device_ops {
1003         int                     (*ndo_init)(struct net_device *dev);
1004         void                    (*ndo_uninit)(struct net_device *dev);
1005         int                     (*ndo_open)(struct net_device *dev);
1006         int                     (*ndo_stop)(struct net_device *dev);
1007         netdev_tx_t             (*ndo_start_xmit) (struct sk_buff *skb,
1008                                                    struct net_device *dev);
1013         void                    (*ndo_change_rx_flags)(struct net_device *dev,
1014                                                        int flags);
1015         void                    (*ndo_set_rx_mode)(struct net_device *dev);
1016         int                     (*ndo_set_mac_address)(struct net_device *dev,
1017                                                        void *addr);
1018         int                     (*ndo_validate_addr)(struct net_device *dev);
1019         int                     (*ndo_do_ioctl)(struct net_device *dev,
1020                                                 struct ifreq *ifr, int cmd);
1021         int                     (*ndo_set_config)(struct net_device *dev,
1022                                                   struct ifmap *map);
1023         int                     (*ndo_change_mtu)(struct net_device *dev,
1024                                                   int new_mtu);
1025         int                     (*ndo_neigh_setup)(struct net_device *dev,
1026                                                    struct neigh_parms *);
1027         void                    (*ndo_tx_timeout) (struct net_device *dev);
1028
1148 };

struct net_device_ops

–1003–>ndo_init是初始化函数指针, 如果这个指针被设置了, 那么在设备被注册的时候会被调用, 用来初始化net_device对象, 相当于C++中的构造函数, 这个指针可以为NULL

–1004–>析构函数,回收清理

–1005–>打开设备, ifconfig xxx up 时会回调

–1006–>关闭设备, ifconfig xxx down 时会回调

–1007–>发送数据,会被dev_queue_xmit()回调

–1016–>设置mac

–1018–>检查mac是否有效

–1019–>对设备‘>网络设备的ioctl操作

–1021–>配置接口, 用于配置读写参数, 读状态, 控制网卡等

–1023–>设置MTU值

–1027超时重发, 超时后会被回调

ndo_init()模板

下面是一个可以借鉴的ndo_init()的实现

void xxx_init(struct net_device *dev)
{
        /* 设备的私有信息结构体 */
        struct xxx_priv *priv;

        /* 检查设备是否存在, 以及设备需要的硬件资源 */
        xxx_hw_init();

        /* 初始化以太网设备的公用成员 */
        ether_setup(dev);

        /* 设置设备的成员函数指针 */
        dev->netdev_ops->ndo_open = xxx_open;
        dev->netdev_ops->ndo_stop = xxx_stop;
        dev->netdev_ops->ndo_set_config = xxx_set_config;
        dev->netdev_ops->ndo_start_xmit = xxx_tx;
        dev->netdev_ops->ndo_do_ioctl = xxx_ioctl;
        dev->netdev_ops->ndo_get_stats = xxx_stats;
        dev->netdev_ops->ndo_change_mtu = xxx_change_mtu;
        dev->netdev_ops->ndo_tx_timeout = xxx_tx_timeout;
        dev->netdev_ops->ndo_watchdog_timeo = xxx_timeout;
        dev->rebuild_header = xxx_rebuild_header;
        dev->hard_header = xxx_header;

        /* 获得私有数据并将其初始化 */
        priv = netdev_priv(dev);
        /* 初始化priv代码 */
}

ndo_open()/ndo_release()模板

int xxx_open(struct net_device *dev)
{
        /* 申请资源 */
        ret = request_irq(dev->irq, &xxx_interrupt, 0, dev->name, dev);

        /* 激活设备的发送队列 */
        netif_start_queue(dev);
}

init xxx_release(struct net_device *dev)
{
        /* 释放资源 */
        free_irq(dev->irq,dev);

        /* 关闭设备的发送队列 */
        netif_stop_queue(dev);
}

ndo_start_xmit()模板

int xxx_tx(struct sk_buff *skb, struct net_device *dev)
{
        int len;
        char *data, shortpkt[ETH_ZLEN];
        if(xxx_send_available(...)){    //发送队列未满, 可以发送
                /* 获得有效数据指针和长度 */
                data = skb->data;
                len = skb->len;
                if(len < ETH_ZLEN){
                        /* 如果帧长小于以太网帧最小长度,补0 */
                        memset(shortpkt,0,ETH_ZLEN);
                        memcpy(shortpkt,skb->data,skb->len);
                        len = ETH_ZLEN;
                data = shortpkt;
                }
                dev->trans_start = jiffies;     //记录发送时间戳

                /* 设置硬件寄存器让硬件将数据发出去 */
                xxx_hw_tx(data,len,dev);

        }else{
                netif_stop_queue(dev);
                ...
        }
}

timeout()模板

这个函数会在超时的时候被调用, 通常用来实现重发.

void xxx_tx_timeout(struct net_device *dev)
{
        ...
        netif_wake_queue(dev);  //重新启动设备发送队列
}

中断处理函数

设备‘>网络设备媒介层相设备驱动功能层发送数据接口, 网卡接收到数据是通过中断的方式上报的, 所以网络驱动中的中断处理函数就是第一时间队接收到的数据进行处理的地方, 这个函数最终一定要调用netif_rx()将收到的数据上报到协议接口层. 下面是一个简单的接收数据中断处理函数模板

static void xxx_rx(struct xxx_device * dev)
{
        ...
        length = get_rev_len(...);
        /* 分配新的套接字缓冲区 */
        skb = dev_alloc_skb(length +2);
        skb_researve(skb, 2);   //对齐
        skb->dev = dev;

        /* 读取硬件上接收到的数据 */
        insw(ioaddr +RX_FRAME_PORT, skb_put(skb, length), length >>1);
        if(length &1){
                skb ->data[length - 1] = inw(ioaddr + RX_FRAME_PORT);
        }
        /* 获取上层协议类型 */
        skb->protocol = eth_type_trans(skb,dev);

        /* 把数据包交给上层 */
        netif_rx(skb);

        /* 记录接收时间戳 */
        dev->last_rx = jiffies;
        ...
}

static void xxx_interrupt(int irq, void *dev_id)
{
        ...
        switch(status & ISQ_EVENT_MASK){
        case ISQ_RECEIVER_EVENT:                                                                         /* 获取数据包 */
            xxx_rx(dev);
            break;
        /* 其他类型中断 */
        }
}

其他API

这些API都在“include/linux/netdevice.h”中声明了

得到私有数据指针
void *netdev_priv(const struct net_device *dev);

设置MAC
int eth_mac_addr(struct net_device *dev, void *p);
检查MAC地址是否有效
int eth_validate_addr(struct net_device *dev);
修改MTU值
int eth_change_mtu(struct net_device *dev, int new_mtu);

//随机生成mac地址
void eth_hw_addr_random(struct net_device *dev)
void eth_random_addr(u8 *addr);

//开启发送队列
void netif_start_queue(struct net_device *dev)
//停止发送队列
void netif_stop_queue(struct net_device *dev)

//runing
void netif_carrier_on(struct net_device *dev)
//not runing
void netif_carrier_off(struct net_device *dev)


以上就是Linux网络设备驱动(一) _驱动模型的全部内容了,更多内容请关注:CPP学习网_CPP大学

本文固定链接:CPP学习网_CPP大学-Linux网络设备驱动(一) _驱动模型

时间: 2024-10-11 03:37:05

Linux网络设备驱动(一) _驱动模型的相关文章

windows 10驱动签名_win 10驱动数字签名_驱动签名注意事项

目前对于驱动开发者而言往往面对着一个问题--windows 10驱动数字签名问题,根据Symantec VeriSign代码签名中国区代理商深圳易维信的客服所介绍,目前颁发的Symantec CodeSign还不支持Windows 10,针对国外的数字签名证书已经有EV版CodeSign,暂时还没有支持中文版的EV CodeSign.所以对于急需解决win 10驱动签名的用户,可以选择申请 GlobalSign EV代码签名证书.不过EV代码签名证书的申请审核要求更为严格,具体可以联系一下深圳易

001_linux驱动之_驱动的加载和卸载

(一)驱动的安装: 1. 可以将驱动程序静态编译进内内核中 2. 也可以将它作为模块在使用的时候再加载 注:在配置内核时候,如果某个配置被设置为m,就表示它将会被编译成模块 (二)加载和卸载驱动使用命令(模块的拓展名为.ko) 1. insmod 命令加载  (使用示例:insmod  first_drv.ko) 2. rmmod 命令卸载 (使用示例:rmmod  first_drv.ko) 3. lsmod 查看内核中已经加载了哪些模块 (使用示例:lsmod) (三) 1. 当使用insm

Linux MTD子系统 _从模型分析到Flash驱动模板

MTD(Memory Technology Device)即常说的Flash等使用存储芯片的存储设备,MTD子系统对应的是块设备驱动框架中的设备驱动层,可以说,MTD就是针对Flash设备设计的标准化硬件驱动框架.本文基于3.14内核,讨论MTD驱动框架. MTD子系统框架 设备节点层:MTD框架可以在/dev下创建字符设备节点(主设备号90)以及块设备节点(主设备号31), 用户通过访问此设备节点即可访问MTD字符设备或块设备. MTD设备层: 基于MTD原始设备, Linux在这一层次定义出

Linux网络设备驱动结构概述

网络设备驱动相比字符型设备的驱动要复杂一些,除了总体上驱动的框架有一些相似外,有很多地方都是不同,但网络设备驱动有一个很大的特点就是有固定的框架可以遵循,具体的框架会在后边详细的叙述.1.网络协议接口层在网络协议接口层,只提供了两个抽象函数dev_queue_xmit()与 netif_rx(),之所以称之为抽象函数,是因为这两个函数抽象了很多底层的操作,不管是那个芯片它在网络协议结构的操作函数都是这两个函数,采用这样的抽象后,给上层带来了很多的方便,给上层协议提供统一的数据包收发接口,无论上层

linux嵌入式驱动-总线设备驱动模型

一个农夫想要合理的理财,他给你未来N天的每天支出(1<=N<=100000), 并计划把这N天分成M个部分(1 <=M <=N)(每个部分的天数是连续的),要求求出这些部分里花费最和最大值最小,输出这个最大值. 100 400 300 100 500 101 400 可以这么划分(100 400) (300 100) (500) (101)(400) ,五个分组里最大值是500,这个划是最佳的了,因为在其他划分里肯定有部分是大于500的,如(100) (400 300) (100

迅为4412开发板Linux驱动教程——总线_设备_驱动注册流程详解

视频下载地址: 驱动注册:http://pan.baidu.com/s/1i34HcDB 设备注册:http://pan.baidu.com/s/1kTlGkcR 总线_设备_驱动注册流程详解 ? 注册流程图 ? 设备一般都需要先注册,才能注册驱动 – 现在越来越多的热拔插设备,反过来了.先注册驱动,设备来了再注册 设备 ? 本节使用的命令 – 查看总线的命令#ls /sys/bus/ – 查看设备号的命令#cat /proc/devices ? 设备都有主设备号和次设备号,否则255个设备号不

字符设备驱动、平台设备驱动、设备驱动模型、sysfs的关系

Linux驱动开发的童鞋们来膜拜吧:-)  学习Linux设备驱动开发的过程中自然会遇到字符设备驱动.平台设备驱动.设备驱动模型和sysfs等相关概念和技术.对于初学者来说会非常困惑,甚至对Linux有一定基础的工程师而言,能够较好理解这些相关技术也相对不错了.要深刻理解其中的原理需要非常熟悉设备驱动相关的框架和模型代码.网络上有关这些技术的文章不少,但多是对其中的某一点进行阐述,很难找到对这些技术进行比较和关联的分析.对于开发者而言,能够熟悉某一点并分享出来已很难得,但对于专注传授技术和经验给

[kernel]字符设备驱动、平台设备驱动、设备驱动模型、sysfs几者之间的比较和关联

转自:http://www.2cto.com/kf/201510/444943.html Linux驱动开发经验总结,绝对干货! 学习Linux设备驱动开发的过程中自然会遇到字符设备驱动.平台设备驱动.设备驱动模型和sysfs等相关概念和技术.对于初学者来说会非常困惑,甚至对Linux有一定基础的工程师而言,能够较好理解这些相关技术也相对不错了.要深刻理解其中的原理需要非常熟悉设备驱动相关的框架和模型代码.网络上有关这些技术的文章不少,但多是对其中的某一点进行阐述,很难找到对这些技术进行比较和关

(55)Linux驱动开发之一驱动概述

驱动概述 驱动用在哪里?非标准类设备的编写和标准类设备的驱动移植. 驱动实际上是随着linux内核相伴而生的.某段代码能够控制我们的硬件去工作,去动,这段代码就称为我们的驱动代码. 技术只是一种手段,一种技巧,我们应该利用技术去搞出产品. 5.1.1_2.什么是驱动1_2 5.1.1.1.理解驱动的概念 (1)驱动一词的字面意思 (2)物理上的驱动 (3)硬件中的驱动 (4)linux内核驱动.软件层面的驱动广义上就是指:这一段代码操作了硬件去动,所以这一段代码就叫硬件的驱动程序.(本质上是电力