Linux字符设备驱动编写和测试

一、字符设备结构体

  字符设备驱动、块设备驱动和网络设备驱动作为linux内核三大驱动设备,字符设备主要完成字节的读写操作,常见的应用有鼠标、键盘等,结构体形式如下所示:

 1 struct cdev{
 3   struct kobject kobj;
 5   struct module *owner;//所说模块
 7   struct file_operations *ops;//字符设备操作方法
 9   struct list_head list;
11   dev_t dev;     //设备
13   unsigned int count;
15 }

  ①、cdev结构体中的dev_t表示32位的设备号,12位为主设备号,20位为次设备号,可通过宏定义MAJOR(dev_t dev)和MINOR(dev_t dev)从dev_t中获得主设备号和次设备号。此外,还可以使用宏定义MKDEV(int major, int minor)通过主设备号和次设备号生成dev_t。

  ②、Linux内核提供了一组函数对字符设备结构体进行操作,可用于操作cdev结构体。

1 void cdev_init(struct cdev *, struct file_operations *); //用于初始化cdev的成员,并建立cdev和file_operation之间的连接
2 struct cdev *cdev_alloc(void);//用于动态申请一个cdev内存
3 void cdev_put(struct cdev *p);
4 int cdev_add(struct cdev *, dev_t, unsigned);//向系统添加一个cdev,完成字符设备的注册,对cdev_add()的调用通常发生在字符设备驱动模块加载函数中
5 void cdev_del(struct cdev *);//删除一个cdev,完成字符设备的注销,对cdev_del()函数的调用则通常发生在字符设备驱动模块卸载函数中

  ③、调用register_chrdev_region()或alloc_chrdev_region()函数向系统申请设备号,再调用cdev_add()函数向系统注册字符设备。

1 int register_chrdev_region(dev_t from, unsigned count, const char *name); //已知起始设备号
2 int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);//位置起始设备号

  ④、file_operations结构体中的成员函数会在应用程序进行Linux的open()、write()、read()、close()等系统调用时最终被内核调用。

   llseek()可以修改一个文件当前的读写位置,并将新位置返回,出错时返回一个负值。

      read()从设备中读取数据,成功时返回读取的字节数,出错时返回一个负值。与用户空间应用程序中的ssize_t read(int fd,void*buf,size_t count)和size_t fread(void*ptr,size_t size,size_t nmemb,FILE*stream)是对应。
   write()向设备发送数据,成功时返回写入的字节数。如果此函数未被实现,当用户进行write()系统调用时,将得到-EINVAL返回值。与用户空间应用程序中的ssize_t write(int fd,constvoid*buf,size_t count)和size_t fwrite(const void*ptr,size_t size,size_t nmemb,FILE*stream)是对应。
   read()和write()如果返回0,则表示end-of-file(EOF)。
   unlocked_ioctl()提供设备相关控制命令的实现(既不是读操作,也不是写操作),当调用成功时,返回给调用程序一个非负值。它与用户空间应用程序调用的int fcntl(int fd,int cmd,.../*arg*/)和intioctl(int d,int request,...)对应。
   mmap()将设备内存映射到进程的虚拟地址空间中,如果设备驱动未实现此函数,用户进行mmap()系统调用时将获得-ENODEV返回值。这个函数对于帧缓冲等设备特别有意义,帧缓冲被映射到用户空间后,应用程序可以直接访问它而无须在内核和应用间进行内存复制。它与用户空间应用程序中的void*mmap(void*addr,size_t length,int prot,int flags,int fd,off_t offset)函数对应。

  ⑤、字符设备驱动模块加载与卸载函数如下:

    static int __init globalmem_init(void) 
    static void __exit globalmem_exit(void)

    在字符设备驱动模块加载函数中应该实现设备号的申请和cdev的注册,而在卸载函数中应实现设备号的释放和cdev的注销。

二、字符结构体编程

  在内核代码的.../drivers/ 目录下,新建一个globalmem文件夹,并在此目录下新建globalmem.c 和相应的Makefile文件

  globalmem.c程序如下:

  1 #include <linux/module.h>
  2 #include <linux/fs.h>
  3 #include <linux/init.h>
  4 #include <linux/cdev.h>
  5 #include <linux/slab.h>
  6 #include <linux/uaccess.h>
  7
  8 #define GLOBALMEM_SIZE 0x1000
  9 #define MEM_CLEAR 0x1
 10 #define GLOBALMEM_MAJOR 230
 11
 12 static int globalmem_major = GLOBALMEM_MAJOR; //定义主设备号
 13 module_param(globalmem_major, int, S_IRUGO);//模块传参
 14
 15 struct globalmem_dev {   //定义globalmen_dev结构体
 16  struct cdev cdev;//字符结构体
 17  unsigned char mem[GLOBALMEM_SIZE];//使用内存
 18 };
 19
 20 struct globalmem_dev *globalmem_devp;//申明globalmem结构对象
 21
 22
 23 //globalmem设备驱动的读函数
 24 static ssize_t globalmem_read(struct file *filp, char __user * buf, size_t size,
 25  loff_t * ppos)
 26 {
 27  unsigned long p = *ppos;
 28  unsigned int count = size;
 29  int ret = 0;
 30  struct globalmem_dev *dev = filp->private_data;
 31
 32  if (p >= GLOBALMEM_SIZE)
 33  return 0;
 34  if (count > GLOBALMEM_SIZE - p)
 35  count = GLOBALMEM_SIZE - p;
 36  if (copy_to_user(buf, dev->mem + p, count)) {
 37  ret = -EFAULT;
 38  } else {
 39  *ppos += count;
 40  ret = count;
 41  printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
 42  }
 43  return ret;
 44 }
 45 //globalmem设备驱动的写函数
 46 static ssize_t globalmem_write(struct file *filp, const char __user * buf,
 47  size_t size, loff_t * ppos)
 48 {
 49  unsigned long p = *ppos;
 50  unsigned int count = size;
 51  int ret = 0;
 52  struct globalmem_dev *dev = filp->private_data;
 53
 54
 55  if (p >= GLOBALMEM_SIZE)
 56  return 0;
 57  if (count > GLOBALMEM_SIZE - p)
 58  count = GLOBALMEM_SIZE - p;
 59
 60  if (copy_from_user(dev->mem + p, buf, count))
 61  ret = -EFAULT;
 62  else {
 63  *ppos += count;
 64  ret = count;
 65  
 66  printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);
 67  }
 68  return ret;
 69 }
 70 //寻址函数
 71 static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
 72 {
 73  loff_t ret = 0;
 74  switch (orig) {
 75  case 0: /* 从文件开头位置seek */
 76  if (offset< 0) {
 77  ret = -EINVAL;
 78  break;
 79  }
 80  if ((unsigned int)offset > GLOBALMEM_SIZE) {
 81  ret = -EINVAL;
 82  break;
 83  }
 84  filp->f_pos = (unsigned int)offset;
 85  ret = filp->f_pos;
 86  break;
 87  case 1: /* 从文件当前位置开始seek */
 88  if ((filp->f_pos + offset) > GLOBALMEM_SIZE) {
 89  ret = -EINVAL;
 90  break;
 91  }
 92  if ((filp->f_pos + offset) < 0) {
 93  ret = -EINVAL;
 94  break;
 95  }
 96  filp->f_pos += offset;
 97  ret = filp->f_pos;
 98  break;
 99  default:
100  ret = -EINVAL;
101  break;
102  }
103  return ret;
104 }
105
106 static long globalmem_ioctl(struct file *filp, unsigned int cmd,
107  unsigned long arg)
108 {
109  struct globalmem_dev *dev = filp->private_data;
110  switch (cmd) {
111  case MEM_CLEAR:
112  memset(dev->mem, 0, GLOBALMEM_SIZE);
113  printk(KERN_INFO "globalmem is set to zero\n");
114  break;
115  default:
116  return -EINVAL;
117  }
118
119  return 0;
120 }
121 //open函数
122 static int globalmem_open(struct inode *inode, struct file *filp)
123 {
124  filp->private_data = globalmem_devp;
125  return 0;
126 }
127
128 //release函数
129 static int globalmem_release(struct inode *inode, struct file *filp)
130 {
131  return 0;
132 }
133
134 /*定义字符结构体方法*/
135 static const struct file_operations globalmem_fops = {
136  .owner = THIS_MODULE,
137  .llseek = globalmem_llseek,
138  .read = globalmem_read,
139  .write = globalmem_write,
140  .unlocked_ioctl = globalmem_ioctl,
141  .open = globalmem_open,
142  .release = globalmem_release,
143 };
144 //字符设备加载函数
145 static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)  
146 {
147  int err, devno = MKDEV(globalmem_major, index);//获取设备结构体dev_t
148
149
150  cdev_init(&dev->cdev, &globalmem_fops);//初始化字符设备和字符设备处理方法
151  dev->cdev.owner = THIS_MODULE;//初始化字符设备所属模块
152  err = cdev_add(&dev->cdev, devno, 1);//添加一个字符设备
153  if (err)
154  printk(KERN_NOTICE "Error %d adding globalmem%d", err, index);
155
156 }
157
158
159
160 //模块初始化
161 static int __init globalmem_init(void) //初始化模块
162 {
163  int ret;
164  dev_t devno = MKDEV(globalmem_major, 0);//获取字符设备结构体
165  if (globalmem_major)
166  ret = register_chrdev_region(devno, 1, "globalmem");//注册此cdev设备
167  else {
168  ret = alloc_chrdev_region(&devno, 0, 1, "globalmem");//申请字符设备cdev空间
169  globalmem_major = MAJOR(devno);//获取主设备号
170  }
171  if (ret < 0)
172  return ret;
173  globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL);//分配globalmem结构体内存
174  if (!globalmem_devp) {
175  ret = -ENOMEM;
176  goto fail_malloc;    //分配失败择跳转
177  }
178
179 //主次设备的不同
180  globalmem_setup_cdev(globalmem_devp, 0);
181  return 0;
182  fail_malloc:
183  unregister_chrdev_region(devno, 1);
184  return ret;
185 }
186 module_init(globalmem_init);
187
188
189 //模块卸载函数
190 static void __exit globalmem_exit(void)
191 {
192  cdev_del(&globalmem_devp->cdev);
193  kfree(globalmem_devp);
194  unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
195 }
196 module_exit(globalmem_exit);
197
198 MODULE_AUTHOR("Barry Song <[email protected]>");
199 MODULE_LICENSE("GPL v2");

  Makefile程序如下:

 1 KVERS = $(shell uname -r)
 2 # Kernel modules
 3 obj-m += globalmem.o
 4
 5 # Specify flags for the module compilation.
 6 #EXTRA_CFLAGS=-g -O0
 7 build: kernel_modules
 8 kernel_modules:
 9     make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
10 clean:
11     make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean

三、字符设备验证

  ①、在globalmem目录下输入make命令

  ②、以管理员身份插入模块,在globalmem目录下输入insmod  globalmem.ko

  ③、输入字符到此字符设备中,创建设备节点:mknod /dev/globalmem c 230 0   //230 0 为你创建设备的主设备与次设备号;写入字符串:echo "hello world!">/dev/globalmem ;查看输入信息:cat /dev/globalmem;查看读写情况:dmesg -c globalmem

四、测试验证代码

  建立globalmemTest.c测试文件,代码如下:

 1 #include<fcntl.h>
 2 #include<stdio.h>
 3
 4
 5 int main(void)
 6 {
 7     char s[] = "Linux Programmer!\n";
 8     char buffer[80];
 9     int fd=open("/dev/globalmem",O_RDWR);//打开globalmem设备,fd返回大于2的数则成功,O_RDWR为权限赋予
10     write(fd,s,sizeof(s));          //将字符串s写入globalmem字符设备中
11     printf("test write %d %s\n",fd,s );  
12         close(fd);  //关闭设备
13     fd=open("/dev/globalmem",O_RDWR);
14     read(fd,buffer,sizeof(buffer));   //读取globalmem设备中存储的数据
15     printf("test read %d %s\n",fd,buffer);  //输出结果显示
16     return 0;
17
18 }

  输入gcc globalmemTest.c a.out,生成a.out,再输入./a.out运行。

原文地址:https://www.cnblogs.com/chenfeifen/p/11781400.html

时间: 2024-10-14 08:56:06

Linux字符设备驱动编写和测试的相关文章

深入理解Linux字符设备驱动

文章从上层应用访问字符设备驱动开始,一步步地深入分析Linux字符设备的软件层次.组成框架和交互.如何编写驱动.设备文件的创建和mdev原理,对Linux字符设备驱动有全面的讲解. 本文整合之前发表的<Linux字符设备驱动剖析>和<Linux 设备文件的创建和mdev>两篇文章,基于linux字符设备驱动的所有相关知识给读者一个完整的呈现. 一.从最简单的应用程序入手 1.很简单,open设备文件,read.write.ioctl,最后close退出.如下: 二./dev目录与文

(57)Linux驱动开发之三Linux字符设备驱动

1.一般情况下,对每一种设备驱动都会定义一个软件模块,这个工程模块包含.h和.c文件,前者定义该设备驱动的数据结构并声明外部函数,后者进行设备驱动的具体实现. 2.典型的无操作系统下的逻辑开发程序是:这种三层的裸机驱动模型是足够满足低耦合.高内聚的特点的. 3.当有操作系统存在时,设备驱动成为了连接硬件和内核的桥梁,这时候的设备驱动对外表现为操作系统的API,与直接裸机开发不同,裸机开发时的设备驱动是应用工程师的API.如果设备驱动都按照操作系统给出的独立于设备的接口而设计,应用程序将可以使用统

Linux I2C设备驱动编写(一)

在Linux驱动中I2C系统中主要包含以下几个成员: I2C adapter 即I2C适配器 I2C driver 某个I2C设备的设备驱动,可以以driver理解. I2C client 某个I2C设备的设备声明,可以以device理解. I2C adapter 是CPU集成或外接的I2C适配器,用来控制各种I2C从设备,其驱动需要完成对适配器的完整描述,最主要的工作是需要完成i2c_algorithm结构体.这个结构体包含了此I2C控制器的数据传输具体实现,以及对外上报此设备所支持的功能类型

Linux字符设备驱动实现

Linux字符设备驱动实现 要求 编写一个字符设备驱动,并利用对字符设备的同步操作,设计实现一个聊天程序.可以有一个读,一个写进程共享该字符设备,进行聊天:也可以由多个读和多个写进程共享该字符设备,进行聊天 主要过程 实现 字符驱动设备 /* 参考:深入浅出linux设备驱动开发 */ #include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <asm/uacc

linux字符设备驱动

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

Linux I2C设备驱动编写(二)

在(一)中简述了Linux I2C子系统的三个主要成员i2c_adapter.i2c_driver.i2c_client.三者的关系也在上一节进行了描述.应该已经算是对Linux I2C子系统有了初步的了解.下面再对他们之间的关系进行代码层的深入分析,我认为对他们的关系了解的越好,越有助于I2C设备的驱动开发及调试. 带着问题去分析可能会更有帮助吧,通过对(一)的了解后,可能会产生以下的几点疑问: i2c_adapter驱动如何添加? i2c_client与i2c_board_info究竟是什么

Linux字符设备驱动框架

字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标.键盘.显示器.串口等等,当我们执行ls -l /dev的时候,就能看到大量的设备文件,c就是字符设备,b就是块设备,网络设备没有对应的设备文件.编写一个外部模块的字符设备驱动,除了要实现编写一个模块所需要的代码之外,还需要编写作为一个字符设备的代码. 驱动模型 Linux一切皆文件,那么作为一个设备文件,它的操作方法接口封装在struct fi

Linux字符设备驱动注册流程

其中一部分从伯乐在线和网络上摘抄的内容,不用于商业用途. 一.linux系统将设备分为3类:字符设备.块设备.网络设备. 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流的设备,常见的字符设备有鼠标.键盘.串口.控制台和LED设备等. 块设备:是指可以从设备的任意位置读取一定长度数据的设备.块设备包括硬盘.磁盘.U盘和SD卡等. 每一个字符设备或块设备都在/d ev目录下对应一个设备文件.linux用户程序通过设备文件(或

linux字符设备驱动--基本知识介绍

一.设备驱动的分类 1.字符设备 字符设备是指那些能一个字节一个字节读取数据的设备,如LED灯.键盘.鼠标等.字符设备一般需要在驱动层实现open().close().read().write().ioctl()等函数. 2.块设备 块设备与字符设备类似,一般是像磁盘一样的设备.在块设备中还可以容纳文件系统,并存储大量的信息.在linux系统中,进行块设备读写时,每次只能传输一个或者多个块.linux也可以让应用程序像访问字符设备一样访问块设备,一次只读取一个字节. 3.网络设备 网络设备主要负