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

Linux驱动开发1——基础知识的更多相关文章

  1. 单片机知识是Linux驱动开发的基础之一

    这是arm裸机1期加强版第1课第2节课程的wiki文字版. 为什么没前途也要学习单片机? 因为它是个很好的入口. 学习单片机可以让我们抛开复杂的软件结构,先掌握硬件操作,如:看原理图.芯片手册.写程序 ...

  2. 驱动开发学习笔记. 0.06 嵌入式linux视频开发之预备知识

    驱动开发读书笔记. 0.06  嵌入式linux视频开发之预备知识 由于毕业设计选择了嵌入式linux视频开发相关的项目,于是找了相关的资料,下面是一下预备知识 UVC : UVC,全称为:USB v ...

  3. Linux驱动开发概述

    原文出处:http://www.cnblogs.com/jacklu/p/4722563.html Linux设备分类 设备的驱动程序也要像裸机程序那样进行一些硬件操作,不同的是驱动程序需要" ...

  4. 嵌入式linux驱动开发之点亮led(驱动编程思想之初体验)

    这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的 ...

  5. 【转】linux驱动开发的经典书籍

    原文网址:http://www.cnblogs.com/xmphoenix/archive/2012/03/27/2420044.html Linux驱动学习的最大困惑在于书籍的缺乏,市面上最常见的书 ...

  6. Linux驱动开发学习的一些必要步骤

      1. 学会写简单的makefile 2. 编一应用程序,可以用makefile跑起来 3. 学会写驱动的makefile 4. 写一简单char驱动,makefile编译通过,可以insmod, ...

  7. 驱动编程思想之初体验 --------------- 嵌入式linux驱动开发之点亮LED

    这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的 ...

  8. Linux 驱动开发

    linux驱动开发总结(一) 基础性总结 1, linux驱动一般分为3大类: * 字符设备 * 块设备 * 网络设备 2, 开发环境构建: * 交叉工具链构建 * NFS和tftp服务器安装 3, ...

  9. linux驱动开发的经典书籍

    转载于:http://www.cnblogs.com/xmphoenix/archive/2012/03/27/2420044.html 参加实习也近一个月了,严重感觉知识不够,真是后悔学校里浪费那么 ...

随机推荐

  1. 使用autotools自动生成Makefile并在此之上使用dh-make生成可发布的deb程序包(详解)

    转自:http://blog.csdn.net/longerzone/article/details/12705507 一.前言 本文将介绍如何使用autotools生成一个Makefile文件,并在 ...

  2. redis学习之旅-初识Redis

    定义: redis是一种支持Key-Value等多种数据结构的存储系统.可用于缓存,事件发布或订阅,高速队列等场景.该数据库使用ANSI C语言编写,支持网络,提供字符串,哈希,列表,队列,集合结构直 ...

  3. mysql简单命令

    库: 增 create database db1:新建一个默认编码的库 create database db1 charset uet8 ;建一个编码为 utf8 的库 删 drop database ...

  4. django中的FBV和CBV??

    django中请求处理方式有2种:FBV 和 CBV 一.FBV FBV(function base views) 就是在视图里使用函数处理请求. 看代码: urls.py from django.c ...

  5. P2523 [HAOI2011]Problem c

    传送门 先考虑如何判断无解,设 $sum[i]$ 表示确定的人中,编号大于 $i$ 的人的人数 如果 $sum[i]>n-i+1$ 则无解,进一步考虑设 $f[i][j]$ 表示当前确定完编号大 ...

  6. 生成EXCEL文件是经常需要用到的功能,我们利用一些开源库可以很容易实现这个功能。

    方法一:利用excellibrary,http://code.google.com/p/excellibrary/ excellibrary是国人写的开源组件,很容易使用,可惜貌似还不支持.xlsx( ...

  7. CollectionView刷新问题,以及定时器与控制器的销毁问题

    1.CollectionView的刷新必须首先保证CollectionView有高度 注意事项:在cell中嵌套CollectionView,如果使用的是AutoLayout的话,一定要注意保证Col ...

  8. Django之AJAX请求

    ---恢复内容开始--- 一.choices字段  1.实列  前端代码 <div class='container'> <div class="row"> ...

  9. MacOS Mojave 安装sshpass

    使用sshpass的场景 安装sshpass及各种常见小问题处理 测试 安全提示 使用sshpass的场景 在MacOS下使用ansible命令(inventory文件中使用了密码验证的方式)或者使用 ...

  10. ocvate常用函数

    1.生成矩阵相关 https://www.coursera.org/learn/machine-learning/lecture/9fHfl/basic-operations 1. 初始化矩阵 a = ...