2021年Linux技术总结(四):Linux 驱动

2023-05-16

一、裸机驱动开发流程

​ 所谓裸机在这里主要是指系统软件平台没有用到操作系统。在基于ARM处理器平台的软件设计中,如果整个系统只需要完成一个相对简单而且独立的任务,那么可以不使用操作系统,只需要考虑在平台上如何正确地执行这个单任务程序。不过,在这种方式下同样需要一个Boot Loader,这个时候的Boot Loader一般是自己写的一个简单的启动代码加载程序。大家所熟悉的各种Boot Loader下的设备驱动,其实就是很好的裸机驱动程序。比如说U-Boot下的网卡驱动、串口驱动、LCD驱动等。
​ 在裸机方式下,ARM的软件集成开发环境就显得极为重要,因为在这种方式下可以把所有代码都放在这个环境里面编写、编译和调试。在这种方式下测试驱动程序,首先要完成CPU的初始化,然后把需要测试的程序装载到系统的RAM区/或者SDRAM中。当然,如果需要处理一些复杂的中断处理的话,最好也把CPU的复位向量表放到RAM区中。把所有程序都调试好之后,再把最后的程序烧写到Flash里面去执行。

​ 所以裸机驱动的开发相比于在Linux设备树系统下开发要麻烦的多,对技术人员要求也要高上很多,所以我也没写过什么复杂的裸机驱动,就一些简单的GPIO、中断、时钟什么的,大概流程就是:
​ 1、熟悉外设;
​ 2、使用外设所需要的引脚;
​ 3、参考开发手册配置相应的寄存器;
​ 4、驱动烧写;
​ 5、测试;

二、Linux下DTB(设备树)驱动框架

Linux 操作系统的驱动与裸机上的驱动有很多不同:

  • 具有分层结构
    1、用户程序在用户模式下,而内核、驱动在sys管理模式下,裸机是在同一个模式下运行
    2、用户程序与内核程序操纵的地址都是虚拟地址
  • 考虑多用户,并发性
    1、多个程序都在访问同一个设备(串口),它们具有互斥访问的特性,同一时间只有一个程序能占用设备
  • 考虑协议
    1、不同设备具有不同的驱动,但是协议是一成不变的,因此需要给应用程序提供一个通用的接口,这样驱动代码可以变,但协议的接口是不变的

​ Linux中的三大类驱动:字符设备驱动、块设备驱动和网络设备驱动,三类驱动的调用框架是一样的,即:应用程序对设备进行操作时,只能通过库中的函数,这个函数就会进入内核,然后内核调用驱动,驱动再操作设备。
在这里插入图片描述

三、字符设备驱动

​ 字符设备驱动是Linux 驱动中最基本的一类设备驱动,字符设备就是指在I/O传输过程中以字符为单位进行传输的设备,读写数据是分先后顺序的。比如GPIO、IIC、SPI,LCD等都属于字符设备。

3.1 字符驱动开发框架

  在裸机上,驱动是直接对寄存器进行操作,在Linux下也是对寄存器的操作,但是Linux下驱动的编写需要符合Linux的驱动框架,Linux驱动开发重点是了解其驱动框架。


  整个Linux驱动开发的流程框架就是:

  • 1、入口函数 module_init 挂载;
  • 2、register_chrdev 注册字符设备;
  • 3、alloc_chrdev_region 动态注册设备号;
  • 4、file_operations 设备操作函数编写(重点);
  • 5、unregister_chrdev_region 动态注销设备号;
  • 6、unregister_chrdev 注销字符设备;
  • 7、出口函数module_exit 卸载;
  • 额外的,需要指出许可MODULE_LICENSE,否则编译无法通过,作者MODULE_AUTHOR可以不写。

​ Linux内核中结构体 file_operations,集合了 Linux内核字符设备驱动操作函数,内容如下所:

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
	int (*iterate) (struct file *, struct dir_context *);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*mremap)(struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **, void **);
	long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);
	void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
	unsigned (*mmap_capabilities)(struct file *);
#endif
};

结构体中比较常用的几个成员:

  • owner拥有该结构体的模块的指针,一般设置为 THIS_MODULE。
  • llseek函数用于修改文件当前的读写位置。
  • read函数用于读取设备文件。
  • write函数用于向设备文件写入 (发送 )数据 。
  • poll是个轮询函数,用于查询设备是否可以进行非阻塞的读写。
  • unlocked_ioctl函数提供对于设备的控制功能,与应用程序中的 ioctl函数对应。
  • compat_ioctl函数与 unlocked_ioctl函数功能一样,区别在于在 64位系统上,32位的应用程序调用将会使用此函数。在 32位的系统上运行 32位的应用程序调用的是unlocked_ioctl。
  • mmap函数用于将将设备的内存映射到进程空间中 (也就是用户空间 ),一般帧缓冲设备会使用此函数,比如 LCD驱动的显存,将帧缓冲 (LCD显存 )映射到用户空间中以后应用程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制。
  • open函数用于打开设备文件。
  • release函数用于释放 (关闭 )设备文件,与应用程序中的 close函数对应。
  • fasync函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中。
  • aio_fsync函数与 fasync函数的功能类似,只是 aio_fsync是异步刷新待处理的数据。

3.2 设备树

​ Device Tree 起源于 OpenFirmware (OF),是一种采用树形结构描述硬件的数据结构,由一系列被命名的结点(node)和属性(property)组成,结点本身可包含子结点,主要由三大部分组成:头(Header)、结构块(Structure block)、字符串块(Strings block)。

  • 头:主要描述设备树的一些基本信息,例如设备树大小,结构块偏移地址,字符串块偏移地址等。偏移地址是相对于设备树头的起始地址计算的。
  • 结构块:一个线性化的结构体,是设备树的主体,以节点node的形式保存了目标单板上的设备信息。
  • 字符串块:通过节点的定义知道节点都有若干属性,而不同的节点的属性又有大量相同的属性名称,因此将这些属性名称提取出一张表,当节点需要应用某个属性名称时直接在属性名字段保存该属性名称在字符串块中的偏移量。
    设备树的编写是通过DTS文件来编写的,这里不讲DTS的语法,DTS的结构图如下:
    在这里插入图片描述

​ 树的主干就是系统总线,IIC 控制器、GPIO 控制器、SPI 控制器等都是接到系统主线上的分支。IIC 控制器有分为IIC1 和IIC2 两种,其中IIC1 上接了FT5206 和AT24C02这两个IIC 设备,IIC2 上只接了MPU6050 这个设备。DTS 文件的主要功能就是按照图中所示的结构来描述板子上的设备信息。

3.4 驱动示例

​ 这是一个控制gpio的驱动代码,内容没什么用,用来了解一下开发流程即可,驱动代码:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/ide.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define CAMERA_CNT			1		  	// 设备号个数
#define CAMERA_NAME			"camera"	// 名字
#define TAKEPICTURE 		1		    // 拍照

// 定义camera设备结构体
struct camera_dev{
	dev_t 				devid;		    // 设备号
	struct cdev 		cdev;		    // cdev 
	struct class 		*class;		    // 类
	struct device 		*device;	    // 设备
	int 				major;		    // 主设备号
	int 				minor;		    // 次设备号
	struct device_node	*nd; 		    // 设备节点
	int 				focus_gpio;	    // 对焦GPIO编号
    int                 shutter_gpio;	// 快门GPIO编号
};

struct camera_dev camera;			/* 相机设备 */

/**
 * @brief			: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int camera_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &camera; /* 设置私有数据 */
	return 0;
}

/**
 * @brief			: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t camera_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}

/**
 * @brief			: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t camera_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue;
	unsigned char databuf[1];
	unsigned char gpiostat;
	struct camera_dev *dev = filp->private_data;

	retvalue = copy_from_user(databuf, buf, cnt);
	if(retvalue < 0) {
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}

	gpiostat = databuf[0];		// 获取状态值

	if(gpiostat == TAKEPICTURE) {	
		gpio_set_value(dev->focus_gpio, 1);	    // 对焦
        msleep(500);
        gpio_set_value(dev->shutter_gpio, 1);	// 拍照
        msleep(500);
        gpio_set_value(dev->focus_gpio, 0);	    // 合焦
        gpio_set_value(dev->shutter_gpio, 0);	// 拍照完成
	}
	else if(gpiostat == 2)
	{
		gpio_set_value(dev->focus_gpio, 1);	    // 合焦
        gpio_set_value(dev->shutter_gpio, 1);	// 拍照完成
	}
	else
	{
		gpio_set_value(dev->focus_gpio, 0);	    // 合焦
        gpio_set_value(dev->shutter_gpio, 0);	// 拍照完成
	}
	return 0;
}

/**
 * @brief			: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int camera_release(struct inode *inode, struct file *filp)
{
	return 0;
}

// 设备操作函数
static struct file_operations camera_fops = {
	.owner 	 = 	THIS_MODULE,
	.open 	 = 	camera_open,
	.read 	 = 	camera_read,
	.write 	 = 	camera_write,
	.release =	camera_release,
};

/**
 * @brief		: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init camera_init(void)
{
	int ret = 0;

	// 设置拍照所使用的GPIO
	// 1、获取设备节点:camera
	camera.nd = of_find_node_by_path("/camera");
	if(camera.nd == NULL) {
		printk("camera node not find!\r\n");
		return -EINVAL;
	} else {
		printk("camera node find!\r\n");
	}

	// 2、 获取设备树中的gpio属性,得到相机所使用的对焦合拍照编号
	camera.focus_gpio = of_get_named_gpio(camera.nd, "focus-gpio", 0);
    camera.shutter_gpio = of_get_named_gpio(camera.nd, "shutter-gpio", 0);
	if(camera.focus_gpio < 0) {
		printk("can't get focus-gpio");
		return -EINVAL;
	}
    if(camera.shutter_gpio < 0) {
		printk("can't get shutter-gpio");
		return -EINVAL;
	}
	printk("focus-gpio num = %d, shutter-gpio num = %d\r\n", camera.focus_gpio, camera.shutter_gpio);

	// 3、设置相机引脚默认状态
	ret = gpio_direction_output(camera.focus_gpio, 0);
	ret = gpio_direction_output(camera.shutter_gpio, 0);

	if(ret < 0) {
		printk("can't set gpio!\r\n");
	}

	// 注册字符设备驱动
	// 1、创建设备号
	if (camera.major) {		//定义了设备号
		camera.devid = MKDEV(camera.major, 0);
		register_chrdev_region(camera.devid, CAMERA_CNT, CAMERA_NAME);
	} else {						//没有定义设备号
		alloc_chrdev_region(&camera.devid, 0, CAMERA_CNT, CAMERA_NAME);	//申请设备号
		camera.major = MAJOR(camera.devid);	//获取分配号的主设备号
		camera.minor = MINOR(camera.devid);	//获取分配号的次设备号
	}
	printk("camera major=%d,minor=%d\r\n",camera.major, camera.minor);	
	
	// 2、初始化cdev
	camera.cdev.owner = THIS_MODULE;
	cdev_init(&camera.cdev, &camera_fops);
	
	// 3、添加一个cdev
	cdev_add(&camera.cdev, camera.devid, CAMERA_CNT);

	// 4、创建类
	camera.class = class_create(THIS_MODULE, CAMERA_NAME);
	if (IS_ERR(camera.class)) {
		return PTR_ERR(camera.class);
	}

	// 5、创建设备
	camera.device = device_create(camera.class, NULL, camera.devid, NULL, CAMERA_NAME);
	if (IS_ERR(camera.device)) {
		return PTR_ERR(camera.device);
	}
	return 0;
}

/**
 * @brief		: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit camera_exit(void)
{
	// 注销字符设备驱动
	cdev_del(&camera.cdev);	// 删除cdev
	unregister_chrdev_region(camera.devid, CAMERA_CNT); 	// 注销设备号

	device_destroy(camera.class, camera.devid);		// 注销设备
	class_destroy(camera.class);					// 注销结构体
}

module_init(camera_init);
module_exit(camera_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("MWBW");

​ 在写驱动的时候,有一个步骤是注册设备,这里的设备信息获取都是来自设备树,因此,驱动代码的编写只是一部分,另外还需要在对应的设备树文件中加入相应的设备节点,这个节点描述的信息要含有驱动所需要的信息,并配置引脚相应的电气属性。例如:

	camera {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "jyaitech-camera";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_camera>;
		focus-gpio = <&gpio1 5 GPIO_ACTIVE_LOW>;
		shutter-gpio = <&gpio1 9 GPIO_ACTIVE_LOW>;
		status = "okay";
	};
	
	pinctrl_camera: cameragrp {
            fsl,pins = <
            	MX6UL_PAD_GPIO1_IO05__GPIO1_IO05        0x10B0
				MX6UL_PAD_GPIO1_IO09__GPIO1_IO09        0x10B0
            >;
    };

3.4 驱动的分离与分层

​ 在写一些简单的字符设备驱动的时候,可以按照上面驱动,直接写死,但是面对复杂的设备,像IIC、SPI、LCD等,就应该考虑下驱动的分离与分层,这样可以极大提高驱动的可重用性。

在这里插入图片描述
​ 这是简单的驱动与设备的关系,是一对一的,也就是每有一个硬件就会有一个对应的设备,很明显这样很容易造成一个系统驱动垃圾堆,所以复杂的驱动一般用下面的方式:

在这里插入图片描述
​ 每个设备控制器都提供一个统一的接口(也叫做主机驱动),每个设备的话也只提供一个驱动程序(设备驱动),每个设备通过统一的接口驱动来访问,这样就可以大大简化驱动文件。

四、块驱动

4.1 块驱动与字符驱动区别

​ 块设备驱动块设备是针对存储设备的驱动,比如 SD卡、 EMMC、 NAND Flash、 Nor Flash、 SPI Flash、机械硬盘、固态硬盘等,驱动要远比字符设备驱动复杂得多,不同类型的存储设备又对应不同的驱动子系统。块设备驱动相比字符设备驱动的主要区别如下:

  • 块设备只能以块为单位进行读写访问,块是 linux虚拟文件系统 (VFS)基本的数据传输单位。字符设备是以字节为单位进行数据传输的,不需要缓冲。
  • 块设备在结构上是可以进行随机访问的,对于这些设备的读写都是按块进行的,块设备使用缓冲区来暂时存放数据,等到条件成熟以后在一次性将缓冲区中的数据写入块设备中。
  • 字符设备是顺序的数据流设备,字符设备是按照字节进行读写访问的。字符设备不需要缓冲区,对于字符设备的访问都是实时的,而且也不需要按照固定的块大小进行访问。
  • 块设备结构的不同其 I/O算法也会不同,比如对于 EMMC、 SD卡、 NAND Flash这类没有任何机械设备的存储设备就可以任意读写任何的扇区 (块设备物 理存储单元 )。但是对于机械硬盘这样带有磁头的设备,读取不同的盘面或者磁道里面的数据,磁头都需要进行移动,因此对于机械硬盘而言,将那些杂乱的访问按照一定的顺序进行排列可以有效提高磁盘性能, linux里面针对不同的存储设备实现了不同的 I/O调度算法。

4.2 块驱动开发框架

​ 与字符设备开发框架大致类似,区别在于:

  • 字符设备驱动我们注册的是字符设备,而块驱动注册的是块设备,注册函数:register_blkdev,注销函数:unregister_blkdev
  • 块设备需要申请gendisk并对其进行配置,gendisk是一个描述磁盘设备的结构体,具体内容如下:
struct gendisk {
	/* major, first_minor and minors are input parameters only,
	 * don't use directly.  Use disk_devt() and disk_max_parts().
	 */
	int major;			/* major number of driver */
	int first_minor;
	int minors;                     /* maximum number of minors, =1 for
                                         * disks that can't be partitioned. */

	char disk_name[DISK_NAME_LEN];	/* name of major driver */
	char *(*devnode)(struct gendisk *gd, umode_t *mode);

	unsigned int events;		/* supported events */
	unsigned int async_events;	/* async events, subset of all */

	/* Array of pointers to partitions indexed by partno.
	 * Protected with matching bdev lock but stat and other
	 * non-critical accesses use RCU.  Always access through
	 * helpers.
	 */
	struct disk_part_tbl __rcu *part_tbl;
	struct hd_struct part0;

	const struct block_device_operations *fops;
	struct request_queue *queue;
	void *private_data;

	int flags;
	struct device *driverfs_dev;  // FIXME: remove
	struct kobject *slave_dir;

	struct timer_rand_state *random;
	atomic_t sync_io;		/* RAID */
	struct disk_events *ev;
#ifdef  CONFIG_BLK_DEV_INTEGRITY
	struct blk_integrity *integrity;
#endif
	int node_id;
};
  • 块设备结构体没有与字符设备一样的read、write这样的读写操作函数,有的操作函数如下:
struct block_device_operations {
	int (*open) (struct block_device *, fmode_t);
	void (*release) (struct gendisk *, fmode_t);
	int (*rw_page)(struct block_device *, sector_t, struct page *, int rw);
	int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
	int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
	long (*direct_access)(struct block_device *, sector_t,
					void **, unsigned long *pfn, long size);
	unsigned int (*check_events) (struct gendisk *disk,
				      unsigned int clearing);
	/* ->media_changed() is DEPRECATED, use ->check_events() instead */
	int (*media_changed) (struct gendisk *);
	void (*unlock_native_capacity) (struct gendisk *);
	int (*revalidate_disk) (struct gendisk *);
	int (*getgeo)(struct block_device *, struct hd_geometry *);
	/* this callback is with swap_lock and sometimes page table lock held */
	void (*swap_slot_free_notify) (struct block_device *, unsigned long);
	struct module *owner;
};
  • 对磁盘的配置过程:
    1、申请gendisk : struct gendisk *alloc_disk(int minors);
    2、将 gendisk 添加到内核 : void add_disk(struct gendisk *disk);
    3、设置 gendisk 容量 : void set_capacity(struct gendisk *disk, sector_t size);
    4、引用计数的调整:
    增加:truct kobject *get_disk(struct gendisk *disk)
    减少:void put_disk(struct gendisk *disk)
    5、删除gendisk: void del_gendisk(struct gendisk *gp)

4.3 块设备读写操作

​ 在上一节提到,块设备结构体没有与字符设备一样的read、write这样的读写操作函数,但它是通过请求队列 request_queue、请求 request 和 bio 结构这些操作进行读写的。

4.3.1 请求队列 request_queue

1、首先申请并初始化一个 request_queue,然后在初始化 gendisk 的时候将request_queue 地址赋值给 gendisk 的 queue 成员变量。request_queue 的申请与初始化通过使用 blk_init_queue 函数完成。
2、分配请求队列并绑定制造请求函数。blk_init_queue 函数其实以及完成了这个操作,但是面对非机械设备,先通过struct request_queue *blk_alloc_queue (gfp_t gfp_mask)函数请求设备,再通过void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)函数绑定制造请求。
3、删除请求队列:void blk_cleanup_queue(struct request_queue *q)

4.3.2 请求request

​ 对请求的处理很简单:获取、开启、对应函数分别为:request *blk_peek_request(struct request_queue *q)、void blk_start_request(struct request *req)、与请求相关的API函数:

函数描述
blk_end_request请求中指定字节数据被处理完成
blk_end_request_all请求中所有数据全部处理完成
blk_end_request_cur当前请求中的 chunk
blk_end_request_err处理完请求,直到下一个错误产生

4.3.3 bio结构

​ bio 是request结构体里面的一个成员,每个 request 里面里面会有多个 bio,bio 保存着最终要读写的数据、地址等信息。

五、网络驱动

5.1 嵌入式网络结构

​ 嵌入式网络硬件分为两部分:MAC 和 PHY,如果芯片支持网络,那么一般是指内部含有MAC,如果没有只能通过外置MAC,但是一般效率不高,因为内部含有MAC的芯片一般都有网络加速引擎。而 PHY一般是外置的。
​ MAC与PHY之间的接口一般是MII/RMII,它们是 IEEE-802.3 定义的以太网标准接口,连接图如下:
在这里插入图片描述
​ MII
在这里插入图片描述
​ RMII

5.2 NAPI处理机制

​ Linux 在这中断、轮询的基础上提出了另外一种网络数据接收的处理方法:NAPI(New API),NAPI 是一种高效的网络处理技术。NAPI 的核心思想就是不全部采用中断来读取网络数据,而是采用中断来唤醒数据接收服务程序,在接收服务程序中采用 POLL 的方法来轮询处理数据。这种方法的好处就是可以提高短数据包的接收效率,减少中断处理的时间。Linux 内核使用结构体 napi_struct 表示 NAPI。
​ 大致过程如下:

  • 初始化 NAPI : netif_napi_add;
  • 使能 NAPI : inline void napi_enable(struct napi_struct *n);
  • 检查 NAPI 是否可以进行调度: inline bool napi_schedule_prep(struct napi_struct *n);
  • NAPI 调度: void __napi_schedule(struct napi_struct *n);
  • NAPI 处理完成:inline void napi_complete(struct napi_struct *n);
  • 关闭NAPI:void napi_disable(struct napi_struct *n);
  • 删除 NAPI:void netif_napi_del(struct napi_struct *napi);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

2021年Linux技术总结(四):Linux 驱动 的相关文章

  • 我应该使用哪个 Linux 发行版作为 Xen 主机? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我为家庭办公室订购了一台服务器 我想用 Xen 对其进行分区 我认为这将使事情保持干净并且更容易维护 我将运行 MySQL PostgreSQL
  • 如何从 Linux 的 shell 中删除所有以 ._ 开头的文件?

    确实如标题所示 我已将许多文件从 Mac 复制到 Raspberry Pi 这导致了许多以前缀开头的多余文件 我想删除以以下开头的文件夹中的每个文件 我该怎么做 尝试类似的方法 cd path to directory rm rf 或者 如
  • “grep -q”的意义是什么

    我正在阅读 grep 手册页 并遇到了 q 选项 它告诉 grep 不向标准输出写入任何内容 如果发现任何匹配 即使检测到错误 也立即以零状态退出 我不明白为什么这可能是理想或有用的行为 在一个程序中 其原因似乎是从标准输入读取 处理 写入
  • Scrapy FakeUserAgentError:获取浏览器时发生错误

    我使用 Scrapy FakeUserAgent 并在我的 Linux 服务器上不断收到此错误 Traceback most recent call last File usr local lib64 python2 7 site pack
  • 如何调用位于其他目录的Makefile?

    我正在尝试这样做 我想打电话给 make Makefile存在于其他目录中 abc可以使用位于不同目录中的 shell 脚本的路径 我该怎么做呢 由于 shell 脚本不允许我cd进入Makefile目录并执行make 我怎样才能编写she
  • 怎样才能使 Windows 成为一个开箱即用的 POSIX 兼容操作系统?

    这个问题的动机是我的一个牵强的梦想 即 nix 平台上可用的许多优秀软件可以轻松移植到 Windows 微软最近对开源和开放性采取了不同的方法 所以我真的很想知道如果微软有这样的倾向 这样的事情会有多可行 我很好奇的一些更具体的事情是 是否
  • gethostbyname() 或 getnameinfo() 如何在后台工作?

    How gethostbyname or getnameinfo 在后台工作 include
  • 如何使用AWK脚本检查表的所有列数据类型? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 在这里 我正在检查表中第一列的数据类型 但我想知道AWK中表的所有列数据类型 我尝试过 但只能获得一列数据类型 例如 Column 1
  • 如何在我的 AWS EC2 实例上安装特定字体?

    我有一个在 AWS EC2 Amazon Linux Elastic Beanstalk 实例上运行的 Python 应用程序 该实例需要某些特定字体才能生成输出 并且想知道如何在部署或实例启动过程中安装它们 我的代码在本地计算机 OS X
  • 伊迪德信息

    重新定义问题 有什么方法可以获取所连接显示器的序列号吗 我想收集显示器的Eid信息 当我使用 logverbose 选项运行 X 时 我可以从 xorg 0 log 文件中获取它 但问题是 如果我切换显示器 拔出当前显示器 然后插入另一个显
  • 标准头文件中的 C 编译器错误 - 未定义的 C++ 定义

    我正在尝试编译 C 程序 但收到许多错误 这些错误是在标准 C 头文件 inttypes h stdio h stat h 等 中遇到的 错误的来源是以下未定义的常量 BEGIN DECLS END DECLS BEGIN NAMESPAC
  • Linux 中热插拔设备时检测设备是否存在

    我正在运行 SPIcode http lxr free electrons com source drivers spi spi omap2 mcspi c在熊猫板上 我想知道其中的哪个功能code http lxr free electr
  • 每个进程每个线程的时间量

    我有一个关于 Windows 和 Linux 中进程和线程的时间量子的问题 我知道操作系统通常为每个线程提供固定的时间量 我知道时间量根据前台或后台线程而变化 也可能根据进程的优先级而变化 每个进程有固定的时间量吗 例如 如果操作系统为每个
  • linux x86 汇编语言 sys_read 调用的第一个参数应为 0 (stdin)

    我正在编写一个简单的汇编程序来从标准输入读取 如 scanf 这是我的代码 section bss num resb 5 section txt global start start mov eax 3 sys read mov ebx 0
  • 适用于 KDE 和 Gnome 的 Gui [重复]

    这个问题在这里已经有答案了 我想为一个现在是 CLI 的应用程序编写一个 gui 它需要在 KDE 和 Gnome DE 中 看起来不错 充分利用用户的外观设置 如果我选择 Qt 或 GTK 我能够做到这一点吗 它们与两个 DE 集成良好吗
  • 让 MongoDB 在 Linux 上监听远程连接

    我已在 Windows 本地计算机上 上成功安装 MongoDB 作为服务 但现在我想将 MongoDb 移动到单独的服务器 所以我将 tarball 解压到网络上的虚拟服务器 运行 Linux 当我从本地计算机使用 PuTTY 连接到服务
  • 归档文件系统或格式

    我正在寻找一种文件类型来存储已退役系统的档案 目前 我们主要使用 tar gz 但从 200GB tar gz 存档中查找并提取几个文件是很麻烦的 因为 tar gz 不支持任何类型的随机访问读取规定 在你明白之前 使用 FUSE 安装 t
  • 没有可用的符号表信息

    我正在测试第三方的库 它崩溃了 当我想查看崩溃的原因时 我的 gdb 告诉我没有可用的调试符号 Program received signal SIGSEGV Segmentation fault Switching to Thread 0
  • arm-linux-gnueabi 编译器选项

    我在用 ARM Linux gnueabi gcc在 Linux 中为 ARM 处理器编译 C 程序 但是 我不确定它编译的默认 ARM 模式是什么 例如 对于 C 代码 test c unsigned int main return 0x
  • PHP 致命错误:未找到“MongoClient”类

    我有一个使用 Apache 的网站 代码如下 当我尝试访问它时 我在 error log 中收到错误 PHP Fatal Error Class MongoClient not found 以下是可能错误的设置 但我认为没有错误 php i

随机推荐

  • realsense D455+ROS+OpenCV4.5完成目标距离检测

    ROS OpenCV 1 环境配置 1 1 realsense SDK2 0安装 通过官网找到最新的SDK包并下载 Intel RealSense SDK 2 0 解压安装包 xff08 librealsense 2 47 0 tar gz
  • 什么是 PID 控制器:工作原理及其应用

    什么是 PID 控制器 xff1a 工作原理及其应用 什么是PID控制器 xff1f 历史PID控制器框图PID控制器的工作P 控制器I 控制器D 控制器 PID控制器的类型开 关控制比例控制标准型PID控制器实时 PID 控制器 调优方法
  • 什么是缓冲区

    1 什么是缓冲区 缓冲区又称为缓存 xff0c 它是内存空间的一部分 也就是说 xff0c 在内存空间中预留了一定的存储空间 xff0c 这些存储空间用来缓冲输入或输出的数据 xff0c 这部分预留的空间就叫做缓冲区 缓冲区根据其对应的是输
  • FreeRTOS系统解析-1、FreeRTOS系统简介

    1 系统简介 不同的的多任务系统有不同的侧重点 以工作站和桌面电脑为例 xff1a 早期的处理器非常昂贵 xff0c 多以那时的多任务用于实现在单处理器上支持多用户 这类系统中的调度算法侧重于让每个用户 公平共享 处理器时间 随着处理器的功
  • 目标检测 YOLOv5 常见的边框(bounding box )坐标矩形框表示方法

    将txt格式的真值框 xff08 Ground Truth xff09 在原图上显示 具体过程坎坷 xff0c 以下博主提供了思路 xff0c 学习了yolo格式label的归一化和坐标求解 xff01 1 https blog csdn
  • momenta实习面经

    走的火箭计划内推 xff0c 链接https mp weixin qq com s zllOky0biV9zn1Qfbg4XZg 线上先做了一套题 xff0c 写的2小时但是打开界面发现倒计时有10小时 xff0c 于是悠哉悠哉慢慢做结果2
  • 树莓派4B + Ubuntu18.04 + RealSense SDK

    有段时间没写博客了 xff0c 今天心血来潮 xff0c 记录一下 我自己的配置在标题写的很清楚 xff0c 用的是ros1 安装步骤我是建议 xff1a ubuntu gt realsense SDK gt ros gt ros wrap
  • ubuntu18.04安装ROS Melodic(最详细配置)

    前期准备 61 61 设置软件源 xff1a 国外的 xff1a sudo sh c 39 echo 34 deb http packages ros org ros ubuntu lsb release sc main 34 gt etc
  • 3步搞定CSDN中代码背景颜色的修改

    1 进入内容管理 xff0c 点击最下方的博客设置 2 修改
  • 2019电赛--无人机题目OpenMV总结

    此文章在我的博客链接 xff1a https sublimerui top archives d508d500 html NOTES xff1a 上一篇相关博文 xff0c 准备阶段OpenMV学习笔记链接 xff1a https blog
  • 大疆精灵4RTK自定义三维航线规划(开源)

    大疆精灵4rtk是无人机摄影测量行业的一款里程碑式的产品 xff0c 极大地拓展了无人机摄影测量的应用领域 然而 xff0c 大疆官方只提供了有限的航线规划功能 xff0c 如带状航线 井字航线 xff0c 5向飞行 xff0c 仿地飞行等
  • docker拉取RabbitMq镜像并安装

    RabbitMQ安装入门篇 文章目录 前言一 Docker拉取RabbitMq镜像二 docker下启动RabbitMq容器三 查看RabbitMq是否启动总结 前言 这篇文章为了方便初学者入门 xff0c 在linux环境下用docker
  • 核间通信--Mailbox原理及内核驱动框架

    https blog csdn net weixin 34007291 article details 86026346 核间通信的主要目标是 xff1a 充分利用硬件提供的机制 xff0c 实现高效的CORE间通信 xff1b 给需要CO
  • mac下Wireshark报错: you don‘t have permission to capture on that device

    1 首先 首先 cd dev ls la grep bp 看见用户组 crw 1 root wheel 23 0 6 18 16 04 bpf0 crw 1 root wheel 23 1 6 20 04 20 bpf1 crw 1 roo
  • 大端模式、小端模式、高字节序、低字节序、MSB、LSB

    https blog 51cto com u 14114084 4930969 text 61 E9 AB 98 E4 BD 8D E5 85 88 E8 A1 8C E5 8D B3 E5 9C A8 E4 BC A0 E8 BE 93
  • PX4+ROS+gazebo+mavros,Ubuntu18.04搭建SITL仿真环境

    前言 介绍 SITL Software in the Loop 软件在环仿真平台 xff0c 与之对应的有 HITL 硬件在环仿真 本文目的是搭建一个无人机软件仿真环境 xff0c 使用PX4开源飞控 xff0c gazebo作为仿真器 x
  • ubuntu-firefox有网但是打不开网页的解决办法

    1 检查ubuntu右上角联网开关是否打开 xff0c 需要勾选Rnable Networking 2 如果能ping通其他主机地址 xff0c 浏览器却连不上网 xff0c 很有可能是DNS域名解析的问题 解决办法如下 xff1a 查看域
  • 我为什么选择了Gitee?

    声明 xff1a 个人见解 xff0c 仅供参考 xff0c 实际情况需要结合自身环境和技术领域做出选择 xff0c 且非常不建议同时使用两个或多个平台 不过个人使用还是推荐Gitee 文章结构 前言一 二者有何区别 xff1f 二 为什么
  • 解决:检测到“_ITERATOR_DEBUG_LEVEL”的不匹配项: 值“0”不匹配值“2””。

    vs2010下 项目 属性 配置属性 C C 43 43 预处理器 预处理定义 添加 ITERATOR DEBUG LEVEL 61 0 即可 xff08 因为我用的2013及以上版本 xff0c 试过这种方法 xff0c 还是出错 xff
  • 2021年Linux技术总结(四):Linux 驱动

    一 裸机驱动开发流程 所谓裸机在这里主要是指系统软件平台没有用到操作系统 在基于ARM处理器平台的软件设计中 xff0c 如果整个系统只需要完成一个相对简单而且独立的任务 xff0c 那么可以不使用操作系统 xff0c 只需要考虑在平台上如