20150518 字符设备驱动

20150518 字符设备驱动

2015-05-18 Lover雪儿

经过这两个月的学习,相信对设备驱动的编写已经有一个大概的了解了,温故而知新,此处我们再一次的系统性的复习一下字符设备驱动,然后,我们来尝试着自己从零实战写一个AD采集的字符设备驱动.

以前学习使用的是老方法来注册字符设备驱动,此处我们使用字符设备的新方法来学习.

本文参考:华清远见的Linux 设备驱动开发详解-字符设备驱动,具体还请看作者原书

.cdev结构体

1 struct cdev{
2     struct kobject kobj; /* 内嵌的 kobject 对象 */
3     struct module *owner;
4     /*所属模块*/
5     struct file_operations *ops; /*文件操作结构体*/
6     struct list_head list;
7     dev_t dev;  /*设备号*/
8     unsigned int count;
9 };

cdev结构体的dev_t成员定义了设备号,为32位,其中高12位为主设备号,低20位为次设备号.

使用MAJOR(dev_t dev)获得主设备号和MINOR(dev_t dev)获得次设备号;

而使用MKDEV(int major, int minor)又可以生成dev_t.

cdev 结构体的另一个重要成员 file_operations 定义了字符设备驱动提供给虚拟文件系统的接口函数。

Linux 2.6 内核提供了一组函数用于操作 cdev 结构体,如下所示:

1 void cdev_init(struct cdev *, struct file_operations *);
2     //初始化 cdev 的成员,并建立 cdev 和 file_operations 之间的连接
3 struct cdev *cdev_alloc(void);
4     //用于动态申请一个 cdev 内存
5 void cdev_put(struct cdev *p);
6 int cdev_add(struct cdev *, dev_t, unsigned);
7 void cdev_del(struct cdev *);
8 //cdev_add()函数和 cdev_del()函数分别向系统添加和删除一个 cdev, 完成字符设备的注册和注销

.分配和释放设备号

在调用cdev_add()函数向系统注册字符设备之前,应首先调用register_chrdev_region()或者alloc_chrdev_region()函数向系统申请设备号.

相反,在调用cdev_del()函数从系统注销字符设备之后,unregister_chrdev_region()应该被调用以释放原先申请的设备号

int register_chrdev_region(dev_t from, unsigned count, const char*name); //用已知起始设备的设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);//用于设备号未知
void unregister_chrdev_region(dev_t from, unsigned count); //卸载

.file_operations 结构体

file_operations 结构体中的成员函数是字符设备驱动程序设计的主体内容, 这些函数实际会在应用程序进行 Linux 的 open()、write()、read()、close()等系统调用时最终被调用.

struct file_operations{
    struct module *owner;
    // 拥有该结构的模块的指针,一般为 THIS_MODULES
    loff_t(*llseek)(struct file *, loff_t, int);
    // 用来修改文件当前的读写位置,并将新位置返回,在出错时,这个函数返回一个负值。
    ssize_t(*read)(struct file *, char _ _user *, size_t, loff_t*);
    // 从设备中同步读取数据,成功时函数返回读取的字节数,出错时返回一个负值。
    ssize_t(*aio_read)(struct kiocb *, char _ _user *, size_t, loff_t);
    // 初始化一个异步的读取操作,设备实现这两个函数后,用户空间可以对该设备文件描述符调用aio_read()、aio_write()等系统调用进行读写。
    ssize_t(*write)(struct file *, const char _ _user *, size_t,loff_t*);
    // 向设备发送数据,成功时该函数返回写入的字节数。如果此函数未被实现,当用户进行 write()系统调用时,将得到-EINVAL 返回值。
    ssize_t(*aio_write)(struct kiocb *, const char _ _user *, size_t,loff_t);
    // 初始化一个异步的写入操作
    int(*readdir)(struct file *, void *, filldir_t);
    // 仅用于读取目录,对于设备文件,该字段为 NULL,设备节点不需要实现它。
    unsigned int(*poll)(struct file *, struct poll_table_struct*);
    // 轮询函数,判断目前是否可以进行非阻塞的读取或写入,当询问的条件未触发时,用户空间进行 select()和 poll()系统调用将引起进程的阻塞。
    int(*ioctl)(struct inode *, struct file *, unsigned int, unsignedlong);
    // 执行设备 I/O 控制命令,当调用成功时,返回给调用程序一个非负值
    long(*unlocked_ioctl)(struct file *, unsigned int, unsigned long);
    // 不使用 BLK 文件系统,将使用此种函数指针代替 ioctl
    long(*compat_ioctl)(struct file *, unsigned int, unsigned long);
    // 在 64 位系统上,32 位的 ioctl 调用将使用此函数指针代替
    int(*mmap)(struct file *, struct vm_area_struct*);
    // 用于请求将设备内存映射到进程地址空间,如果设备驱动未实现此函数,用户进行 mmap()系统调用时将获得-ENODEV 返回值
    int(*open)(struct inode *, struct file*);
    // 打开,驱动程序可以不实现这个函数,在这种情况下,设备的打开操作永远成功。
    int(*flush)(struct file*);
    int(*release)(struct inode *, struct file*);
    // 关闭
    int(*synch)(struct file *, struct dentry *, int datasync);
    // 刷新待处理的数据
    int(*aio_fsync)(struct kiocb *, int datasync);
    // 异步 fsync
    int(*fasync)(int, struct file *, int);
    // 通知设备 FASYNC 标志发生变化
    int(*lock)(struct file *, int, struct file_lock*);
    ssize_t(*readv)(struct file *, const struct iovec *, unsigned long,loff_t*);
    ssize_t(*writev)(struct file *, const struct iovec *, unsigned long,loff_t*);
    // readv 和 writev:分散/聚集型的读写操作
    ssize_t(*sendfile)(struct file *, loff_t *, size_t, read_actor_t,void*);
    // 通常为 NULL
    ssize_t(*sendpage)(struct file *, struct page *, int, size_t,loff_t *, int);
    // 通常为 NULL
    unsigned long(*get_unmapped_area)(struct file *,unsigned long,unsigned long,unsigned long, unsigned long);
    // 在进程地址空间找到一个将底层设备中的内存段映射的位置
    int(*check_flags)(int);
    // 允许模块检查传递给 fcntl(F_SETEL...)调用的标志
    int(*dir_notify)(struct file *filp, unsigned long arg);
    // 仅对文件系统有效,驱动程序不必实现
    int(*flock)(struct file *, int, struct file_lock*);
};

.字符设备组成

1.系统加载与卸载函数

 1 //设备结构体
 2 struct xxx_dev_t{
 3     struct cdev cdev;
 4     ...
 5 } xxx_dev;
 6 //设备驱动模块加载函数
 7 static int _ _init xxx_init(void){
 8     ...
 9     cdev_init(&xxx_dev.cdev, &xxx_fops); //初始化 cdev
10     xxx_dev.cdev.owner = THIS_MODULE;
11     //获取字符设备号
12     if (xxx_major){
13         register_chrdev_region(xxx_dev_no, 1, DEV_NAME);
14     }else{
15         alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);
16     }
17     ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1); //注册设备
18     …
19 }
20 /*设备驱动模块卸载函数*/
21 static void _ _exit xxx_exit(void){
22     unregister_chrdev_region(xxx_dev_no, 1); //释放占用的设备号
23     cdev_del(&xxx_dev.cdev); //注销设备
24     ...
25 }

2.file_operations结构体

 1 /* 读设备*/
 2 ssize_t xxx_read(struct file *filp, char _ _user *buf, size_t count,loff_t*f_pos){
 3     ...
 4     copy_to_user(buf, ..., ...);
 5     ...
 6 }
 7 /* 写设备*/
 8 ssize_t xxx_write(struct file *filp, const char _ _user *buf, size_tloff_t *f_pos){
 9     ...
10     copy_from_user(..., buf, ...);
11     ...
12 }
13 /* ioctl 函数 */
14 int xxx_ioctl(struct inode *inode, struct file *filp, unsigned intunsigned long arg){
15     ...
16     switch (cmd){
17         case XXX_CMD1:  …  break;
18         case XXX_CMD2:  …  break;
19         default:/* 不能支持的命令 */ return - ENOTTY;
20     }
21     return 0;
22 }
23 struct file_operations xxx_fops ={
24     .owner = THIS_MODULE,
25     .read = xxx_read,
26     .write = xxx_write,
27     .ioctl = xxx_ioctl,
28     ...
29 };
30
31 //由于内核空间与用户空间的内存不能直接互访,因此借助函数 copy_from_user()完成用户空间到内核空间的复制
32 unsigned long copy_from_user(void *to, const void _ _user *from, unsignedlong count);
33 unsigned long copy_to_user(void _ _user *to, const void *from, unsignedlong count);
34 //读和写函数中的_ _user 是一个宏,表明其后的指针指向用户空间
35 //上述函数均返回不能被复制的字节数,因此,如果完全复制成功,返回值为 0。
36 //如果要复制的内存是简单类型, 如 char、 int、 long 等, 则可以使用简单的 put_user()和 get_user(),如下所示:
37     int val; //内核空间整型变量
38     ...
39     get_user(val, (int *) arg); //用户空间到内核空间,arg 是用户空间的地址
40     ...
41     put_user(val, (int *) arg); //内核空间到用户空间,arg 是用户空间的地址

.globalmem虚拟设备实例描述

1.ioctl()命令

linux系统建议以下表格所示的方式定义ioctl()命令:


设备类型


序列号


方向


数据尺寸


8bit


8bit


2bit


13/14bit

命令码的设备类型字段为一个“幻数” ,可以是 0~0xff 之间的值,内核中的ioctl-number.txt 给出了一些推荐的和已经被使用的“幻数”,新设备驱动定义“幻数”的时候要避免与其冲突.

命令码的方向字段为 2 位,该字段表示数据传送的方向, 可能的值是_IOC_NONE(无数据传输)、_IOC_READ(读)、_IOC_WRITE(写)和_IOC_READ|_IOC_WRITE(双向)。数据传送的方向是从应用程序的角度来看的。

命令码的数据长度字段表示涉及的用户数据的大小,这个成员的宽度依赖于体系结构,通常是 13 位或者 14 位。

内核还定义了_IO()、_IOR()、_IOW()和_IOWR()这 4 个宏来辅助生成命令

 1 #define _IO(type,nr)
 2 #define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr), 3     (_IOC_TYPECHECK(size)))
 4 #define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr), 5     (_IOC_TYPECHECK(size)))
 6 #define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),  7     (_IOC_TYPECHECK(size)))
 8 /*_IO、 _IOR 等使用的 _IOC 宏*/
 9 #define _IOC(dir,type,nr,size) 10     (((dir) << _IOC_DIRSHIFT) | 11     ((type) << _IOC_TYPESHIFT) | 12     ((nr) << _IOC_NRSHIFT) | 13     ((size) << _IOC_SIZESHIFT))
14 //这几个宏的作用是根据传入的 type (设备类型字段)、 nr(序列号字段)和 size(数据长度字段)和宏名隐含的方向字段移位组合生成命令码
15
16 //由于 globalmem 的 MEM_CLEAR 命令不涉及数据传输,因此它可以定义如下:
17 #define GLOBALMEM_MAGIC ...
18 #define MEM_CLEAR _IO(GLOBALMEM_MAGIC,0)

2.预定义命令

内核中预定义了一些 I/O 控制命令,如果某设备驱动中包含了与预定义命令一样的命令,这些命令会被当作预定义命令被内核处理而不是被设备驱动处理,预定义命令有如下 4 种。

①FIOCLEX:即 File IOctl Close on Exec,对文件设置专用标志,通知内核当exec()系统调用发生时自动关闭打开的文件。

②FIONCLEX:即 File IOctl Not CLose on Exec,与 FIOCLEX 标志相反,清除由 FIOCLEX 命令设置的标志。

③FIOQSIZE:获得一个文件或者目录的大小,当用于设备文件时,返回一个ENOTTY 错误。

④FIONBIO:即 File IOctl Non-Blocking I/O,这个调用修改在 filp->f_flags 中的 O_NONBLOCK 标志。FIOCLEX、FIONCLEX、FIOQSIZE 和 FIONBIO 这些宏的定义如下:

#define    FIONCLEX    0x5450
#define    FIOCLEX    0x5451
#define    FIOQSIZE    0x5460
#define    FIONBIO    0x5421
由以上定义可以看出,FIOCLEX、FIONCLEX、FIOQSIZE 和 FIONBIO 的幻数为“T”。

3.globalmem代码:

  1 #include <linux/module.h>
  2 #include <linux/types.h>
  3 #include <linux/fs.h>
  4 #include <linux/kernel.h>
  5 #include <linux/device.h>
  6 #include <linux/errno.h>
  7 #include <linux/mm.h>
  8 #include <linux/sched.h>
  9 #include <linux/init.h>
 10 #include <linux/cdev.h>
 11 #include <asm/io.h>
 12 #include <asm/system.h>
 13 #include <asm/uaccess.h>
 14
 15 #define GLOBALMEM_SIZE    0x1000    /*全局内存大小:4KB*/
 16 #define MEM_CLEAR         0x1     /*清零全局内存*/
 17 #define GLOBALMEM_MAJOR 0        /*预设的 globalmem 的主设备号*/
 18
 19
 20 static int globalmem_major = GLOBALMEM_MAJOR;
 21 /*globalmem 设备结构体*/
 22 struct globalmem_dev
 23 {
 24     struct cdev cdev;                     /*cdev 结构体*/
 25     unsigned char mem[GLOBALMEM_SIZE];     /*全局内存*/
 26 };
 27
 28 struct globalmem_dev *globalmem_devp; /*设备结构体实例*/
 29
 30 //自动添加设备节点
 31 static struct class *cls = NULL;
 32
 33
 34 /*文件打开函数*/
 35 static int globalmem_open(struct inode *inode, struct file *filp){
 36     /*将设备结构体指针赋值给文件私有数据指针*/
 37     filp->private_data = globalmem_devp;
 38     return 0;
 39 }
 40 //释放函数
 41 static int globalmem_release(struct inode *inode, struct file *filp){
 42     return 0;
 43 }
 44 //文件读函数
 45 static ssize_t globalmem_read(struct file *filp, char __user *buf,size_t count,loff_t *ppos)
 46 {
 47     unsigned long p = *ppos;
 48     int ret = 0;
 49     struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
 50     /*分析和获取有效的读长度*/
 51     if (p >= GLOBALMEM_SIZE) //要读的偏移位置越界
 52         return count ? - ENXIO: 0;
 53     if (count > GLOBALMEM_SIZE - p)//要读的字节数太大
 54         count = GLOBALMEM_SIZE - p;
 55     /*内核空间→用户空间*/
 56     if (copy_to_user(buf, (void*)(dev->mem + p), count))
 57     {
 58         ret = - EFAULT;
 59     }else{
 60         *ppos += count;
 61         ret = count;
 62         printk(KERN_INFO "read %d bytes(s) from %ld\n", count, p);
 63     }
 64     return ret;
 65 }
 66 //文件写函数
 67 static ssize_t globalmem_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
 68 {
 69     unsigned long p = *ppos;
 70     int ret = 0;
 71     struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
 72     /*分析和获取有效的写长度*/
 73     if (p >= GLOBALMEM_SIZE)     //要写的偏移位置越界
 74         return count ? - ENXIO: 0;
 75     if (count > GLOBALMEM_SIZE - p) //要写的字节数太多
 76         count = GLOBALMEM_SIZE - p;
 77
 78     /*用户空间→内核空间*/
 79     if (copy_from_user(dev->mem + p, buf, count)){
 80         ret = - EFAULT;
 81     }else{
 82         *ppos += count;
 83         ret = count;
 84         printk(KERN_INFO "written %d bytes(s) from %ld\n", count, p);
 85     }
 86     return ret;
 87 }
 88 //文件定位函数
 89 static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
 90 {
 91     loff_t ret;
 92     switch (orig){
 93         case 0:   /*从文件开头开始偏移*/
 94             if (offset < 0){
 95                 ret = - EINVAL;
 96                 break;
 97             }
 98             if ((unsigned int)offset > GLOBALMEM_SIZE) //偏移越界
 99             {
100                 ret = - EINVAL;
101                 break;
102             }
103             filp->f_pos = (unsigned int)offset;
104             ret = filp->f_pos;
105             break;
106
107         case 1:  /*从当前位置开始偏移*/
108             if ((filp->f_pos + offset) > GLOBALMEM_SIZE) //偏移越界
109             {
110                 ret = - EINVAL;
111                 break;
112             }
113             if ((filp->f_pos + offset) < 0)
114             {
115                 ret = - EINVAL;
116                 break;
117             }
118             filp->f_pos += offset;
119             ret = filp->f_pos;
120             break;
121         default:  ret = - EINVAL; break;
122     }
123     return ret;
124 }
125 //
126 static int globalmem_ioctl(struct inode *inodep, struct file *filp,unsigned int cmd, unsigned long arg)
127 {
128     struct globalmem_dev *dev = filp->private_data;
129     switch (cmd){
130         case MEM_CLEAR:        //清除全局内存
131             memset(dev->mem, 0, GLOBALMEM_SIZE);
132             printk(KERN_INFO "globalmem is set to zero\n");
133             break;
134         default:
135             return - EINVAL; //其他不支持的命令
136             break;
137     }
138     return 0;
139 }
140 //定义file_operatetiont
141 static const struct file_operations globalmem_fops =
142 {
143     .owner = THIS_MODULE,
144     .llseek = globalmem_llseek,
145     .read = globalmem_read,
146     .write = globalmem_write,
147     .ioctl = globalmem_ioctl,
148     .open = globalmem_open,
149     .release = globalmem_release,
150 };
151
152 /*globalmem 设备驱动模块加载函数*/
153 static int globalmem_init(void){
154     int result,err;
155     dev_t devno = MKDEV(globalmem_major, 0);
156         /* 申请字符设备驱动区域*/
157     if (globalmem_major){
158         result = register_chrdev_region(devno, 1, "globalmem");
159     }else{
160         /* 动态获得主设备号 */
161         result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
162         globalmem_major = MAJOR(devno);
163     }
164     if (result < 0)
165         return result;
166     printk("request major %d,minor %d\n",globalmem_major,MINOR(devno));
167
168     /*动态申请设备结构体的内存*/
169     globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
170     if(!globalmem_devp){    /* 申请失败 */
171         result = -ENOMEM;
172         goto fail_malloc;
173     }
174     memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
175
176     /*初始化设备结构体*/
177     cdev_init(&globalmem_devp->cdev, &globalmem_fops);
178     globalmem_devp->cdev.owner = THIS_MODULE;
179     globalmem_devp->cdev.ops = &globalmem_fops;
180     /* 注册cdev */
181     err = cdev_add(&globalmem_devp->cdev, devno, 1);
182     if (err)
183         printk(KERN_NOTICE "Error %d adding globalmem", err);
184
185     //自动创建设备节点
186     cls = class_create(THIS_MODULE,"globalmem");
187     device_create(cls, NULL, devno,NULL,"globalmem");
188
189     return 0;
190 fail_malloc: unregister_chrdev_region(devno,1);
191     return result;
192 }
193 /*globalmem 设备驱动模块卸载函数*/
194 static void globalmem_exit(void)
195 {
196     dev_t devno = MKDEV(globalmem_major, 0);
197
198     device_destroy(cls,devno);
199     class_destroy(cls);
200     cdev_del(&globalmem_devp->cdev); /*删除 cdev 结构*/
201     kfree(globalmem_devp);    /* 释放设备结构体内存 */
202     unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);/*注销设备*/
203 }
204
205 module_init(globalmem_init);
206 module_exit(globalmem_exit);
207 MODULE_LICENSE("GPL");

globalmem.c

测试:

[email protected] /mnt/nfs/module/47_globalmem# insmod globalmem.ko
request major 249,minor 0
[email protected]-iMX257 /mnt/nfs/module/47_globalmem# ls /dev/globalmem
/dev/globalmem
[email protected]-iMX257 /mnt/nfs/module/47_globalmem# echo "hello" > /dev/globalmem
written 6 bytes(s) from 0
[email protected]-iMX257 /mnt/nfs/module/47_globalmem# cat /dev/globalmem
read 4096 bytes(s) from 0
hello

4.[改进]支持两个globalmem设备的驱动

附程序:

  1 #include <linux/module.h>
  2 #include <linux/types.h>
  3 #include <linux/fs.h>
  4 #include <linux/kernel.h>
  5 #include <linux/device.h>
  6 #include <linux/errno.h>
  7 #include <linux/mm.h>
  8 #include <linux/sched.h>
  9 #include <linux/init.h>
 10 #include <linux/cdev.h>
 11 #include <asm/io.h>
 12 #include <asm/system.h>
 13 #include <asm/uaccess.h>
 14
 15 #define GLOBALMEM_SIZE    0x1000    /*全局内存大小:4KB*/
 16 #define MEM_CLEAR         0x1     /*清零全局内存*/
 17 #define GLOBALMEM_MAJOR 0        /*预设的 globalmem 的主设备号*/
 18
 19
 20 static int globalmem_major = GLOBALMEM_MAJOR;
 21 /*globalmem 设备结构体*/
 22 struct globalmem_dev
 23 {
 24     struct cdev cdev;                     /*cdev 结构体*/
 25     unsigned char mem[GLOBALMEM_SIZE];     /*全局内存*/
 26 };
 27
 28 struct globalmem_dev *globalmem_devp; /*设备结构体实例*/
 29
 30 //自动添加设备节点
 31 static struct class *cls = NULL;
 32
 33
 34 /*文件打开函数*/
 35 static int globalmem_open(struct inode *inode, struct file *filp){
 36     /*将设备结构体指针赋值给文件私有数据指针*/
 37     filp->private_data = globalmem_devp;
 38     return 0;
 39 }
 40 //释放函数
 41 static int globalmem_release(struct inode *inode, struct file *filp){
 42     return 0;
 43 }
 44 //文件读函数
 45 static ssize_t globalmem_read(struct file *filp, char __user *buf,size_t count,loff_t *ppos)
 46 {
 47     unsigned long p = *ppos;
 48     int ret = 0;
 49     struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
 50     /*分析和获取有效的读长度*/
 51     if (p >= GLOBALMEM_SIZE) //要读的偏移位置越界
 52         return count ? - ENXIO: 0;
 53     if (count > GLOBALMEM_SIZE - p)//要读的字节数太大
 54         count = GLOBALMEM_SIZE - p;
 55     /*内核空间→用户空间*/
 56     if (copy_to_user(buf, (void*)(dev->mem + p), count))
 57     {
 58         ret = - EFAULT;
 59     }else{
 60         *ppos += count;
 61         ret = count;
 62         printk(KERN_INFO "read %d bytes(s) from %ld\n", count, p);
 63     }
 64     return ret;
 65 }
 66 //文件写函数
 67 static ssize_t globalmem_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
 68 {
 69     unsigned long p = *ppos;
 70     int ret = 0;
 71     struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
 72     /*分析和获取有效的写长度*/
 73     if (p >= GLOBALMEM_SIZE)     //要写的偏移位置越界
 74         return count ? - ENXIO: 0;
 75     if (count > GLOBALMEM_SIZE - p) //要写的字节数太多
 76         count = GLOBALMEM_SIZE - p;
 77
 78     /*用户空间→内核空间*/
 79     if (copy_from_user(dev->mem + p, buf, count)){
 80         ret = - EFAULT;
 81     }else{
 82         *ppos += count;
 83         ret = count;
 84         printk(KERN_INFO "written %d bytes(s) from %ld\n", count, p);
 85     }
 86     return ret;
 87 }
 88 //文件定位函数
 89 static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
 90 {
 91     loff_t ret;
 92     switch (orig){
 93         case 0:   /*从文件开头开始偏移*/
 94             if (offset < 0){
 95                 ret = - EINVAL;
 96                 break;
 97             }
 98             if ((unsigned int)offset > GLOBALMEM_SIZE) //偏移越界
 99             {
100                 ret = - EINVAL;
101                 break;
102             }
103             filp->f_pos = (unsigned int)offset;
104             ret = filp->f_pos;
105             break;
106
107         case 1:  /*从当前位置开始偏移*/
108             if ((filp->f_pos + offset) > GLOBALMEM_SIZE) //偏移越界
109             {
110                 ret = - EINVAL;
111                 break;
112             }
113             if ((filp->f_pos + offset) < 0)
114             {
115                 ret = - EINVAL;
116                 break;
117             }
118             filp->f_pos += offset;
119             ret = filp->f_pos;
120             break;
121         default:  ret = - EINVAL; break;
122     }
123     return ret;
124 }
125 //
126 static int globalmem_ioctl(struct inode *inodep, struct file *filp,unsigned int cmd, unsigned long arg)
127 {
128     struct globalmem_dev *dev = filp->private_data;
129     switch (cmd){
130         case MEM_CLEAR:        //清除全局内存
131             memset(dev->mem, 0, GLOBALMEM_SIZE);
132             printk(KERN_INFO "globalmem is set to zero\n");
133             break;
134         default:
135             return - EINVAL; //其他不支持的命令
136             break;
137     }
138     return 0;
139 }
140 //定义file_operatetiont
141 static const struct file_operations globalmem_fops =
142 {
143     .owner = THIS_MODULE,
144     .llseek = globalmem_llseek,
145     .read = globalmem_read,
146     .write = globalmem_write,
147     .ioctl = globalmem_ioctl,
148     .open = globalmem_open,
149     .release = globalmem_release,
150 };
151
152 /*globalmem 设备驱动模块加载函数*/
153 static int globalmem_init(void){
154     int result,err;
155     dev_t devno = MKDEV(globalmem_major, 0);
156         /* 申请字符设备驱动区域*/
157     if (globalmem_major){
158         result = register_chrdev_region(devno, 2, "globalmem");
159     }else{
160         /* 动态获得主设备号 */
161         result = alloc_chrdev_region(&devno, 0, 2, "globalmem");
162         globalmem_major = MAJOR(devno);
163     }
164     if (result < 0)
165         return result;
166     printk("request major %d,minor %d\n",globalmem_major,MINOR(devno));
167
168     /*动态申请设备结构体的内存*/
169     globalmem_devp = kmalloc(2 * sizeof(struct globalmem_dev), GFP_KERNEL);
170     if(!globalmem_devp){    /* 申请失败 */
171         result = -ENOMEM;
172         goto fail_malloc;
173     }
174     memset(globalmem_devp, 0, 2 * sizeof(struct globalmem_dev));
175
176     /*初始化设备结构体*/
177     cdev_init(&globalmem_devp[0].cdev, &globalmem_fops);
178     globalmem_devp[0].cdev.owner = THIS_MODULE;
179     globalmem_devp[0].cdev.ops = &globalmem_fops;
180     /* 注册cdev */
181     err = cdev_add(&globalmem_devp[0].cdev, devno, 1);
182     if (err)
183         printk(KERN_NOTICE "Error %d adding globalmem", err);
184     /*初始化设备结构体*/
185     cdev_init(&globalmem_devp[1].cdev, &globalmem_fops);
186     globalmem_devp[1].cdev.owner = THIS_MODULE;
187     globalmem_devp[1].cdev.ops = &globalmem_fops;
188     /* 注册cdev */
189     err = cdev_add(&globalmem_devp[1].cdev, devno, 1);
190     if (err)
191         printk(KERN_NOTICE "Error %d adding globalmem", err);
192
193     //自动创建设备节点
194     cls = class_create(THIS_MODULE,"globalmem");
195     device_create(cls, NULL, MKDEV(globalmem_major,0),NULL,"globalmem0");
196     device_create(cls, NULL, MKDEV(globalmem_major,1),NULL,"globalmem1");
197
198     return 0;
199 fail_malloc: unregister_chrdev_region(devno,1);
200     return result;
201 }
202 /*globalmem 设备驱动模块卸载函数*/
203 static void globalmem_exit(void)
204 {
205     device_destroy(cls,MKDEV(globalmem_major, 0));
206     device_destroy(cls,MKDEV(globalmem_major, 1));
207     class_destroy(cls);
208     cdev_del(&globalmem_devp->cdev); /*删除 cdev 结构*/
209     kfree(globalmem_devp);    /* 释放设备结构体内存 */
210     unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);/*注销设备*/
211 }
212
213 module_init(globalmem_init);
214 module_exit(globalmem_exit);
215 MODULE_LICENSE("GPL");

globalmem2.c

测试:

[email protected] /mnt/nfs/module/47_globalmem# insmod globalmem.ko
request major 247,minor 0
[email protected]-iMX257 /mnt/nfs/module/47_globalmem# ls /dev/globalmem*
/dev/globalmem0  /dev/globalmem1
[email protected]-iMX257 /mnt/nfs/module/47_globalmem#
[email protected]-iMX257 /mnt/nfs/module/47_globalmem# echo "hello" > /dev/globalmem0
written 6 bytes(s) from 0
[email protected]-iMX257 /mnt/nfs/module/47_globalmem# cat /dev/globalmem0
read 4096 bytes(s) from 0
hello
时间: 2024-10-10 14:18:13

20150518 字符设备驱动的相关文章

20150518 Linux设备驱动中的并发控制

20150518 Linux设备驱动中的并发控制 2015-05-18 Lover雪儿 总结一下并发控制的相关知识: 本文参考:华清远见<Linux 设备驱动开发详解>—第7章 Linux 设备驱动中的并发控制,更多详细内容请看原书 一.并发与竞态 并发(concurrency)指的是多个执行单元同时.并行被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量.静态变量等)的访问则很容易导致竞态(race conditions). 在 Linux 内核中,主要的竞态发生于如下几种情况:

register_chrdev_region/alloc_chrdev_region和cdev注册字符设备驱动

内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region().alloc_chrdev_region() 和 register_chrdev(). (1)register_chrdev  比较老的内核注册的形式   早期的驱动(2)register_chrdev_region/alloc_chrdev_region + cdev  新的驱动形式 (3)register_chrdev()函数是老版本里面的设备号注册函数,可以实现静态和动态注册两种方法

linux字符设备驱动

一.字符设备.字符设备驱动与用户空间访问该设备的程序三者之间的关系. 如图,在Linux内核中使用cdev结构体来描述字符设备,通过其成员dev_t来定义设备号(分为主.次设备号)以确定字符设备的唯一性.通过其成员file_operations来定义字符设备驱动提供给VFS的接口函数,如常见的open().read().write()等. 在Linux字符设备驱动中,模块加载函数通过register_chrdev_region( ) 或alloc_chrdev_region( )来静态或者动态获

linux 字符设备驱动开发详解

一.设备的分类及特点 1.字符设备 字符设备是面向数据流的设备,没有请求缓冲区,对设备的存取只能按顺序按字节的存取而不能随机访问.    Linux下的大多设备都是字符设备.应用程序是通过字符设备节点来访问字符设备的.通常至少需要实现 open, close, read, 和 write 等系统调用.    设备节点一般都由mknod命令都创建在/dev目录下,包含了设备的类型.主/次设备号以及设备的访问权限控制等,如:crw-rw----  1 root  root 4, 64 Feb 18

linux设备驱动第三篇:写一个简单的字符设备驱动

在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存. 下面就开始学习如何写一个简单的字符设备驱动.首先我们来分解一下字符设备驱动都有那些结构或者方法组成,也就是说实现一个可以使用的字符设备驱动我们必须做些什么工作. 1.主设备号和次设备号 对于字符设备的访问是通过文件系统中的设备名称进行的.他们通常位于/dev目录下.如下: [plain] vie

13、字符设备驱动的使用

编译和安装驱动 下面是通过一个例子来学会使用驱动程序: 1---驱动程序: Memdev.c #include <linux/module.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/cdev.h> #include <asm/uaccess.h> int dev1_registers[5]; int dev2_registers[5]; struct cdev

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

在上一篇中我们已经了解了字符设备驱动的原理,也了解了应用层调用内核函数的机制,但是我们每次操作设备,都必须首先通过mknod命令创建一个设备文件名,比如说我们要打开u盘,硬盘等这些设备,难道我们还要自己创建,就如同刘老师常说的一句话,这也太山寨了吧,所以我们今天我们来点比较专业的,让函数帮我们自动创建: 在Linux 下,设备和驱动通常都需要挂接在一种总线上,总线有PCI.USB.I2C.SPI 等等,总线是处理器和设备之间的通道,在设备模型中,所有的设备都通过总线相连,一总线来管理设备和驱动函

字符设备驱动模型

1.设备描述结构cdev驱动模型种类繁多,这就需要我从众多的模型中提取出他们的一些共性:a.驱动初始化a.1 分配设备描述结构a.2 初始化设备描述结构a.3 注册设备描述结构a.4 硬件初始化b.实现设备操作c.驱动注销 ------------------------------------------------------------------ 设备描述结构:在任何一种驱动模型中,设备都会用的内核中的一种结构来描述,我们的字符设备在内核中使用struct cdev 来来描述.struc

Linux字符设备驱动剖析

一.先看看设备应用程序 1.很简单,open设备文件,read.write.ioctl,最后close退出.如下: intmain(int argc ,char *argv[]){ unsigned char val[1] = 1; int fd =open("/dev/LED",O_RDWR);//打开设备 write(fd,val,1);//写入设备,这里代表LED全亮 close(fd);//关闭设备 return 0; } 二./dev目录与文件系统 2./dev是根文件系统下