linux设备中有字符设备和块设备,它们是有区别的。
字符设备:能够像字节流(类似文件)一样被访问的设备,由字符设备驱动程序来实现这种特性。串口、鼠标、键盘、摄像头、声卡和显卡等就是典型的字符设备。
块设备:块设备和字符设备一样也是通过linux系统下/dev目录的文件系统节点来访问。块设备上能够容纳文件系统,如:U盘,SD卡,磁盘等。
字符设备一般至少需要实现open、read、close和write系统调用。
而这些系统调用是通过一个file_operations结构体来实现的。
内核中file_operations结构体的内容
上面结构体给出了我们需要定义的内容,但是我们现在只需要用到一部分即可。
编写内核中的驱动函数key_interr.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
struct class *interr_class;
struct class_device *interr_class_device; //自动在dev下注册设备
volatile unsigned long *gpfcon; //将要用到的寄存器定义出来
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;
static DECLARE_WAIT_QUEUE_HEAD(button_waitq); //休眠的进程
static struct fasync_struct *button_async; //异步通知机制要用到的定义
static volatile int ev_press = 0; //按键事件中断标志,有中断时为1
struct file_operations key_drv_ops = {
.woner = THIS_MODULE,
.open = key_interr_open, //对应的函数
.read = key_interr_read,
.release = key_interr_close,
.fasync = key_interr_fasync,
};
struct pin_desc { //定义一个结构体来描述引脚
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[4] = { //查开发板的中断引脚
{S3C2410_GPF0,0X01},
{S3C2410_GPF2,0X02},
{S3C2410_GPG3,0X03},
{S3C2410_GPG11,0X04},
};
static unsigned char key_val;
static irqreturn_t key_irq(int irq, void *dev_id) //中断函数
{
struct pin_desc *pin_id = (struct pin_desc *)dev_id;
unsigned int pinval = s3c2410_gpio_getpin(pin_id->pin);
if(pinval) {
key_val = pin_id->key_val; //有中断时,key_val为原来的值
}
else {
key_val = 0x80 | pin_id->key_val; //无中断时,为0x80及其他
}
ev_press = 1; //发生中断,中断事件标志为1
wake_up_interruptible(&button_waitq); //唤醒进程
kill_fasync (&button_async, SIGIO, POLL_IN); //发送信号给应用程序
return IRQ_RETVAL(IRQ_HANDLED);
}
static int key_interr_open(struct inode *inode, struct file *file)
{
request_irq(IRQ_EINT0, key_irq, IRQT_BOTHEDGE, "S1", &pins_desc[0]); //设置中断
request_irq(IRQ_EINT0, key_irq, IRQT_BOTHEDGE, "S2", &pins_desc[1]);
request_irq(IRQ_EINT0, key_irq, IRQT_BOTHEDGE, "S3", &pins_desc[2]);
request_irq(IRQ_EINT0, key_irq, IRQT_BOTHEDGE, "S4", &pins_desc[3]);
}
ssize_t key_interr_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
if (size != 1)
return -EINVAL;
wait_event_interruptible(button_waitq, ev_press); //没有中断,进程休眠
copy_to_user(buf, &key_val, 1); //将数据传递给应用程序
ev_press = 0;
return 1;
}
static int key_interr_close(void)
{
free_irq(IRQ_EINT0, &pins_desc[0]);
free_irq(IRQ_EINT2, &pins_desc[1]);
free_irq(IRQ_EINT11, &pins_desc[2]);
free_irq(IRQ_EINT19, &pins_desc[3]);
return 0;
}
static int key_interr_fasync (int fd, struct file *filp, int on)
{
return fasync_helper (fd, filp, on, &button_async); //异步通知机制,初始化button_async结构体
}
int major; //主设备号
static int key_interr_init(void)
{
major = register_chrdev(0, "key_drv", &key_drv_ops); //将驱动程序注册到chrdev中
interr_class = class_create(THIS_MODULE, "key_drv");
interr_class_device = class_device_create(&interr_class, NULL, MKDEV(major,0),NULL, "key" );
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); //因为使用了MMU,因此不能直接访问物理地址,需要进行IO映射
gpfdat = gpfcon + 1;
gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
gpgdat = gpfcon + 1;
return 0;
}
static int key_interr_exit(void)
{
unregister_chrdev(major, "key_drv");
class_device_unregister(interr_class_device);
class_destroy(interr_class);
iounmap(gpfcon);
iounmap(gpgcon);
return 0;
}
MODULE_INIT(key_interr_init); //对入口函数和出口函数进行修饰才能使用
MODULE_EXIT(key_interr_exit);
MODULE_LICENSE("GPL");
编写应用程序中的函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fd;
void my_signal_fun(int signum)
{
unsigned char key_val;
read(fd, &key_val, 1);
printf("key_val: 0x%x\n", key_val);
}
int main(int argc, char **argv)
{
unsigned char key_val;
int ret;
int Oflags;
signal(SIGIO, my_signal_fun); //当有信号从内核中发出时,调用my_signal_fun函数,进行读操作
fd = open("/dev/buttons", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
fcntl(fd, F_SETOWN, getpid()); //将进程ID告诉驱动程序
Oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, Oflags | FASYNC);
while (1)
{
sleep(1000); //休眠
}
return 0;
}
在进行试验的时候,按下开发板上的按钮,发现显示出来的数值比原本的次数多出来一次。查阅了相关资料发现是因为开发板上的按钮存在机械抖动,会多发出几次信号。为了解决这个问题,引入定时器的概念,在内核中有一个定时功能,在发出信号后的一段时间再调用相关函数,在这个过程中如果又有信号过来则重新计时。比如说我们定时是10ms,当我们按下按钮的时候就有了一个信号,10ms开始计时,但由于机械抖动,按钮又发来了一个信号,因此计时又从0开始,到10ms后再进行相应操作。
现在将定时器的代码加入到原来的代码中。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
#include <asm/gpio.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h> //添加几个头文件
struct class *interr_class;
struct class_device *interr_class_device; //自动在dev下注册设备
volatile unsigned long *gpfcon; //将要用到的寄存器定义出来
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;
static DECLARE_WAIT_QUEUE_HEAD(button_waitq); //休眠的进程
static struct fasync_struct *button_async; //异步通知机制要用到的定义
static struct timer_list key_timer; //定义timer结构体
static volatile int ev_press = 0; //按键事件中断标志,有中断时为1
struct file_operations key_drv_ops = {
.woner = THIS_MODULE,
.open = key_interr_open, //对应的函数
.read = key_interr_read,
.release = key_interr_close,
.fasync = key_interr_fasync,
};
struct pin_desc { //定义一个结构体来描述引脚
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[4] = { //查开发板的中断引脚
{S3C2410_GPF0,0X01},
{S3C2410_GPF2,0X02},
{S3C2410_GPG3,0X03},
{S3C2410_GPG11,0X04},
};
static unsigned char key_val;
static struct pin_desc *pindesc;
static irqreturn_t key_irq(int irq, void *dev_id)
{
pindesc = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer, jiffies+HZ/100); //设置定时时间为10ms
return IRQ_RETVAL(IRQ_HANDLED);
}
static void timer_key(unsigned int data) //中断函数
{
struct pin_desc *pin_irq = pindesc;
unsigned int pinval = s3c2410_gpio_getpin(pin_irq->pin);
if(pinval) {
key_val = pin_id->key_val; //有中断时,key_val为原来的值
}
else {
key_val = 0x80 | pin_id->key_val; //无中断时,为0x80及其他
}
ev_press = 1; //发生中断,中断事件标志为1
wake_up_interruptible(&button_waitq); //唤醒进程
kill_fasync (&button_async, SIGIO, POLL_IN); //发送信号给应用程序
}
static int key_interr_open(struct inode *inode, struct file *file)
{
request_irq(IRQ_EINT0, key_irq, IRQT_BOTHEDGE, "S1", &pins_desc[0]); //设置中断
request_irq(IRQ_EINT0, key_irq, IRQT_BOTHEDGE, "S2", &pins_desc[1]);
request_irq(IRQ_EINT0, key_irq, IRQT_BOTHEDGE, "S3", &pins_desc[2]);
request_irq(IRQ_EINT0, key_irq, IRQT_BOTHEDGE, "S4", &pins_desc[3]);
}
ssize_t key_interr_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
if (size != 1)
return -EINVAL;
wait_event_interruptible(button_waitq, ev_press); //没有中断,进程休眠
copy_to_user(buf, &key_val, 1); //将数据传递给应用程序
ev_press = 0;
return 1;
}
static int key_interr_close(void)
{
free_irq(IRQ_EINT0, &pins_desc[0]);
free_irq(IRQ_EINT2, &pins_desc[1]);
free_irq(IRQ_EINT11, &pins_desc[2]);
free_irq(IRQ_EINT19, &pins_desc[3]);
return 0;
}
static int key_interr_fasync (int fd, struct file *filp, int on)
{
return fasync_helper (fd, filp, on, &button_async); //异步通知机制,初始化button_async结构体
}
int major; //主设备号
static int key_interr_init(void)
{
major = register_chrdev(0, "key_drv", &key_drv_ops); //将驱动程序注册到chrdev中
interr_class = class_create(THIS_MODULE, "key_drv");
interr_class_device = class_device_create(&interr_class, NULL, MKDEV(major,0),NULL, "key" );
timer_init(&key_timer); //初始化timer
key_timer.function = timer_key(); //设置定时结束后的跳转函数
add_timer(&key_timer); //将结构体添加到内核中
gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16); //因为使用了MMU,因此不能直接访问物理地址,需要进行IO映射
gpfdat = gpfcon + 1;
gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
gpgdat = gpfcon + 1;
return 0;
}
static int key_interr_exit(void)
{
unregister_chrdev(major, "key_drv");
class_device_unregister(interr_class_device);
class_destroy(interr_class);
del_timer(&key_timer); //卸载定时器
iounmap(gpfcon);
iounmap(gpgcon);
return 0;
}
MODULE_INIT(key_interr_init); //对入口函数和出口函数进行修饰才能使用
MODULE_EXIT(key_interr_exit);
MODULE_LICENSE("GPL");
编写应用程序中的函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fd;
void my_signal_fun(int signum)
{
unsigned char key_val;
read(fd, &key_val, 1);
printf("key_val: 0x%x\n", key_val);
}
int main(int argc, char **argv)
{
unsigned char key_val;
int ret;
int Oflags;
signal(SIGIO, my_signal_fun); //当有信号从内核中发出时,调用my_signal_fun函数,进行读操作
fd = open("/dev/buttons", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
fcntl(fd, F_SETOWN, getpid()); //将进程ID告诉驱动程序
Oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, Oflags | FASYNC);
while (1)
{
sleep(1000); //休眠
}
return 0;
}