11、Linux 阻塞和非阻塞 IO 实验
1、阻塞
1、等待队列头
1、wait_queue_head_t
2、init_waitqueue_head 函数初始化等待队列头
void init_waitqueue_head(wait_queue_head_t *q)
3、等待唤醒
当设备可以使用的时候就要唤醒进入休眠态的进程, 唤醒可以使用如下两个函数:
void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)
4、等待事件
1、wait_event(wq, condition);
等待以 wq 为等待队列头的等待队列被唤醒,前提是 condition 条件必须满足(为真),否则一直阻塞 。
此 函 数 会 将 进 程 设 置 为TASK_UNINTERRUPTIBLE 状态
2、wait_event_timeout(wq, condition, timeout)
功能和 wait_event 类似,但是此函数可以添加超时时间,以 jiffies 为单位。
此函数有返回值,如果返回 0 的话表示超时时间到,而且 condition为假。
为 1 的话表示 condition 为真,也就是条件满足了。
3、wait_event_interruptible(wq, condition)
与 wait_event 函数类似,但是此函数将进程设置为 TASK_INTERRUPTIBLE,就是可以被信号打断。
4、wait_event_interruptible_timeout(wq,condition, timeout)
与 wait_event_timeout 函数类似,此函数也将进程设置为 TASK_INTERRUPTIBLE,可以被信号打断
5、使用宏 DECLARE_WAITQUEUE 定义并初始化一个等待队列项
DECLARE_WAITQUEUE(name, tsk)
name 就是等待队列项的名字, tsk 表示这个等待队列项属于哪个任务(进程),一般设置为
current , 在 Linux 内 核 中 current 相 当 于 一 个 全 局 变 量 , 表 示 当 前 进 程 。
因 此 宏 DECLARE_WAITQUEUE 就是给当前正在运行的进程创建并初始化了一个等待队列项。
6、将队列项添加/移除等待队列头
当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列头中,只有添加到等待队列头中以后进程才能进入休眠态。
当设备可以访问以后再将进程对应的等待队列项从等待队列头中移除即可
1、等待队列项添加:
void add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)
q:等待队列项要加入的等待队列头。 wait: 要加入的等待队列项。
2、等待队列项移除
void remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)
2、非阻塞
轮询
poll、 epoll 和 select 可以用于处理轮询, 应用程序通过 select、 epoll 或 poll 函数来
查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据。 当应用程序调
用 select、 epoll 或 poll 函数的时候设备驱动程序中的 poll 函数就会执行,因此需要在设备驱动
程序中编写 poll 函数。
1、poll 函数原型
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)
filp: 要打开的设备文件(文件描述符)。
wait: 结构体 poll_table_struct 类型指针,又应用程序传递进来的。 一般将此参数传递给poll_wait 函数。
返回值:向应用程序返回设备或者资源状态,可以返回的资源状态如下:
POLLIN 有数据可以读取。
POLLPRI 有紧急的数据需要读取。
POLLOUT 可以写数据。
POLLERR 指定的文件描述符发生错误。
POLLHUP 指定的文件描述符挂起。
POLLNVAL 无效的请求。
POLLRDNORM 等同于 POLLIN,普通数据可读
poll_wait 函数
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
参数 wait_address 是要添加到 poll_table 中的等待队列头,参数 p 就是 poll_table,
就是file_operations 中 poll 函数的 wait 参数。
12、异步通知实验
1、设置信号所使用的信号处理函数
sighandler_t signal(int signum, sighandler_t handler)
signum:要设置处理函数的信号。
handler: 信号的处理函数。
返回值: 设置成功的话返回信号的前一个处理函数,设置失败的话返回 SIG_ERR。
信号处理函数原型
typedef void (*sighandler_t)(int)
2、驱动中的信号处理
1、 fasync_struct 结构体
struct fasync_struct
{
spinlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next;
struct file *fa_file;
struct rcu_head fa_rcu;
};
2、fasync 函数
1、int (*fasync) (int fd, struct file *filp, int on)
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
fasync_helper 函数的前三个参数就是 fasync 函数的那三个参数,
第四个参数就是要初始化
的 fasync_struct 结构体指针变量。
当应用程序通过“ fcntl(fd, F_SETFL, flags | FASYNC)” 改变
fasync 标记的时候,驱动程序 file_operations 操作集中的 fasync 函数就会执行
2、fasync_struct 的释放函数同样为 fasync_helper
1、 kill_fasync 函数
void kill_fasync(struct fasync_struct **fp, int sig, int band)
fp:要操作的 fasync_struct。
sig: 要发送的信号。
band: 可读时设置为 POLL_IN,可写时设置为 POLL_OUT。
3、应用程序对异步通知的处理
1、注册信号处理函数
应用程序根据驱动程序所使用的信号来设置信号的处理函数,应用程序使用 signal 函数来
设置信号的处理函数。 前面已经详细的讲过了,这里就不细讲了。
2、将本应用程序的进程号告诉给内核
使用 fcntl(fd, F_SETOWN, getpid())将本应用程序的进程号告诉给内核。
3、开启异步通知
flags = fcntl(fd, F_GETFL); /* 获取当前的进程状态 */
fcntl(fd, F_SETFL, flags | FASYNC); /* 开启当前进程异步通知功能 */
重点就是通过 fcntl 函数设置进程状态为 FASYNC,经过这一步,驱动程序中的 fasync 函数就会执行。
13、platform 设备驱动实验
我们在前面几章编写的设备驱动都非常的简单,都是对 IO进行最简单的读写操作。像 I2C、SPI、 LCD 等这些复杂外设的驱动就不能这么去写了, Linux 系统要考虑到驱动的可重用性,因此提出了驱动的分离与分层这样的软件思路,在这个思路下诞生了我们将来最常打交道的platform 设备驱动,也叫做平台设备驱动。
1、 Linux 驱动的分离与分层
1、 驱动的分隔与分离
2、 驱动的分层
2、 platform 平台驱动模型简介
1、platform 总线
Linux系统内核使用 bus_type结构体表示总线,此结构体定义在文件 include/linux/device.h
struct bus_type {
const char *name; /* 总线名字 */
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs;
const struct attribute_group **bus_groups; /* 总线属性 */
const struct attribute_group **dev_groups; /* 设备属性 */
const struct attribute_group **drv_groups; /* 驱动属性 */
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
**int (*match)(struct device dev, struct device_driver drv);
match 函数,此函数很重要,单词 match 的意思就是“匹配、相配”,因此此函数
就是完成设备和驱动之间匹配的,总线就是使用 match 函数来根据注册的设备来查找对应的驱
动,或者根据注册的驱动来查找相应的设备,因此每一条总线都必须实现此函数。
match 函数有两个参数: dev 和 drv,这两个参数分别为 device 和 device_driver 类型,也就是设备和驱动。
platform 总线是 bus_type 的一个具体实例,定义在文件 drivers/base/platform.c,
platform 总线定义如下:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
platform_bus_type 就是 platform 平台总线,其中 platform_match 就是匹配函数。
platform_match 函数定义在文件 drivers/base/platform.c
static int platform_match(struct device *dev,struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/*When driver_override is set,only bind to the matching driver*/
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}
驱动和设备的匹配有四种方法
1、第一种匹配方式, OF 类型的匹配
if (of_driver_match_device(dev, drv))
return 1;
of_driver_match_device 函数定义在文件 include/linux/of_device.h
2、ACPI 匹配方式
if (acpi_driver_match_device(dev, drv))
return 1;
3、id_table 匹配
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
4、设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。
return (strcmp(pdev->name, drv->name) == 0);
3、platform 驱动
platform_driver 结 构 体 表 示 platform 驱 动
此 结 构 体 定 义 在 文 件include/linux/platform_device.h 中
struct platform_driver
{
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
1、 probe 函数,当驱动与设备匹配成功以后 probe 函数就会执行
int (*probe)(struct platform_device *);
2、 driver 成员,为 device_driver 结构体变量, Linux 内核里面大量使用到了面向对象的思维,
device_driver 相当于基类,提供了最基础的驱动框架。 plaform_driver 继承了这个基类,
然后在此基础上又添加了一些特有的成员变量。
struct device_driver driver;
device_driver 结构体定义在 include/linux/device.h,
device_driver 结构体内容如下:
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups;
const struct dev_pm_ops *pm;
struct driver_private *p;
};
const struct of_device_id *of_match_table;
of_match_table 就是采用设备树的时候驱动使用的匹配表,同样是数组,
每个匹配项都为 of_device_id 结构体类型
结构体定义在文件 include/linux/mod_devicetable.h
struct of_device_id {
char name[32];
char type[32];
char compatible[128];
const void *data;
};
char compatible[128];
compatible 非常重要,因为对于设备树而言,就是通过设备节点的 compatible 属
性值和 of_match_table 中每个项目的 compatible 成员变量进行比较,
如果有相等的就表示设备和此驱动匹配成功。
3、id_table 表,也就是我们上一小节讲解 platform 总线匹配驱动和设备的时候采用的
第三种方法, id_table 是个表 (也就是数组 ),每个元素的类型为 platform_device_id
const struct platform_device_id *id_table;
platform_device_id 结构体内容如下:
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
4、在编写 platform 驱动的时候
1、定义一个 platform_driver 结构体变量,然后实现结构体中的各个成员变量,
重点是实现匹配方法以及 probe 函数。
2、当驱动和设备匹配成功以后 probe函数就会执行,
具体的驱动程序在 probe 函数里面编写,比如字符设备驱动等等。
5、定义并初始化好 platform_driver 结构体变量以后需要在驱动入口函数里面调用
platform_driver_register 函数向 Linux 内核注册一个 platform 驱动
platform_driver_register 函数原型
int platform_driver_register (struct platform_driver *driver)
driver:要注册的 platform 驱动。
返回值: 负数,失败; 0,成功。
6、还需要在驱动卸载函数中通过 platform_driver_unregister 函数卸载 platform 驱动
platform_driver_unregister 函数原型如下:
void platform_driver_unregister(struct platform_driver *drv)
函数参数和返回值含义如下:
drv:要卸载的 platform 驱动。
返回值: 无。
7、platform 设备
platform_device 这个结构体表示 platform 设备
platform_device 结构体定义在文件
include/linux/platform_device.h 中结构体内容如下:
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
1、const char *name;
name 表示设备名字,要和所使用的 platform 驱动的 name 字段相同,否则的话设
备就无法匹配到对应的驱动。比如对应的 platform 驱动的 name 字段为“ xxx-gpio”,那么此 name
字段也要设置为“ xxx-gpio”。
2、u32 num_resources;
num_resources 表示资源数量,一般为"struct resource *resource;" resource 资源的大小。
3、struct resource *resource;
resource 表示资源,也就是设备信息,比如外设寄存器等。
Linux 内核使用 resource结构体表示资源,
resource 结构体内容如下:
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
start 和 end 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止
地址, name 表示资源名字, flags 表示资源类型,可选的资源类型都定义在了文件
include/linux/ioport.h 里面
29 #define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
30
31 #define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
32 #define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
33 #define IORESOURCE_MEM 0x00000200
34 #define IORESOURCE_REG 0x00000300 /* Register offsets */
35 #define IORESOURCE_IRQ 0x00000400
36 #define IORESOURCE_DMA 0x00000800
37 #define IORESOURCE_BUS 0x00001000
......
104 /* PCI control bits. Shares IORESOURCE_BITS with above PCI ROM. */
105 #define IORESOURCE_PCI_FIXED (1<<4) /* Do not move resource */
在以前不支持设备树的 Linux版本中,用户需要编写 platform_device变量来描述设备信息,
然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中,
此函数原型如下所示:
1、int platform_device_register(struct platform_device *pdev)
pdev: 要注册的 platform 设备。
返回值: 负数,失败; 0,成功
2、如果不再使用 platform 的话可以通过 platform_device_unregister 函数注销掉相应的 platform
设备, platform_device_unregister 函数原型如下:
void platform_device_unregister(struct platform_device *pdev)
pdev:要注销的 platform 设备。
返回值: 无。
14、Linux 自带的 LED 灯驱动实验
1、make menuconfig
按照如下路径打开 LED 驱动配置项:
-> Device Drivers
-> LED Support (NEW_LEDS [=y])
->LED Support for GPIO connected LEDs
15、Linux MISC 驱动实验
1、 MISC 设备驱动简介
1、所有的 MISC 设备驱动的主设备号都为 10,不同的设备使用不同的从设备号
miscdevice 设备
miscdevice是一个结构体
struct miscdevice {
int minor; /* 子设备号 */
const char *name; /* 设备名字 */
const struct file_operations *fops; /* 设备操作集 */
struct list_head list;
struct device *parent;
struct device *this_device;
const struct attribute_group **groups;
const char *nodename;
umode_t mode;
};
2、定义一个 MISC 设备(miscdevice 类型)以后我们需要设置 minor、 name 和 fops 这三个成员变量。
minor 表示子设备号, MISC 设备的主设备号为 10,这个是固定的,
需要用户指定子设备号, Linux 系统已经预定义了一些 MISC 设备的子设备号
这些预定义的子设备号定义在include/linux/miscdevice.h 文件中
13 #define PSMOUSE_MINOR 1
14 #define MS_BUSMOUSE_MINOR 2 /* unused */
15 #define ATIXL_BUSMOUSE_MINOR 3 /* unused */
16 /*#define AMIGAMOUSE_MINOR 4 FIXME OBSOLETE */
17 #define ATARIMOUSE_MINOR 5 /* unused */
18 #define SUN_MOUSE_MINOR 6 /* unused */
......
52 #define MISC_DYNAMIC_MINOR 255
3、 当设置好 miscdevice 以后就需要使用 misc_register 函数向系统中注册一个 MISC 设备
int misc_register(struct miscdevice * misc)
函数参数和返回值含义如下:
misc:要注册的 MISC 设备。
返回值: 负数,失败; 0,成功。
4、
int misc_deregister(struct miscdevice *misc)
函数参数和返回值含义如下:
misc:要注销的 MISC 设备。
返回值: 负数,失败; 0,成功。