linux 驱动学习(一)简单的字符设备驱动程序

  linux 系统将设备分为三种类型:字符设备、块设备和网络接口设备。

  文章将先给出字符设备驱动程序,会贴出字符设备驱动程序的测试程序并记录测试过程。参照程序记录知识点,可能会不全,以后会慢慢加 。

注释版

 1 #include "linux/kernel.h" //内核头文件,含有一些内核常用函数的原形定义
 2 #include "linux/module.h" //包含大量加载模块需要的函数和符号的定义
 3 #include "linux/fs.h"     //包含了文件操作相关struct的定义,例如大名鼎鼎的struct file_operations
 4 #include "linux/init.h"   //包含了模块的初始化宏定义,及一些其他函数的初始化函数
 5 #include "linux/types.h"  //对一些特殊类型的定义,例如dev_t, off_t, pid_t.其实这些类型大部分都是unsigned int型通过一连串的typedef变过来的,只是为了方便阅读。
 6 #include "linux/errno.h"  //包含了对返回值的宏定义,这样用户程序可以用perror输出错误信息。
 7 #include "linux/uaccess.h"  //包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义。
 8 #include "linux/kdev_t.h"   //知识点一 包含MAJOR(dev)、MINOR(dev) 、MKDEV(ma,mi)、print_dev_t(buffer, dev)、format_dev_t(buffer, dev)函数
 9 #define MAX_SIZE 1024
10 
11 static int my_open(struct inode *inode, struct file *file);//设备打开函数
12 static int my_release(struct inode *inode, struct file *file);//设备释放
13 static ssize_t my_read(struct file *file, char __user *user, size_t t, loff_t *f);//copy_to_user 从设备中获取数据
14 static ssize_t my_write(struct file *file, const char __user *user, size_t t, loff_t *f);//copy_from_user 写数据到设备中
15 
16 static char message[MAX_SIZE] = "-------congratulations--------!";//设备中的字符串
17 static int device_num = 0;//设备号
18 static int counter = 0;//计数用(设备调用次数)
19 static int mutex = 0;//互斥用
20 static char* devName = "myDevice";//设备名
21 
22 struct file_operations pStruct =
23 { open:my_open, release:my_release, read:my_read, write:my_write, };//file_operations结构体,知识点二
24 
25 /* 注册模块 */
26 int init_module()
27 {
28     int ret;
29     /* 函数中第一个参数是告诉系统,新注册的设备的主设备号由系统分配,
30      * 第二个参数是新设备注册时的设备名字,
31      * 第三个参数是指向file_operations的指针,
32      * 当用设备号为0创建时,系统一个可以用的设备号创建模块 */
33     ret = register_chrdev(0, devName, &pStruct);
34     if (ret < 0)
35     {
36         printk("regist failure!\n");
37         return -1;
38     }
39     else
40     {
41         printk("the device has been registered!\n");
42         device_num = ret;
43         printk("<1>the virtual device's major number %d.\n", device_num);
44         printk("<1>Or you can see it by using\n");
45         printk("<1>------more /proc/devices-------\n");
46         printk("<1>To talk to the driver,create a dev file with\n");
47         printk("<1>------'mknod /dev/myDevice c %d 0'-------\n", device_num);
48         printk("<1>Use \"rmmode\" to remove the module\n");
49 
50         return 0;
51     }
52 }
53 /* 注销模块,函数名很特殊 */
54 void cleanup_module()
55 {
56     unregister_chrdev(device_num, devName);
57     printk("unregister it success!\n");
58 }
59 
60 static int my_open(struct inode *inode, struct file *file)
61 {
62         if(mutex)
63                 return -EBUSY;
64         mutex = 1;//上锁
65     printk("<1>main  device : %d\n", MAJOR(inode->i_rdev));
66     printk("<1>slave device : %d\n", MINOR(inode->i_rdev));
67     printk("<1>%d times to call the device\n", ++counter);
68     try_module_get(THIS_MODULE);
69     return 0;
70 }
71 /* 每次使用完后会release */
72 static int my_release(struct inode *inode, struct file *file)
73 {
74     printk("Device released!\n");
75     module_put(THIS_MODULE);
76         mutex = 0;//开锁
77     return 0;
78 }
79 
80 static ssize_t my_read(struct file *file, char __user *user, size_t t, loff_t *f)
81 {
82     if(copy_to_user(user,message,sizeof(message)))
83     {
84         return -EFAULT;
85     }
86     return sizeof(message);
87 }
88 
89 static ssize_t my_write(struct file *file, const char __user *user, size_t t, loff_t *f)
90 {
91     if(copy_from_user(message,user,sizeof(message)))
92     {
93         return -EFAULT;
94     }
95     return sizeof(message);
96 }
View Code

纯净版

 1 #include "linux/kernel.h"
 2 #include "linux/module.h"
 3 #include "linux/fs.h"
 4 #include "linux/init.h"
 5 #include "linux/types.h"
 6 #include "linux/errno.h"
 7 #include "linux/uaccess.h"
 8 #include "linux/kdev_t.h"
 9 #define MAX_SIZE 1024
10 
11 static int my_open(struct inode *inode, struct file *file);
12 static int my_release(struct inode *inode, struct file *file);
13 static ssize_t my_read(struct file *file, char __user *user, size_t t, loff_t *f);
14 static ssize_t my_write(struct file *file, const char __user *user, size_t t, loff_t *f);
15 
16 static char message[MAX_SIZE] = "-------congratulations--------!";
17 static int device_num = 0;//设备号
18 static int counter = 0;//计数用
19 static int mutex = 0;//互斥用
20 static char* devName = "myDevice";//设备名
21 
22 struct file_operations pStruct =
23 { open:my_open, release:my_release, read:my_read, write:my_write, };
24 
25 /* 注册模块 */
26 int init_module()
27 {
28     int ret;
29     /* 函数中第一个参数是告诉系统,新注册的设备的主设备号由系统分配,
30      * 第二个参数是新设备注册时的设备名字,
31      * 第三个参数是指向file_operations的指针,
32      * 当用设备号为0创建时,系统一个可以用的设备号创建模块 */
33     ret = register_chrdev(0, devName, &pStruct);
34     if (ret < 0)
35     {
36         printk("regist failure!\n");
37         return -1;
38     }
39     else
40     {
41         printk("the device has been registered!\n");
42         device_num = ret;
43         printk("<1>the virtual device's major number %d.\n", device_num);
44         printk("<1>Or you can see it by using\n");
45         printk("<1>------more /proc/devices-------\n");
46         printk("<1>To talk to the driver,create a dev file with\n");
47         printk("<1>------'mknod /dev/myDevice c %d 0'-------\n", device_num);
48         printk("<1>Use \"rmmode\" to remove the module\n");
49 
50         return 0;
51     }
52 }
53 /* 注销模块,函数名很特殊 */
54 void cleanup_module()
55 {
56     unregister_chrdev(device_num, devName);
57     printk("unregister it success!\n");
58 }
59 
60 static int my_open(struct inode *inode, struct file *file)
61 {
62         if(mutex)
63                 return -EBUSY;
64         mutex = 1;//上锁
65     printk("<1>main  device : %d\n", MAJOR(inode->i_rdev));
66     printk("<1>slave device : %d\n", MINOR(inode->i_rdev));
67     printk("<1>%d times to call the device\n", ++counter);
68     try_module_get(THIS_MODULE);
69     return 0;
70 }
71 /* 每次使用完后会release */
72 static int my_release(struct inode *inode, struct file *file)
73 {
74     printk("Device released!\n");
75     module_put(THIS_MODULE);
76         mutex = 0;//开锁
77     return 0;
78 }
79 
80 static ssize_t my_read(struct file *file, char __user *user, size_t t, loff_t *f)
81 {
82     if(copy_to_user(user,message,sizeof(message)))
83     {
84         return -EFAULT;
85     }
86     return sizeof(message);
87 }
88 
89 static ssize_t my_write(struct file *file, const char __user *user, size_t t, loff_t *f)
90 {
91     if(copy_from_user(message,user,sizeof(message)))
92     {
93         return -EFAULT;
94     }
95     return sizeof(message);
96 }
View Code

 

字符驱动程序Makefile文件

 

 1 # If KERNELRELEASE is defined, we've been invoked from the
 2 # kernel build system and can use its language.
 3 ifeq ($(KERNELRELEASE),)
 4     # Assume the source tree is where the running kernel was built
 5     # You should set KERNELDIR in the environment if it's elsewhere
 6     KERNELDIR ?= /lib/modules/$(shell uname -r)/build
 7     # The current directory is passed to sub-makes as argument
 8     PWD := $(shell pwd)
 9 modules:
10     $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
11 modules_install:
12     $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
13 clean:
14     rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
15 .PHONY: modules modules_install clean
16 else
17     # called from kernel build system: just declare what our modules are
18     obj-m := devDrv.o
19 endif
View Code

 

 在linux终断输入make后生成devDriver.ko文件。

 

 在linux终断输入sudo insmod devDriver.ko  装载模块

 

在linux终断输入 dmesg  查看模块信息

在linux终断输入 lsmod  查看模块列表

在linux终断输入

cat /proc/devices
Character devices:
...
245 devDriver
...
看到字符设备下出现了devDriver,这是函数add_cdev成功将devDriver加入到了系统中,系统给devDriver分配的主设备号是245。
现在可以确认scull驱动程序已经运行在系统中了,那么怎么操作它呢?
我们首先要建立一个文件节点,使用mknod 命令,mknod 用法是这样的,mknod name type major minor,分别接节点名,类型,b表示块设备,c表示字符设备(scull是字符设备,因此用c),major是主设备号(scull主设备号是249),minor为次设备号。
sudo mknod /dev/devDriver0 c 245 0
ls -l /dev
可以看到,有一个叫devDriver0的设备出现在dev目录下。现在可以对这个字符设备进行读写操作了
 
测试代码
 1 #include <sys/types.h>
 2 #include <sys/stat.h>
 3 #include <stdlib.h>
 4 #include <string.h>
 5 #include <stdio.h>
 6 #include <fcntl.h>
 7 #include <unistd.h>
 8 #define MAX_SIZE 1024
 9 
10 int main(void)
11 {
12     int fd;
13     char buf[MAX_SIZE];
14     char get[MAX_SIZE];
15     char devName[20], dir[50] = "/dev/";
16     system("ls /dev/");
17     printf("Please input the device's name you wanna to use :");
18     gets(devName);
19     strcat(dir, devName);
20     fd = open(dir, O_RDWR | O_NONBLOCK);
21     if (fd != -1)
22     {
23         read(fd, buf, sizeof(buf));
24         printf("The device was inited with a string : %s\n", buf);
25          /* 测试写 */
26         printf("Please input a string  :\n");
27         gets(get);
28         write(fd, get, sizeof(get));
29         /* 测试读 */
30         read(fd, buf, sizeof(buf)); 
31         system("dmesg");
32         printf("\nThe string in the device now is : %s\n", buf);
33         close(fd);
34         return 0;
35     }
36     else
37     {
38         printf("Device open failed\n");
39         return -1;
40     }
41 }
View Code

在linux终断输入 sudo  gcc -o text text.c   编译测试代码

在linux终断输入 text ,按照测试提示步骤完成测试。

 

测试完成后 在linux终断输入 rmmod 卸载驱动。


知识点一 :linux/kdev_t.h 包含的函数

#define MINORBITS       20 //次设备号的占位数目
#define MINORMASK       ((1U << MINORBITS) - 1)//低20位的掩码,相当于0xfffff

#define MAJOR(dev)      ((unsigned int) ((dev) >> MINORBITS)) //得到主设备号
#define MINOR(dev)      ((unsigned int) ((dev) & MINORMASK))  //得到次设备号
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))       //将主,次设备号重新“合成”为一个数,返回

#define print_dev_t(buffer, dev)                    \
    sprintf((buffer), "%u:%u\n", MAJOR(dev), MINOR(dev))    //打印主次设备号

#define format_dev_t(buffer, dev)                       \
    ({                                  \ //把主,次设备号写入到buffer指向的内存中
        sprintf(buffer, "%u:%u", MAJOR(dev), MINOR(dev));       \
        buffer;                         \
    })
View Code


知识点二:file_operations结构体详细分析

整体结构如下:

 1 struct file_operations {
 2         struct module *owner;    //防止模块还在被使用的时候被卸载
 3         loff_t        (*llseek) ();
 4         ssize_t       (*read) ();
 5         ssize_t       (*write) ();
 6         ssize_t       (*aio_read) ();
 7         ssize_t       (*aio_write) ();
 8         int           (*readdir) ();
 9         unsigned int (*poll) ();
10         int           (*ioctl) ();
11         long          (*unlocked_ioctl) ();
12         long          (*compat_ioctl) ();
13         int           (*mmap) ();
14         int           (*open) ();
15         int           (*flush) ();
16         int           (*release) ();
17         int           (*fsync) ();
18         int           (*aio_fsync) ();
19         int           (*fasync) ();
20         int           (*lock) ();
21         ssize_t       (*sendfile) ();
22         ssize_t       (*sendpage) ();
23         unsigned long (*get_unmapped_area) ();
24         int           (*check_flags) ();
25         int           (*dir_notify) ();
26         int           (*flock) ();
27         ssize_t       (*splice_write) ();
28         ssize_t       (*splice_read) ();
29 };
View Code

file_operations结构体作用:Linux使用file_operations结构访问驱动程序的函数,这个结构的每一个成员的名字都对应着一个调用。用户进程利用在对设备文件进行诸如read/write操作的时候,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数,这是Linux的设备驱动程序工作的基本原理。

file_operations结构体常用函数

一般情况下,进行设备驱动程序的设计只是比较注重下面的几个方法:
struct file_operations ***_ops={
.owner = THIS_MODULE,
.llseek = ***_llseek,
.read = ***_read,
.write = ***_write,
.ioctl = ***_ioctl,
.open = ***_open,
.release = ***_release,
};

file_operations结构体常用函数解释

owner:不是一个操作; 它是一个指向拥有这个结构的模块的指针.这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为
THIS_MODULE, 一个在  中定义的宏.这个宏比较复杂,在进行简单学习操作的时候,一般初始化为THIS_MODULE。

 

loff_t (*llseek) (struct file * filp , loff_t p, int orig);
(指针参数filp为进行读取信息的目标文件结构体指针;参数 p 为文件定位的目标偏移量;参数orig为对文件定位
的起始地址,这个值可以为文件开头(SEEK_SET,0,当前位置(SEEK_CUR,1),文件末尾(SEEK_END,2))
llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值.
loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示.
如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器。

ssize_t (*read) (struct file * filp, char __user * buffer, size_t    size , loff_t * p); (指针参数 filp 为进行读取信息的目标文件,指针参数buffer 为对应放置信息的缓冲区(即用户空间内存地址), 参数size为要读取的信息长度,参数 p 为读的位置相对于文件开头的偏移,在读取信息后,这个指针一般都会移动,移动的值为要读取信息的长度值) 这个函数用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).

ssize_t (*aio_read)(struct kiocb * , char __user * buffer, size_t size , loff_t   p); 可以看出,这个函数的第一、三个参数和本结构体中的read()函数的第一、三个参数是不同 的, 异步读写的第三个参数直接传递值,而同步读写的第三个参数传递的是指针,因为AIO从来不需要改变文件的位置。 异步读写的第一个参数为指向kiocb结构体的指针,而同步读写的第一参数为指向file结构体的指针,每一个I/O请求都对应一个kiocb结构体); 初始化一个异步读 -- 可能在函数返回前不结束的读操作.如果这个方法是 NULL, 所有的操作会由 read 代替进行(同步地). (有关linux异步I/O,可以参考有关的资料,《linux设备驱动开发详解》中给出了详细的解答)

ssize_t (*write) (struct file * filp, const char __user *   buffer, size_t count, loff_t * ppos); (参数filp为目标文件结构体指针,buffer为要写入文件的信息缓冲区,count为要写入信息的长度, ppos为当前的偏移位置,这个值通常是用来判断写文件是否越界) 发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数. (注:这个操作和上面的对文件进行读的操作均为阻塞操作)

ssize_t (*aio_write)(struct kiocb *, const char __user * buffer, size_t count, loff_t * ppos);       初始化设备上的一个异步写.参数类型同aio_read()函数;

int (*open) (struct inode * inode , struct file * filp ) ; (inode 为文件节点,这个节点只有一个,无论用户打开多少个文件,都只是对应着一个inode结构; 但是filp就不同,只要打开一个文件,就对应着一个file结构体,file结构体通常用来追踪文件在运行时的状态信息) 尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知. 与open()函数对应的是release()函数。

int (*flush) (struct file *); flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作. 这个必须不要和用户查询请求的 fsync 操作混淆了. 当前, flush 在很少驱动中使用; SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果 flush 为 NULL, 内核简单地忽略用户应用程序的请求.

int (*release) (struct inode *, struct file *); release ()函数当最后一个打开设备的用户进程执行close()系统调用的时候,内核将调用驱动程序release()函数: void release(struct inode inode,struct file *file),release函数的主要任务是清理未结束的输入输出操作,释放资源,用户自定义排他标志的复位等。     在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.

 

知识点三:(未完待续...)

 

转载于:https://www.cnblogs.com/starsKing/p/6098664.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值