设备注册,驱动注册和生成设备节点的区别:
生成设备节点是对上的,为了应用程序可以调用和驱动进行通信。
设备注册,驱动注册是驱动嵌入到内核中。
关于杂项设备
杂项设备(设备号10)
对一部分字符设备的封装,还有一部分不好归类驱动也归到了杂项设备
为什么引入杂项设备
节省主设备号
如果所有的驱动都是用字符设备,那么所有的设备号很快就用完了,总共255个设备号
驱动写起来相对简单
如果直接使用封装好的杂项设备,那么就可以减少一步注册主设备号的过程
1.区分概念:
设备注册:platform_device,查看:/sys/devices/platform
驱动注册:platform_driver
生成设备节点:为了让应用程序可以调用,是"对上"的接口。
与设备注册无关,设备节点名称不需要与设备名称相同。查看:/dev/*
一般将设备节点的注册放在probe中,也可以放到init函数中。
杂项设备,或者说是对一部分字符设备的封装或者一部分不好分类的。
可以节省主设备号,驱动写起来相对简单(用封装好的杂项设备可以减少一步注册主设备号的过程)
Linux设备驱动一般分为:字符设备,块设备,网络设备。也可以按照子系统来分类,如:mm,led等
2.源代码位置
杂项设备初始化源代码:/drivers/char/misc.c,属于内核中强制编译的。
杂项设备注册头文件:include/linux/miscdevice.h,主要的结构体为miscdevice
struct miscdevice {
int minor; //设备号,一般系统分配随机取值
const char *name; //生成设备节点的名称,无限制
const struct file_operations *fops; //指向一个设备节点文件,对文件的操作
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
mode_t mode;
};
extern int misc_register(struct miscdevice * misc); //生成设备节点的函数,一般在probe中被调用
extern int misc_deregister(struct miscdevice *misc);
杂项设备内核文件结构体,设备节点本质是文件一个特殊文件,包括文件名,打开关闭等,文件结构体的头文件为include/linux/fs.h,结构体为file_operations(非常重要)
//相当重要的结构体
/*
* NOTE:
* all file operations except setlease can be called without
* the big kernel lock held in all filesystems.
*/
struct file_operations {
struct module *owner; //必选参数,一般是THIS_MODULE
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
/* remove by cym 20130408 support for MT660.ko */
#if 0
//#ifdef CONFIG_SMM6260_MODEM
#if 1// liang, Pixtree also need to use ioctl interface...
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
#endif
#endif
/* end remove */
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //非必选,对GPIO操作,应用向底层驱动传值的
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *); //必选的参数,打开文件函数
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *); //必选的参数,关闭文件函数
int (*fsync) (struct file *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
/* add by cym 20130408 support for MT6260 and Pixtree */
#if defined(CONFIG_SMM6260_MODEM) || defined(CONFIG_USE_GPIO_AS_I2C)
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); //此处ioctl与上面的不一样
#endif
/* end add */
};
参数很多,根据要求选择,必选的是
- .owner 一般是THIS_MODULE
- .open 打开文件函数
- .release 关闭文件函数
这里在必选之外使用参数 - .unlocked_ioctl对GPIO操作,应用向底层驱动传值
3. 驱动代码案例
在probe_linux_module基础上写devicenode_linux_module驱动,注意函数调用顺序,/dev下查看
devicenode_linux_module.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
//misc
#include <linux/miscdevice.h>
//注册设备节点的文件结构体
#include <linux/fs.h>
#define DRIVER_NAME "hello_ctl"
#define DEVICE_NAME "hello_ctl123"
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("NANZH");
static int hello_open(struct inode *inode, struct file *file){
printk(KERN_EMERG "hello open\n"); //调用步骤4-1
return 0;
}
static int hello_release(struct inode *inode, struct file *file){
printk(KERN_EMERG "hello release\n"); //调用步骤4-2
return 0;
}
static long hello_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
//打开之后操作的,所以参数里面不需要inode
printk(KERN_EMERG "hello ioctl\n"); //调用步骤4-3
printk("cmd is %d, arg is %d\n", cmd, arg);
return 0;
}
//文件设备操作结构体
static struct file_operations hello_ops = {
.owner = THIS_MODULE,
// int (*open) (struct inode *, struct file *);
.open = hello_open, //调用步骤3-1
// int (*release) (struct inode *, struct file *)
.release = hello_release, //调用步骤3-2
// long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
.unlocked_ioctl = hello_ioctl, //调用步骤3-3
};
//杂项设备结构体
static struct miscdevice hello_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &hello_ops, //调用步骤2
};
static int hello_probe(struct platform_device *pdv){
printk(KERN_EMERG "initialized\n");
misc_register(&hello_dev); //一般在probe中调用.调用步骤1
return 0;
}
static int hello_remove(struct platform_device *pdv){
printk(KERN_EMERG "remove\n");
misc_deregister(&hello_dev);
return 0;
}
static void hello_shutdown(struct platform_device *pdv){
;
}
static int hello_suspend(struct platform_device *pdv, pm_message_t pmt){
return 0;
}
static int hello_resume(struct platform_device *pdev){
return 0;
}
struct platform_driver hello_driver = {
.probe = hello_probe,
.remove = hello_remove,
.shutdown = hello_shutdown,
.suspend = hello_suspend,
.resume = hello_resume,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
}
};
static int hello_init(void)
{
int DriverState;
printk(KERN_EMERG "hello world enter!\n");
DriverState=platform_driver_register(&hello_driver);
printk(KERN_EMERG "DriverState is %d\n", DriverState);
return 0;
}
static void hello_exit(void)
{
printk(KERN_EMERG "hello world exit!\n");
platform_driver_unregister(&hello_driver); //unregister会查找release,如果找不到会报错
}
module_init(hello_init);
module_exit(hello_exit);
代码分析
接下来我们对代码进行简要的分析,下面的部分和我们前面注册驱动的代码是基本相同的,我们在hello_probe (也就是驱动的初始化函数)中调用了misc_register()来注册杂项设备节点,杂项设备节点就像是一个挂载在设备上的设备一样,它的本质也是一个设备,所以说如果注册成功我们应该可以在/dev/中查看到我们注册的设备hello_ctl_dev。我们在hello_remove中调用了misc_deregister()来卸载杂项设备节点,说明我们rmmod这个模块后/dev/中的hello_ctl_dev就不存在了
Makefile
obj-m += devicenode_linux_module.o
KDIR := /home/nan/iTOP4412/iTop4412_Kernel_3.0
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -rf *.o
测试结果:
# 板子上:
root@iTOP4412-ubuntu-desktop:# insmod devicenode_linux_module.ko
[ 112.202074] hello world enter!
[ 112.203980] initialized
[ 112.236250] DriverState is 0
查看生成的设备节点:
root@iTOP4412-ubuntu-desktop:# ls -l /dev/hello_ctl123
crw------- 1 root root 10, 46 Nov 22 05:55 /dev/hello_ctl123
root@iTOP4412-ubuntu-desktop:# rmmod devicenode_linux_module
[ 136.243713] hello world exit!
[ 136.246474] remove
以上
编写简单应用调用驱动
基本原理
在前面我们生成了设备节点,而且也给设备节点注册了内核文件结构体,同时在内核文件结构体中我们也注册了open、close、ioctl的函数,我们做这些的目的是什么呢,当然是将这些函数供给上层的应用使用,这样才能完成我们驱动开发的使命。
当然,Linux一切皆文件的准则大大的方便了我们的操作,我们只要把/dev/中的设备节点作为一个文件进行操作那么就可以调用我们的驱动, Linux系统调用中的文件操作被映射为我们所写的驱动函数(这个过程还不是特别的了解),我们调用系统调用的函数,实质上执行了我们驱动中的函数。
继编写简单的应用来调用设备节点:
根据上面生成的/dev/hello_ctl123节点来调用,基于应用层的,所以使用基本的C文件形式。
4. 函数头文件
# include <stdio.h> //printf
# include <sys/types.h> //基本系统数据类型,依据编译环境保持32位值还是64位值
# include <sys/stat.h> //系统调用头文件,如目录、管道、socket、字符、块的属性等
# include <fcntl.h> //定义了open函数
# include <unistd> //定义了close函数
# include <sys/ioctl.h> //定义了ioctl函数
编译器使用的是arm-2009q3,即arm-none-linux-gnueabi-gcc。编译使用的头文件在编译器的libc下,如:
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/usr/include/fcntl.h
5. 函数
open:返回文件描述符
ioctl:应用向驱动传值
close:关闭文件
编译方法:arm-none-linux-gnueabi-gcc -o invoke_hello123 invoke_hello123.c
6.详细的C文件:invoke_hello123.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
int main()
{
int fd;
char *hello_node = "/dev/hello_ctl123"; //设备节点的路径
fd = open(hello_node, O_RDWR|O_NDELAY); //只读,非阻塞方式
if (fd < 0)
perror("open");
else{
printf("App open %s success\n",hello_node);
ioctl(fd, 1, 6);
}
close(fd);
return 0;
}
在开发板上运行./invoke_hello123出错的log和解决如下
# ERROR
root@iTOP4412-ubuntu-desktop:~/tests/3# ./invoke_hello123
-bash: ./invoke_hello123: No such file or directory
解决方法:https://blog.csdn.net/qq_22863619/article/details/80294232
iTOP4412-ubuntu-desktop:~/tests/3# readelf -a invoke_hello123 | grep Requesting
[Requesting program interpreter: /lib/ld-linux.so.3]
由此发现缺少的是库文件:/lib/ld-linux.so.3
遂到toolchain中查找:
nan@nanzh:~/iTOP4412/4-1$ find /usr/local/arm/arm-2009q3/ -iname "ld-linux.so.3"
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib/ld-linux.so.3
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/thumb2/lib/ld-linux.so.3
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/armv4t/lib/ld-linux.so.3
nan@nanzh:~/iTOP4412/4-1$ ls -l /usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib/ld-linux.so.3
lrwxrwxrwx 1 nan nan 12 10月 17 2009 /usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib/ld-linux.so.3 -> ld-2.10.1.so
于是将ld-2.10.1.so拷贝到开发板中并设置软连接
root@iTOP4412-ubuntu-desktop:# cp /mnt/ld-2.10.1.so /lib
root@iTOP4412-ubuntu-desktop:/lib# ln -s ld-2.10.1.so ld-linux.so.3
#ERROR 2
root@iTOP4412-ubuntu-desktop:~/tests/3# ./invoke_hello123
./invoke_hello123: error while loading shared libraries: libgcc_s.so.1: cannot open shared object file: No such file or directory
方法:
nan@nanzh:~/iTOP4412/4-1$ find /usr/local/arm/arm-2009q3/ -iname "libgcc_s.so.1"
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib/libgcc_s.so.1
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/thumb2/lib/libgcc_s.so.1
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/armv4t/lib/libgcc_s.so.1
nan@nanzh:~/iTOP4412/4-1$ ls -l /usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib/libgcc_s.so.1
-rw-r--r-- 1 nan nan 69371 10月 17 2009 /usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib/libgcc_s.so.1
拷贝到板子上继续
root@iTOP4412-ubuntu-desktop:~/tests/3# cp /mnt/libgcc_s.so.1 /lib/
root@iTOP4412-ubuntu-desktop:~/tests/3# ./invoke_hello123
./invoke_hello123: error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory
nan@nanzh:~/iTOP4412/4-1$ find /usr/local/arm/arm-2009q3/ -iname "libc.so.6"
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib/libc.so.6
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/thumb2/lib/libc.so.6
/usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/armv4t/lib/libc.so.6
nan@nanzh:~/iTOP4412/4-1$ ls -l /usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib/libc.so.6
lrwxrwxrwx 1 nan nan 14 10月 17 2009 /usr/local/arm/arm-2009q3/arm-none-linux-gnueabi/libc/lib/libc.so.6 -> libc-2.10.1.so
root@iTOP4412-ubuntu-desktop:~/tests/3# mount /dev/sdb3 /mnt
root@iTOP4412-ubuntu-desktop:~/tests/3# cp /mnt/libc-2.10.1.so /lib
root@iTOP4412-ubuntu-desktop:~/tests/3# cd /lib
root@iTOP4412-ubuntu-desktop:/lib# ln -s libc-2.10.1.so libc.so.6
以上总结,缺少库:/lib/ld-linux.so.3,libgcc_s.so.1,libc.so.6
使用readelf查看的具体内容如下。(ldd用于动态库)
重点区分
设备节点是“对上”的,为了让应用程序可以调用
一定注意生成设备节点和设备注册没有关系,而且设备节点名称不需要和设备名称相同
一般情况下,是将设备节点注册放到probe中,但是放到init函数中的驱动也是有的