目录
IO操作
两个阶段
阻塞操作
非阻塞操作
非阻塞模式实验
dts_led.c文件
app.c文件
Makefile文件
执行过程
阻塞IO:等待队列
wait_queue_head结构体:等待队列头
初始化等待队列头
init_waitqueue_head()
DECLARE_WAIT_QUEUE_HEAD(name)
wait_queue_entry_t:等待队列元素
DECLARE_WAITQUEUE(name, tsk):初始化静态队列
add_wait_queue():添加等待队列
remove_wait_queue():移除等待队列
等待事件
唤醒
阻塞模式实验
dts_led.c文件
App.c文件
Makefile文件
执行过程
IO操作
在用户空间进行数据发送/读取。
方式:glibc库的文件操作接口,linux内核开放的系统调用接口。
两个阶段
用户空间《=》内核空间:用户空间调用glibc库的文件操作接口或者系统调用函数,陷入内核态执行。
内核空间《=》file_operation:在内核空间找到文件对应的fops,回调执行对应的函数指针。
阻塞操作
请求的资源没有准备好时,进程/线程睡眠等待,直到数据准备完毕。
非阻塞操作
请求的资源没有准备好时,直接返回错误信息。open函数的非阻塞标志:O_NONBLOCK。
非阻塞模式实验
要求体现非阻塞模式下,打开一个文件失败会立即返回。
dts_led.c文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#define DEV_NAME "rgb_led"
#define DEV_CNT (1)
int rgb_led_red;
int rgb_led_green;
int rgb_led_blue;
struct semaphore sem;
static dev_t led_devno;
static struct cdev led_chrdev;
struct class *class_led;
struct device *device;
struct device_node *rgb_led_device_node;
static int led_chrdev_open(struct inode *inode, struct file *filp)
{
if(filp->f_flags & O_NONBLOCK){
if(down_trylock(&sem)){
return -EBUSY;
}
}else{
down(&sem);
}
printk("open form driver\n");
return 0;
}
static ssize_t led_chrdev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int ret, error;
unsigned char receive_data[10]; //用于保存接收到的数据
unsigned int write_data = 0;
if(cnt > 10) cnt = 10;
error = copy_from_user(receive_data, buf, cnt);
if(error < 0) return -1;
ret = kstrtoint(receive_data, 16, &write_data);
if(ret) return -1;
/* 设置GPIO1_04输出电平 */
if(write_data & 0x04){
gpio_set_value(rgb_led_red, 0);
}else{
gpio_set_value(rgb_led_red, 1);
}
/* 设置GPIO4_20输出电平 */
if(write_data & 0x02){
gpio_set_value(rgb_led_green, 0);
}else{
gpio_set_value(rgb_led_green, 1);
}
/* 设置GPIO4_19输出电平 */
if(write_data & 0x01){
gpio_set_value(rgb_led_blue, 0);
}else{
gpio_set_value(rgb_led_blue, 1);
}
return cnt;
}
static int led_chrdev_release(struct inode *inode, struct file *filp)
{
up(&sem);
printk(KERN_ALERT "finished!!!\n");
return 0;
}
static struct file_operations led_chrdev_fops = {
.owner = THIS_MODULE,
.open = led_chrdev_open,
.write = led_chrdev_write,
.release = led_chrdev_release,
};
static int led_probe(struct platform_device *pdv)
{
int ret = -1; //保存错误状态码
unsigned int register_data = 0;
printk(KERN_ALERT "match successed!\n");
/* 获取rgb_led的设备树节点 */
rgb_led_device_node = of_find_node_by_path("/rgb_led");
if(rgb_led_device_node == NULL){
printk(KERN_ERR "get rgb_led failed!\n");
return -1;
}
/* 获取red led GPIO 引脚号 */
rgb_led_red = of_get_named_gpio(rgb_led_device_node, "rgb_led_red", 0);
if(rgb_led_red < 0){
printk(KERN_ERR "rgb_led_red failed!\n");
return -1;
}
/* 获取green led GPIO 引脚号 */
rgb_led_green = of_get_named_gpio(rgb_led_device_node, "rgb_led_green", 0);
if(rgb_led_green < 0){
printk(KERN_ERR "rgb_led_green failed!\n");
return -1;
}
/* 获取blue led GPIO 引脚号 */
rgb_led_blue = of_get_named_gpio(rgb_led_device_node, "rgb_led_blue", 0);
if(rgb_led_blue < 0){
printk(KERN_ERR "rgb_led_blue failed!\n");
return -1;
}
/* 设置GPIO为输出模式,并默认高电平 */
gpio_direction_output(rgb_led_red, 1);
gpio_direction_output(rgb_led_green, 1);
gpio_direction_output(rgb_led_blue, 1);
/* 第一步
* 采用动态分配的方式获取设备编号,次设备号为0
* 设备名称为rgb-leds,可通过命令cat /proc/devices查看
* DEV_CNT为1,当前只申请一个设备编号
*/
ret = alloc_chrdev_region(&led_devno, 0, DEV_CNT, DEV_NAME);
if(ret < 0){
printk("fail to alloc led_devno\n");
goto alloc_err;
}
/* 第二步
* 关联字符设备结构体cdev与文件操作结构体file_operations
*/
led_chrdev.owner = THIS_MODULE;
cdev_init(&led_chrdev, &led_chrdev_fops);
/* 第三步
* 添加设备到cdev_map哈希表中
*/
ret = cdev_add(&led_chrdev, led_devno, DEV_CNT);
if(ret < 0){
printk("fail to add cdev\n");
goto add_err;
}
/* 第四步:创建类 */
class_led = class_create(THIS_MODULE, DEV_NAME);
/* 第五步:创建设备 */
device = device_create(class_led, NULL, led_devno, NULL, DEV_NAME);
return 0;
alloc_err:
return -1;
add_err:
//添加设备失败时,需要注销设备号
unregister_chrdev_region(led_devno, DEV_CNT);
printk("error!\n");
}
static const struct of_device_id rgb_led[] = {
{.compatible = "fire,rgb_led"},
{/* sentinel */}
};
/* 定义平台设备结构体 */
struct platform_driver led_platform_driver = {
.probe = led_probe,
.driver = {
.name = "rgb-leds-platform",
.owner = THIS_MODULE,
.of_match_table = rgb_led,
}
};
static int __init led_platform_driver_init(void)
{
int DriverState;
sema_init(&sem, 1);
DriverState = platform_driver_register(&led_platform_driver);
printk(KERN_ALERT "DriverState is %d\n", DriverState);
return 0;
}
static void __exit led_platform_driver_exit(void){
/* 销毁设备 */
device_destroy(class_led, led_devno);
/* 删除设备号 */
cdev_del(&led_chrdev);
/* 取消注册字符设备 */
unregister_chrdev_region(led_devno, DEV_CNT);
/* 销毁类 */
class_destroy(class_led);
platform_driver_unregister(&led_platform_driver);
printk(KERN_ALERT "led_platform_driver exit\n");
}
module_init(led_platform_driver_init);
module_exit(led_platform_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("couvrir");
MODULE_DESCRIPTION("led module");
MODULE_ALIAS("led module");
app.c文件
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
if(argc != 2){
printf("commend error!\n");
return -1;
}
int fd = open("/dev/rgb_led", O_RDWR | O_NONBLOCK);
if(fd < 0){
printf("open file:/dev/rgb_led failed!!!\n");
return -1;
}
int error = write(fd, argv[1], sizeof(argv[1]));
if(error < 0){
printf("write file error!\n");
close(fd);
}
sleep(10);
error = close(fd);
if(error < 0){
printf("close file error!\n");
}
return 0;
}
Makefile文件
照旧
执行过程
虚拟机:
执行make和make copy。生成.ko文件。
开发板(在挂载目录下执行):
sudo insmod dts_led.ko
ls /dev/rgb_led
sudo /mnt/App 1 &
sudo /mnt/App 2
sudo /mnt/App 1 &
sudo /mnt/App 2
sudo rmmod dts_led.ko
阻塞IO:等待队列
wait_queue_head结构体:等待队列头
该结构体存放在内核/include/linux/wait.h文件。
struct wait_queue_head
{
spinlock_t lock; // 自旋锁
struct list_head head; // 链表节点类型
};
typedef struct wait_queue_head wait_queue_head_t; // 起别名
初始化等待队列头
init_waitqueue_head()
该函数存放在内核/include/linux/wait.h文件。
// 初始化,注意wq_head是指针
#define init_waitqueue_head(wq_head) \
do { \
static struct lock_class_key __key; \
\
__init_waitqueue_head((wq_head), #wq_head, &__key); \
} while (0)
DECLARE_WAIT_QUEUE_HEAD(name)
// 定义,再初始化
#define DECLARE_WAIT_QUEUE_HEAD(name) \
struct wait_queue_head name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
wait_queue_entry_t:等待队列元素
该结构体存放在内核/include/linux/wait.h文件。
struct wait_queue_entry
{
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head entry; // 通过此节点链接到等待队列(头)上
};
typedef struct wait_queue_entry wait_queue_entry_t
DECLARE_WAITQUEUE(name, tsk):初始化静态队列
该宏存放在内核/include/linux/wait.h文件。
// 静态定义一个等待队列元素
#define DECLARE_WAITQUEUE(name, tsk) \
struct wait_queue_entry name = __WAITQUEUE_INITIALIZER(name, tsk)
add_wait_queue():添加等待队列
该函数存放在内核/include/linux/wait.c文件。
/*
* 把等待队列元素加入等待队列头
* wq_head:等待队列项要加入的等待队列头
* wq_entry:要加入的等待队列项
*/
void add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry);
//返回值:无
remove_wait_queue():移除等待队列
该函数存放在内核/include/linux/wait.c文件。
/*
* 把等待队列元素从等待队列头中移除
* wq_head:等待队列项要移除的等待队列头
* wq_entry:要移除的等待队列项
*/
void remove_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry);
//返回值:无
等待事件
三个宏接口存放在内核/include/linux/wait.c文件。让进程或线程陷入休眠态。
// 被唤醒时只有condition的值为1,进程才能被唤醒。唤醒函数详见下
// 下面三个api 第一个参数为结构体变量,不是结构体指针变量
/*
* wq_head:等待队列项要加入的等待队列头
* condition:唤醒条件
* timeout:超时时间
*/
// 陷入休眠态后,进程或者线程屏蔽信号
wait_event(wq_head, condition)
// 陷入休眠态后,进程或者线程仍能接收信号
wait_event_interruptible(wq_head, condition)
// 设置休眠的超时时间,进程或者线程休眠超过限定时间会自动唤醒
wait_event_timeout(wq_head, condition, timeout)
要想wait_event()函数跳出,需要两个条件:
condition = 1;
在内核的另一个地方调用了wake_up()函数来唤醒对应的等待队列。
wake_up()函数每次只能唤醒一个进程,而且是从队列头开始唤醒的,而wait_event()函数每次会将新建的等待队列插到队列头,因此最后调用wait_event()函数的进程先被唤醒。
如果要唤醒某个特定的进程,只能使用wake_up_all()函数唤醒所有进程,然后再通过condition条件来控制(每个进程使用不同的变量来控制,在wake_up_all()函数后只将要唤醒的进程的变量置为真)。
唤醒
该函数存放在内核/kernel/sched/wait.c文件。用来唤醒等待队列上休眠的进程或线程。
// 唤醒时,只有对应的condition为1才能被唤醒
void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)
阻塞模式实验
向App应用程序输入0时(执行进程1,挂后台),希望这个进程1能够进入等待;
向App应用程序输入1时(执行进程2),希望这个进程2能够唤醒进程1并退出进程2;
进程1中判断写入值,如果为1,红灯亮。
dts_led.c文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#define DEV_NAME "rgb_led"
#define DEV_CNT (1)
int rgb_led_red;
int rgb_led_green;
int rgb_led_blue;
wait_queue_head_t wait_queue;
unsigned int write_data = 0;
static dev_t led_devno;
static struct cdev led_chrdev;
struct class *class_led;
struct device *device;
struct device_node *rgb_led_device_node;
static int led_chrdev_open(struct inode *inode, struct file *filp)
{
printk("open form driver\n");
return 0;
}
static ssize_t led_chrdev_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int ret, error;
unsigned char receive_data[10]; //用于保存接收到的数据
if(cnt > 10) cnt = 10;
error = copy_from_user(receive_data, buf, cnt);
if(error < 0) return -1;
ret = kstrtoint(receive_data, 16, &write_data);
if(ret) return -1;
if(write_data){
wake_up(&wait_queue);
return cnt;
}else{
wait_event(wait_queue, write_data);
}
/* 设置GPIO1_04输出电平 */
if(write_data){
gpio_set_value(rgb_led_red, 0);
}
return cnt;
}
static int led_chrdev_release(struct inode *inode, struct file *filp)
{
printk(KERN_ALERT "finished!!!\n");
return 0;
}
static struct file_operations led_chrdev_fops = {
.owner = THIS_MODULE,
.open = led_chrdev_open,
.write = led_chrdev_write,
.release = led_chrdev_release,
};
static int led_probe(struct platform_device *pdv)
{
int ret = -1; //保存错误状态码
unsigned int register_data = 0;
printk(KERN_ALERT "match successed!\n");
/* 获取rgb_led的设备树节点 */
rgb_led_device_node = of_find_node_by_path("/rgb_led");
if(rgb_led_device_node == NULL){
printk(KERN_ERR "get rgb_led failed!\n");
return -1;
}
/* 获取red led GPIO 引脚号 */
rgb_led_red = of_get_named_gpio(rgb_led_device_node, "rgb_led_red", 0);
if(rgb_led_red < 0){
printk(KERN_ERR "rgb_led_red failed!\n");
return -1;
}
/* 获取green led GPIO 引脚号 */
rgb_led_green = of_get_named_gpio(rgb_led_device_node, "rgb_led_green", 0);
if(rgb_led_green < 0){
printk(KERN_ERR "rgb_led_green failed!\n");
return -1;
}
/* 获取blue led GPIO 引脚号 */
rgb_led_blue = of_get_named_gpio(rgb_led_device_node, "rgb_led_blue", 0);
if(rgb_led_blue < 0){
printk(KERN_ERR "rgb_led_blue failed!\n");
return -1;
}
/* 设置GPIO为输出模式,并默认高电平 */
gpio_direction_output(rgb_led_red, 1);
gpio_direction_output(rgb_led_green, 1);
gpio_direction_output(rgb_led_blue, 1);
/* 第一步
* 采用动态分配的方式获取设备编号,次设备号为0
* 设备名称为rgb-leds,可通过命令cat /proc/devices查看
* DEV_CNT为1,当前只申请一个设备编号
*/
ret = alloc_chrdev_region(&led_devno, 0, DEV_CNT, DEV_NAME);
if(ret < 0){
printk("fail to alloc led_devno\n");
goto alloc_err;
}
/* 第二步
* 关联字符设备结构体cdev与文件操作结构体file_operations
*/
led_chrdev.owner = THIS_MODULE;
cdev_init(&led_chrdev, &led_chrdev_fops);
/* 第三步
* 添加设备到cdev_map哈希表中
*/
ret = cdev_add(&led_chrdev, led_devno, DEV_CNT);
if(ret < 0){
printk("fail to add cdev\n");
goto add_err;
}
/* 第四步:创建类 */
class_led = class_create(THIS_MODULE, DEV_NAME);
/* 第五步:创建设备 */
device = device_create(class_led, NULL, led_devno, NULL, DEV_NAME);
return 0;
alloc_err:
return -1;
add_err:
//添加设备失败时,需要注销设备号
unregister_chrdev_region(led_devno, DEV_CNT);
printk("error!\n");
}
static const struct of_device_id rgb_led[] = {
{.compatible = "fire,rgb_led"},
{/* sentinel */}
};
/* 定义平台设备结构体 */
struct platform_driver led_platform_driver = {
.probe = led_probe,
.driver = {
.name = "rgb-leds-platform",
.owner = THIS_MODULE,
.of_match_table = rgb_led,
}
};
static int __init led_platform_driver_init(void)
{
int DriverState;
init_waitqueue_head(&wait_queue);
DriverState = platform_driver_register(&led_platform_driver);
printk(KERN_ALERT "DriverState is %d\n", DriverState);
return 0;
}
static void __exit led_platform_driver_exit(void){
/* 销毁设备 */
device_destroy(class_led, led_devno);
/* 删除设备号 */
cdev_del(&led_chrdev);
/* 取消注册字符设备 */
unregister_chrdev_region(led_devno, DEV_CNT);
/* 销毁类 */
class_destroy(class_led);
platform_driver_unregister(&led_platform_driver);
printk(KERN_ALERT "led_platform_driver exit\n");
}
module_init(led_platform_driver_init);
module_exit(led_platform_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("couvrir");
MODULE_DESCRIPTION("led module");
MODULE_ALIAS("led module");
App.c文件
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
if(argc != 2){
printf("commend error!\n");
return -1;
}
int fd = open("/dev/rgb_led", O_RDWR);
if(fd < 0){
printf("open file:/dev/rgb_led failed!!!\n");
return -1;
}
int error = write(fd, argv[1], sizeof(argv[1]));
if(error < 0){
printf("write file error!\n");
close(fd);
}
error = close(fd);
if(error < 0){
printf("close file error!\n");
}
return 0;
}
Makefile文件
照旧
执行过程
虚拟机:
执行make和make copy。生成.ko文件。
开发板(在挂载目录下执行):
sudo insmod dts_led.ko
sudo App 0 &
sudo App 1
sudo rmmod dts_led.ko