• Linux驱动开发1——基础知识


    1、三类驱动

    字符设备驱动:字节流,/dev下有设备节点,file_operations,inode, file

    块设备驱动:数据块,/dev下有设备节点,通常有文件系统

    网络设备驱动:网络报文的收发,通过eth接口,其上为内核网络协议栈

    2、驱动模块的加载和注销

    #include <linux/init.h>
    #include <linux/module.h>

    static int __init init_func(void)
    {
    /* Initialize Code */
    return 0;
    }

    static void __exit cleanup_func(void)
    {
    /* Cleanup Code */
    }
    module_init(init_func); //加载驱动模块 module_exit(cleanup_func); //注销驱动模块

    insmod 加载驱动(函数sys_init_module通过vmalloc分配内核内存来存放驱动模块的代码段,借助内核符号表来解决模块中的内核引用,并且调用模块的初始化函数完成初始化)

    rmmod 卸载驱动

    lsmod 查看系统中的模块(通过读取/proc/modules实现,驱动模块信息也可以在/sys/module中找到)

    depmod 分析模块的依赖性

    modprobe 智能地添加和删除内核模块

    modinfo 显示模块信息

    3、内核空间和用户空间

    用户态通过系统调用进入内核态,执行系统调用的内核代码在进程的上下文工作,即该系统调用代码代表调用进程并可以存取该进程的地址空间(无论是虚拟内存地址空间的内核段还是用户段)

    硬件中断挂起当前执行进程时,中断处理函数在中断上下文工作,对进程来说是异步的,不和任何特定进程相关。

    驱动模块中的一部分函数(open/close/read/write/ioctl/lseek等)负责处理系统调用,一些函数负责处理中断。

    tips: 系统调用接口中获取当前调用进程信息

    #include <linux/current.h>
    #include <linux/sched.h>
    
    printk(KERN_INFO "The process is "%s" (pid %i)
    ", current->comm, current->pid); 

    4、并发和可重入

    驱动代码编程必须考虑到并发,并发的来源包括:

    1)多个用户态进程同时调用驱动

    2)驱动试图做其他事情时,异步中断中止驱动正在执行的事情(中断优先级最高)

    3)SMP系统中,驱动同时在多个CPU核上并发执行

    结果是,Linux内核代码,包括驱动代码,必须是可重入的——能够同时在多个上下文中运行。

    5、驱动输出符号给其他模块

    EXPORT_SYMBOL(name);
    EXPORT_SYMBOL_GPL(name);

    6、模块信息

    MODULE_LICENSE("GPL");
    取值范围:
    "GPL" - 适用GNU通用公共许可的任何版本
    "GPL v2" - 只适用GPL版本2
    "GPL and additional rights"
    "Dual BSD/GPL"
    "Dual MPL/GPL"
    "Proprietary"
    
    MODULE_AUTHOR("作者");
    MODULE_DESCRIPTION("描述信息");
    MODULE_VERSION("版本号");
    MODULE_ALIAS("别名");
    MODULE_DEVICE_TABLE("模块支持的设备列表");

    7、模块参数

    #include <linux/stat.h> //权限值
    #include <linux/moduleparam.h>
    
    static char *whom = "justin";
    module_param(whom, charp, S_IRUGO);
    
    static int howmany = 1;
    module_param(howmany, int, S_IRUGO);
    
    支持的数据类型:boot/invboot/charp/int/long/short/uint/ulong/ushort
    
    数组类型:
    module_param_array(name, type, num, perm);
    name:数组名称
    type:数组元素类型
    num:整形变量
    perm:权限(SIRUGO-所有人只读;SIRUGO|S_IWUSR-所有人可读,root可读写)

    8、调试方法

    8.1、错误码

    #include <linux/errno.h>
    正确的使用错误码,如:
    -ENODEV
    -ENOMEM

    8.2、调试打印

    #include <linux/kernel.h>
    
    int printk(const char *fmt, ...);

    #define KERN_EMERG      KERN_SOH "0"    /* system is unusable */
    #define KERN_ALERT      KERN_SOH "1"    /* action must be taken immediately */
    #define KERN_CRIT       KERN_SOH "2"    /* critical conditions */
    #define KERN_ERR        KERN_SOH "3"    /* error conditions */
    #define KERN_WARNING    KERN_SOH "4"    /* warning conditions */
    #define KERN_NOTICE     KERN_SOH "5"    /* normal but significant condition */
    #define KERN_INFO       KERN_SOH "6"    /* informational */
    #define KERN_DEBUG      KERN_SOH "7"    /* debug-level messages */

    可以通过/proc/sys/kernel/printk节点修改打印级别
    int console_printk[4] = {
            CONSOLE_LOGLEVEL_DEFAULT,       /* console_loglevel */
            MESSAGE_LOGLEVEL_DEFAULT,       /* default_message_loglevel */
            CONSOLE_LOGLEVEL_MIN,           /* minimum_console_loglevel */
            CONSOLE_LOGLEVEL_DEFAULT,       /* default_console_loglevel */
    };

    有一些printk的变种函数
    #define pr_emerg(fmt, ...)
            printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__)
    #define pr_alert(fmt, ...)
            printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__)
    #define pr_crit(fmt, ...)
            printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__)
    #define pr_err(fmt, ...)
            printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)
    #define pr_warning(fmt, ...)
            printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__)
    #define pr_warn pr_warning
    #define pr_notice(fmt, ...)
            printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__)
    #define pr_info(fmt, ...)
            printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)

    驱动代码中通常使用pr_debug()dev_debug()

    #if defined(CONFIG_DYNAMIC_DEBUG)
    /* dynamic_pr_debug() uses pr_fmt() internally so we don't need it here */
    #define pr_debug(fmt, ...)
            dynamic_pr_debug(fmt, ##__VA_ARGS__)
    #elif defined(DEBUG)
    #define pr_debug(fmt, ...)
            printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
    #else
    #define pr_debug(fmt, ...)
            no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
    #endif

    #ifdef DEBUG
    #define dev_dbg(dev, format, arg...)       
        dev_printk(KERN_DEBUG , dev , format , ## arg)
    #else
    static inline int __attribute__ ((format (printf, 2, 3)))
    dev_dbg(struct device * dev, const char * fmt, ...)
    {
        return 0;
    }
    #endif

    8.3、procfs

    8.3.1、create_proc_entry() & remove_proc_entry() 新版本内核已经废弃,采用proc_create() & proc_remove()替代

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/proc_fs.h>
    
    #define PROC_DIR_NAME "test/test"
    
    static int value = 0;
    module_param(value, int, S_IRUGO);
    
    static int proc_test_read(char *page, char **start, off_t offset, int count, int *eof, void *data)
    {
        sprintf(page, "value=%d", value);
        printk(KERN_INFO "%s: value=%d
    ", __func__, value);
    
        return 0;
    }
    
    static int proc_test_write(struct file *file, const char *buffer, unsigned long count, void *data)
    {
        sscanf(buffer, "%d", &value);
        printk(KERN_INFO "%s: value=%d
    ", __func__, value);
    
        return count;
    }
    
    static int __init proc_test_init(void)
    {
        int ret = 0;
        struct proc_dir_entry *entry = NULL;
    
        printk(KERN_INFO "%s entry
    ", __func__);
    
        /* create procfs entry point under /proc */
        entry = create_proc_entry(PROC_DIR_NAME, 0666, NULL);
        if (entry)
        {
            entry->read_proc = proc_test_read;
            entry->write_proc = proc_test_write;
        }
    
        printk(KERN_INFO "%s exit
    ", __func__);
    
        return ret;
    }
    
    static void __exit proc_test_exit(void)
    {
        printk(KERN_INFO "%s entry
    ", __func__);
    
        /* remove procfs entry point */
        remove_proc_entry(PROC_DIR_NAME, NULL);
    }
    
    module_init(proc_test_init);
    module_exit(proc_test_exit);
    
    MODULE_LICENSE("Dual BSD/GPL");

     8.3.2、proc_create() & proc_remove()

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/proc_fs.h>
    #include <linux/seq_file.h>
    #include <asm/uaccess.h>
     
    #define PROC_DIR_NAME "test"
    #define PROC_NODE_NAME "test"
     
    static bool flag = 0;
    struct proc_dir_entry *proc_test_dir = NULL;
    struct proc_dir_entry *proc_test_file = NULL;
     
    static int proc_test_show(struct seq_file *m, void *v)
    {
        seq_printf(m, "%s ", flag?"true":"false");
        return 0;
    }
     
    static ssize_t proc_test_write(struct file *file, const char __user *buffer, size_t count, loff_t *pos)
    {
        char mode = 0;
        if (0 < count)
        {
            if (get_user(mode, buffer))
            {
                return -EFAULT;
            }
            flag = (mode != '0');
        }
        return count;
    }
     
    static int proc_test_open(struct inode *inode, struct file *file)
    {
        return single_open(file, proc_test_show, NULL);
    }
     
    static const struct file_operations proc_test_fops = {
        .owner = THIS_MODULE,
        .open = proc_test_open,
        .read = seq_read,
        .write = proc_test_write,
        .llseek = seq_lseek,
        .release = single_release,
    };
     
    static int __init proc_test_init(void)
    {
        /* create /proc/test directory */
        proc_test_dir = proc_mkdir(PROC_DIR_NAME, NULL);
        if (NULL == proc_test_dir)
            return -ENOMEM;
     
        /* create /proc/test/test node */
        proc_test_file = proc_create(PROC_NODE_NAME, 0644, proc_test_dir, &proc_test_fops);
        if (NULL == proc_test_file)
        {
            /* on failure */
            proc_remove(proc_test_dir);
            return -ENOMEM;
        }
     
        return 0;
    }
     
    static void __exit proc_test_exit(void)
    {
        /* remove /proc/test/test node */
        proc_remove(proc_test_file);
        /* remove /proc/test directory */

        proc_remove(proc_test_dir);
    }
     
    module_init(proc_test_init);
    module_exit(proc_test_exit);
     
    MODULE_LICENSE("Dual BSD/GPL");

    root# cat /proc/test/test

    false
    root# echo 1 > /proc/test/test
    root# cat /proc/test/test
    true

    9、设备号

    #include <linux/types.h>
    
    dev_t类型标识设备号,32位,高12位用作主设备号,低20位用作次设备号。
    
    #include <linux/kdev_t.h>
    
    获取主设备号:MAJOR(dev_t devno);
    获取次设备号:MINOR(dev_t devno);
    获取设备号:MKDEV(int major, int minor);

     9.1、字符设备设备号分配

    #include <linux/fs.h>
    
    int register_chrdev_region(dev_t first, unsigned int count, char *name);
    静态分配字符设备号,从fist开始的count个,name为设备名称(name会出现在/proc/devices和sysfs中),成功返回0,失败返回一个负的错误码
    
    int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
    动态分配字符设备号,主设备号动态分配,次设备号从firstminor开始的count个,name为设备名称(动态分配的主设备号可以在/proc/devices中获取)
    
    void unregister_chrdev_region(dev_t first, unsigned int count);
    注销字符设备号,从first开始的count个

     9.2、创建设备节点

    9.2.1、手动创建设备节点

    root@chgao-virtual-machine# insmod ./global_val.ko
    
    root@chgao-virtual-machine# lsmod
    Module                  Size  Used by
    global_val             16384  0
    bnep                   20480  2
    cpuid                  16384  0
    nfnetlink_queue        20480  0
    nfnetlink_log          20480  0
    nfnetlink              16384  2 nfnetlink_log,nfnetlink_queue
    bluetooth             520192  5 bnep
    btrfs                 987136  0
    xor                    24576  1 btrfs
    raid6_pq              102400  1 btrfs
    ufs                    73728  0
    qnx4                   16384  0
    hfsplus               106496  0
    hfs                    57344  0
    minix                  36864  0
    ntfs                   98304  0
    msdos                  20480  0
    jfs                   180224  0
    xfs                   966656  0
    libcrc32c              16384  1 xfs
    binfmt_misc            20480  1
    vmw_balloon            20480  0
    coretemp               16384  0
    crct10dif_pclmul       16384  0
    crc32_pclmul           16384  0
    aesni_intel           167936  0
    aes_x86_64             20480  1 aesni_intel
    lrw                    16384  1 aesni_intel
    gf128mul               16384  1 lrw
    glue_helper            16384  1 aesni_intel
    ablk_helper            16384  1 aesni_intel
    cryptd                 20480  2 aesni_intel,ablk_helper
    joydev                 20480  0
    input_leds             16384  0
    serio_raw              16384  0
    vmw_vmci               65536  1 vmw_balloon
    shpchp                 36864  0
    i2c_piix4              24576  0
    8250_fintek            16384  0
    mac_hid                16384  0
    parport_pc             32768  1
    ppdev                  20480  0
    lp                     20480  0
    parport                49152  3 lp,ppdev,parport_pc
    autofs4                40960  2
    vmwgfx                237568  2
    ttm                    98304  1 vmwgfx
    drm_kms_helper        139264  1 vmwgfx
    syscopyarea            16384  1 drm_kms_helper
    sysfillrect            16384  1 drm_kms_helper
    sysimgblt              16384  1 drm_kms_helper
    fb_sys_fops            16384  1 drm_kms_helper
    psmouse               126976  0
    mptspi                 24576  3
    mptscsih               40960  1 mptspi
    drm                   360448  5 ttm,drm_kms_helper,vmwgfx
    vmxnet3                57344  0
    mptbase               102400  2 mptspi,mptscsih
    scsi_transport_spi     32768  1 mptspi
    pata_acpi              16384  0
    floppy                 73728  0
    fjes                   28672  0
    
    root@chgao-virtual-machine# cat /proc/devices 
    Character devices:
      1 mem
      4 /dev/vc/0
      4 tty
      4 ttyS
      5 /dev/tty
      5 /dev/console
      5 /dev/ptmx
      5 ttyprintk
      6 lp
      7 vcs
     10 misc
     13 input
     21 sg
     29 fb
     89 i2c
     99 ppdev
    108 ppp
    128 ptm
    136 pts
    180 usb
    189 usb_device
    203 cpu/cpuid
    226 drm
    248 globalvar
    249 bsg
    250 watchdog
    251 rtc
    252 dimmctl
    253 ndctl
    254 tpm
    
    Block devices:
      1 ramdisk
      2 fd
    259 blkext
      7 loop
      8 sd
      9 md
     11 sr
     65 sd
     66 sd
     67 sd
     68 sd
     69 sd
     70 sd
     71 sd
    128 sd
    129 sd
    130 sd
    131 sd
    132 sd
    133 sd
    134 sd
    135 sd
    252 device-mapper
    253 virtblk
    254 mdp
    
    root@chgao-virtual-machine# mknod /dev/globalval c 248 0
    root@chgao-virtual-machine# ll /dev/globalval
    crw-r--r-- 1 root root 248, 0 3月  28 10:09 /dev/globalval

    9.2.2、自动创建设备节点

    #include <linux/device.h>
    
    struct class *class_create(struct module *owner, const char *name);
    
    void class_destroy(struct class *cls);
    
    struct device *device_create(struct class *cls, struct device *parent,
                                 dev_t devt, void *drvdata,
                                 const char *fmt, ...);
    
    void device_destroy(struct class *cls, dev_t devt);
    
    
    class_create()在sysfs文件系统下创建class, device_create()在sysfs文件系统下创建device并触发uevent,用户态守护进程udevd收到uevent事件后,根据/etc/udev/udev.conf规则在/dev下创建设备文件。

    10、内存分配

    #include <linux/slab.h>
    
    void* kmalloc(size_t size, int flags);
    
    void kfree(void *ptr);

    11、用户态和内核态数据交互

    #include <asm/uaccess.h>
    
    unsigned long copy_to_user(void __user *to, const void *from, unsigned long count);
    unsigned long copy_from_user(void *to, const void __user *from, unsigned long count);
    成功返回0,失败返回错误码,驱动应该返回-EFAULT给用户

    读写1/2/4/8字节的数据,更高效的方法是使用下述函数:
    get_user(local, ptr);
    __get_user(local, ptr);
    put_user(datum, ptr);
    __put_user(datum, ptr);
  • 相关阅读:
    gt_argmax_overlaps = overlaps.argmax(axis=0) ValueError: attempt to get argmax of an empty sequence错误处理
    VS2013 "当前不会命中断点.还没有为该文档家在任何符号" 解决办法
    Spatial Transformer Networks
    1*1卷积
    how to detect circles and rectangle?
    opencv error: insufficient memory错误解决办法
    无人零售
    alt-opt and end2end
    开心的小明_动态规划
    国王和金矿问题_动态规划
  • 原文地址:https://www.cnblogs.com/justin-y-lin/p/10600668.html
Copyright © 2020-2023  润新知