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/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
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);

最新文章

  1. AppCan学习笔记----关闭页面listview动态加载数据
  2. IOS textView获取光标定位,以及选中
  3. sersync2 安装,配置
  4. Android优化——UI优化(三)使用ViewStub延迟加载
  5. struts 的问题是由于没有写的name有缺少的项,没有完全对应
  6. MVC4商城项目四:应用Bundle捆绑压缩技术
  7. T4模版
  8. 在Visual Studio 2012中使用VMSDK开发领域特定语言1
  9. 阿里云服务器Tomcat无法从外部访问
  10. JS常用基础知识
  11. linux for循环 fork() 产生子进程
  12. SpriteKit 学习体会贴(不断完善中)
  13. redis.conf配置详解(转)
  14. 5. Longest Palindromic Substring - Unsolved
  15. java中的中文字符转码技术
  16. nodejs 模板引擎jade的使用
  17. OpenStack OVS GRE/VXLAN
  18. JQuery 实现 Tab 切换 index
  19. Python解析SWAN气象雷达数据--(解析、生成ASCII、Image、netCDF)
  20. 找到当前mysql group replication 环境的primary结点

热门文章

  1. java基础笔记(2)
  2. numpy库的认识以及数组的创建
  3. tensorflow学习笔记二----------变量
  4. nginx动静分离与网关
  5. 二: Jvm内存模型
  6. Linux之文件内容查阅
  7. ztree点击加号+触发ajax请求
  8. springboot 初探 、基础及配置
  9. zabbix命令之:zabbix_get命令
  10. bzoj4764 弹飞大爷 LCT