Linux设备驱动学习三 设备节点的生成和调用:杂项设备驱动的注册和调用

设备注册,驱动注册和生成设备节点的区别:

生成设备节点是对上的,为了应用程序可以调用和驱动进行通信。

设备注册,驱动注册是驱动嵌入到内核中。

 

关于杂项设备
杂项设备(设备号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函数中的驱动也是有的

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值