一、字符设备结构体
字符设备驱动、块设备驱动和网络设备驱动作为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运行。