linux采用模块方法,添加一个新的设备
该文转载自:http://rangercyh.blog.51cto.com/1444712/521244
系统调用是操作系统内核和应用程序之间的接口,而设备驱动程序是操作系统内核和机器硬件之间的接口。
设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件, 应用程序可以像操作普通文件一样对硬件设备进行操作。
设备驱动程序是内核的一部分,它完成以下的功能:
(1) 对设备初始化和释放.
(2) 把数据从内核传送到硬件和从硬件读取数据.
(3) 读取应用程序传送给设备文件的数据和回送应用程序请求的数据.
(4) 检测和处理设备出现的错误.
Linux支持三中不同类型的设备:字符设备(character devices)、块设备(block devices)和网络设备(network interfaces)。
字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了。
块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待.
用户进程是通过设备文件来与实际的硬件打交道,每个设备文件都都有其文件属性(c/b),表示是字符设备还是块设备。
另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,
比如有两个软盘,就可以用从设备号来区分他们.设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序.。
设备驱动程序工作的基本原理:
用户进程利用系统调用对设备进行诸如read/write操作,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。
最后,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作。
如果你的驱动程序陷入死循环,你只有重新启动机器了。
下面我们就来添加一个字符设备:
- 编写设备驱动源代码
在设备驱动程序中有一个非常重要的结构file_operations,该结构的每个域都对应着一个系统调用。
用户进程利用系统调用对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。
- struct file_operations {
- int (*seek) (struct inode * ,struct file *, off_t ,int);
- int (*read) (struct inode * ,struct file *, char ,int);
- int (*write) (struct inode * ,struct file *, off_t ,int);
- int (*readdir) (struct inode * ,struct file *, struct dirent * ,int);
- int (*select) (struct inode * ,struct file *, int ,select_table *);
- int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long);
- int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);
- int (*open) (struct inode * ,struct file *);
- int (*release) (struct inode * ,struct file *);
- int (*fsync) (struct inode * ,struct file *);
- int (*fasync) (struct inode * ,struct file *,int);
- int (*check_media_change) (struct inode * ,struct file *);
- int (*revalidate) (dev_t dev);
- }
编写设备驱动程序的主要工作是编写子函数,并填充file_operations的各个域。
例如:
- Struct file_operations my_fops={
- .read=my_read,
- .write=my_write,
- .open=my_open,
- .release=my_release
- }
然后再定义函数my_read,my_write,my_open,my_release相应的函数体。
例如:
- static ssize_t my_open(struct inode *inode,struct file *file){
- static int counter=0;
- if(Device_Open)
- return -EBUSY;
- Device_Open++;
- /*写入设备的信息*/
- sprintf(msg,"the device has been called %d times\n",counter++);
- msg_ptr=msg;
- return 0;
- }
同时对于可卸载的内核模块(LKM),至少还有两个基本的模块:
例如本例中的:
- static int __init my_init(void){
- int result;
- result=register_chrdev(0,"sky_driver",&my_fops);
- if(result<0){
- printk("error:can not register the device\n");
- return -1;
- }
- if(my_major==0){
- my_major=result;
- printk("<1>hehe,the device has been registered!\n");
- printk("<1>the virtual device was assigned major number %d.\n",my_major);
- printk("<1>To talk to the driver,create a dev file with\n");
- printk("<1>'mknod/dev/my c %d 0'\n",my_major);
- printk("<1>Remove the dev and the file when done\n");
- }
- return 0;
- }
- static void __exit my_exit(void){
- unregister_chrdev(my_major,"sky_driver");
- printk("<1>unloading the device\n");
- }
my_init 用于注册设备,获得设备的主设备号
调用register_chrdev(0,“sky_driver(设备名)”,&my_fops);
my_exit 用于注销设备
调用unregister_chrdev(my_major, “sky_driver(设备名)”);
然后在程序尾再调用这两个函数
Module_init(my_init);
Module_exit(my_exit)
MODULE_LICENSE(“GPL”);
编写自己的驱动程序源文件mydriver.c:
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/fs.h>
- #include <linux/init.h>
- #include <linux/uaccess.h>
- #if CONFIG_MODVERSIONS == 1
- #define MODVERSIONS
- #include <linux/version.h>
- #endif
- #define DEVICE_NUM 0 //随机产生一个设备号
- static int device_num = 0; //用来保存创建成功后的设备号
- static char buffer[1024] = "mydriver"; //数据缓冲区
- static int open_nr = 0; //打开设备的进程数,用于内核的互斥
- //函数声明
- static int mydriver_open(struct inode *inode, struct file *filp);
- static int mydriver_release(struct inode *inode, struct file* filp);
- static ssize_t mydriver_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos);
- static ssize_t mydriver_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos);
- //填充file_operations结构相关入口
- static struct file_operations mydriver_fops = {
- .read = mydriver_read,
- .write = mydriver_write,
- .open = mydriver_open,
- .release = mydriver_release,
- };
- //打开函数
- static int mydriver_open(struct inode *inode, struct file *filp)
- {
- printk("\nMain device is %d, and the slave device is %d\n", MAJOR(inode->i_rdev), MINOR(inode->i_rdev));
- if (open_nr == 0) {
- open_nr++;
- try_module_get(THIS_MODULE);
- return 0;
- }
- else {
- printk(KERN_ALERT "Another process open the char device.\n");//进程挂起
- return -1;
- }
- }
- //读函数
- static ssize_t mydriver_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos)
- {
- //if (buf == NULL) return 0;
- if (copy_to_user(buf, buffer, sizeof(buffer))) //读缓冲
- {
- return -1;
- }
- return sizeof(buffer);
- }
- //写函数,将用户的输入字符串写入
- static ssize_t mydriver_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos)
- {
- //if (buf == NULL) return 0;
- if (copy_from_user(buffer, buf, sizeof(buffer))) //写缓冲
- {
- return -1;
- }
- return sizeof(buffer);
- }
- //释放设备函数
- static int mydriver_release(struct inode *inode, struct file* filp)
- {
- open_nr--; //进程数减1
- printk("The device is released!\n");
- module_put(THIS_MODULE);
- return 0;
- }
- //注册设备函数
- static int __init mydriver_init(void)
- {
- int result;
- printk(KERN_ALERT "Begin to init Char Device!"); //注册设备
- //向系统的字符登记表登记一个字符设备
- result = register_chrdev(DEVICE_NUM, "mydriver", &mydriver_fops);
- if (result < 0) {
- printk(KERN_WARNING "mydriver: register failure\n");
- return -1;
- }
- else {
- printk("mydriver: register success!\n");
- device_num = result;
- return 0;
- }
- }
- //注销设备函数
- static void __exit mydriver_exit(void)
- {
- printk(KERN_ALERT "Unloading...\n");
- unregister_chrdev(device_num, "mydriver"); //注销设备
- printk("unregister success!\n");
- }
- //模块宏定义
- module_init(mydriver_init);
- module_exit(mydriver_exit);
- MODULE_LICENSE("GPL");
- 编译该设备驱动代码
然后将设备驱动源文件复制到/usr/src/linux/drivers/misc下

修改misc目录下的Makefile文件,只要在最后添加一句即可:obj-m +=mydriver.o。

在/usr/src/linux/drivers/misc路径下执行命令:Make -C /usr/src/linux SUBDIRS=$PWD modules编译成功将得到mydriver.ko文件。

可以在misc目录下观察得到了mydriver.ko文件。

继续执行insmod ./mydriver.ko命令挂载内核中的模块。
然后通过lsmod命令可以看到增加的设备模块mydriver。

输入cat /var/log/messages可以看到设备注册成功。

此时进入/proc/devices文件会看到在字符设备中有250 mydriver。前面的是系统分配的主设备号,后面是设备注册名。
进入在/dev路径下,执行命令:
mknod /dev/mydriver c 250 0
第一个参数是新建设备文件的地址和名字。
第二个参数是指创建的是字符设备文件。
第三个参数是主设备号,第四个参数是从设备号,自己随便取。
执行成功会在/dev/char中看到一个新的设备文件mydriver

至此设备添加成功。
- 编译测试程序。
编写测试代码如下:
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <string.h>
- #include <stdio.h>
- #include <fcntl.h>
- int main(void)
- {
- int fd;
- char buf[1024];
- char get[1024];
- memset(get, 0, sizeof(get));
- memset(buf, 0, sizeof(buf));
- printf("please enter a string you want input to mydriver:\n");
- gets(get);
- fd = open("/dev/mydriver", O_RDWR, S_IRUSR|S_IWUSR);//打开设备
- if (fd > 0) {
- read(fd, &buf, sizeof(buf));
- printf("The message in mydriver now is: %s\n", buf);
- //将输入写入设备
- write(fd, &get, sizeof(get));
- //读出设备的信息并打印
- read(fd, &buf, sizeof(buf));
- printf("The message changed to: %s\n", buf);
- sleep(1);
- }
- else {
- printf("OMG...");
- return -1;
- }
- close(fd);//释放设备
- return 0;
- }
gcc -o mydriver_test mydriver_test.c
./mydriver_test

输入任意字符串,驱动程序将字符串拷贝进新加入的设备,然后再读取出来,设备中保留字符串信息,再次输入将覆盖原来的信息
linux采用模块方法,添加一个新的设备的更多相关文章
- Linux 在添加一个新账号后却没有权限怎么办
当添加一个新账号后,我们可能会发现新账号sudo 时会报告不在sudoers中,使用su -s时输入密码后也会认证失败 上网搜索大部分都要求修改/etc/sudoers中的内容,但修改这个文件必须需要 ...
- Flink资料(6) -- 如何添加一个新的Operator
false false false false EN-US ZH-CN X-NONE /* Style Definitions */ table.MsoNormalTable {mso-style-n ...
- 012.Adding a New Field --【添加一个新字段】
Adding a New Field 添加一个新字段 2016-10-14 3 分钟阅读时长 作者 By Rick Anderson In this section you'll use Entity ...
- Mysql学习(一)添加一个新的用户并用golang操作Mysql
Mysql添加一个新的用户并赋予权限 添加一个自己的用户到mysql 首先我们需要先用root用户登录mysql,但是刚安装完没有密码,我们先跳过密码 ailumiyana@ailumiyana:~/ ...
- RK平台Android4.4 添加一个新的遥控器支持以及添加特殊按键【转】
本文转载自:http://blog.csdn.net/coding__madman/article/details/52904063 版权声明:本文为博主原创文章,未经博主允许不得转载. 瑞芯微平台 ...
- 【IntelliJ IDEA】添加一个新的tomcat,tomcat启动无法访问欢迎页面,空白页,404
===================================第一部分,添加一个tomcat================================================== ...
- JS对象 数组连接 concat() 方法用于连接两个或多个数组。此方法返回一个新数组,不改变原来的数组。 语法 arrayObject.concat(array1,array2,.arrayN)
concat() 方法用于连接两个或多个数组.此方法返回一个新数组,不改变原来的数组. 语法 arrayObject.concat(array1,array2,...,arrayN) 参数说明: 注意 ...
- destoon 添加一个新的模块
根目录rename,中config.inc.php文件/module/rename下两个文件,my.inc.php ,rename.class.php/module/rename/admin/三个文件 ...
- 使用layui 做后台管理界面,在Tab中的链接点击后添加一个新TAB的解决方法
给链接或按钮 添加 onclick="self.parent.addTab('百度','http://www.baidu.com','icon-add')" 如: <a h ...
随机推荐
- VisualBox会造成VPN连接不上问题
今天连接VPN查找点资料,但是怎么也连接不上,一直出现651错误,开始以为是服务器配置原因,重新配置后发现问题依旧,后查找相关资料,发现有提到VisualPC, 感觉VisualBox跟VisualP ...
- Yii2 TimestampBehavior行为
<?php /** * @link http://www.yiiframework.com/ * @copyright Copyright (c) 2008 Yii Software LLC * ...
- .NET托管代码和非托管代码
.net托管代码是运行在.NET FRAMEWORK上的,类似于JAVA虚拟机托管代码:属安全代码,因为不涉及指针,但相对性能上较低,C#写出来的东西就可以认为是托管代码非托管代码:非安全的,可以使用 ...
- 【转】浅谈HTTP中Get与Post的区别
转自:http://www.cnblogs.com/hyddd Http定义了与服务器交互的不同方法,最基本的方法有4种,分别是GET,POST,PUT,DELETE.URL全称是资源描述符,我们可以 ...
- cocos2dx jsb 在IOS与安卓下的一些不同之处
cocos2dx版本 2.1.4 1:字体大小 cocosbuilder publish后应该注意IOS和安卓目录下的字体文件的规格是否齐全: 2:cc.LabelTTF 在ios下,cc.Label ...
- I'm back
亲爱的博友们, 请忽略这一条, 这只是我个人的一个记录.
- HDU2001java
import java.util.*;import java.text.DecimalFormat;class Main{public static void main(String args[]){ ...
- KafkaSpout 浅析
最近在使用storm做一个实时计算的项目,Spout需要从 KAFKA 集群中读取数据,为了提高开发效率,直接使用了Storm提供的KAFKA插件.今天抽空看了一下KafkaSpout的源码,记录下心 ...
- Linux常用系统调用
转载 http://www.ibm.com/developerworks/cn/linux/kernel/syscall/part1/appendix.html#icomments 按照惯例,这个列表 ...
- 简单的实现QQ通信功能(二)
第二部分:功能需求以及大体思路 一:功能需求: 1. 角色:登录用户. 2. 登录: (1)检查用户名和密码是否正确,正确登录成功,否则提醒用户名或密码错误. (2)登录时可以选择登录状态,送入数据库 ...