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)\n", 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\n", __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\n", __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\n", __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\n", __func__);

    return ret;
}

static void __exit proc_test_exit(void)
{
    printk(KERN_INFO "%s entry\n", __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\n", 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);

 

posted on 2019-03-26 15:53  者旨於陽  阅读(741)  评论(0编辑  收藏  举报

导航