您的位置:首页 > 运维架构 > Linux

linux设备驱动之字符设备驱动模型(1)

2015-09-12 16:15 561 查看
一:字符设备驱动

  在linux下面,应用层看到的一切皆为文件(名字)所有的设备都是文件,都可以调用open,read,write来操作,而在内核中每个中每个设备有唯一的对应一个设备号;

    APP (名字)
    OS (设备号)
    HW
  下面我们写一个简单的字符设备驱动,再应用层我们打开一个设备,看看它是怎么来调用内核中的函数的;

  首先我们使用命令mknod 创建一个名为wangcai的设备名,在应用曾打开它;

  mknod /dev/设备文件名 c 主设备号 次设备号

  命令 mknod wangcai c 9 0


  在注册字符设备是我们需要用到这几个struct cdev,原型如下:


struct cdev
{
struct kobject kobj;          // 内嵌的kobject对象,描述设备引用计数
struct module *owner;         // 所属模块,一般赋值为THIS_MODULE
struct file_operations *ops;  // 文件操作结构体
struct list_head list;
dev_t dev;                    // 设备号
unsigned int count;
};


  cdev结构体的dev_t定义了设备号,32位。高12位为主设备号,低20位为次设备号。

  (1)应用层打开设备

#include <stdio.h>
#include <fcntl.h>

int main()
{
int fd = 0;
fd = open("wangcai", O_RDWR);
if(fd < 0) {
perror("open error");
return 1;
}

return 0;
}


  (2)在内核中册了设备文件wangcai和方法ops

  

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("bunfly");
int file_open(struct inode *no, struct file *fp);

struct cdev wangcai;//设备
struct file_operations fops;//方法
dev_t devno;

int bunfly_init()
{
fops.open = file_open;//调用open
//wangcai.ops = &fops;
cdev_init(&wangcai, &fops);//初始化cdev成员,建立cdev和file_operations之间的连接
//wangcai.dev = (9, 0);
devno = MKDEV(9, 0);//字符设备号注册
//insert_list(&wangcai);
cdev_add(&wangcai, devno, 1);// 向系统添加一个dev,完成字符设备的注册,常用于模块加载函数中

}

int bunfly_exit()
{
printk("this is bunfly_exit\n");
}

module_init(bunfly_init);
module_exit(bunfly_exit);

int file_open(struct inode *no, struct file *fp)
{
printk("this is file_open\n");

return 0;
}


  下面代码是实现字符设备的读写操作:

  (1)应用层

  write:

#include <stdio.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char *argv[])
{
if(argc < 3) {
printf("using %s <dev> <msg>\n", argv[0]);
return 1;
}

int fd = 0;
int ret = 0;

fd = open(argv[1], O_RDWR);
if(fd < 0) {
perror("open error");

return 1;
}

ret = write(fd, argv[2], strlen(argv[2]));
if(ret < 0) {
perror("write error");
return 1;
}
close(fd);

return 0;
}


  read:

  

#include <stdio.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char *argv[])
{
if(argc != 2) {
printf("using %s <dev>\n", argv[0]);
return 1;
}

int fd = 0;
int ret = 0;
unsigned char data[1024] = {0};

fd = open(argv[1], O_RDWR);
if(fd < 0) {
perror("open error");

return 1;
}

ret = read(fd, data, 1024);
if(ret < 0) {
perror("write error");
return 1;
}

printf("data is: %s\n", data);

close(fd);

return 0;
}


  

  (2)内核  

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("bunfly");

int file_open(struct inode *no, struct file *fp);
ssize_t file_read(struct file *fp, char *buff, size_t size, loff_t * loff);
ssize_t file_write(struct file *fp, const char *buff, size_t size, loff_t *loff);

struct cdev wangcai;//设备
struct file_operations fops;//方法
dev_t devno;
unsigned char data[1024] = {0};

int bunfly_init()
{
fops.open = file_open;
fops.read = file_read;
fops.write = file_write;

//wangcai.ops = &fops;
cdev_init(&wangcai, &fops);//初始化cdev成员,建立cdev和file_operations之间的连接
//wangcai.dev = (9, 0);
devno = MKDEV(9, 0);//字符设备号注册
//insert_list(&wangcai);
cdev_add(&wangcai, devno, 1);// 向系统添加一个dev,完成字符设备的注册,常用于模块加载函数中

}

int bunfly_exit()
{
cdev_del(&wangcai);   /*注销设备*/
unregister_chrdev_region(MKDEV(9, 0), 1);
printk("this is bunfly_exit\n");
}

module_init(bunfly_init);
module_exit(bunfly_exit);

int file_open(struct inode *no, struct file *fp)
{
return 0;
}
ssize_t file_read(struct file *fp, char *buff, size_t size, loff_t *loff)
{
strcpy(buff, data);
return size;
}

ssize_t file_write(struct file *fp, const char *buff, size_t size, loff_t *loff)
{
memset(data, 0, 1024);
strcpy(data, buff);
return size;
}
59


  下面代码是通过ioctl()函数来控制灯亮灯灭:

  (1)应用层

  

#include <stdio.h>
#include <string.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
if(argc != 3) {
printf("using %s <devname> 1:0\n", argv[0]);
return 1;
}

int fd = 0;
fd = open(argv[2], O_RDWR);
if(fd < 0) {
perror("open");
return 1;
}

ioctl(fd, atoi(argv[2]));
close(fd);
return 0;
}


  (2)内核

  

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/gpio.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("bunfly");

long my_ioctl(struct file *fp, unsigned int id, unsigned long fd);
int file_open(struct inode *no, struct file *fp);

struct cdev wangcai;
struct file_operations fops;
dev_t devno;
unsigned long gpio_virt;
unsigned long *gpm4con, *gpm4dat;

int bunfly_init()
{
fops.open = file_open;
fops.unlocked_ioctl = my_ioctl;

//wangcai.ops = &fops;
cdev_init(&wangcai, &fops);//初始化cdev成员,建立cdev和file_operations之间的连接
//wangcai.dev = (9, 0);
devno = MKDEV(9, 0);//字符设备号注册
//insert_list(&wangcai);
cdev_add(&wangcai, devno, 1);// 向系统添加一个dev,完成字符设备的注册,常用于模块加载函数中

gpio_virt = ioremap(0x11000000, SZ_4K);//led物理地址到虚拟地址映射
gpm4con = gpio_virt + 0x02e0;
gpm4dat = gpio_virt + 0x02e4;

return 0;
}

int bunfly_exit()
{
cdev_del(&wangcai);   /*注销设备*/
unregister_chrdev_region(MKDEV(9, 0), 1);
printk("this is bunfly_exit\n");

return 0;
}

module_init(bunfly_init);
module_exit(bunfly_exit);

int file_open(struct inode *no, struct file *fp)
{
return 0;
}

long my_ioctl(struct file *fp, unsigned int id, unsigned long fd)
{
if(id == 1) {
*gpm4con = 0x1111;
*gpm4dat = 0x0;
}
if(id == 0) {
*gpm4con = 0x1111;
*gpm4dat = 0xf;
}
}


    通过上面的代码我们已经了解了字符设备驱动的原理,在linux下应用层看到的设备都只 是一个名字,应用层打开一个设备最终会调到内核中的file_operations方法来进行读写操作,如果我们只创建一个的设备的时候,我们可以对他正 常的读写,那如果当我们有两个设备时,我们是否还能正常的进行读写操作,明显是存在问题的,就是第二次的数据会将第一次的数据覆盖,因为我们只有一个数据存储的data;那么解决这个问题的方法就是封装;

  下面是具体代码:

  

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("bunfly");

int file_open(struct inode *no, struct file *fp);
ssize_t file_read(struct file *fp, char *buff, size_t size, loff_t *loff);
ssize_t file_write(struct file *fp, const char *buff, size_t size, loff_t *loff);

struct file_operations fops;//方法

/*封装*/
struct bunfly_cdev {
dev_t devno;//设备号
struct cdev cdev;
unsigned char data[1024];
};

int bunfly_init()
{
fops.open = file_open;
fops.read = file_read;
fops.write = file_write;

struct bunfly_cdev wangcai;
cdev_init(&wangcai.cdev, &fops);//初始化cdev成员,建立cdev和file_operations之间的连接
wangcai.devno = MKDEV(9, 0);//注册设备号
cdev_add(&wangcai.cdev, wangcai.devno, 1);// 向系统添加一个dev,完成字符设备的注册,常用于模块加载函数中

struct bunfly_cdev tugou;
cdev_init(&tugou.cdev, &fops);
tugou.devno = MKDEV(9, 1);
cdev_add(&tugou.cdev, tugou.devno, 1);

return 0;
}

int bunfly_exit()
{
printk("this is bunfly_exit\n");

return 0;
}

module_init(bunfly_init);
module_exit(bunfly_exit);

int file_open(struct inode *no, struct file *fp)
{
struct cdev *addr = no->i_cdev;//找到struct cdev dev 在struct bunfly_cdev中的地址
struct bunfly_cdev *this = container_of(addr, struct bunfly_cdev, cdev);
fp->private_data = this;       //父类在子类中的地址  //子类类型

return 0;
}
ssize_t file_read(struct file *fp, char *buff, size_t size, loff_t *loff)
{
struct bunfly_cdev *this = fp->private_data;
strcpy(buff,this-> data);

return size;
}

ssize_t file_write(struct file *fp, const char *buff, size_t size, loff_t *loff)
{
struct bunfly_cdev *this = fp->private_data;
memset(this->data, 0, 1024);
strcpy(this->data, buff);

return size;
}


  代码中有看到了container_of,再次强调掌握,还需要注意的是:

  (1)每一个设备文件仅有inode结构体 ;


  (2)每打开一次文件就创建一个file 结构体;


  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: