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 <baohua@kernel.org>");
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運行。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM