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/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <asm/uaccess.h>
#define PROC_NODE_NAME "test"
struct proc_dir_entry *proc_test_dir = NULL;
struct proc_dir_entry *proc_test_file = NULL;
{
seq_printf(m, "%s ", flag?"true":"false");
}
{
char mode = 0;
{
if (get_user(mode, buffer))
{
return -EFAULT;
}
flag = (mode != '0');
}
}
{
return single_open(file, proc_test_show, NULL);
}
.owner = THIS_MODULE,
.open = proc_test_open,
.read = seq_read,
.write = proc_test_write,
.llseek = seq_lseek,
.release = single_release,
};
{
proc_test_dir = proc_mkdir(PROC_DIR_NAME, NULL);
if (NULL == proc_test_dir)
return -ENOMEM;
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;
}
}
{
/* remove /proc/test/test node */
proc_remove(proc_test_file);
proc_remove(proc_test_dir);
}
module_exit(proc_test_exit);
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);