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 = ;
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 = ;
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 ;
} 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 = ;
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, , 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/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\n", 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);
}
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
bnep
cpuid
nfnetlink_queue
nfnetlink_log
nfnetlink nfnetlink_log,nfnetlink_queue
bluetooth bnep
btrfs
xor btrfs
raid6_pq btrfs
ufs
qnx4
hfsplus
hfs
minix
ntfs
msdos
jfs
xfs
libcrc32c xfs
binfmt_misc
vmw_balloon
coretemp
crct10dif_pclmul
crc32_pclmul
aesni_intel
aes_x86_64 aesni_intel
lrw aesni_intel
gf128mul lrw
glue_helper aesni_intel
ablk_helper aesni_intel
cryptd aesni_intel,ablk_helper
joydev
input_leds
serio_raw
vmw_vmci vmw_balloon
shpchp
i2c_piix4
8250_fintek
mac_hid
parport_pc
ppdev
lp
parport lp,ppdev,parport_pc
autofs4
vmwgfx
ttm vmwgfx
drm_kms_helper vmwgfx
syscopyarea drm_kms_helper
sysfillrect drm_kms_helper
sysimgblt drm_kms_helper
fb_sys_fops drm_kms_helper
psmouse
mptspi
mptscsih mptspi
drm ttm,drm_kms_helper,vmwgfx
vmxnet3
mptbase mptspi,mptscsih
scsi_transport_spi mptspi
pata_acpi
floppy
fjes root@chgao-virtual-machine# cat /proc/devices
Character devices:
mem
/dev/vc/
tty
ttyS
/dev/tty
/dev/console
/dev/ptmx
ttyprintk
lp
vcs
misc
input
sg
fb
i2c
ppdev
ppp
ptm
pts
usb
usb_device
cpu/cpuid
drm
globalvar
bsg
watchdog
rtc
dimmctl
ndctl
tpm Block devices:
ramdisk
fd
blkext
loop
sd
md
sr
sd
sd
sd
sd
sd
sd
sd
sd
sd
sd
sd
sd
sd
sd
sd
device-mapper
virtblk
mdp root@chgao-virtual-machine# mknod /dev/globalval c
root@chgao-virtual-machine# ll /dev/globalval
crw-r--r-- root root , 3月 : /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);
最新文章
- AppCan学习笔记----关闭页面listview动态加载数据
- IOS textView获取光标定位,以及选中
- sersync2 安装,配置
- Android优化——UI优化(三)使用ViewStub延迟加载
- struts 的问题是由于没有写的name有缺少的项,没有完全对应
- MVC4商城项目四:应用Bundle捆绑压缩技术
- T4模版
- 在Visual Studio 2012中使用VMSDK开发领域特定语言1
- 阿里云服务器Tomcat无法从外部访问
- JS常用基础知识
- linux for循环 fork() 产生子进程
- SpriteKit 学习体会贴(不断完善中)
- redis.conf配置详解(转)
- 5. Longest Palindromic Substring - Unsolved
- java中的中文字符转码技术
- nodejs 模板引擎jade的使用
- OpenStack OVS GRE/VXLAN
- JQuery 实现 Tab 切换 index
- Python解析SWAN气象雷达数据--(解析、生成ASCII、Image、netCDF)
- 找到当前mysql group replication 环境的primary结点