文章目录
前言
linux驱动是学习linux必不可少环节,下面记录linux驱动学习过程中的知识点。
linux常用设备驱动主要有以下几种:
- 杂项设备
- 字符设备
- 块设备
- 网络设备
包括使用到的宏定义以及api,下面进行详细介绍,
一、杂项设备(misc device)
简介
Misc设备是Linux内核中一种特殊的设备类型,用于表示不适合其他常规设备类别的设备。“Misc"是"miscellaneous”(杂项)的缩写,意味着这些设备没有特定的分类或类别。
Misc设备通常是一些不常见或特殊的硬件设备,它们的功能不适合归类为字符设备、块设备或网络设备等。这些设备可能具有各种不同的功能,例如传感器、电源管理设备、电子计时器等。
在Linux内核中,Misc设备由miscdevice结构体表示。Misc设备的注册和管理通常是通过miscdevice和misc_register函数来完成的。用户空间程序可以通过/dev/misc设备节点来访问Misc设备。
Misc设备的一些示例包括:
-
实时时钟(Real-time Clock):用于提供系统的日期和时间信息。
-
温度传感器(Temperature Sensors):用于测量环境或设备的温度。
-
LED控制器(LED Controllers):用于控制LED灯的亮度和状态。
-
震动反馈器(Haptic Feedback Devices):用于提供触觉反馈效果,例如在触摸屏上模拟振动感。
-
触摸屏控制器(Touchscreen Controllers):用于处理和解释触摸屏输入。
-
虚拟设备(Virtual Devices):这些设备不依赖于具体的硬件,而是模拟出来的设备,用于测试和开发目的。
Misc设备的具体功能和实现方式取决于硬件厂商和设备驱动程序的设计。它们提供了一种灵活的方式来管理和访问那些没有特定设备类别的硬件设备。
具体开发步骤
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/uaccess.h>
#define BUTTON_MINOR 20
static char kernel_buf[64]="Kernel Init Info";
int hello_open(struct inode * pnode, struct file * pfile)
{
printk(KERN_INFO"this hello open!\n");
return 0;
}
//unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
ssize_t hello_read(struct file * pfile, char __user * pbuf, size_t count, loff_t * poffset)
{
printk(KERN_INFO"kernel read!\n");
copy_to_user( pbuf, kernel_buf, count);
return count;
}
//unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
ssize_t hello_write(struct file * pfile, const char __user * pbuf, size_t count, loff_t * poffset)
{
printk(KERN_INFO"kernel write!\n");
copy_from_user(kernel_buf, pbuf, count);
return count;
}
static struct file_operations hello_ops = {
.owner = THIS_MODULE,
.open = hello_open,
.read = hello_read,
.write = hello_write,
};
static struct miscdevice hello_misc_device = {
BUTTON_MINOR,
"hello",
&hello_ops,//fops
};
int __init hello_init(void)
{
// if (!machine_is_netwinder())
// return -ENODEV;
printk (KERN_INFO "THis is Hello World Init\n");
if (misc_register (&hello_misc_device)) {
printk (KERN_WARNING "nwbutton: Couldn't register device 10, "
"%d.\n", BUTTON_MINOR);
return -EBUSY;
}
return 0;
}
int __init hello_exit(void)
{
misc_deregister (&hello_misc_device);
return 0;
}
module_init(hello_init)
module_exit(hello_exit)
MODULE_AUTHOR("kknight");
MODULE_LICENSE("GPL");
struct miscdevice {
int minor; // 次设备号
const char *name; // 设备名称
const struct file_operations *fops; // 指向设备文件操作的指针
struct list_head list; // 链接到miscdevice链表的指针
struct device *parent; // 父设备指针
struct device *this_device; // 当前设备指针
const char *nodename; // 设备节点名称
umode_t mode; // 设备节点权限
};
#minor :Misc设备的次设备号,用于标识设备在系统中的唯一性。
#name :Misc设备的名称,用于标识设备的可读标识符。
#fops :指向struct file_operations结构体的指针,包含了设备的文件操作函数,如打开、关闭、读取、写入等操作。
#list :用于将struct miscdevice链接到内核中的Misc设备链表。
#parent :指向父设备的指针,用于建立设备层次结构关系。
#this_device:指向当前设备的指针,用于表示设备本身。
#nodename :设备节点的名称,用于在/dev目录下创建设备节点。
#mode :设备节点的权限,用于控制用户对设备节点的访问权限。
上面贴了一个简单的杂项设备驱动代码,实现一个杂项设备驱动代码,可以按以下步骤走
- 按驱动框架,写好
module_init(xxx_init),module_exit(xxx_exit),MODULE_AUTHOR("xxx"),MODULE_LICENSE("GPL")
- 在
xxx_init
函数中,实现杂项设备注册功能misc_deregister (&hello_misc_device)
,定义misc device
结构体,并为其属性(设备号,名字,行为函数)赋值
二、字符设备
简介
字符设备(Character Device)是Linux内核中的一种设备类型,用于与用户空间应用程序通过字符流进行交互。字符设备提供了对设备的逐字符的读取和写入操作,而不需要进行块级别的访问。
字符设备可以代表各种设备,例如终端设备、串口、打印机、声卡等。与块设备不同,字符设备以字符为单位进行访问,每个字符都是独立的,可以按需读取或写入。
在Linux内核中,字符设备由字符设备驱动程序负责管理和控制。字符设备驱动程序是内核模块,负责与硬件设备进行交互,并提供对设备的访问接口。这些驱动程序实现了字符设备的操作函数,包括打开设备、关闭设备、读取数据、写入数据等。
字符设备驱动程序的核心是实现struct file_operations
结构体中定义的各种函数指针,例如:
open
:打开设备时调用的函数,用于初始化设备和分配资源。release
:关闭设备时调用的函数,用于释放资源。read
:从设备读取数据时调用的函数,将设备数据传输到用户空间。write
:向设备写入数据时调用的函数,将用户空间数据传输到设备。ioctl
:处理设备的控制命令时调用的函数,用于设备特定的操作。
用户空间的应用程序可以通过设备节点(例如/dev/tty
)访问字符设备。它们可以使用标准的I/O操作(如open
、read
、write
)或特定的系统调用(如ioctl
)与字符设备进行交互。
总结起来,字符设备是Linux内核中的一种设备类型,用于通过字符流与用户空间应用程序进行交互。字符设备驱动程序管理和控制字符设备,提供对设备的访问接口。用户空间程序可以通过设备节点进行字符设备的读取和写入操作。
具体开发步骤
- 老版开发
在xxx_init
中使用register_chrdev注册设备,再使用mknod /dev/chrdevbase c 200 0
注册设备节点,最后即可通过设备节点对设备进行访问操作
- 新版开发
基于pinctrl+gpio接口开发一个led驱动的流程
- 实现设备树
#在 iomux节点中的evk节点下,新增pinctrl描述
pinctrl_led: ledgrp{
fsl,pin=<
# MX6UL_PAD_GPIO1_IO03__GPIO1_IO03是宏定义,5个整形值,代表了寄存器配置 0x10B0配置电器属性
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
>;
};
#在根节点中下增加gpioled节点,
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "atkalpha-gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
- 实现驱动代码
初始化init流程如下:
1.使用of_find_node_by_path
找到节点
2.使用of_get_named_gpio
找到指定的gpio,并获取gpio编号
3.使用gpio_direction_output
设置输入输出
4.alloc_chrdev_region
,申请设备号
5.cdev_init
,初始化cdev的fops和owner=THIS_MODULE
6.cdev_add
,将cdev加入到内核链表
7.class_create
,创建类
8.device_create
,创建设备节点(‘/dev/xxx’),与之前的设备驱动联系起来
int __init pingpio_init(void)
{
int ret = 0;
/* 设置 LED 所使用的 GPIO */
/* 1、获取设备节点:gpioled */
devhello.nd = of_find_node_by_path("/gpioled");
if(devhello.nd == NULL) {
printk("devhello node cant not found!\r\n");
return -EINVAL;
} else {
printk("devhello node has been found!\r\n");
}
/* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
devhello.led_gpio = of_get_named_gpio(devhello.nd, "led-gpio", 0);
if(devhello.led_gpio < 0) {
printk("can't get led-gpio");
return -EINVAL;
}
printk("led-gpio num = %d\r\n", devhello.led_gpio);
/* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
ret = gpio_direction_output(devhello.led_gpio, 1);
if(ret < 0) {
printk("can't set gpio!\r\n");
}
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (devhello.major) { /* 定义了设备号 */
devhello.devid = MKDEV(devhello.major, 0);
register_chrdev_region(devhello.devid, GPIOLED_CNT,
GPIOLED_NAME);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&devhello.devid, 0, GPIOLED_CNT,
GPIOLED_NAME); /* 申请设备号 */
devhello.major = MAJOR(devhello.devid); /* 获取分配号的主设备号 */
devhello.minor = MINOR(devhello.devid); /* 获取分配号的次设备号 */
}
printk("devhello major=%d,minor=%d\r\n",devhello.major,
devhello.minor);
/* 2、初始化 cdev */
devhello.cdev.owner = THIS_MODULE;
cdev_init(&devhello.cdev, &charhello_ops);
/* 3、添加一个 cdev */
cdev_add(&devhello.cdev, devhello.devid, GPIOLED_CNT);
/* 4、创建类 */
devhello.pclass = class_create(THIS_MODULE, GPIOLED_NAME);
if (IS_ERR(devhello.pclass)) {
return PTR_ERR(devhello.pclass);
}
/* 5、创建设备 */
devhello.pdevice = device_create(devhello.pclass, NULL,
devhello.devid, NULL, GPIOLED_NAME);
if (IS_ERR(devhello.pdevice)) {
return PTR_ERR(devhello.pdevice);
}
return 0;
}
操作函数write代码如下:
ssize_t chrhello_write(struct file * p1, const char __user * puserbuf2, size_t count, loff_t * poffset)
{
int ret;
printk(KERN_INFO"kernel write\r\n");
ret = copy_from_user(kernelbuf, puserbuf2,count);
if(kernelbuf[0] == 0)
{
// led_switch(0);
gpio_set_value(devhello.led_gpio, 0);
}
else
{
gpio_set_value(devhello.led_gpio, 1);
}
return 0;
}
三、块设备
四、宏定义
-
__iomem
__iomem
宏的作用是向编译器提供一个提示,用于指示该内存区域是 I/O 内存,以便在编译期间进行特定的处理。这种处理可能包括对内存访问的优化或限制,以确保对 I/O 内存的读写操作按照正确的顺序进行,并避免一些优化问题。需要注意的是,__iomem
宏本身只是一个提示,它并不会在运行时对内存进行任何特殊处理。其作用仅限于编译器层面,用于确保对 I/O 内存的访问按照正确的规则进行处理 -
__init
__init
宏用于标记那些只在系统初始化阶段使用的函数和数据。这些函数和数据仅在启动期间需要,一旦初始化完成,它们所占用的内存空间可以被释放或重用
五、内核API
of
系列
设备树是一种用于描述硬件设备的数据结构,通常以树状结构组织。每个设备节点在设备树中都有一个唯一的路径,类似于文件系统中的路径。linux内核提供了一组of
开头的api函数来访问设备树,可在linux/of.h
查看api定义,下面列举一些常用的:
struct device_node *of_find_node_by_path(const char *path);
:根据给定的路径查找设备树中对应的设备节点
const void *of_find_property(const struct device_node *np, const char *name, int *lenp)
:函数可以在给定的设备树节点中查找指定名称的属性,并返回对应属性的指针
of_property_read_string
of_property_read_u32_array
readl,writel
系列
函数在 Linux 内核中的声明位于 <asm/io.h>
头文件中。该头文件定义了用于访问 I/O 内存和寄存器的函数和宏
atomic
原子操作
函数 | 功能 |
---|---|
ATOMIC_INIT(int i) | 初始化原子变量,如atomic_t v = ATOMIC_INIT(0); |
int atomic_read(atomic_t *v) | 读取 v 的值,并且返回 |
void atomic_set(atomic_t *v, int i) | 向 v 写入 i 值 |
void atomic_add(int i, atomic_t *v) | 给 v 加上 i 值 |
void atomic_sub(int i, atomic_t *v) | 从 v 减去 i 值 |
void atomic_inc(atomic_t *v) | 给 v 加 1,也就是自增 |
void atomic_dec(atomic_t *v) | 从 v 减 1,也就是自减 |
int atomic_dec_return(atomic_t *v) | 从 v 减 1,并且返回 v 的值 |
int atomic_inc_return(atomic_t *v) | 给 v 加 1,并且返回 v 的值 |
int atomic_sub_and_test(int i, atomic_t *v) | 从 v 减 i,如果结果为 0 就返回真,否则返回假 |
int atomic_dec_and_test(atomic_t *v) | 从 v 减 1,如果结果为 0 就返回真,否则返回假 |
int atomic_inc_and_test(atomic_t *v) | 给 v 加 1,如果结果为 0 就返回真,否则返回假 |
int atomic_add_negative(int i, atomic_t *v) | 给 v 加 i,如果结果为负就返回真,否则返回假 |
spinlock
自旋锁
一般先获取锁再设置变量
函数 | 功能 |
---|---|
int spin_lock_init(spinlock_t *lock) | 初始化一个自选锁 |
void spin_lock(spinlock_t *lock) | 获取指定的自旋锁,也叫做加锁 |
void spin_unlock(spinlock_t *lock) | 释放指定的自旋锁 |
int spin_trylock(spinlock_t *lock) | 尝试获取指定的自旋锁,如果没有获取到就返回 0 |
int spin_is_locked(spinlock_t *lock) | 检查指定的自旋锁是否被获取,如果没有被获取就返回非 0,否则返回 0 |
void spin_lock_irqsave(spinlock_t *lock,unsigned long flags) | 保存中断状态,禁止本地中断,并获取自旋锁 |
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags) | 将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁 |
semaphore
信号量
使用基本流程
struct semaphore sem; /* 定义信号量 */
sema_init(&sem, 1); /* 初始化信号量 */
down(&sem); /* 申请信号量 */
/* 临界区 */
up(&sem); /* 释放信号量 */
__set_current_state
用于设置当前进程状态
void __set_current_state(unsigned long state);
其中,state参数表示新的进程状态,它应该是以下宏之一:
TASK_RUNNING
:表示进程正在运行或可运行。
TASK_INTERRUPTIBLE
:表示进程处于可中断的等待态,即进程正在等待某个事件的发生,如果收到一个信号,进程会被唤醒。
TASK_UNINTERRUPTIBLE
:表示进程处于不可中断的等待态,即进程正在等待某个事件的发生,但即使收到一个信号,进程也不会被唤醒。
TASK_STOPPED
:表示进程被停止。
通过调用__set_current_state函数,可以将当前进程的状态设置为指定的值。这通常在内核中的等待操作中使用,以便将进程置于适当的等待状态,使得调度器可以根据进程状态来决定下一步的调度行为。
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。