一、字符設備結構體
字符設備驅動、塊設備驅動和網絡設備驅動作為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運行。