Linux内核及驱动开发学习笔记(基于Exynos4412)

向内核添加新功能

静态加载法

即新功能源码与内核其它代码一起编译进uImage文件内

动态加载法

即新功能源码与内核其它源码不一起编译,而是独立编译成内核的插件(被称为内核模块)文件.ko


模块传参

module_param(name, type, perm); // 将指定的全局变量设置成模块参数

name:全局变量名
type:
    使用符号      实际类型                传参方式
    bool                bool                     insmod xxx.ko  变量名=0 或 1
    invbool           bool                     insmod xxx.ko  变量名=0 或 1
    charp             char *                   insmod xxx.ko  变量名="字符串内容"
    short              short                     insmod xxx.ko  变量名=数值
    int                    int                        insmod xxx.ko  变量名=数值
    long                long                      insmod xxx.ko  变量名=数值
    ushort         unsigned short         insmod xxx.ko  变量名=数值
    uint             unsigned int             insmod xxx.ko  变量名=数值
    ulong          unsigned long          insmod xxx.ko  变量名=数值
perm:给对应文件 /sys/module/name/parameters/变量名 指定操作权限
    #define S_IRWXU 00700
    #define S_IRUSR 00400
    #define S_IWUSR 00200
    #define S_IXUSR 00100
    #define S_IRWXG 00070
    #define S_IRGRP 00040
    #define S_IWGRP 00020
    #define S_IXGRP 00010
    #define S_IRWXO 00007
    #define S_IROTH 00004
    #define S_IWOTH 00002  //不要用 编译出错
    #define S_IXOTH 00001

module_param_array(name, type, &num, perm);

name、type、perm同module_param,type指数组中元素的类型
&num:存放数组大小变量的地址,可以填NULL(确保传参个数不越界)
传参方式 insmod xxx.ko  数组名=元素值0,元素值1,...元素值num-1  

示例代码

#include <linux/module.h>
#include <linux/kernel.h>

int gx = 10;
char* gstr = "hello";
int garr[5] = {1, 2, 3, 4, 5};

module_param(gx, int, 0664);
module_param(gstr, charp, 0664);
module_param_array(garr, int, NULL, 0664);


int __init testparam_init(void)
{
	int i = 0;
	printk("gx = %d\n", gx);
	printk("gstr = %s\n", gstr);
	
	for (i = 0; i < 5; i++) {
		printk("%d ", garr[i]);
	}
	printk("\n");
	return 0;
}

void __exit testparam_exit(void)
{
	printk("testparam will exit\n");
}

MODULE_LICENSE("GPL");

module_init(testparam_init);
module_exit(testparam_exit);

插ko:

shrek@ubuntu16:~/share/mydrivercode$ sudo insmod ./testparam.ko
shrek@ubuntu16:~/share/mydrivercode$ dmesg 
[ 1167.269738] gx = 10
[ 1167.269739] gstr = hello
[ 1167.269739] 1 
[ 1167.269739] 2 
[ 1167.269740] 3 
[ 1167.269740] 4 
[ 1167.269740] 5 
shrek@ubuntu16:~/share/mydrivercode$ sudo insmod ./testparam.ko gx=1000 gstr="hi" garr=5,6,7,8,9
shrek@ubuntu16:~/share/mydrivercode$ sudo dmesg 
[ 1145.753620] gx = 1000
[ 1145.753621] gstr = hi
[ 1145.753622] 5 
[ 1145.753622] 6 
[ 1145.753622] 7 
[ 1145.753622] 8 
[ 1145.753622] 9 

模块依赖

既然内核模块的代码与其它内核代码共用统一的运行环境,也就是说模块只是存在形式上独立,运行上其实和内核其它源码是一个整体,它们隶属于同一个程序,因此一个模块或内核其它部分源码应该可以使用另一个模块的一些全局特性。

一个模块中这些可以被其它地方使用的名称被称为导出符号,所有导出符号被填在同一个表中这个表被称为符号表

最常用的可导出全局特性为全局变量函数

查看符号表的命令:nm
nm查看elf格式的可执行文件或目标文件中包含的符号表,用法:

nm  文件名  (可以通过man nm查看一些字母含义)

两个用于导出模块中符号名称的宏:

EXPORT_SYMBOL(函数名或全局变量名)
EXPORT_SYMBOL_GPL(函数名或全局变量名)   需要GPL许可证协议验证

使用导出符号的地方,需要对这些符号进行extern声明后才能使用这些符号

B模块使用了A模块导出的符号,此时称B模块依赖于A模块,则:

1. 编译次序:先编译模块A,再编译模块B,当两个模块源码在不同目录时,需要:i. 先编译导出符号的模块A ii. 拷贝A模块目录中的Module.symvers到B模块目录 iii. 编译使用符号的模块B。否则编译B模块时有符号未定义错误
2. 加载次序:先插入A模块,再插入B模块,否则B模块插入失败
3. 卸载次序:先卸载B模块,在卸载A模块,否则A模块卸载失败

补充说明:
内核符号表(直接当文本文件查看)

运行时可查看 /proc/kallsyms
编译后可查看 /boot/System.map

示例代码

moduleA.c:

#include <linux/module.h>
#include <linux/kernel.h>

int gx = 10;
EXPORT_SYMBOL(gx);

int __init modulea_init(void)
{
	printk("In moduleA init, gx = %d\n", gx);
	return 0;
}

void __exit modulea_exit(void)
{
	printk("modulea will exit\n");
}

MODULE_LICENSE("GPL");

module_init(modulea_init);
module_exit(modulea_exit);

moduleB.c:

#include <linux/module.h>
#include <linux/kernel.h>

extern int gx;

int __init moduleb_init(void)
{
	printk("In moduleB init, gx = %d\n", gx);
	return 0;
}

void __exit moduleb_exit(void)
{
	printk("moduleb will exit\n");
}

MODULE_LICENSE("GPL");

module_init(moduleb_init);
module_exit(moduleb_exit);

Makefile(注意编译顺序):

ifeq ($(KERNELRELEASE),)

ifeq ($(ARCH),arm)
KERNELDIR ?= /home/shrek/Linux_4412/kernel/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)

modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install

clean:
	rm -rf  *.o  *.ko  .*.cmd  *.mod.*  modules.order  Module.symvers   .tmp_versions

else
obj-m += moduleA.o

obj-m += moduleB.o

endif

插ko(注意插入顺序和卸载顺序):

shrek@ubuntu16:~/share/mydrivercode/twomodules$ sudo insmod ./moduleA.ko
shrek@ubuntu16:~/share/mydrivercode/twomodules$ sudo insmod ./moduleB.ko
shrek@ubuntu16:~/share/mydrivercode/twomodules$ sudo dmesg
[ 8145.865709] In moduleA init, gx = 10
[ 8147.510935] In moduleB init, gx = 10
shrek@ubuntu16:~/share/mydrivercode/twomodules$ sudo rmmod moduleB
shrek@ubuntu16:~/share/mydrivercode/twomodules$ sudo rmmod moduleA

内核空间和用户空间

为了彻底解决一个应用程序出错不影响系统和其它app的运行,操作系统给每个app一个独立的假想的地址空间,这个假想的地址空间被称为虚拟地址空间(也叫逻辑地址),操作系统也占用其中固定的一部分,32位Linux的虚拟地址空间大小为4G,并将其划分两部分:

1. 0~3G 用户空间 :每个应用程序只能使用自己的这份虚拟地址空间

2. 3G~4G 内核空间:内核使用的虚拟地址空间,应用程序不能直接使用这份地址空间,但可以通过一些系统调用函数与其中的某些空间进行数据通信

实际内存操作时,需要将虚拟地址映射到实际内存的物理地址,然后才进行实际的内存读写


执行流

有开始有结束总体顺序执行的一段独立代码,又被称为代码上下文 

计算机系统中的执行流的分类:

任务流--任务上下文(都参与CPU时间片轮转,都有任务五状态:就绪态  运行态  睡眠态  僵死态  暂停态)
   1.  进程
   2.  线程
       内核线程:内核创建的线程
       应用线程:应用进程创建的线程

异常流--异常上下文
   1. 中断
   2. 其它异常

应用编程可能涉及到的执行流:

1. 进程
2. 线程     

内核编程可能涉及到的执行流:  

1. 应用程序自身代码运行在用户空间,处于用户态   -----------------  用户态app
2. 应用程序正在调用系统调用函数,运行在内核空间,处于内核态,即代码是内核代码但处于应用执行流(即属于一个应用进程或应用线程) -----------------  内核态app
3. 一直运行于内核空间,处于内核态,属于内核内的任务上下文 ----------------- 内核线程
4. 一直运行于内核空间,处于内核态,专门用来处理各种异常 ----------------- 异常上下文


模块编程与应用编程的比较

不同点内核模块应用程序
API来源不能使用任何库函数各种库函数均可以使用
运行空间内核空间用户空间
运行权限特权模式运行非特权模式运行
编译方式静态编译进内核镜像或编译特殊的ko文件elf格式的应用程序可执行文件
运行方式模块中的函数在需要时被动调用从main开始顺序执行
入口函数init_modulemain
退出方式cleanup_modulemain函数返回或调用exit
浮点支持一般不涉及浮点运算,因此printk不支持浮点数据支持浮点运算,printf可以打印浮点数据
并发考虑需要考虑多种执行流并发的竞态情况只需考虑多任务并行的竞态
程序出错可能会导致整个系统崩溃只会让自己崩溃

内核接口头文件查询

大部分API函数包含的头文件在include/linux目录下,因此:

1. 首先在 include/linux 查询指定函数:grep  名称  ./   -r   -n
2. 找不到则更大范围的 include 目录下查询,命令同上


字符设备驱动

linux的文件种类:

1. -:普通文件
2. d:目录文件
3. p:管道文件
4. s:本地socket文件
5. l:链接文件
6. c:字符设备
7. b:块设备


Linux内核按驱动程序实现模型框架的不同,将设备分为三类:

1. 字符设备:按字节流形式进行数据读写的设备,一般情况下按顺序访问,数据量不大,一般不设缓存
2. 块设备:按整块进行数据读写的设备,最小的块大小为512字节(一个扇区),块的大小必须是扇区的整数倍,Linux系统的块大小一般为4096字节,随机访问,设缓存以提高效率
3. 网络设备:针对网络数据收发的设备


设备号

内核中同类设备的区分

内核用设备号来区分同类里不同的设备,设备号是一个无符号32位整数,数据类型为dev_t,设备号分为两部分:

1. 主设备号:占高12位,用来表示驱动程序相同的一类设备
2. 次设备号:占低20位,用来表示被操作的哪个具体设备

应用程序打开一个设备文件时,通过设备号来查找定位内核中管理的设备。

MKDEV宏用来将主设备号和次设备号组合成32位完整的设备号,用法:

dev_t devno;
int major = 251; // 主设备号
int minor = 2; // 次设备号
devno = MKDEV(major,minor);

MAJOR宏用来从32位设备号中分离出主设备号,用法:

dev_t devno = MKDEV(249,1);
int major = MAJOR(devno);

MINOR宏用来从32位设备号中分离出次设备号,用法:

dev_t devno = MKDEV(249,1);
int minor = MINOR(devno);

如果已知一个设备的主次设备号,应用层指定好设备文件名,那么可以用mknod命令在/dev目录创建代表这个设备的文件,即此后应用程序对此文件的操作就是对其代表的设备操作,mknod用法如下:

@ cd /dev
@ mknod 设备文件名 设备种类(c为字符设备,b为块设备)  主设备号  次设备号    //ubuntu下需加sudo执行

在应用程序中如果要创建设备可以调用系统调用函数mknod,其原型如下:

int mknod(const char *pathname, mode_t mode, dev_t dev);

pathname:带路径的设备文件名,无路径默认为当前目录,一般都创建在/dev下
mode:文件权限 位或 S_IFCHR/S_IFBLK
dev:32位设备号
返回值:成功为0,失败-1


申请和注销设备号

字符驱动开发的第一步是通过模块的入口函数向内核添加本设备驱动的代码框架,主要完成:

1. 申请设备号
2. 定义、初始化、向内核添加代表本设备的结构体元素

int register_chrdev_region(dev_t from, unsigned count, const char *name)
功能:手动分配设备号,先验证设备号是否被占用,如果没有则申请占用该设备号
参数:
    from:自己指定的设备号
    count:申请的设备数量
    name:/proc/devices文件中与该设备对应的名字,方便用户层查询主设备号
返回值:
    成功为0,失败负数,绝对值为错误码


int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
功能:动态分配设备号,查询内核里未被占用的设备号,如果找到则占用该设备号
参数:
    dev:分配设备号成功后用来存放分配到的设备号
    baseminior:起始的次设备号,一般为0
    count:申请的设备数量
    name:/proc/devices文件中与该设备对应的名字,方便用户层查询主次设备号
返回值:
    成功为0,失败负数,绝对值为错误码
 

分配成功后在/proc/devices 可以查看到申请到主设备号和对应的设备名,mknod时参数可以参考查到的此设备信息


void unregister_chrdev_region(dev_t from, unsigned count)
功能:释放设备号
参数:
    from:已成功分配的设备号将被释放
    count:申请成功的设备数量
 

释放后 /proc/devices 文件对应的记录消失


示例代码(申请和注销设备号)

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>

int major = 11;
int minor = 0;
int mychar_num = 1;

int __init mychar_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	ret = register_chrdev_region(devno, mychar_num, "mychar");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
		if (ret) {
			printk("get devno faile\n");
			return -1;
		}
		major = MAJOR(devno);
	}

	return 0;
}

void __exit mychar_exit(void)
{
	dev_t devno = MKDEV(major, minor);
	unregister_chrdev_region(devno, mychar_num);
}


MODULE_LICENSE("GPL");

module_init(mychar_init);
module_exit(mychar_exit);

插ko之后可通过 /proc/devices 查看:

shrek@ubuntu16:~/share/mydrivercode$ cat /proc/devices | grep mychar
 11 mychar

注册字符设备

void cdev_init(struct cdev *cdev, const struct file_operations *fops)

cdev结构体

struct cdev
{
    struct kobject kobj;//表示该类型实体是一种内核对象
    struct module *owner;//填THIS_MODULE,表示该字符设备从属于哪个内核模块
    const struct file_operations *ops;//指向空间存放着针对该设备的各种操作函数地址
    struct list_head list;//链表指针域
    dev_t dev;//设备号
    unsigned int count;//设备数量
};

1. 直接定义:定义结构体全局变量

2. 动态申请:struct  cdev * cdev_alloc()

file_operations结构体

struct file_operations 
{
   struct module *owner;           //填THIS_MODULE,表示该结构体对象从属于哪个内核模块
   int (*open) (struct inode *, struct file *);    //打开设备
   int (*release) (struct inode *, struct file *);    //关闭设备
   ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);    //读设备
   ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);    //写设备
   loff_t (*llseek) (struct file *, loff_t, int);        //定位
   long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//读写设备参数,读设备状态、控制设备
   unsigned int (*poll) (struct file *, struct poll_table_struct *);    //POLL机制,实现多路复用的支持
   int (*mmap) (struct file *, struct vm_area_struct *); //映射内核空间到用户层
   int (*fasync) (int, struct file *, int); //信号驱动
   //......
};

一般定义一个struct file_operations类型的全局变量并用自己实现各种操作函数名对其进行初始化


int cdev_add(struct cdev *p, dev_t dev, unsigned int count)
功能:将指定字符设备添加到内核
参数:
    p:指向被添加的设备
    dev:设备号
    count:设备数量,一般填1


void cdev_del(struct cdev *p)
功能:从内核中移除一个字符设备
参数:
    p:指向被移除的字符设备


字符设备驱动开发步骤:

1.  如果设备有自己的一些控制数据,则定义一个包含struct cdev cdev成员的结构体struct mydev,其它成员根据设备需求,设备简单则直接用struct cdev
2.  定义一个struct mydev或struct cdev的全局变量来表示本设备;也可以定义一个struct mydev或struct cdev的全局指针(记得在init时动态分配)
3.  定义三个全局变量分别来表示主设备号、次设备号、设备数
4.  定义一个struct file_operations结构体变量,其owner成员置成THIS_MODULE
5.  module init函数流程:a. 申请设备号 b. 如果是全局设备指针则动态分配代表本设备的结构体元素 c. 初始化struct cdev成员 d. 设置struct cdev的owner成员为THIS_MODULE  e. 添加字符设备到内核
6.  module exit函数:a. 注销设备号 b. 从内核中移除struct cdev  c. 如果如果是全局设备指针则释放其指向空间
7.  编写各个操作函数并将函数名初始化给struct file_operations结构体变量

验证操作步骤:

1. 编写驱动代码mychar.c
2. make生成ko文件
3. insmod内核模块
4. 查阅字符设备用到的设备号(主设备号):cat  /proc/devices  |  grep  申请设备号时用的名字
5. 创建设备文件(设备节点) : mknod   /dev/???   c   上一步查询到的主设备号    代码中指定初始次设备号
6. 编写app验证驱动(testmychar_app.c)
7. 编译运行app,dmesg命令查看内核打印信息


示例代码(注册字符设备)

mychar.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>

int major = 11;
int minor = 0;
int mychar_num = 1;

struct cdev mydev;

int mychar_open(struct inode* pnode, struct file* pfile)
{
	printk("mychar_open is called\n");
	return 0;
}

int mychar_close(struct inode* pnode, struct file* pfile)
{
	printk("mychar_close is called\n");
	return 0;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.release = mychar_close,
};

int __init mychar_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 申请设备号 */
	ret = register_chrdev_region(devno, mychar_num, "mychar");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
		if (ret) {
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}

	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&mydev, &myops);

	/* 给struct cdev对象添加到内核对应的数据结构里 */
	mydev.owner = THIS_MODULE;
	cdev_add(&mydev, devno, mychar_num);

	return 0;
}

void __exit mychar_exit(void)
{
	dev_t devno = MKDEV(major, minor);
	cdev_del(&mydev);
	unregister_chrdev_region(devno, mychar_num);
}


MODULE_LICENSE("GPL");

module_init(mychar_init);
module_exit(mychar_exit);

test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    int fd = -1;

    if (argc < 2) {
        printf("The argument is too few\n");
        return 1;
    }

    fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        printf("open %s failed\n", argv[1]);
        return 2;
    }

    close(fd);
    fd = -1;
    return 0;
}

测试结果

shrek@ubuntu16:~/share/mydrivercode$ sudo mknod /dev/mydev c 11 0
shrek@ubuntu16:~/share/mydrivercode$ sudo insmod ./mychar.ko
shrek@ubuntu16:~/share/mydrivercode$ ./a.out /dev/mydev 
shrek@ubuntu16:~/share/mydrivercode$ dmesg 
[11215.776355] mychar_open is called
[11215.776358] mychar_close is called

两个操作函数中常用的结构体

内核中记录文件元信息的结构体

struct inode
{
    //....
    dev_t  i_rdev; // 设备号
    struct cdev  *i_cdev; // 如果是字符设备才有此成员,指向对应设备驱动程序中的加入系统的struct cdev对象
    //....
}
/*
    1. 内核中每个该结构体对象对应着一个实际文件,一对一
    2. open一个文件时如果内核中该文件对应的inode对象已存在则不再创建,不存在才创建
    3. 内核中用此类型对象关联到对此文件的操作函数集(对设备而言就是关联到具体驱动代码)
*/

读写文件内容过程中用到的一些控制性数据组合而成的对象------文件操作引擎(文件操控器)

struct file
{
    //...
    mode_t f_mode; // 不同用户的操作权限,驱动一般不用
    loff_t f_pos; // position 数据位置指示器,需要控制数据开始读写位置的设备有用
    unsigned int f_flags; // open时的第二个参数flags存放在此,驱动中常用
    struct file_operations *f_op; // open时从struct inode中i_cdev的对应成员获得地址,驱动开发中用来协助理解工作原理,内核中使用
    void *private_data; // 本次打开文件的私有数据,驱动中常来在几个操作函数间传递共用数据
    struct dentry *f_dentry; // 驱动中一般不用,除非需要访问对应文件的inode,用法flip->f_dentry->d_inode
    int refcnt; // 引用计数,保存着该对象地址的位置个数,close时发现refcnt为0才会销毁该struct file对象
    //...
};
/*
    1. open函数被调用成功一次,则创建一个该对象,因此可以认为一个该类型的对象对应一次指定文件的操作
    2. open同一个文件多次,每次open都会创建一个该类型的对象
    3. 文件描述符数组中存放的地址指向该类型的对象
    4. 每个文件描述符都对应一个struct file对象的地址
*/

常用操作函数说明

int (*open) (struct inode *, struct file *);	//打开设备
/*
	指向函数一般用来对设备进行硬件上的初始化,对于一些简单的设备该函数只需要return 0,对应open系统调用,是open系统调用函数实现过程中调用的函数,
*/

int (*release) (struct inode *, struct file *);	//关闭设备
/*
	,指向函数一般用来对设备进行硬件上的关闭操作,对于一些简单的设备该函数只需要return 0,对应close系统调用,是close系统调用函数实现过程中调用的函数
*/

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);	//读设备
/*
	指向函数用来将设备产生的数据读到用户空间,对应read系统调用,是read系统调用函数实现过程中调用的函数
*/

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);    //写设备
/*
	指向函数用来将用户空间的数据写进设备,对应write系统调用,是write系统调用函数实现过程中调用的函数
*/

loff_t (*llseek) (struct file *, loff_t, int);		//数据操作位置的定位
/*
	指向函数用来获取或设置设备数据的开始操作位置(位置指示器),对应lseek系统调用,是lseek系统调用函数实现过程中调用的函数
*/


long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//读写设备参数,读设备状态、控制设备
/*
	指向函数用来获取、设置设备一些属性或设备的工作方式等非数据读写操作,对应ioctl系统调用,是ioctl系统调用函数实现过程中调用的函数
*/

unsigned int (*poll) (struct file *, struct poll_table_struct *);//POLL机制,实现对设备的多路复用方式的访问
/*
	指向函数用来协助多路复用机制完成对本设备可读、可写数据的监控,对应select、poll、epoll_wait系统调用,是select、poll、epoll_wait系统调用函数实现过程中调用的函数
*/
  
int (*fasync) (int, struct file *, int); //信号驱动
/*
	指向函数用来创建信号驱动机制的引擎,对应fcntl系统调用的FASYNC标记设置,是fcntl系统调用函数FASYNC标记设置过程中调用的函数
*/

读写操作实现

ssize_t xxx_read(struct file *filp, char __user *pbuf, size_t count, loff_t *ppos);
完成功能:读取设备产生的数据
参数:
    filp:指向open产生的struct file类型的对象,表示本次read对应的那次open
    pbuf:指向用户空间一块内存,用来保存读到的数据
    count:用户期望读取的字节数
    ppos:对于需要位置指示器控制的设备操作有用,用来指示读取的起始位置,读完后也需要变更位置指示器的指示位置
 返回值:
    本次成功读取的字节数,失败返回-1

内核空间拷贝到用户空间
put_user(x, ptr) // 宏
x:char、int类型的简单变量名

unsigned long copy_to_user (void __user * to, const void * from, unsigned long n)
成功为返回0,失败非0


ssize_t xxx_write (struct file *filp, const char __user *pbuf, size_t count, loff_t *ppos);  
完成功能:向设备写入数据
参数:
    filp:指向open产生的struct file类型的对象,表示本次write对应的那次open
    pbuf:指向用户空间一块内存,用来保存被写的数据
    count:用户期望写入的字节数
    ppos:对于需要位置指示器控制的设备操作有用,用来指示写入的起始位置,写完后也需要变更位置指示器的指示位置
 返回值:
    本次成功写入的字节数,失败返回-1

用户空间拷贝到内核空间
get_user(x, ptr) // 宏
x:char、int类型的简单变量名

unsigned long copy_from_user (void * to, const void __user * from, unsigned long n)
成功为返回0,失败非0


示例代码(读写操作)

mychar.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

#define BUF_LEN 100

int major = 11;
int minor = 0;
int mychar_num = 1;

struct cdev mydev;

char mydev_buf[BUF_LEN];
int curlen = 0;

int mychar_open(struct inode* pnode, struct file* pfile)
{
	printk("mychar_open is called\n");
	return 0;
}

int mychar_close(struct inode* pnode, struct file* pfile)
{
	printk("mychar_close is called\n");
	return 0;
}

ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
{
	int size = 0;
	int ret = 0;

	if (count > curlen) {
		size = curlen;
	} else {
		size = count;
	}

	ret = copy_to_user(puser, mydev_buf, size);
	if (ret) {
		printk("copy_to_user failed\n");
		return -1;
	}

	memcpy(mydev_buf, mydev_buf + size, curlen - size); // 将未读走的数据前移至buf头

	curlen = curlen - size;
	return size;
}

ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
{
	int size = 0;
	int ret = 0;

	if (count > BUF_LEN - curlen) {
		size = BUF_LEN - curlen;
	} else {
		size = count;
	}

	ret = copy_from_user(mydev_buf + curlen, puser, size);
	if (ret) {
		printk("copy_from_user failed\n");
		return -1;
	}

	curlen = curlen + size;
	return size;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.release = mychar_close,
	.read = mychar_read,
	.write = mychar_write,
};

int __init mychar_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 申请设备号 */
	ret = register_chrdev_region(devno, mychar_num, "mychar");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
		if (ret) {
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}

	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&mydev, &myops);

	/* 给struct cdev对象添加到内核对应的数据结构里 */
	mydev.owner = THIS_MODULE;
	cdev_add(&mydev, devno, mychar_num);

	return 0;
}

void __exit mychar_exit(void)
{
	dev_t devno = MKDEV(major, minor);
	cdev_del(&mydev);
	unregister_chrdev_region(devno, mychar_num);
}


MODULE_LICENSE("GPL");

module_init(mychar_init);
module_exit(mychar_exit);

test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    int fd = -1;
    char buf[8] = "";

    if (argc < 2) {
        printf("The argument is too few\n");
        return 1;
    }

    fd = open(argv[1], O_RDWR);
    if (fd < 0) {
        printf("open %s failed\n", argv[1]);
        return 2;
    }

    write(fd, "hello", 6);
    read(fd, buf, 8);
    printf("buf = %s\n",buf);

    close(fd);
    fd = -1;
    return 0;
}

测试结果

shrek@ubuntu16:~/share/mydrivercode$ sudo mknod /dev/mydev c 11 0
shrek@ubuntu16:~/share/mydrivercode$ sudo chmod a+w /dev/mydev
shrek@ubuntu16:~/share/mydrivercode$ sudo insmod ./mychar.ko
shrek@ubuntu16:~/share/mydrivercode$ ./a.out /dev/mydev 
buf = hello

避免使用全局变量的写法

已知成员的地址获得所在结构体变量的地址:container_of(成员地址, 结构体类型名, 成员在结构体中的名称)

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

#define BUF_LEN 100

int major = 11;
int minor = 0;
int mychar_num = 1;

struct mychar_dev
{
	struct cdev mydev;

	char mydev_buf[BUF_LEN];
	int curlen;
};

struct mychar_dev gmydev;

int mychar_open(struct inode* pnode, struct file* pfile)
{
	pfile->private_data = (void *)(container_of(pnode->i_cdev, struct mychar_dev, mydev));
	printk("mychar_open is called\n");
	return 0;
}

int mychar_close(struct inode* pnode, struct file* pfile)
{
	printk("mychar_close is called\n");
	return 0;
}

ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	if (count > pmydev->curlen) {
		size = pmydev->curlen;
	} else {
		size = count;
	}

	ret = copy_to_user(puser, pmydev->mydev_buf, size);
	if (ret) {
		printk("copy_to_user failed\n");
		return -1;
	}

	memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);

	pmydev->curlen -= size;
	return size;
}

ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	if (count > BUF_LEN - pmydev->curlen) {
		size = BUF_LEN - pmydev->curlen;
	} else {
		size = count;
	}

	ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
	if (ret) {
		printk("copy_from_user failed\n");
		return -1;
	}

	pmydev->curlen += size;
	return size;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.release = mychar_close,
	.read = mychar_read,
	.write = mychar_write,
};

int __init mychar_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 申请设备号 */
	ret = register_chrdev_region(devno, mychar_num, "mychar");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
		if (ret) {
			printk("get devno faile\n");
			return -1;
		}
		major = MAJOR(devno);
	}

	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&gmydev.mydev, &myops);

	/* 给struct cdev对象添加到内核对应的数据结构里 */
	gmydev.mydev.owner = THIS_MODULE;
	cdev_add(&gmydev.mydev, devno, mychar_num);

	return 0;
}

void __exit mychar_exit(void)
{
	dev_t devno = MKDEV(major, minor);
	cdev_del(&gmydev.mydev);
	unregister_chrdev_region(devno, mychar_num);
}


MODULE_LICENSE("GPL");

module_init(mychar_init);
module_exit(mychar_exit);

ioctl操作实现

long xxx_ioctl (struct file *filp, unsigned int cmd, unsigned long arg);
功能:对相应设备做指定的控制操作(各种属性的设置获取等等)
参数:
    filp:指向open产生的struct file类型的对象,表示本次ioctl对应的那次open
    cmd:用来表示做的是哪一个操作
    arg:和cmd配合用的参数
返回值:成功为0,失败-1

cmd组成

1. dir(direction),ioctl 命令访问模式(属性数据传输方向),占据 2 bit,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据;
2. type(device type),设备类型,占据 8 bit,在一些文献中翻译为 “幻数” 或者 “魔数”,可以为任意 char 型字符,例如 
   ‘a’、’b’、’c’ 等等,其主要作用是使 ioctl 命令有唯一的设备标识;
3. nr(number),命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增;
4. size,涉及到 ioctl 函数 第三个参数 arg ,占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度,如果在驱动的 ioctl 实现中不检查,通常可以忽略该参数;

#define _IOC(dir,type,nr,size) (((dir)<<_IOC_DIRSHIFT)| \
                               ((type)<<_IOC_TYPESHIFT)| \
                               ((nr)<<_IOC_NRSHIFT)| \
                               ((size)<<_IOC_SIZESHIFT))
/* used to create numbers */

// 定义不带参数的 ioctl 命令
#define _IO(type,nr)   _IOC(_IOC_NONE,(type),(nr),0)

//定义带读参数的ioctl命令(copy_to_user) size为类型名
#define _IOR(type,nr,size)  _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))

//定义带写参数的 ioctl 命令(copy_from_user) size为类型名
#define _IOW(type,nr,size)  _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

//定义带读写参数的 ioctl 命令 size为类型名
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

/* used to decode ioctl numbers */
#define _IOC_DIR(nr)        (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr)       (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr)     (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr)      (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)

示例代码(ioctl操作)

mychar.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

#include "mychar.h"

#define BUF_LEN 100

int major = 11;
int minor = 0;
int mychar_num = 1;

struct mychar_dev
{
	struct cdev mydev;

	char mydev_buf[BUF_LEN];
	int curlen;
};

struct mychar_dev gmydev;

int mychar_open(struct inode* pnode, struct file* pfile)
{
	pfile->private_data = (void *)(container_of(pnode->i_cdev, struct mychar_dev, mydev));
	printk("mychar_open is called\n");
	return 0;
}

int mychar_close(struct inode* pnode, struct file* pfile)
{
	printk("mychar_close is called\n");
	return 0;
}

ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	if (count > pmydev->curlen) {
		size = pmydev->curlen;
	} else {
		size = count;
	}

	ret = copy_to_user(puser, pmydev->mydev_buf, size);
	if (ret) {
		printk("copy_to_user failed\n");
		return -1;
	}

	memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);

	pmydev->curlen -= size;
	return size;
}

ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	if (count > BUF_LEN - pmydev->curlen) {
		size = BUF_LEN - pmydev->curlen;
	} else {
		size = count;
	}

	ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
	if (ret) {
		printk("copy_from_user failed\n");
		return -1;
	}

	pmydev->curlen += size;
	return size;
}

long mychar_ioctl(struct file* pfile, unsigned int cmd, unsigned long arg)
{
	int __user *pret = (int*)arg;
	int maxlen = BUF_LEN;
	int ret = 0;
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;

	switch (cmd) {
		case MYCHAR_IOCTL_GET_MAXLEN:
			ret = copy_to_user(pret, &maxlen, sizeof(int));
			if (ret) {
				printk("copy_to_user MAXLEN failed\n");
				return -1;
			}
			break;
		case MYCHAR_IOCTL_GET_CURLEN:
			ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));
			if (ret) {
				printk("copy_to_user CURLEN failed\n");
				return -1;
			}
			break;
		default:
			printk("The cmd is unknow\n");
			return -1;
	}
	return 0;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.release = mychar_close,
	.read = mychar_read,
	.write = mychar_write,
	.unlocked_ioctl = mychar_ioctl,
};

int __init mychar_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 申请设备号 */
	ret = register_chrdev_region(devno, mychar_num, "mychar");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
		if (ret) {
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}

	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&gmydev.mydev, &myops);

	/* 给struct cdev对象添加到内核对应的数据结构里 */
	gmydev.mydev.owner = THIS_MODULE;
	cdev_add(&gmydev.mydev, devno, mychar_num);

	return 0;
}

void __exit mychar_exit(void)
{
	dev_t devno = MKDEV(major, minor);
	cdev_del(&gmydev.mydev);
	unregister_chrdev_region(devno, mychar_num);
}


MODULE_LICENSE("GPL");

module_init(mychar_init);
module_exit(mychar_exit);

mychar.h

#ifndef MY_CHAR_H
#define MY_CHAR_H

#include <asm/ioctl.h>

#define MY_CHAR_MAGIC 'k'

#define MYCHAR_IOCTL_GET_MAXLEN _IOR(MY_CHAR_MAGIC,1,int*)
#define MYCHAR_IOCTL_GET_CURLEN _IOR(MY_CHAR_MAGIC,2,int*)


#endif

test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/ioctl.h>

#include "mychar.h"

int main(int argc, char* argv[])
{
    int fd = -1;
    char buf[8] = "";
    int max = 0;
    int cur = 0;

    if (argc < 2) {
        printf("The argument is too few\n");
        return 1;
    }

    fd = open(argv[1], O_RDWR);
    if (fd < 0) {
        printf("open %s failed\n", argv[1]);
        return 2;
    }

    ioctl(fd, MYCHAR_IOCTL_GET_MAXLEN, &max);
    printf("max len is %d\n", max);

    write(fd, "hello", 6);

    ioctl(fd, MYCHAR_IOCTL_GET_CURLEN, &cur);
	printf("cur len is %d\n",cur);

    read(fd, buf, 8);
    printf("buf = %s\n",buf);

    close(fd);
    fd = -1;
    return 0;
}

测试结果

shrek@ubuntu16:~/share/mydrivercode$ sudo mknod /dev/mydev c 11 0
shrek@ubuntu16:~/share/mydrivercode$ sudo insmod ./mychar.ko
shrek@ubuntu16:~/share/mydrivercode$ ./a.out /dev/mydev 
max len is 100
cur len is 6
buf = hello

printk

//日志级别
#define    KERN_EMERG    "<0>"    /* system is unusable            */
#define    KERN_ALERT    "<1>"    /* action must be taken immediately    */
#define    KERN_CRIT    "<2>"    /* critical conditions            */
#define    KERN_ERR    "<3>"    /* error conditions            */

#define    KERN_WARNING    "<4>"    /* warning conditions            */

#define    KERN_NOTICE    "<5>"    /* normal but significant condition    */
#define    KERN_INFO    "<6>"    /* informational            */
#define    KERN_DEBUG    "<7>"    /* debug-level messages            */

用法:printk(KERN_INFO"....",....)
printk(KERN_INFO"Hello World"); =====> printk("<6>""Hello World") ====> printk("<6>Hello World")

指定 dmesg 打印的内容:dmesg --level=emerg,alert,crit,err,warn,notice,info,debug

自定义标签:

#define HELLO_DEBUG
#undef PDEBUG
#ifdef HELLO_DEBUG
#define PDEBUG(fmt, args...) printk(KERN_DEBUG fmt, ##args)
#else
#define PDEBUG(fmt, args...)
#endif

阻塞和非阻塞

应用层:

方法一:open时由 O_NONBLOCK 指示read、write时是否阻塞

方法二:open以后可以由fcntl函数来改变是否阻塞:

flags = fcntl(fd, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);

驱动层:

通过等待队列

wait_queue_head_t //等待队列头数据类型

init_waitqueue_head(wait_queue_head_t *pwq) //初始化等待队列头
    
wait_event_interruptible(wq,condition)
/*
功能:条件不成立则让任务进入浅度睡眠,直到条件成立醒来
    wq:等待队列头
    condition:C语言表达式
返回:正常唤醒返回0,信号唤醒返回非0(此时读写操作函数应返回-ERESTARTSYS)
*/
        
wait_event(wq,condition) //深度睡眠

wake_up_interruptible(wait_queue_head_t *pwq)
        
wake_up(wait_queue_head_t *pwq)
    
    
/*
1. 读、写用不同的等待队列头rq、wq
2. 无数据可读、可写时调用wait_event_interruptible(rq、wq,条件)
3. 写入数据成功时唤醒rq,读出数据成功唤醒wq
*/

示例代码(阻塞和非阻塞)

mychar.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/uaccess.h>

#include "mychar.h"

#define BUF_LEN 100

int major = 11;
int minor = 0;
int mychar_num = 1;

struct mychar_dev
{
	struct cdev mydev;

	char mydev_buf[BUF_LEN];
	int curlen;

	wait_queue_head_t rq;
	wait_queue_head_t wq;
};

struct mychar_dev gmydev;

int mychar_open(struct inode* pnode, struct file* pfile)
{
	pfile->private_data = (void *)(container_of(pnode->i_cdev, struct mychar_dev, mydev));
	printk("mychar_open is called\n");
	return 0;
}

int mychar_close(struct inode* pnode, struct file* pfile)
{
	printk("mychar_close is called\n");
	return 0;
}

ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	if (pmydev->curlen <= 0) {
		if (pfile->f_flags & O_NONBLOCK) {
			// 非阻塞
			printk("O_NONBLOCK No Data Read\n");
			return -1;
		} else {
			// 阻塞
			ret = wait_event_interruptible(pmydev->rq, pmydev->curlen > 0);
			if (ret) {
				printk("Wake up by signal\n");
				return -ERESTARTSYS;
			}
		}
	}

	if (count > pmydev->curlen) {
		size = pmydev->curlen;
	} else {
		size = count;
	}

	ret = copy_to_user(puser, pmydev->mydev_buf, size);
	if (ret) {
		printk("copy_to_user failed\n");
		return -1;
	}

	memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);

	pmydev->curlen -= size;

	wake_up_interruptible(&pmydev->wq);

	return size;
}

ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	if (pmydev->curlen >= BUF_LEN) {
		if (pfile->f_flags & O_NONBLOCK) {
			printk("O_NONBLOCK Can not write data\n");
			return -1;
		} else {
			ret = wait_event_interruptible(pmydev->wq, pmydev->curlen < BUF_LEN);
			if (ret) {
				printk("Wake up by signal\n");
				return -ERESTARTSYS;
			}
		}
	}

	if (count > BUF_LEN - pmydev->curlen) {
		size = BUF_LEN - pmydev->curlen;
	} else {
		size = count;
	}

	ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
	if (ret) {
		printk("copy_from_user failed\n");
		return -1;
	}

	pmydev->curlen += size;

	wake_up_interruptible(&pmydev->rq);

	return size;
}

long mychar_ioctl(struct file* pfile, unsigned int cmd, unsigned long arg)
{
	int __user *pret = (int*)arg;
	int maxlen = BUF_LEN;
	int ret = 0;
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;

	switch (cmd) {
		case MYCHAR_IOCTL_GET_MAXLEN:
			ret = copy_to_user(pret, &maxlen, sizeof(int));
			if (ret) {
				printk("copy_to_user MAXLEN failed\n");
				return -1;
			}
			break;
		case MYCHAR_IOCTL_GET_CURLEN:
			ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));
			if (ret) {
				printk("copy_to_user CURLEN failed\n");
				return -1;
			}
			break;
		default:
			printk("The cmd is unknow\n");
			return -1;
	}
	return 0;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.release = mychar_close,
	.read = mychar_read,
	.write = mychar_write,
	.unlocked_ioctl = mychar_ioctl,
};

int __init mychar_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 申请设备号 */
	ret = register_chrdev_region(devno, mychar_num, "mychar");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
		if (ret) {
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}

	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&gmydev.mydev, &myops);

	/* 给struct cdev对象添加到内核对应的数据结构里 */
	gmydev.mydev.owner = THIS_MODULE;
	cdev_add(&gmydev.mydev, devno, mychar_num);

	init_waitqueue_head(&gmydev.rq);
	init_waitqueue_head(&gmydev.wq);
	return 0;
}

void __exit mychar_exit(void)
{
	dev_t devno = MKDEV(major, minor);
	cdev_del(&gmydev.mydev);
	unregister_chrdev_region(devno, mychar_num);
}


MODULE_LICENSE("GPL");

module_init(mychar_init);
module_exit(mychar_exit);

mychar.h同ioctl

test.c(测试非阻塞,如果测试阻塞则将 O_NONBLOCK 去掉即可)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/ioctl.h>

#include "mychar.h"

int main(int argc, char* argv[])
{
    int fd = -1;
    char buf[8] = "";
    int ret = 0;

    if (argc < 2) {
        printf("The argument is too few\n");
        return 1;
    }

    fd = open(argv[1], O_RDWR | O_NONBLOCK);
    if (fd < 0) {
        printf("open %s failed\n", argv[1]);
        return 2;
    }

    ret = read(fd, buf, 8);
    if (ret < 0) {
        printf("read data failed\n");
    } else {
        printf("buf = %s\n",buf);
    }

    close(fd);
    fd = -1;
    return 0;
}

测试结果

非阻塞:

shrek@ubuntu16:~/share/mydrivercode$ sudo mknod /dev/mydev c 11 0
shrek@ubuntu16:~/share/mydrivercode$ sudo chmod a+w /dev/mydev
shrek@ubuntu16:~/share/mydrivercode$ sudo insmod ./mychar.ko
shrek@ubuntu16:~/share/mydrivercode$ ./a.out /dev/mydev 
read data failed

阻塞:

执行测试代码一直阻塞,直至给设备写入数据结束阻塞

shrek@ubuntu16:~/share/mydrivercode$ echo "hello" > /dev/mydev
shrek@ubuntu16:~/share/mydrivercode$ ./a.out /dev/mydev 
buf = hello

多路复用

应用层

select:位运算实现,监控的描述符数量有限(32位机1024,64位机2048),效率差
poll:链表实现,监控的描述符数量不限,效率差
epoll:效率最高,监控的描述符数量不限

select:

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
/*	功能:监听多个描述符,阻塞等待有一个或者多个文件描述符,准备就绪。
		内核将没有准备就绪的文件描述符,从集合中清掉了。
	参数:  nfds			最大文件描述符数 ,加1
			readfds		读文件描述符集合
			writefds		写文件描述符集合
			exceptfds		其他异常的文件描述符集合
			timeout		超时时间(NULL)
	返回值:当timeout为NULL时返回0,成功:准备好的文件描述的个数  出错:-1 
		   当timeout不为NULL时,如超时设置为0,则select为非阻塞,超时设置 > 0,则无描述符可被操作的情况下阻塞指定长度的时间 
*/
void FD_CLR(int fd, fd_set *set);
//功能:将fd 从集合中清除掉

int  FD_ISSET(int fd, fd_set *set);
//功能:判断fd 是否存在于集合中
 
void FD_SET(int fd, fd_set *set);
//功能:将fd 添加到集合中

void FD_ZERO(fd_set *set);
//功能:将集合清零

//使用模型:

while(1)
{
    /*得到最大的描述符maxfd*/
    /*FD_ZERO清空描述符集合*/
	/*将被监控描述符加到相应集合rfds里  FD_SET*/
    /*设置超时*/
    ret = select(maxfd+1,&rfds,&wfds,NULL,NULL);
    if(ret < 0)
    {
        if(errno == EINTR)//错误时信号引起的
        {
        	continue;   
        }
        else
        {
            break;
        }
    }
    else if(ret == 0)
    {//超时
        //.....
    }
    else
    { //> 0 ret为可被操作的描述符个数
        if(FD_ISSET(fd1,&rfds))
        {//读数据
            //....
        }
        if(FD_ISSET(fd2,&rfds))
        {//读数据
            //....
        }
        ///.....
        if(FD_ISSET(fd1,&wfds))
        {//写数据
            //....
        }
    }
}

驱动层

void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p);
/*功能:将等待队列头添加至poll_table表中
  参数:struct file :设备文件
  Wait_queue_head_t :等待队列头
  Poll_table :poll_table表
*/

/*该函数与select、poll、epoll_wait函数相对应,协助这些多路监控函数判断本设备是否有数据可读写*/
unsigned int xxx_poll(struct file *filp, poll_table *wait) //函数名初始化给struct file_operations的成员.poll
{
    unsigned int mask = 0;
    /*
    	1. 将所有等待队列头加入poll_table表中
    	2. 判断是否可读,如可读则mask |= POLLIN | POLLRDNORM;
    	3. 判断是否可写,如可写则mask |= POLLOUT | POLLWRNORM;
    */
    
    return mask;
}

示例代码(select)

mychar.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/uaccess.h>

#include "mychar.h"

#define BUF_LEN 100

int major = 11;
int minor = 0;
int mychar_num = 1;

struct mychar_dev
{
	struct cdev mydev;

	char mydev_buf[BUF_LEN];
	int curlen;

	wait_queue_head_t rq;
	wait_queue_head_t wq;
};

struct mychar_dev gmydev;

int mychar_open(struct inode* pnode, struct file* pfile)
{
	pfile->private_data = (void *)(container_of(pnode->i_cdev, struct mychar_dev, mydev));
	printk("mychar_open is called\n");
	return 0;
}

int mychar_close(struct inode* pnode, struct file* pfile)
{
	printk("mychar_close is called\n");
	return 0;
}

ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	if (pmydev->curlen <= 0) {
		if (pfile->f_flags & O_NONBLOCK) {
			// 非阻塞
			printk("O_NONBLOCK No Data Read\n");
			return -1;
		} else {
			// 阻塞
			ret = wait_event_interruptible(pmydev->rq, pmydev->curlen > 0);
			if (ret) {
				printk("Wake up by signal\n");
				return -ERESTARTSYS;
			}
		}
	}

	if (count > pmydev->curlen) {
		size = pmydev->curlen;
	} else {
		size = count;
	}

	ret = copy_to_user(puser, pmydev->mydev_buf, size);
	if (ret) {
		printk("copy_to_user failed\n");
		return -1;
	}

	memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);

	pmydev->curlen -= size;

	wake_up_interruptible(&pmydev->wq);

	return size;
}

ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	if (pmydev->curlen >= BUF_LEN) {
		if (pfile->f_flags & O_NONBLOCK) {
			printk("O_NONBLOCK Can not write data\n");
			return -1;
		} else {
			ret = wait_event_interruptible(pmydev->wq, pmydev->curlen < BUF_LEN);
			if (ret) {
				printk("Wake up by signal\n");
				return -ERESTARTSYS;
			}
		}
	}

	if (count > BUF_LEN - pmydev->curlen) {
		size = BUF_LEN - pmydev->curlen;
	} else {
		size = count;
	}

	ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
	if (ret) {
		printk("copy_from_user failed\n");
		return -1;
	}

	pmydev->curlen += size;

	wake_up_interruptible(&pmydev->rq);

	return size;
}

long mychar_ioctl(struct file* pfile, unsigned int cmd, unsigned long arg)
{
	int __user *pret = (int*)arg;
	int maxlen = BUF_LEN;
	int ret = 0;
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;

	switch (cmd) {
		case MYCHAR_IOCTL_GET_MAXLEN:
			ret = copy_to_user(pret, &maxlen, sizeof(int));
			if (ret) {
				printk("copy_to_user MAXLEN failed\n");
				return -1;
			}
			break;
		case MYCHAR_IOCTL_GET_CURLEN:
			ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));
			if (ret) {
				printk("copy_to_user CURLEN failed\n");
				return -1;
			}
			break;
		default:
			printk("The cmd is unknow\n");
			return -1;
	}
	return 0;
}

unsigned int mychar_poll(struct file* pfile, poll_table* ptb)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	unsigned int mask = 0;

	poll_wait(pfile, &pmydev->rq, ptb);
	poll_wait(pfile, &pmydev->wq, ptb);

	if (pmydev->curlen > 0) {
		mask |= POLLIN | POLLRDNORM;
	}

	if (pmydev->curlen < BUF_LEN) {
		mask |= POLLOUT | POLLWRNORM;
	}

	return mask;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.release = mychar_close,
	.read = mychar_read,
	.write = mychar_write,
	.unlocked_ioctl = mychar_ioctl,
	.poll = mychar_poll,
};

int __init mychar_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 申请设备号 */
	ret = register_chrdev_region(devno, mychar_num, "mychar");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
		if (ret) {
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}

	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&gmydev.mydev, &myops);

	/* 给struct cdev对象添加到内核对应的数据结构里 */
	gmydev.mydev.owner = THIS_MODULE;
	cdev_add(&gmydev.mydev, devno, mychar_num);

	init_waitqueue_head(&gmydev.rq);
	init_waitqueue_head(&gmydev.wq);
	return 0;
}

void __exit mychar_exit(void)
{
	dev_t devno = MKDEV(major, minor);
	cdev_del(&gmydev.mydev);
	unregister_chrdev_region(devno, mychar_num);
}


MODULE_LICENSE("GPL");

module_init(mychar_init);
module_exit(mychar_exit);

mychar.h同ioctl

test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>

#include "mychar.h"

int main(int argc, char* argv[])
{
    int fd = -1;
    char buf[8] = "";
    int ret = 0;
    fd_set rfds;

    if (argc < 2) {
        printf("The argument is too few\n");
        return 1;
    }

    fd = open(argv[1], O_RDWR);
    if (fd < 0) {
        printf("open %s failed\n", argv[1]);
        return 2;
    }

    while (1) {
        FD_ZERO(&rfds);
        FD_SET(fd, &rfds);
        ret = select(fd + 1, &rfds, NULL, NULL, NULL);
        if (ret < 0) {
            if (errno == EINTR) {
                continue;
            } else {
                printf("select error\n");
                break;
            }
        }
        if (FD_ISSET(fd, &rfds)) {
            read(fd, buf, 8);
            printf("buf = %s\n", buf);
        }
    }

    close(fd);
    fd = -1;
    return 0;
}

测试结果

执行测试代码后一直等待是否有数据可读,有则读取数据

shrek@ubuntu16:~/share/mydrivercode$ echo "hello" > /dev/mydev 
shrek@ubuntu16:~/share/mydrivercode$ echo "hello" > /dev/mydev 
shrek@ubuntu16:~/share/mydrivercode$ echo "hello" > /dev/mydev 
shrek@ubuntu16:~/share/mydrivercode$ echo "hello" > /dev/mydev
shrek@ubuntu16:~/share/mydrivercode$ ./a.out /dev/mydev 
buf = hello

buf = hello

buf = hello

buf = hello

信号驱动

Tips

信号驱动存在丢失信号的风险

应用层:信号注册+fcntl

signal(SIGIO, input_handler); //注册信号处理函数

fcntl(fd, F_SETOWN, getpid());//将描述符设置给对应进程,好由描述符获知PID

oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, oflags | FASYNC);//将该设备的IO模式设置成信号驱动模式

void input_handler(int signum)//应用自己实现的信号处理函数,在此函数中完成读写
{
    //读数据
}

//应用模板
int main()
{
	int fd = open("/dev/xxxx",O_RDONLY);

	fcntl(fd, F_SETOWN, getpid());

	oflags = fcntl(fd, F_GETFL);
	fcntl(fd, F_SETFL, oflags | FASYNC);

	signal(SIGIO,xxxx_handler);

	//......
}
    
void xxxx_handle(int signo)
{//读写数据
    
}

驱动层:实现fasync函数

/*设备结构中添加如下成员*/
struct fasync_struct *pasync_obj;

/*应用调用fcntl设置FASYNC时调用该函数产生异步通知结构对象,并将其地址设置到设备结构成员中*/
static int hello_fasync(int fd, struct file *filp, int mode) //函数名初始化给struct file_operations的成员.fasync
{
	struct hello_device *dev = filp->private_data; 
	return fasync_helper(fd, filp, mode, &dev->pasync_obj);
}

/*写函数中有数据可读时向应用层发信号*/
if (dev->pasync_obj)
       kill_fasync(&dev->pasync_obj, SIGIO, POLL_IN);
       
/*release函数中释放异步通知结构对象*/
if (dev->pasync_obj) 
	fasync_helper(-1, filp, 0, &dev->pasync_obj);

int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **pp);
/*
	功能:产生或释放异步通知结构对象
	参数:
	返回值:成功为>=0,失败负数
*/

void kill_fasync(struct fasync_struct **, int, int);
/*	
	功能:发信号
	参数:
		struct fasync_struct ** 指向保存异步通知结构地址的指针
		int 	信号 SIGIO/SIGKILL/SIGCHLD/SIGCONT/SIGSTOP
		int 	读写信息POLLIN、POLLOUT
*/

示例代码(信号驱动)

mychar.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/uaccess.h>

#include "mychar.h"

#define BUF_LEN 100

int major = 11;
int minor = 0;
int mychar_num = 1;

struct mychar_dev
{
	struct cdev mydev;

	char mydev_buf[BUF_LEN];
	int curlen;

	wait_queue_head_t rq;
	wait_queue_head_t wq;

	struct fasync_struct* pasync_obj;
};

struct mychar_dev gmydev;

int mychar_open(struct inode* pnode, struct file* pfile)
{
	pfile->private_data = (void *)(container_of(pnode->i_cdev, struct mychar_dev, mydev));
	return 0;
}

int mychar_close(struct inode* pnode, struct file* pfile)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	if (pmydev->pasync_obj != NULL) {
		fasync_helper(-1, pfile, 0, &pmydev->pasync_obj);
	}
	return 0;
}

ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	if (pmydev->curlen <= 0) {
		if (pfile->f_flags & O_NONBLOCK) {
			// 非阻塞
			printk("O_NONBLOCK No Data Read\n");
			return -1;
		} else {
			// 阻塞
			ret = wait_event_interruptible(pmydev->rq, pmydev->curlen > 0);
			if (ret) {
				printk("Wake up by signal\n");
				return -ERESTARTSYS;
			}
		}
	}

	if (count > pmydev->curlen) {
		size = pmydev->curlen;
	} else {
		size = count;
	}

	ret = copy_to_user(puser, pmydev->mydev_buf, size);
	if (ret) {
		printk("copy_to_user failed\n");
		return -1;
	}

	memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);

	pmydev->curlen -= size;

	wake_up_interruptible(&pmydev->wq);

	return size;
}

ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	if (pmydev->curlen >= BUF_LEN) {
		if (pfile->f_flags & O_NONBLOCK) {
			printk("O_NONBLOCK Can not write data\n");
			return -1;
		} else {
			ret = wait_event_interruptible(pmydev->wq, pmydev->curlen < BUF_LEN);
			if (ret) {
				printk("Wake up by signal\n");
				return -ERESTARTSYS;
			}
		}
	}

	if (count > BUF_LEN - pmydev->curlen) {
		size = BUF_LEN - pmydev->curlen;
	} else {
		size = count;
	}

	ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
	if (ret) {
		printk("copy_from_user failed\n");
		return -1;
	}

	pmydev->curlen += size;

	wake_up_interruptible(&pmydev->rq);

	if (pmydev->pasync_obj != NULL) {
		kill_fasync(&pmydev->pasync_obj, SIGIO, POLL_IN);
	}

	return size;
}

long mychar_ioctl(struct file* pfile, unsigned int cmd, unsigned long arg)
{
	int __user *pret = (int*)arg;
	int maxlen = BUF_LEN;
	int ret = 0;
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;

	switch (cmd) {
		case MYCHAR_IOCTL_GET_MAXLEN:
			ret = copy_to_user(pret, &maxlen, sizeof(int));
			if (ret) {
				printk("copy_to_user MAXLEN failed\n");
				return -1;
			}
			break;
		case MYCHAR_IOCTL_GET_CURLEN:
			ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));
			if (ret) {
				printk("copy_to_user CURLEN failed\n");
				return -1;
			}
			break;
		default:
			printk("The cmd is unknow\n");
			return -1;
	}
	return 0;
}

unsigned int mychar_poll(struct file* pfile, poll_table* ptb)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	unsigned int mask = 0;

	poll_wait(pfile, &pmydev->rq, ptb);
	poll_wait(pfile, &pmydev->wq, ptb);

	if (pmydev->curlen > 0) {
		mask |= POLLIN | POLLRDNORM;
	}

	if (pmydev->curlen < BUF_LEN) {
		mask |= POLLOUT | POLLWRNORM;
	}

	return mask;
}

int mychar_fasync(int fd, struct file* pfile, int mode) {
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	return fasync_helper(fd, pfile, mode, &pmydev->pasync_obj);
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.release = mychar_close,
	.read = mychar_read,
	.write = mychar_write,
	.unlocked_ioctl = mychar_ioctl,
	.poll = mychar_poll,
	.fasync = mychar_fasync,
};

int __init mychar_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 申请设备号 */
	ret = register_chrdev_region(devno, mychar_num, "mychar");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
		if (ret) {
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}

	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&gmydev.mydev, &myops);

	/* 给struct cdev对象添加到内核对应的数据结构里 */
	gmydev.mydev.owner = THIS_MODULE;
	cdev_add(&gmydev.mydev, devno, mychar_num);

	init_waitqueue_head(&gmydev.rq);
	init_waitqueue_head(&gmydev.wq);
	return 0;
}

void __exit mychar_exit(void)
{
	dev_t devno = MKDEV(major, minor);
	cdev_del(&gmydev.mydev);
	unregister_chrdev_region(devno, mychar_num);
}


MODULE_LICENSE("GPL");

module_init(mychar_init);
module_exit(mychar_exit);

mychar.h同ioctl

test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <signal.h>

#include "mychar.h"

int fd = -1;

void sigio_handler(int signo);

int main(int argc, char* argv[])
{
    int flg = 0;

    if (argc < 2) {
        printf("The argument is too few\n");
        return 1;
    }

    signal(SIGIO, sigio_handler);

    fd = open(argv[1], O_RDWR);
    if (fd < 0) {
        printf("open %s failed\n", argv[1]);
        return 2;
    }

    fcntl(fd, F_SETOWN, getpid());
    flg = fcntl(fd, F_GETFL);
    flg |= FASYNC;
    fcntl(fd, F_SETFL, flg);

    while (1) {}

    close(fd);
    fd = -1;
    return 0;
}

void sigio_handler(int signo)
{
    char buf[8] = "";
    read(fd, buf, 8);
    printf("buf = %s\n", buf);
}

测试结果同多路复用


并发控制

上下文和并发场合

执行流:有开始有结束总体顺序执行的一段代码  又称上下文

应用编程:任务上下文
内核编程:

1. 任务上下文:五状态 可阻塞
   a. 应用进程或线程运行在用户空间
   b. 应用进程或线程运行在内核空间(通过调用syscall来间接使用内核空间)
   c. 内核线程始终在内核空间
2. 异常上下文:不可阻塞
   中断上下文

竞态:多任务并行执行时,如果在一个时刻同时操作同一个资源,会引起资源的错乱,这种错乱情形被称为竞态

共享资源:可能会被多个任务同时使用的资源

临界区:操作共享资源的代码段

为了解决竞态,需要提供一种控制机制,来避免在同一时刻使用共享资源,这种机制被称为并发控制机制

并发控制机制分类:

1. 原子操作类
2. 忙等待类
3. 阻塞类

通用并发控制机制的一般使用套路:

/*互斥问题:*/
并发控制机制初始化为可用
P操作

临界区

V操作

/*同步问题:*/
// 并发控制机制初始化为不可用
// 先行方:
......
V操作
    
// 后行方:
P操作
......

中断屏蔽

一种同步机制的辅助手段

禁止本cpu中断                   使能本cpu中断
local_irq_disable();            local_irq_enable();            
local_irq_save(flags);        local_irq_restore(flags);    与cpu的中断位相关
local_bh_disable();            local_bh_enable();            与中断低半部有关,关闭、打开软中断

禁止中断
临界区    //临界区代码不能占用太长时间,需要很快完成
打开中断

适用场合:中断上下文与某任务共享资源时,或多个不同优先级的中断上下文间共享资源时

原子变量

原子变量:存取不可被打断的特殊整型变量
a.设置原子量的值
void atomic_set(atomic_t *v,int i);    // 设置原子量的值为i
atomic_t v = ATOMIC_INIT(0);        // 定义原子变量v并初始化为0

v = 10;//错误

b.获取原子量的值
atomic_read(atomic_t *v);               // 返回原子量的值

c.原子变量加减
void atomic_add(int i,atomic_t *v);  // 原子变量增加i
void atomic_sub(int i,atomic_t *v);  // 原子变量减少i

d.原子变量自增自减
void atomic_inc(atomic_t *v);         // 原子变量增加1
void atomic_dec(atomic_t *v);        // 原子变量减少1

e.操作并测试:运算后结果为0则返回真,否则返回假
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i,atomic_t *v);

原子位操作方法:
a.设置位
void set_bit(nr, void *addr);            // 设置addr的第nr位为1
b.清除位
void clear_bit(nr , void *addr);        // 清除addr的第nr位为0
c.改变位
void change_bit(nr , void *addr);    // 改变addr的第nr位为1
d.测试位
void test_bit(nr , void *addr);           // 测试addr的第nr位是否为1

适用场合:共享资源为单个整型变量的互斥场合


示例代码(原子变量,使设备文件同一时间只能打开一次)

openonce.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/uaccess.h>
#include <asm/atomic.h>

int major = 11;
int minor = 0;
int openonce_num = 1;

struct openonce_dev
{
	struct cdev mydev;

    atomic_t openflag;
};

struct openonce_dev gmydev;

int openonce_open(struct inode* pnode, struct file* pfile)
{
	struct openonce_dev* pmydev = NULL;
    pfile->private_data = (void *)(container_of(pnode->i_cdev, struct openonce_dev, mydev));
	pmydev = (struct openonce_dev*)pfile->private_data;

    if (atomic_dec_and_test(&pmydev->openflag)) {
        return 0;
    } else {
        atomic_inc(&pmydev->openflag);
        printk("The device is opened already\n");
        return -1;
    }
    
    return 0;
}

int openonce_close(struct inode* pnode, struct file* pfile)
{
	struct openonce_dev *pmydev = (struct openonce_dev *)pfile->private_data;
	atomic_set(&pmydev->openflag, 1);
	return 0;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = openonce_open,
	.release = openonce_close,
};

int __init openonce_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 申请设备号 */
	ret = register_chrdev_region(devno, openonce_num, "openonce");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, openonce_num, "openonce");
		if (ret) {
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}

	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&gmydev.mydev, &myops);

	/* 给struct cdev对象添加到内核对应的数据结构里 */
	gmydev.mydev.owner = THIS_MODULE;
	cdev_add(&gmydev.mydev, devno, openonce_num);

	atomic_set(&gmydev.openflag, 1);
	return 0;
}

void __exit openonce_exit(void)
{
	dev_t devno = MKDEV(major, minor);
	cdev_del(&gmydev.mydev);
	unregister_chrdev_region(devno, openonce_num);
}


MODULE_LICENSE("GPL");

module_init(openonce_init);
module_exit(openonce_exit);

test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/ioctl.h>

int main(int argc, char* argv[])
{
    int fd = -1;

    if (argc < 2) {
        printf("The argument is too few\n");
        return 1;
    }

    fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        printf("open %s failed\n", argv[1]);
        return 2;
    }

    while (1) {}

    close(fd);
    fd = -1;
    return 0;
}

测试结果

第一次打开设备文件成功,第二次打开设备文件失败


自旋锁:基于忙等待的并发控制机制

a.定义自旋锁
spinlock_t  lock;

b.初始化自旋锁
spin_lock_init(spinlock_t *);

c.获得自旋锁
spin_lock(spinlock_t *);    //成功获得自旋锁立即返回,否则自旋在那里直到该自旋锁的保持者释放

spin_trylock(spinlock_t *);    //成功获得自旋锁立即返回真,否则返回假,而不是像上一个那样"在原地打转”

d.释放自旋锁
spin_unlock(spinlock_t *);

#include <linux/spinlock.h>
定义spinlock_t类型的变量lock
spin_lock_init(&lock)后才能正常使用spinlock


spin_lock(&lock);
临界区
spin_unlock(&lock);

适用场合:

1. 异常上下文之间或异常上下文与任务上下文之间共享资源时
2. 任务上下文之间且临界区执行时间很短时
3. 互斥问题


示例代码(自旋锁)

openonce.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/uaccess.h>
#include <asm/atomic.h>

int major = 11;
int minor = 0;
int openonce_num = 1;

struct openonce_dev
{
	struct cdev mydev;

    int openflag;
	spinlock_t lock;
};

struct openonce_dev gmydev;

int openonce_open(struct inode* pnode, struct file* pfile)
{
	struct openonce_dev* pmydev = NULL;
    pfile->private_data = (void *)(container_of(pnode->i_cdev, struct openonce_dev, mydev));
	pmydev = (struct openonce_dev*)pfile->private_data;

	spin_lock(&pmydev->lock);
    if (pmydev->openflag) {
        pmydev->openflag = 0;
		spin_unlock(&pmydev->lock);
		return 0;
    } else {
        spin_unlock(&pmydev->lock);
        printk("The device is opened already\n");
        return -1;
    }
}

int openonce_close(struct inode* pnode, struct file* pfile)
{
	struct openonce_dev *pmydev = (struct openonce_dev *)pfile->private_data;
	spin_lock(&pmydev->lock);
	pmydev->openflag = 1;
	spin_unlock(&pmydev->lock);
	return 0;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = openonce_open,
	.release = openonce_close,
};

int __init openonce_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 申请设备号 */
	ret = register_chrdev_region(devno, openonce_num, "openonce");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, openonce_num, "openonce");
		if (ret) {
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}

	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&gmydev.mydev, &myops);

	/* 给struct cdev对象添加到内核对应的数据结构里 */
	gmydev.mydev.owner = THIS_MODULE;
	cdev_add(&gmydev.mydev, devno, openonce_num);

	gmydev.openflag = 1;

	spin_lock_init(&gmydev.lock);
	return 0;
}

void __exit openonce_exit(void)
{
	dev_t devno = MKDEV(major, minor);
	cdev_del(&gmydev.mydev);
	unregister_chrdev_region(devno, openonce_num);
}


MODULE_LICENSE("GPL");

module_init(openonce_init);
module_exit(openonce_exit);

test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/ioctl.h>

int main(int argc, char* argv[])
{
    int fd = -1;

    if (argc < 2) {
        printf("The argument is too few\n");
        return 1;
    }

    fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        printf("open %s failed\n", argv[1]);
        return 2;
    }

    while (1) {}

    close(fd);
    fd = -1;
    return 0;
}

测试结果

第一次打开设备文件成功,第二次打开设备文件失败


信号量:基于阻塞的并发控制机制

a.定义信号量
struct semaphore sem;

b.初始化信号量
void sema_init(struct semaphore *sem, int val);

c.获得信号量P
int down(struct semaphore *sem);//深度睡眠

int down_interruptible(struct semaphore *sem);//浅度睡眠

d.释放信号量V
void up(struct semaphore *sem);


#include <linux/semaphore.h>
 

适用场合:任务上下文之间且临界区执行时间较长时的互斥或同步问题


示例代码(信号量)

mychar.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/uaccess.h>

#include "mychar.h"

#define BUF_LEN 100

int major = 11;
int minor = 0;
int mychar_num = 1;

struct mychar_dev
{
	struct cdev mydev;

	char mydev_buf[BUF_LEN];
	int curlen;
	struct semaphore sem;

	wait_queue_head_t rq;
	wait_queue_head_t wq;

	struct fasync_struct* pasync_obj;
};

struct mychar_dev gmydev;

int mychar_open(struct inode* pnode, struct file* pfile)
{
	pfile->private_data = (void *)(container_of(pnode->i_cdev, struct mychar_dev, mydev));
	return 0;
}

int mychar_close(struct inode* pnode, struct file* pfile)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	if (pmydev->pasync_obj != NULL) {
		fasync_helper(-1, pfile, 0, &pmydev->pasync_obj);
	}
	return 0;
}

ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	down(&pmydev->sem);
	if (pmydev->curlen <= 0) {
		if (pfile->f_flags & O_NONBLOCK) {
			// 非阻塞
			up(&pmydev->sem);
			printk("O_NONBLOCK No Data Read\n");
			return -1;
		} else {
			// 阻塞
			up(&pmydev->sem);
			ret = wait_event_interruptible(pmydev->rq, pmydev->curlen > 0);
			if (ret) {
				printk("Wake up by signal\n");
				return -ERESTARTSYS;
			}
			down(&pmydev->sem);
		}
	}

	if (count > pmydev->curlen) {
		size = pmydev->curlen;
	} else {
		size = count;
	}

	ret = copy_to_user(puser, pmydev->mydev_buf, size);
	if (ret) {
		up(&pmydev->sem);
		printk("copy_to_user failed\n");
		return -1;
	}

	memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);

	pmydev->curlen -= size;

	up(&pmydev->sem);
	wake_up_interruptible(&pmydev->wq);

	return size;
}

ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	down(&pmydev->sem);
	if (pmydev->curlen >= BUF_LEN) {
		if (pfile->f_flags & O_NONBLOCK) {
			up(&pmydev->sem);
			printk("O_NONBLOCK Can not write data\n");
			return -1;
		} else {
			up(&pmydev->sem);
			ret = wait_event_interruptible(pmydev->wq, pmydev->curlen < BUF_LEN);
			if (ret) {
				printk("Wake up by signal\n");
				return -ERESTARTSYS;
			}
			down(&pmydev->sem);
		}
	}

	if (count > BUF_LEN - pmydev->curlen) {
		size = BUF_LEN - pmydev->curlen;
	} else {
		size = count;
	}

	ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
	if (ret) {
		up(&pmydev->sem);
		printk("copy_from_user failed\n");
		return -1;
	}

	pmydev->curlen += size;

	up(&pmydev->sem);

	wake_up_interruptible(&pmydev->rq);

	if (pmydev->pasync_obj != NULL) {
		kill_fasync(&pmydev->pasync_obj, SIGIO, POLL_IN);
	}

	return size;
}

long mychar_ioctl(struct file* pfile, unsigned int cmd, unsigned long arg)
{
	int __user *pret = (int*)arg;
	int maxlen = BUF_LEN;
	int ret = 0;
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;

	switch (cmd) {
		case MYCHAR_IOCTL_GET_MAXLEN:
			ret = copy_to_user(pret, &maxlen, sizeof(int));
			if (ret) {
				printk("copy_to_user MAXLEN failed\n");
				return -1;
			}
			break;
		case MYCHAR_IOCTL_GET_CURLEN:
			down(&pmydev->sem);
			ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));
			up(&pmydev->sem);
			if (ret) {
				printk("copy_to_user CURLEN failed\n");
				return -1;
			}
			break;
		default:
			printk("The cmd is unknow\n");
			return -1;
	}
	return 0;
}

unsigned int mychar_poll(struct file* pfile, poll_table* ptb)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	unsigned int mask = 0;

	poll_wait(pfile, &pmydev->rq, ptb);
	poll_wait(pfile, &pmydev->wq, ptb);

	down(&pmydev->sem);
	if (pmydev->curlen > 0) {
		mask |= POLLIN | POLLRDNORM;
	}

	if (pmydev->curlen < BUF_LEN) {
		mask |= POLLOUT | POLLWRNORM;
	}
	up(&pmydev->sem);

	return mask;
}

int mychar_fasync(int fd, struct file* pfile, int mode) {
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	return fasync_helper(fd, pfile, mode, &pmydev->pasync_obj);
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.release = mychar_close,
	.read = mychar_read,
	.write = mychar_write,
	.unlocked_ioctl = mychar_ioctl,
	.poll = mychar_poll,
	.fasync = mychar_fasync,
};

int __init mychar_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 申请设备号 */
	ret = register_chrdev_region(devno, mychar_num, "mychar");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
		if (ret) {
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}

	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&gmydev.mydev, &myops);

	/* 给struct cdev对象添加到内核对应的数据结构里 */
	gmydev.mydev.owner = THIS_MODULE;
	cdev_add(&gmydev.mydev, devno, mychar_num);

	init_waitqueue_head(&gmydev.rq);
	init_waitqueue_head(&gmydev.wq);

	sema_init(&gmydev.sem, 1);
	return 0;
}

void __exit mychar_exit(void)
{
	dev_t devno = MKDEV(major, minor);
	cdev_del(&gmydev.mydev);
	unregister_chrdev_region(devno, mychar_num);
}


MODULE_LICENSE("GPL");

module_init(mychar_init);
module_exit(mychar_exit);

互斥锁:基于阻塞的互斥机制

a.初始化
struct mutex  my_mutex;
mutex_init(&my_mutex);

b.获取互斥体
void  mutex_lock(struct mutex *lock);

c.释放互斥体
void mutex_unlock(struct mutex *lock);

1. 定义对应类型的变量
2. 初始化对应变量

P/加锁
临界区
V/解锁


#include <linux/mutex.h>
 

适用场合:任务上下文之间且临界区执行时间较长时的互斥问题


示例代码(互斥锁)

mychar.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/uaccess.h>

#include "mychar.h"

#define BUF_LEN 100

int major = 11;
int minor = 0;
int mychar_num = 1;

struct mychar_dev
{
	struct cdev mydev;

	char mydev_buf[BUF_LEN];
	int curlen;
	struct mutex lock;

	wait_queue_head_t rq;
	wait_queue_head_t wq;

	struct fasync_struct* pasync_obj;
};

struct mychar_dev gmydev;

int mychar_open(struct inode* pnode, struct file* pfile)
{
	pfile->private_data = (void *)(container_of(pnode->i_cdev, struct mychar_dev, mydev));
	return 0;
}

int mychar_close(struct inode* pnode, struct file* pfile)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	if (pmydev->pasync_obj != NULL) {
		fasync_helper(-1, pfile, 0, &pmydev->pasync_obj);
	}
	return 0;
}

ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	mutex_lock(&pmydev->lock);
	if (pmydev->curlen <= 0) {
		if (pfile->f_flags & O_NONBLOCK) {
			// 非阻塞
			mutex_unlock(&pmydev->lock);
			printk("O_NONBLOCK No Data Read\n");
			return -1;
		} else {
			// 阻塞
			mutex_unlock(&pmydev->lock);
			ret = wait_event_interruptible(pmydev->rq, pmydev->curlen > 0);
			if (ret) {
				printk("Wake up by signal\n");
				return -ERESTARTSYS;
			}
			mutex_lock(&pmydev->lock);
		}
	}

	if (count > pmydev->curlen) {
		size = pmydev->curlen;
	} else {
		size = count;
	}

	ret = copy_to_user(puser, pmydev->mydev_buf, size);
	if (ret) {
		mutex_unlock(&pmydev->lock);
		printk("copy_to_user failed\n");
		return -1;
	}

	memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);

	pmydev->curlen -= size;

	mutex_unlock(&pmydev->lock);
	wake_up_interruptible(&pmydev->wq);

	return size;
}

ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	mutex_lock(&pmydev->lock);
	if (pmydev->curlen >= BUF_LEN) {
		if (pfile->f_flags & O_NONBLOCK) {
			mutex_unlock(&pmydev->lock);
			printk("O_NONBLOCK Can not write data\n");
			return -1;
		} else {
			mutex_unlock(&pmydev->lock);
			ret = wait_event_interruptible(pmydev->wq, pmydev->curlen < BUF_LEN);
			if (ret) {
				printk("Wake up by signal\n");
				return -ERESTARTSYS;
			}
			mutex_lock(&pmydev->lock);
		}
	}

	if (count > BUF_LEN - pmydev->curlen) {
		size = BUF_LEN - pmydev->curlen;
	} else {
		size = count;
	}

	ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
	if (ret) {
		mutex_unlock(&pmydev->lock);
		printk("copy_from_user failed\n");
		return -1;
	}

	pmydev->curlen += size;

	mutex_unlock(&pmydev->lock);

	wake_up_interruptible(&pmydev->rq);

	if (pmydev->pasync_obj != NULL) {
		kill_fasync(&pmydev->pasync_obj, SIGIO, POLL_IN);
	}

	return size;
}

long mychar_ioctl(struct file* pfile, unsigned int cmd, unsigned long arg)
{
	int __user *pret = (int*)arg;
	int maxlen = BUF_LEN;
	int ret = 0;
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;

	switch (cmd) {
		case MYCHAR_IOCTL_GET_MAXLEN:
			ret = copy_to_user(pret, &maxlen, sizeof(int));
			if (ret) {
				printk("copy_to_user MAXLEN failed\n");
				return -1;
			}
			break;
		case MYCHAR_IOCTL_GET_CURLEN:
			mutex_lock(&pmydev->lock);
			ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));
			mutex_unlock(&pmydev->lock);
			if (ret) {
				printk("copy_to_user CURLEN failed\n");
				return -1;
			}
			break;
		default:
			printk("The cmd is unknow\n");
			return -1;
	}
	return 0;
}

unsigned int mychar_poll(struct file* pfile, poll_table* ptb)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	unsigned int mask = 0;

	poll_wait(pfile, &pmydev->rq, ptb);
	poll_wait(pfile, &pmydev->wq, ptb);

	mutex_lock(&pmydev->lock);
	if (pmydev->curlen > 0) {
		mask |= POLLIN | POLLRDNORM;
	}

	if (pmydev->curlen < BUF_LEN) {
		mask |= POLLOUT | POLLWRNORM;
	}
	mutex_unlock(&pmydev->lock);

	return mask;
}

int mychar_fasync(int fd, struct file* pfile, int mode) {
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	return fasync_helper(fd, pfile, mode, &pmydev->pasync_obj);
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.release = mychar_close,
	.read = mychar_read,
	.write = mychar_write,
	.unlocked_ioctl = mychar_ioctl,
	.poll = mychar_poll,
	.fasync = mychar_fasync,
};

int __init mychar_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 申请设备号 */
	ret = register_chrdev_region(devno, mychar_num, "mychar");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
		if (ret) {
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}

	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&gmydev.mydev, &myops);

	/* 给struct cdev对象添加到内核对应的数据结构里 */
	gmydev.mydev.owner = THIS_MODULE;
	cdev_add(&gmydev.mydev, devno, mychar_num);

	init_waitqueue_head(&gmydev.rq);
	init_waitqueue_head(&gmydev.wq);

	mutex_init(&gmydev.lock);
	return 0;
}

void __exit mychar_exit(void)
{
	dev_t devno = MKDEV(major, minor);
	cdev_del(&gmydev.mydev);
	unregister_chrdev_region(devno, mychar_num);
}


MODULE_LICENSE("GPL");

module_init(mychar_init);
module_exit(mychar_exit);

选择并发控制机制的原则

1. 不允许睡眠的上下文需要采用忙等待类,可以睡眠的上下文可以采用阻塞类。在异常上下文中访问的竞争资源一定采用忙等待类。
2. 临界区操作较长的应用建议采用阻塞类,临界区很短的操作建议采用忙等待类。
3. 中断屏蔽仅在有与中断上下文共享资源时使用。
4. 共享资源仅是一个简单整型量时用原子变量


内核定时器

时钟中断

硬件有一个时钟装置,该装置每隔一定时间发出一个时钟中断(称为一次时钟嘀嗒-tick),对应的中断处理程序就将全局变量jiffies_64加1

jiffies_64   是一个全局64位整型, jiffies全局变量为其低32位的全局变量,程序中一般用jiffies

HZ:可配置的宏,表示1秒钟产生的时钟中断次数,一般设为100或200

延时机制

1. 短延迟:忙等待

1. void ndelay(unsigned long nsecs)
2. void udelay(unsigned long usecs)
3. void mdelay(unsigned long msecs)

2. 长延迟:忙等待

   使用jiffies比较宏来实现

time_after(a,b)    //a > b
time_before(a,b)   //a < b
   
// 延迟100个jiffies
unsigned long delay = jiffies + 100;
while(time_before(jiffies,delay))
{
    ;
}
   
// 延迟2s
unsigned long delay = jiffies + 2*HZ;
while(time_before(jiffies,delay))
{
    ;
}

3. 睡眠延迟----阻塞类

void msleep(unsigned int msecs);
unsigned long msleep_interruptible(unsigned int msecs);

延时机制的选择原则:

1. 异常上下文中只能采用忙等待类
2. 任务上下文短延迟采用忙等待类,长延迟采用阻塞类

定时器

(1)定义定时器结构体

struct timer_list 
{
    struct list_head entry;
    unsigned long expires;  // 期望的时间值 jiffies + x * HZ
    void (*function)(unsigned long); // 时间到达后,执行的回调函数,软中断异常上下文
    unsigned long data;
};

(2)初始化定时器 

init_timer(struct timer_list *)

(3)增加定时器 ------ 定时器开始计时

void add_timer(struct timer_list *timer);

(4)删除定时器 -------定时器停止工作

int del_timer(struct timer_list * timer);

(5)修改定时器 

 int mod_timer(struct timer_list *timer, unsigned long expires);

参考代码模板

定义struct timer_list tl类型的变量

init_timer(...);//模块入口函数

//模块入口函数或open或希望定时器开始工作的地方
tl.expires = jiffies + n * HZ //n秒
tl.function = xxx_func;
tl.data = ...;

add_timer(....);


//不想让定时器继续工作时
del_timer(....);

void xxx_func(unsigned long arg)
{
    ......
    mod_timer(....);//如需要定时器继续隔指定时间再次调用本函数
}

内核内存管理

内核内存管理框架

内核将物理内存等分成N块4KB,称之为一页,每页都用一个struct page来表示,采用伙伴关系算法维护

内核地址空间划分图:

3G~3G+896M:低端内存,直接映射  虚拟地址 = 3G + 物理地址
细分为:ZONE_DMA、ZONE_NORMAL
分配方式:
        1. kmalloc:小内存分配,slab算法
        2. get_free_page:整页分配,2的n次方页,n最大为10

大于3G+896M:高端内存
细分为:vmalloc区、持久映射区、固定映射区
分配方式:vmalloc:虚拟地址连续,物理地址不连续

内核中常用动态分配

kmalloc

函数原型:

void *kmalloc(size_t size, gfp_t flags);
void *kzalloc(size_t size, gfp_t flags); // 分配内存同时置零

kmalloc() 申请的内存位于直接映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的内存大小有限制,不能超过128KB。 
   
较常用的 flags(分配内存的方法):

GFP_ATOMIC —— 分配内存的过程是一个原子过程,分配内存的过程不会被(高优先级进程或中断)打断;
GFP_KERNEL —— 正常分配内存;
GFP_DMA —— 给 DMA 控制器分配内存,需要使用该标志(DMA要求分配虚拟地址和物理地址连续)。

flags 的参考用法: 
 |– 进程上下文,可以睡眠      GFP_KERNEL 
 |– 异常上下文,不可以睡眠     GFP_ATOMIC 
 |  |– 中断处理程序          GFP_ATOMIC 
 |  |– 软中断            GFP_ATOMIC 
 |  |– Tasklet            GFP_ATOMIC 
 |– 用于DMA的内存,可以睡眠   GFP_DMA | GFP_KERNEL 
 |– 用于DMA的内存,不可以睡眠  GFP_DMA |GFP_ATOMIC 
   
对应的内存释放函数为:

void kfree(const void *objp);

vmalloc

void *vmalloc(unsigned long size);

vmalloc() 函数则会在虚拟内存空间给出一块连续的内存区,但这片连续的虚拟内存在物理内存中并不一定连续。由于 vmalloc() 没有保证申请到的是连续的物理内存,因此对申请的内存大小没有限制,如果需要申请较大的内存空间就需要用此函数了。

对应的内存释放函数为:

void vfree(const void *addr);

注意:vmalloc() 和 vfree() 可以睡眠,因此不能从异常上下文调用。


kmalloc & vmalloc 的比较

kmalloc()、kzalloc()、vmalloc() 的共同特点是:

1. 用于申请内核空间的内存;
2. 内存以字节为单位进行分配;
3. 所分配的内存虚拟地址上连续;

kmalloc()、kzalloc()、vmalloc() 的区别是:

1. kzalloc 是强制清零的 kmalloc 操作;(以下描述不区分 kmalloc 和 kzalloc)
2. kmalloc 分配的内存大小有限制(128KB),而 vmalloc 没有限制;
3. kmalloc 可以保证分配的内存物理地址是连续的,但是 vmalloc 不能保证;
4. kmalloc 分配内存的过程可以是原子过程(使用 GFP_ATOMIC),而 vmalloc 分配内存时则可能产生阻塞;
5. kmalloc 分配内存的开销小,因此 kmalloc 比 vmalloc 要快;

一般情况下,内存只有在要被 DMA 访问的时候才需要物理上连续,但为了性能上的考虑,内核中一般使用 kmalloc(),而只有在需要获得大块内存时才使用 vmalloc()。

分配选择原则:

1. 小内存(< 128k)用kmalloc,大内存用vmalloc或get_free_page
2. 如果需要比较大的内存,并且要求使用效率较高时用get_free_page,否则用vmalloc


示例代码(互斥锁的代码中设备结构体改用指针并用kmalloc分配内存)

mychar.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <asm/uaccess.h>

#include "mychar.h"

#define BUF_LEN 100

int major = 11;
int minor = 0;
int mychar_num = 1;

struct mychar_dev
{
	struct cdev mydev;

	char mydev_buf[BUF_LEN];
	int curlen;
	struct mutex lock;

	wait_queue_head_t rq;
	wait_queue_head_t wq;

	struct fasync_struct* pasync_obj;
};

struct mychar_dev *pgmydev = NULL;

int mychar_open(struct inode* pnode, struct file* pfile)
{
	pfile->private_data = (void *)(container_of(pnode->i_cdev, struct mychar_dev, mydev));
	return 0;
}

int mychar_close(struct inode* pnode, struct file* pfile)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	if (pmydev->pasync_obj != NULL) {
		fasync_helper(-1, pfile, 0, &pmydev->pasync_obj);
	}
	return 0;
}

ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	mutex_lock(&pmydev->lock);
	if (pmydev->curlen <= 0) {
		if (pfile->f_flags & O_NONBLOCK) {
			// 非阻塞
			mutex_unlock(&pmydev->lock);
			printk("O_NONBLOCK No Data Read\n");
			return -1;
		} else {
			// 阻塞
			mutex_unlock(&pmydev->lock);
			ret = wait_event_interruptible(pmydev->rq, pmydev->curlen > 0);
			if (ret) {
				printk("Wake up by signal\n");
				return -ERESTARTSYS;
			}
			mutex_lock(&pmydev->lock);
		}
	}

	if (count > pmydev->curlen) {
		size = pmydev->curlen;
	} else {
		size = count;
	}

	ret = copy_to_user(puser, pmydev->mydev_buf, size);
	if (ret) {
		mutex_unlock(&pmydev->lock);
		printk("copy_to_user failed\n");
		return -1;
	}

	memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);

	pmydev->curlen -= size;

	mutex_unlock(&pmydev->lock);
	wake_up_interruptible(&pmydev->wq);

	return size;
}

ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	mutex_lock(&pmydev->lock);
	if (pmydev->curlen >= BUF_LEN) {
		if (pfile->f_flags & O_NONBLOCK) {
			mutex_unlock(&pmydev->lock);
			printk("O_NONBLOCK Can not write data\n");
			return -1;
		} else {
			mutex_unlock(&pmydev->lock);
			ret = wait_event_interruptible(pmydev->wq, pmydev->curlen < BUF_LEN);
			if (ret) {
				printk("Wake up by signal\n");
				return -ERESTARTSYS;
			}
			mutex_lock(&pmydev->lock);
		}
	}

	if (count > BUF_LEN - pmydev->curlen) {
		size = BUF_LEN - pmydev->curlen;
	} else {
		size = count;
	}

	ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
	if (ret) {
		mutex_unlock(&pmydev->lock);
		printk("copy_from_user failed\n");
		return -1;
	}

	pmydev->curlen += size;

	mutex_unlock(&pmydev->lock);

	wake_up_interruptible(&pmydev->rq);

	if (pmydev->pasync_obj != NULL) {
		kill_fasync(&pmydev->pasync_obj, SIGIO, POLL_IN);
	}

	return size;
}

long mychar_ioctl(struct file* pfile, unsigned int cmd, unsigned long arg)
{
	int __user *pret = (int*)arg;
	int maxlen = BUF_LEN;
	int ret = 0;
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;

	switch (cmd) {
		case MYCHAR_IOCTL_GET_MAXLEN:
			ret = copy_to_user(pret, &maxlen, sizeof(int));
			if (ret) {
				printk("copy_to_user MAXLEN failed\n");
				return -1;
			}
			break;
		case MYCHAR_IOCTL_GET_CURLEN:
			mutex_lock(&pmydev->lock);
			ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));
			mutex_unlock(&pmydev->lock);
			if (ret) {
				printk("copy_to_user CURLEN failed\n");
				return -1;
			}
			break;
		default:
			printk("The cmd is unknow\n");
			return -1;
	}
	return 0;
}

unsigned int mychar_poll(struct file* pfile, poll_table* ptb)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	unsigned int mask = 0;

	poll_wait(pfile, &pmydev->rq, ptb);
	poll_wait(pfile, &pmydev->wq, ptb);

	mutex_lock(&pmydev->lock);
	if (pmydev->curlen > 0) {
		mask |= POLLIN | POLLRDNORM;
	}

	if (pmydev->curlen < BUF_LEN) {
		mask |= POLLOUT | POLLWRNORM;
	}
	mutex_unlock(&pmydev->lock);

	return mask;
}

int mychar_fasync(int fd, struct file* pfile, int mode) {
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	return fasync_helper(fd, pfile, mode, &pmydev->pasync_obj);
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.release = mychar_close,
	.read = mychar_read,
	.write = mychar_write,
	.unlocked_ioctl = mychar_ioctl,
	.poll = mychar_poll,
	.fasync = mychar_fasync,
};

int __init mychar_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 申请设备号 */
	ret = register_chrdev_region(devno, mychar_num, "mychar");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
		if (ret) {
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}

	pgmydev = (struct mychar_dev*)kmalloc(sizeof(struct mychar_dev), GFP_KERNEL);
	if (NULL == pgmydev) {
		unregister_chrdev_region(devno, mychar_num);
		printk("kmalloc for struct mychar_dev failed\n");
		return -1;
	}

	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&pgmydev->mydev, &myops);

	/* 给struct cdev对象添加到内核对应的数据结构里 */
	pgmydev->mydev.owner = THIS_MODULE;
	cdev_add(&pgmydev->mydev, devno, mychar_num);

	init_waitqueue_head(&pgmydev->rq);
	init_waitqueue_head(&pgmydev->wq);

	mutex_init(&pgmydev->lock);
	return 0;
}

void __exit mychar_exit(void)
{
	dev_t devno = MKDEV(major, minor);
	cdev_del(&pgmydev->mydev);
	unregister_chrdev_region(devno, mychar_num);

	kfree(pgmydev);
	pgmydev = NULL;
}


MODULE_LICENSE("GPL");

module_init(mychar_init);
module_exit(mychar_exit);

IO访问:访问外设控制器的寄存器

两种方式:

1. IO端口:X86上用IO指令访问
2. IO内存:外设寄存器在SOC芯片手册上都有相应物理地址

IO内存访问接口:

static inline void __iomem *ioremap(unsigned long offset, unsigned long size)
/*
功能:实现IO管脚的映射
参数:offset:该管脚的偏移地址
	 Size:该管脚映射空间的大小
返回值:成功返回映射的虚拟地址,失败NULL
*/

static inline void iounmap(volatile void __iomem *addr)
/*
功能:解除io管脚的映射
参数:addr:io管脚映射的地址
*/

unsigned readb(void *addr); // 1字节   或ioread8(void *addr)
unsigned readw(void *addr); // 2字节   或ioread16(void *addr)
unsigned readl(void *addr); // 4字节   或ioread32(void *addr)
/*
功能:读取寄存器的值
参数:addr  地址
返回值:读到的数据
*/

void writeb(unsigned value, void *addr); // 1字节   或iowrite8(u8 value, void *addr)
void writew(unsigned value, void *addr); // 2字节  或iowrite16(u16 value, void *addr)
void writel(unsigned value, void *addr); // 4字节  或iowrite32(u32 value, void *addr)
/*
 功能:向指定的寄存器中,写入数据。
 参数:value:待写入寄存器中的数据
	  Address:寄存器的虚拟地址
*/

LED驱动

原理图

SOC芯片手册

GPX2_7 led2  GPX2CON----0x11000C40---28~31-----0001      GPX2DAT----0x11000C44-----7

GPX1_0 led3  GPX1CON----0x11000C20---    0~3-----0001      GPX1DAT----0x11000C24-----0

GPF3_4 led4  GPF3CON----0x114001E0--- 16~19-----0001      GPF3DAT----0x114001E4-----4

GPF3_5 led5  GPF3CON----0x114001E0--- 20~23-----0001      GPF3DAT----0x114001E4-----5

编写驱动步骤

a. 设计设备数据类型

b. 考虑需要支持的函数

c. 模块入口:ioremap + 设置成输出

d. 模块出口:iounmap

e. 编写关灯函数和开灯函数,实现ioctl


示例代码(LED驱动)

leddrv.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>

#include "leddrv.h"

#define GPX1CON 0x11000C20
#define GPX1DAT 0x11000C24

#define GPX2CON 0x11000C40
#define GPX2DAT 0x11000C44

#define GPF3CON 0x114001E0
#define GPF3DAT 0x114001E4

int major = 11;
int minor = 0;
int myled_num = 1;

struct myled_dev
{
    struct cdev mydev;

    volatile unsigned long *pled2_con;
    volatile unsigned long *pled2_dat;

    volatile unsigned long *pled3_con;
	volatile unsigned long *pled3_dat;

	volatile unsigned long *pled4_con;
	volatile unsigned long *pled4_dat;

	volatile unsigned long *pled5_con;
	volatile unsigned long *pled5_dat;
};

struct myled_dev *pgmydev = NULL;

int myled_open(struct inode *pnode, struct file *pfile)
{
    pfile->private_data =(void*)(container_of(pnode->i_cdev,struct myled_dev,mydev));
    return 0;
}

int myled_close(struct inode *pnode, struct file *pfile)
{
    return 0;
}

void led_on(struct myled_dev *pmydev, int ledno)
{
    switch (ledno) {
        case 2:
            writel(readl(pmydev->pled2_dat) | (0x1 << 7), pmydev->pled2_dat);
            break;
        case 3:
            writel(readl(pmydev->pled3_dat) | (0x1 << 0), pmydev->pled3_dat);
			break;
		case 4:
			writel(readl(pmydev->pled4_dat) | (0x1 << 4), pmydev->pled4_dat);
			break;
		case 5:
			writel(readl(pmydev->pled5_dat) | (0x1 << 5), pmydev->pled5_dat);
			break;
    }
}

void led_off(struct myled_dev *pmydev, int ledno)
{
    switch (ledno) {
        case 2:
			writel(readl(pmydev->pled2_dat) & (~(0x1 << 7)), pmydev->pled2_dat);
			break;
		case 3:
			writel(readl(pmydev->pled3_dat) & (~(0x1 << 0)), pmydev->pled3_dat);
			break;
		case 4:
			writel(readl(pmydev->pled4_dat) & (~(0x1 << 4)), pmydev->pled4_dat);
			break;
		case 5:
			writel(readl(pmydev->pled5_dat) & (~(0x1 << 5)), pmydev->pled5_dat);
			break;
    }
}

long myled_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
    struct myled_dev *pmydev = (struct myled_dev*)pfile->private_data;

    if (arg < 2 || arg > 5) {
        return -1;
    }

    switch (cmd) {
        case MY_LED_ON:
            led_on(pmydev, arg);
            break;
        case MY_LED_OFF:
            led_off(pmydev, arg);
            break;
        default:
            return -1;
    }

    return 0;
}

struct file_operations myops = {
    .owner = THIS_MODULE,
    .open = myled_open,
    .release = myled_close,
    .unlocked_ioctl = myled_ioctl,
};

void ioremap_ledreg(struct myled_dev *pmydev)
{
    pmydev->pled2_con = ioremap(GPX2CON, 4);
    pmydev->pled2_dat = ioremap(GPX2DAT, 4);

    pmydev->pled3_con = ioremap(GPX1CON, 4);
	pmydev->pled3_dat = ioremap(GPX1DAT, 4);
	
	pmydev->pled4_con = ioremap(GPF3CON, 4);
	pmydev->pled4_dat = ioremap(GPF3DAT, 4);
	
	pmydev->pled5_con = pmydev->pled4_con;
	pmydev->pled5_dat = pmydev->pled4_dat;
}

void iounmap_ledreg(struct myled_dev *pmydev)
{
    iounmap(pmydev->pled2_con);
    iounmap(pmydev->pled2_dat);
    pmydev->pled2_con = NULL;
    pmydev->pled2_dat = NULL;

    iounmap(pmydev->pled3_con);
	iounmap(pmydev->pled3_dat);
	pmydev->pled3_con = NULL;
	pmydev->pled3_dat = NULL;
	
	iounmap(pmydev->pled4_con);
	iounmap(pmydev->pled4_dat);
	pmydev->pled4_con = NULL;
	pmydev->pled4_dat = NULL;
	
	pmydev->pled5_con = NULL;
	pmydev->pled5_dat = NULL;
}

void set_output_ledconreg(struct myled_dev *pmydev)
{
    writel((readl(pmydev->pled2_con) & (~(0xF << 28))) | (0x1 << 28), pmydev->pled2_con);
	writel((readl(pmydev->pled3_con) & (~(0xF <<  0))) | (0x1 <<  0), pmydev->pled3_con);
	writel((readl(pmydev->pled4_con) & (~(0xF << 16))) | (0x1 << 16), pmydev->pled4_con);
	writel((readl(pmydev->pled5_con) & (~(0xF << 20))) | (0x1 << 20), pmydev->pled5_con);

	writel(readl(pmydev->pled2_dat) & (~(0x1 << 7)), pmydev->pled2_dat);
	writel(readl(pmydev->pled3_dat) & (~(0x1 << 0)), pmydev->pled3_dat);
	writel(readl(pmydev->pled4_dat) & (~(0x1 << 4)), pmydev->pled4_dat);
	writel(readl(pmydev->pled5_dat) & (~(0x1 << 5)), pmydev->pled5_dat);
}

int __init myled_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 申请设备号 */
	ret = register_chrdev_region(devno, myled_num, "myled");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, myled_num, "myled");
		if (ret) {
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}

    pgmydev = (struct myled_dev*)kmalloc(sizeof(struct myled_dev), GFP_KERNEL);
    if (NULL == pgmydev) {
        unregister_chrdev_region(devno, myled_num);
        printk("kmalloc failed\n");
        return -1;
    }
    memset(pgmydev, 0, sizeof(struct myled_dev));

	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&pgmydev->mydev, &myops);

	/* 给struct cdev对象添加到内核对应的数据结构里 */
	pgmydev->mydev.owner = THIS_MODULE;
	cdev_add(&pgmydev->mydev, devno, myled_num);

	/* ioremap */
	ioremap_ledreg(pgmydev);

	/* con-register set output */
    set_output_ledconreg(pgmydev);

	return 0;
}

void __exit myled_exit(void)
{
	dev_t devno = MKDEV(major, minor);

	/* iounmap */
    iounmap_ledreg(pgmydev);

	cdev_del(&pgmydev->mydev);
	unregister_chrdev_region(devno, myled_num);

    kfree(pgmydev);
    pgmydev = NULL;
}


MODULE_LICENSE("GPL");

module_init(myled_init);
module_exit(myled_exit);

leddrv.h

#ifndef LED_DRIVER_H
#define LED_DRIVER_H

#define LED_DEV_MAGIC 'g'

#define MY_LED_OFF _IO(LED_DEV_MAGIC, 0)
#define MY_LED_ON  _IO(LED_DEV_MAGIC, 1)

#endif

testled.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>

#include "leddrv.h"

int main(int argc, char *argv[])
{
    int fd = -1;
    int onoff = 0;
    int no = 0;

    if (argc < 4) {
        printf("The argument is too few\n");
        return 1;
    }

    sscanf(argv[2], "%d", &onoff);
    sscanf(argv[3], "%d", &no);

    if (no < 2 || no > 5) {
        printf("led-no is invalid\n");
        return 2;
    }

    fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        printf("open %s failed\n", argv[1]);
        return 3;
    }

    if (onoff) {
        ioctl(fd, MY_LED_ON, no);
    } else {
        ioctl(fd, MY_LED_OFF, no);
    }

    close(fd);
    fd = -1;
    return 0;
}

测试结果

ubuntu上将 leddrv.c 编译成 arm 架构的 ko 文件,将 testled.c 编译成 arm 架构的可执行文件,再将这两个文件拷贝到挂载的根文件系统目录下

shrek@ubuntu16:~/share/mydrivercode$ make ARCH=arm
shrek@ubuntu16:~/share/mydrivercode$ arm-none-linux-gnueabi-gcc ./testled.c -o testled
shrek@ubuntu16:~/share/mydrivercode$ cp leddrv.ko /opt/4412/rootfs/
shrek@ubuntu16:~/share/mydrivercode$ cp testled /opt/4412/rootfs/

开发板上挂载好根文件系统,执行如下命令

[root@farsight]#insmod leddrv.ko
[root@farsight]#cat /proc/devices | grep led
 11 myled
[root@farsight]#mknod /dev/leddev c 11 0
[root@farsight]#./testled /dev/leddev 1 2
[root@farsight]#./testled /dev/leddev 1 3
[root@farsight]#./testled /dev/leddev 1 4
[root@farsight]#./testled /dev/leddev 1 5
[root@farsight]#./testled /dev/leddev 0 5
[root@farsight]#./testled /dev/leddev 0 4
[root@farsight]#./testled /dev/leddev 0 3
[root@farsight]#./testled /dev/leddev 0 2

看到开发板上的LED灯可以控制亮灭


设备树

起源

减少垃圾代码
减轻驱动开发工作量
驱动代码和设备信息分离
参考Open Firmware设计
用来记录硬件平台中各种硬件设备的属性信息

基本组成

两种源文件:

1. xxxxx.dts dts是device tree source的缩写
2. xxxxx.dtsi dtsi是device tree source include的缩写,意味着这样源文件用于被dts文件包含用

基本语法

dts文件主体内容由多个节点组成
每个节点可以包含0或多个子节点,形成树状关系
每个dts文件都有一个根节点,其它节点都是它的子孙
根节点一般来描述整个开发板硬件平台,其它节点用来表示具体设备、总线的属性信息
各个节点可以有多个属性,每个属性用key-value键值对来表示

节点语法:

[label:] node-name[@unit-address] {    
	[properties definitions];    
	[child nodes];
};

[]表示可选项
label:可选项,节点别名,为了缩短节点访问路径,后续节点中可以使用  &label 来表示引用指定节点
node-name:节点名
unit-address:可选项,设备地址,一般填写该设备寄存器组或内存块的首地址
properties definitions:可选项,属性定义
child nodes:可选项,子节点

属性语法:

[label:] property-name = value;
[label:] property-name;

属性可以无值
有值的属性,可以有三种取值:
1. arrays of cells(1个或多个32位数据, 64位数据使用2个32位数据表示,空格分隔),用尖括号表示(< >)
2. string(字符串), 用双引号表示(" ")
3. bytestring(1个或多个字节,空格分隔),用方括号表示([])
4. 用,分隔的多值

特殊节点

根节点

根节点表示整块开发板的信息

#address-cells   // 在子节点的reg属性中, 使用多少个u32整数来描述地址(address)
#size-cells      // 在子节点的reg属性中, 使用多少个u32整数来描述大小(size)
compatible       // 定义一系列的字符串, 用来指定内核中哪个machine_desc可以支持本设备,即描述其兼容哪些平台                         
model            // 比如有2款板子配置基本一致, 它们的compatible是一样的,那么就通过model来分辨这2款板子

/memory

所有设备树文件的必需节点,它定义了系统物理内存的 layout

device_type = "memory";
reg             //用来指定内存的地址、大小

/chosen

传递内核启动时使用的参数parameter

bootargs  //字符串,内核启动参数, 跟u-boot中设置的bootargs作用一样

/cpus  多核CPU支持

/cpus节点下有1个或多个cpu子节点,cpu子节点中用reg属性用来标明自己是哪一个cpu

所以 /cpus 中有以下2个属性:

#address-cells   // 在它的子节点的reg属性中, 使用多少个u32整数来描述地址(address)
#size-cells      // 在它的子节点的reg属性中, 使用多少个u32整数来描述大小(size) 必须设置为0

常用属性

phandle

数字形式的节点标识,在后续节点中属性值性质表示某节点时,可以引用对应节点

如:

pic@10000000 {    
	phandle = <1>;    
	interrupt-controller;
};
another-device-node {    
	interrupt-parent = <1>;   // 使用phandle值为1来引用上述节点
};

地址  ---  重要

reg属性:表示内存区域region,语法:

reg = <address1 length1 [address2 length2] [address3 length3]>;

#address-cells:reg属性中, 使用多少个u32整数来描述地址(address),语法:

#address-cells = <数字>;

#size-cells:reg属性中, 使用多少个u32整数来描述大小(size),语法:

#size-cells = <数字>;

compatible  ---  重要

驱动和设备(设备节点)的匹配依据,compatible(兼容性)的值可以有不止一个字符串以满足不同的需求,语法:

compatible = "字符串1","字符串2",...;

中断  ---  重要

a. 中断控制器节点用的属性:

interrupt-controller 一个无值空属性用来声明这个node接收中断信号,表示该节点是一个中断控制器

#interrupt-cells 这是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符

b. 中断源设备节点用的属性:

interrupt-parent:标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的,语法:

interrupt-parent = <引用某中断控制器节点>

interrupts 一个中断标识符列表,表示每一个中断输出信号,语法:

interrupts = <中断号 触发方式>

1 low-to-high 上升沿触发
2 high-to-low 下降沿触发
4 high level  高电平触发
8 low level   低电平触发

gpio  ---  重要

gpio也是最常见的IO口,常用的属性有:

a. 对于GPIO控制器:

gpio-controller,无值空属性,用来说明该节点描述的是一个gpio控制器

#gpio-cells,用来表示要用几个cell描述一个 GPIO引脚

b. 对于GPIO使用者节点:

gpio使用节点的属性

xxx-gpio = <&引用GPIO控制器 GPIO标号 工作模式>
工作模式:
1 低电平有效 GPIO_ACTIVE_HIGH
0 高电平有效 GPIO_ACTIVE_LOW

属性设置套路

一般来说,每一种设备的节点属性设置都会有一些套路,比如可以设置哪些属性?属性值怎么设置?那怎么知道这些套路呢,有两种思路:

1. 抄类似的dts,比如我们自己项目的平台是4412,那么就可以抄exynos4412-tiny4412.dts、exynos4412-smdk4412.dts这类相近的dts
2. 查询内核中的文档,比如Documentation/devicetree/bindings/i2c/i2c-imx.txt就描述了imx平台的i2c属性设置方法;Documentation/devicetree/bindings/fb就描述了lcd、lvds这类属性设置方法

常用接口

struct device_node  对应设备树中的一个节点
struct property 对应节点中一个属性

of_find_node_by_path

/*
include/of.h
of_find_node_by_path - 通过路径查找指定节点
@path - 带全路径的节点名,也可以是节点的别名
成功:得到节点的首地址;失败:NULL
*/
struct device_node * of_find_node_by_path(const char *path);

of_find_property

/*
include/of.h
of_find_property - 提取指定属性的值
@np - 设备节点指针
@name - 属性名称
@lenp - 属性值的字节数
成功:属性值的首地址;失败:NULL
*/
struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);

of_get_named_gpio

/*
 * include/of_gpio.h
 * of_get_named_gpio - 从设备树中提取gpio口
 * @np - 设备节点指针
 * @propname - 属性名
 * @index - gpio口引脚标号 
 * 成功:得到GPIO口编号;失败:负数,绝对值是错误码
 */
int of_get_named_gpio(struct device_node *np, const char *propname, int index);

irq_of_parse_and_map

/*
	功能:获得设备树中的中断号并进行映射
	参数:node:设备节点
		 index:序号
	返回值:成功:中断号	失败:错误码
*/
unsigned int irq_of_parse_and_map(struct device_node *node, int index);

读属性值

/*
of_property_read_string - 提取字符串(属性值)
@np - 设备节点指针
@propname - 属性名称
@out_string - 输出参数,指向字符串(属性值)
成功:0;失败:负数,绝对值是错误码
*/
int of_property_read_string(struct device_node *np, const char *propname, const char **out_string);

读数值

int of_property_read_u8(const struct device_node *np,const char *propname,u8 *out_value)

int of_property_read_u16(const struct device_node *np,const char *propname,u16 *out_value)

int of_property_read_u32(const struct device_node *np,const char *propname,u32 *out_value)

判断属性是否存在

int of_property_read_bool(const struct device_node *np,const char *propname)

读数组

int of_property_read_u32_array(const struct device_node *np,const char *propname,u32 *out_value,size_t sz)

GPIO接口

向内核申请GPIO

int gpio_request(unsigned gpio,const char *label) 

功能:其实就是让内核检查一下该GPIO引脚是否被其它设备占用,如果没有占用则返回0并用label做一下标记,表示被本设备占用,否则返回负数

void gpio_free(unsigned gpio)

功能:去除本设备对该GPIO的占用标记,表示本设备向内核归还对该GPIO引脚的使用权,此后其它设备可占用该GPIO引脚

设置GPIO方向

int gpio_direction_input(unsigned gpio)

int gpio_direction_output(unsigned gpio,int value)

读写GPIO数据

int gpio_get_value(unsigned gpio)

int gpio_set_value(unsigned gpio,int value)

LED驱动设备树版

1. 在设备树源文件的根节点下添加本设备的节点(该节点中包含本设备用到的资源信息)

   ..../linux3.14/arch/arm/boot/dts/exynos4412-fs4412.dts

fs4412-leds {
    compatible = "4412,led2-5";
    led2-gpio = <&gpx2 7 0>;
    led3-gpio = <&gpx1 0 0>;
    led4-gpio = <&gpf3 4 0>;
    led5-gpio = <&gpf3 5 0>;
};

 2. 在linux内核源码的顶层目录下执行:make dtbs  (生成对应的dtb文件)

 3. cp   ?????.dtb   /tftpboot

 4. 编写驱动代码:

    a. 通过本设备在设备树中的路径找到对应节点(struct device_node类型的地址值)

    b. 调用 of_get_named_gpio 函数得到某个GPIO的编号

    c. struct leddev结构体中记录所有用到的GPIO编号

    d. 使用某个GPIO引脚前需先通过gpio_request函数向内核申请占用该引脚,不用该引脚时可通过gpio_free归还给内核

    e. 通过gpio_direction_input和gpio_direction_output函数来设置某个GPIO的作用

    f. 通过gpio_get_value函数可以获取某个GPIO引脚的当前电平

    g. 通过gpio_set_value函数可以改变某个GPIO引脚的电平


示例代码(LED驱动,设备树版)

leddrv.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>

#include "leddrv.h"

int major = 11;
int minor = 0;
int myled_num = 1;

struct myled_dev
{
    struct cdev mydev;

    unsigned int led2gpio;
	unsigned int led3gpio;
	unsigned int led4gpio;
	unsigned int led5gpio;
};

struct myled_dev *pgmydev = NULL;

int myled_open(struct inode *pnode, struct file *pfile)
{
    pfile->private_data =(void*)(container_of(pnode->i_cdev,struct myled_dev,mydev));
    return 0;
}

int myled_close(struct inode *pnode, struct file *pfile)
{
    return 0;
}

void led_on(struct myled_dev *pmydev, int ledno)
{
    switch (ledno) {
        case 2:
            gpio_set_value(pmydev->led2gpio, 1);
            break;
        case 3:
            gpio_set_value(pmydev->led3gpio, 1);
			break;
		case 4:
			gpio_set_value(pmydev->led4gpio, 1);
			break;
		case 5:
			gpio_set_value(pmydev->led5gpio, 1);
			break;
    }
}

void led_off(struct myled_dev *pmydev, int ledno)
{
    switch (ledno) {
        case 2:
			gpio_set_value(pmydev->led2gpio, 0);
			break;
		case 3:
			gpio_set_value(pmydev->led3gpio, 0);
			break;
		case 4:
			gpio_set_value(pmydev->led4gpio, 0);
			break;
		case 5:
			gpio_set_value(pmydev->led5gpio, 0);
			break;
    }
}

long myled_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
    struct myled_dev *pmydev = (struct myled_dev*)pfile->private_data;

    if (arg < 2 || arg > 5) {
        return -1;
    }

    switch (cmd) {
        case MY_LED_ON:
            led_on(pmydev, arg);
            break;
        case MY_LED_OFF:
            led_off(pmydev, arg);
            break;
        default:
            return -1;
    }

    return 0;
}

struct file_operations myops = {
    .owner = THIS_MODULE,
    .open = myled_open,
    .release = myled_close,
    .unlocked_ioctl = myled_ioctl,
};

void request_leds_gpio(struct myled_dev *pmydev, struct device_node *pnode) // 申请占用设备
{
	pmydev->led2gpio = of_get_named_gpio(pnode, "led2-gpio", 0);
	gpio_request(pmydev->led2gpio, "led2");

	pmydev->led3gpio = of_get_named_gpio(pnode, "led3-gpio", 0);
	gpio_request(pmydev->led3gpio, "led3");

	pmydev->led4gpio = of_get_named_gpio(pnode, "led4-gpio", 0);
	gpio_request(pmydev->led4gpio, "led4");

	pmydev->led5gpio = of_get_named_gpio(pnode, "led5-gpio", 0);
	gpio_request(pmydev->led5gpio, "led5");

}

void set_leds_gpio_output(struct myled_dev *pmydev) // 配置成输出
{
	gpio_direction_output(pmydev->led2gpio, 0);
	gpio_direction_output(pmydev->led3gpio, 0);
	gpio_direction_output(pmydev->led4gpio, 0);
	gpio_direction_output(pmydev->led5gpio, 0);
}

void free_leds_gpio(struct myled_dev *pmydev) // free设备
{
	gpio_free(pmydev->led2gpio);
	gpio_free(pmydev->led3gpio);
	gpio_free(pmydev->led4gpio);
	gpio_free(pmydev->led5gpio);
}

int __init myled_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);
	struct device_node *pnode = NULL;

	pnode = of_find_node_by_path("/fs4412-leds"); // 设备树中的节点名
	if (NULL == pnode) {
		printk("find node by path failed\n");
		return -1;
	}

	ret = register_chrdev_region(devno, myled_num, "myled");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, myled_num, "myled");
		if (ret) {
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}

    pgmydev = (struct myled_dev*)kmalloc(sizeof(struct myled_dev), GFP_KERNEL);
    if (NULL == pgmydev) {
        unregister_chrdev_region(devno, myled_num);
        printk("kmalloc failed\n");
        return -1;
    }
    memset(pgmydev, 0, sizeof(struct myled_dev));

	cdev_init(&pgmydev->mydev, &myops);

	pgmydev->mydev.owner = THIS_MODULE;
	cdev_add(&pgmydev->mydev, devno, myled_num);

	/* ioremap */
	request_leds_gpio(pgmydev, pnode);

	/* con-register set output */
    set_leds_gpio_output(pgmydev);

	return 0;
}

void __exit myled_exit(void)
{
	dev_t devno = MKDEV(major, minor);

    /* iounmap */
	free_leds_gpio(pgmydev);

	cdev_del(&pgmydev->mydev);
	unregister_chrdev_region(devno, myled_num);

    kfree(pgmydev);
    pgmydev = NULL;
}


MODULE_LICENSE("GPL");

module_init(myled_init);
module_exit(myled_exit);

leddrv.h

#ifndef LED_DRIVER_H
#define LED_DRIVER_H

#define LED_DEV_MAGIC 'g'

#define MY_LED_OFF _IO(LED_DEV_MAGIC, 0)
#define MY_LED_ON  _IO(LED_DEV_MAGIC, 1)

#endif

testled.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>

#include "leddrv.h"

int main(int argc, char *argv[])
{
    int fd = -1;
    int onoff = 0;
    int no = 0;

    if (argc < 4) {
        printf("The argument is too few\n");
        return 1;
    }

    sscanf(argv[2], "%d", &onoff);
    sscanf(argv[3], "%d", &no);

    if (no < 2 || no > 5) {
        printf("led-no is invalid\n");
        return 2;
    }

    fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        printf("open %s failed\n", argv[1]);
        return 3;
    }

    if (onoff) {
        ioctl(fd, MY_LED_ON, no);
    } else {
        ioctl(fd, MY_LED_OFF, no);
    }

    close(fd);
    fd = -1;
    return 0;
}

测试结果

ubuntu上将 leddrv.c 编译成 arm 架构的 ko 文件,将 testled.c 编译成 arm 架构的可执行文件,再将这两个文件拷贝到挂载的根文件系统目录下

shrek@ubuntu16:~/share/mydrivercode$ make ARCH=arm
shrek@ubuntu16:~/share/mydrivercode$ arm-none-linux-gnueabi-gcc ./testled.c -o testled
shrek@ubuntu16:~/share/mydrivercode$ cp leddrv.ko /opt/4412/rootfs/
shrek@ubuntu16:~/share/mydrivercode$ cp testled /opt/4412/rootfs/

开发板上挂载好根文件系统,执行如下命令

[root@farsight]#insmod leddrv.ko
[root@farsight]#cat /proc/devices | grep led
 11 myled
[root@farsight]#mknod /dev/leddev c 11 0
[root@farsight]#./testled /dev/leddev 1 2
[root@farsight]#./testled /dev/leddev 1 3
[root@farsight]#./testled /dev/leddev 1 4
[root@farsight]#./testled /dev/leddev 1 5
[root@farsight]#./testled /dev/leddev 0 5
[root@farsight]#./testled /dev/leddev 0 4
[root@farsight]#./testled /dev/leddev 0 3
[root@farsight]#./testled /dev/leddev 0 2

看到开发板上的LED灯可以控制亮灭


中断处理

什么是中断

一种硬件上的通知机制,用来通知CPU发生了某种需要立即处理的事件

分为:

1. 内部中断  CPU执行程序的过程中,发生的一些硬件出错、运算出错事件(如分母为0、溢出等等),不可屏蔽
2. 外部中断  外设发生某种情况,通过一个引脚的高、低电平变化来通知CPU (如外设产生了数据、某种处理完毕等等)


中断处理原理

任何一种中断产生,CPU都会暂停当前执行的程序,跳转到内存固定位置执行一段程序,该程序被称为总的中断服务程序,在该程序中区分中断源,然后进一步调用该中断源对应的处理函数。

中断源对应的处理函数被称为分中断处理程序,一般每一个分中断处理程序对应一个外设产生的中断

写驱动时,如果外设有中断,则需要编写一个函数(分中断处理程序)来处理这种中断


中断接口

中断申请 

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
/*
参数:
    irq:所申请的中断号
    handler:该中断号对应的中断处理函数
    flags:中断触发方式或处理方式 
        触发方式:IRQF_TRIGGER_NONE         //无触发
                  IRQF_TRIGGER_RISING      //上升沿触发
                  IRQF_TRIGGER_FALLING     //下降沿触发
                  IRQF_TRIGGER_HIGH        //高电平触发
                  IRQF_TRIGGER_LOW         //低电平触发
        处理方式:
                  IRQF_DISABLED            //用于快速中断,处理中屏蔽所有中断
                  IRQF_SHARED              //共享中断
        name:中断名 /proc/interrupts
        dev:传递给中断例程的参数,共享中断时用于区分那个设备,一般为对应设备的结构体地址,无共享中断时写NULL
返回值:成功:0 失败:错误码
*/

中断释放

void free_irq(unsigned int irq, void *dev_id);
/*
功能:释放中断号
参数:
    irq:设备号
    dev_id:共享中断时用于区分那个设备一般强转成设备号,无共享中断时写NULL
*/

中断处理函数原型

typedef irqreturn_t (*irq_handler_t)(int, void *);
/*
参数:
    int:中断号
    void*:对应的申请中断时的dev_id
返回值:
    typedef enum irqreturn irqreturn_t;    //中断返回值类型
    enum irqreturn {
        IRQ_NONE    = (0 << 0),
        IRQ_HANDLED    = (1 << 0),
        IRQ_WAKE_THREAD    = (1 << 1),
    };
    返回IRQ_HANDLED表示处理完了,返回IRQ_NONE在共享中断表示不处理
*/

按键驱动

exynos4412-fs4412.dts中增加节点

mykey2_node {
    compatible = "mykey2,key2";
    key2-gpio = <&gpx1 1 0>;
    interrupt-parent = <&gpx1>;
    interrupts = <1 3>;
};

示例代码(按键驱动)

key.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <asm/uaccess.h>

#include "key.h"

int major = 11;
int minor = 0;
int key2_num = 1;

struct key2_dev
{
	struct cdev mydev;

	int gpio; // GPIO号
    int irqno; // 中断号

    struct keyvalue data; // 按键产生的数据
    int newflag; // 按键产生标志
    spinlock_t lock;

	wait_queue_head_t rq;
};

struct key2_dev *pgmydev = NULL;

int key2_open(struct inode* pnode, struct file* pfile)
{
	pfile->private_data = (void *)(container_of(pnode->i_cdev, struct key2_dev, mydev));
	return 0;
}

int key2_close(struct inode* pnode, struct file* pfile)
{
	return 0;
}

ssize_t key2_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
{
	struct key2_dev *pmydev = (struct key2_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

    if (count < sizeof(struct keyvalue)) {
        printk("expect read size is invalid\n");
        return -1;
    }

	spin_lock(&pmydev->lock);
	if (!pmydev->newflag) {
        if (pfile->f_flags & O_NONBLOCK) {
            // 非阻塞
            spin_unlock(&pmydev->lock);
            printk("O_NONBLOCK No Data Read\n");
            return -1;
        } else {
            // 阻塞
            spin_unlock(&pmydev->lock);
            ret = wait_event_interruptible(pmydev->rq, pmydev->newflag == 1);
            if (ret) {
                printk("Wake up by signal\n");
				return -ERESTARTSYS;
            }
            spin_lock(&pmydev->lock);
        }
    }

	if (count > sizeof(struct keyvalue)) {
		size = sizeof(struct keyvalue);
	} else {
		size = count;
	}

	ret = copy_to_user(puser, &pmydev->data, size);
	if (ret) {
		spin_unlock(&pmydev->lock);
		printk("copy_to_user failed\n");
		return -1;
	}

	pmydev->newflag = 0;

    spin_unlock(&pmydev->lock);

	return size;
}

unsigned int key2_poll(struct file* pfile, poll_table* ptb)
{
	struct key2_dev *pmydev = (struct key2_dev *)pfile->private_data;
	unsigned int mask = 0;

	poll_wait(pfile, &pmydev->rq, ptb);

	spin_lock(&pmydev->lock);
	if (pmydev->newflag) {
		mask |= POLLIN | POLLRDNORM;
	}
	spin_unlock(&pmydev->lock);

	return mask;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = key2_open,
	.release = key2_close,
	.read = key2_read,
	.poll = key2_poll,
};

irqreturn_t key2_irq_handle(int no, void *arg)
{
    struct key2_dev *pmydev = (struct key2_dev*)arg;
    int status1 = 0;
    int status2 = 0;
    int status = 0;

    status1 = gpio_get_value(pmydev->gpio);
    mdelay(1);
    status2 = gpio_get_value(pmydev->gpio);

    if (status1 != status2) {
        return IRQ_NONE;
    }

    status = status1;

    spin_lock(&pmydev->lock);
    if (status == pmydev->data.status) {
        spin_unlock(&pmydev->lock);
        return IRQ_NONE;
    }

    pmydev->data.code = KEY2;
    pmydev->data.status = status;
    pmydev->newflag = 1;

    spin_unlock(&pmydev->lock);
    wake_up(&pmydev->rq);

    return IRQ_HANDLED;
}

int __init key2_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

    struct device_node *pnode = NULL;

    pnode = of_find_node_by_path("/mykey2_node"); // 设备树中的节点
    if (NULL == pnode) {
        printk("find node failed\n");
        return -1;
    }

    pgmydev = (struct key2_dev*)kmalloc(sizeof(struct key2_dev), GFP_KERNEL);
    if (NULL == pgmydev) {
        printk("kmalloc for struct key2_dev failed\n");
        return -1;
    }

    pgmydev->gpio = of_get_named_gpio(pnode, "key2-gpio", 0); // 得到GPIO的编号
    pgmydev->irqno = irq_of_parse_and_map(pnode, 0); // 得到中断号

	ret = register_chrdev_region(devno, key2_num, "key2");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, key2_num, "key2");
		if (ret) {
            kfree(pgmydev);
            pgmydev = NULL;
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}

	cdev_init(&pgmydev->mydev, &myops);

	pgmydev->mydev.owner = THIS_MODULE;
	cdev_add(&pgmydev->mydev, devno, key2_num);

	init_waitqueue_head(&pgmydev->rq);

	spin_lock_init(&pgmydev->lock);

    ret = request_irq(pgmydev->irqno, key2_irq_handle, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "key2", pgmydev); // 中断申请
    if (ret) {
        printk("request_irq failed\n");
        cdev_del(&pgmydev->mydev);
        kfree(pgmydev);
        pgmydev = NULL;
        unregister_chrdev_region(devno, key2_num);
        return -1;
    }

	return 0;
}

void __exit key2_exit(void)
{
	dev_t devno = MKDEV(major, minor);
    free_irq(pgmydev->irqno, pgmydev); // 释放中断
	cdev_del(&pgmydev->mydev);
	unregister_chrdev_region(devno, key2_num);

	kfree(pgmydev);
	pgmydev = NULL;
}


MODULE_LICENSE("GPL");

module_init(key2_init);
module_exit(key2_exit);

key.h

#ifndef KEY_H
#define KEY_H

enum KEYCODE
{
	KEY2 = 1002,
	KEY3,
	KEY4,
};

enum KEY_STATUS
{
	KEY_DOWN = 0,
	KEY_UP,
};

struct keyvalue
{
	int code; // which KEY
	int status;
};

#endif

testkey.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>

#include "key.h"

int main(int argc, char *argv[])
{
    int fd = -1;
    struct keyvalue keydata = {0};
    int ret = 0;

    if (argc < 2) {
        printf("The argument is too few\n");
        return 1;
    }

    fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        printf("open %s failed\n", argv[1]);
        return 3;
    }

    while ((ret = read(fd, &keydata, sizeof(keydata))) == sizeof(keydata)) {
        if (keydata.status == KEY_DOWN) {
            printf("Key2 is down!\n");
        } else {
            printf("Key2 is up!\n");
        }
    }

    close(fd);
    fd = -1;
    return 0;
}

测试结果

ubuntu上将 key.c 编译成 arm 架构的 ko 文件,将 testkey.c 编译成 arm 架构的可执行文件,再将这两个文件拷贝到挂载的根文件系统目录下

shrek@ubuntu16:~/share/mydrivercode$ make ARCH=arm
shrek@ubuntu16:~/share/mydrivercode$ arm-none-linux-gnueabi-gcc testkey.c -o testkey
shrek@ubuntu16:~/share/mydrivercode$ cp key.ko /opt/4412/rootfs/
shrek@ubuntu16:~/share/mydrivercode$ cp testkey /opt/4412/rootfs/

开发板上挂载好根文件系统,执行如下命令

[root@farsight]#insmod key.ko
[root@farsight]#cat /proc/devices | grep key
 11 key2
[root@farsight]#mknod /dev/mykey c 11 0
[root@farsight]#./testkey /dev/mykey 
Key2 is down!
Key2 is up!

当按下开发板的 key2 按键时,终端可以打印出相应信息


上半部与下半部

起源:

1. 中断处理程序执行时间过长引起的问题
2. 有些设备的中断处理程序必须要处理一些耗时操作


下半部机制之tasklet --- 基于软中断

结构体

struct tasklet_struct
{
​    struct tasklet_struct *next;
​    unsigned long state;
​    atomic_t count;
​    void (*func)(unsigned long);
​    unsigned long data;
};

定义tasklet的中断底半部处理函数

void tasklet_func(unsigned long data);

初始化 tasklet

DECLARE_TASKLET(name, func, data);
/*
定义变量并初始化
参数:name:中断底半部tasklet的名称
     Func:中断底半部处理函数的名字
     data:给中断底半部处理函数传递的参数
*/
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)

调度 tasklet

void tasklet_schedule(struct tasklet_struct *t)
//参数:t:tasklet的结构体

示例代码(tasklet)

key_tasklet.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <asm/uaccess.h>

#include "key.h"

int major = 11;
int minor = 0;
int key2_num = 1;

struct key2_dev
{
	struct cdev mydev;

	int gpio;
    int irqno;

    struct keyvalue data;
    int newflag;
    spinlock_t lock;

	wait_queue_head_t rq;
    
    struct tasklet_struct tsk;
};

struct key2_dev *pgmydev = NULL;

int key2_open(struct inode* pnode, struct file* pfile)
{
	pfile->private_data = (void *)(container_of(pnode->i_cdev, struct key2_dev, mydev));
	return 0;
}

int key2_close(struct inode* pnode, struct file* pfile)
{
	return 0;
}

ssize_t key2_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
{
	struct key2_dev *pmydev = (struct key2_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

    if (count < sizeof(struct keyvalue)) {
        printk("expect read size is invalid\n");
        return -1;
    }

	spin_lock(&pmydev->lock);
	if (!pmydev->newflag) {
        if (pfile->f_flags & O_NONBLOCK) {
            // 非阻塞
            spin_unlock(&pmydev->lock);
            printk("O_NONBLOCK No Data Read\n");
            return -1;
        } else {
            // 阻塞
            spin_unlock(&pmydev->lock);
            ret = wait_event_interruptible(pmydev->rq, pmydev->newflag == 1);
            if (ret) {
                printk("Wake up by signal\n");
				return -ERESTARTSYS;
            }
            spin_lock(&pmydev->lock);
        }
    }

	if (count > sizeof(struct keyvalue)) {
		size = sizeof(struct keyvalue);
	} else {
		size = count;
	}

	ret = copy_to_user(puser, &pmydev->data, size);
	if (ret) {
		spin_unlock(&pmydev->lock);
		printk("copy_to_user failed\n");
		return -1;
	}

	pmydev->newflag = 0;

    spin_unlock(&pmydev->lock);

	return size;
}

unsigned int key2_poll(struct file* pfile, poll_table* ptb)
{
	struct key2_dev *pmydev = (struct key2_dev *)pfile->private_data;
	unsigned int mask = 0;

	poll_wait(pfile, &pmydev->rq, ptb);

	spin_lock(&pmydev->lock);
	if (pmydev->newflag) {
		mask |= POLLIN | POLLRDNORM;
	}
	spin_unlock(&pmydev->lock);

	return mask;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = key2_open,
	.release = key2_close,
	.read = key2_read,
	.poll = key2_poll,
};

irqreturn_t key2_irq_handle(int no, void *arg)
{
    struct key2_dev *pmydev = (struct key2_dev*)arg;
    
    tasklet_schedule(&pmydev->tsk);

    return IRQ_HANDLED;
}

void bottom_irq_func(unsigned long arg)
{
    struct key2_dev *pmydev = (struct key2_dev*)arg;
    int status1 = 0;
    int status2 = 0;
    int status = 0;

    status1 = gpio_get_value(pmydev->gpio);
    mdelay(1);
    status2 = gpio_get_value(pmydev->gpio);

    if (status1 != status2) {
        return;
    }

    status = status1;

    spin_lock(&pmydev->lock);
    if (status == pmydev->data.status) {
        spin_unlock(&pmydev->lock);
        return;
    }

    pmydev->data.code = KEY2;
    pmydev->data.status = status;
    pmydev->newflag = 1;

    spin_unlock(&pmydev->lock);
    wake_up(&pmydev->rq);

    return;
}

int __init key2_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

    struct device_node *pnode = NULL;

    pnode = of_find_node_by_path("/mykey2_node");
    if (NULL == pnode) {
        printk("find node failed\n");
        return -1;
    }

    pgmydev = (struct key2_dev*)kmalloc(sizeof(struct key2_dev), GFP_KERNEL);
    if (NULL == pgmydev) {
        printk("kmalloc for struct key2_dev failed\n");
        return -1;
    }

    pgmydev->gpio = of_get_named_gpio(pnode, "key2-gpio", 0);
    pgmydev->irqno = irq_of_parse_and_map(pnode, 0);

	/* 申请设备号 */
	ret = register_chrdev_region(devno, key2_num, "key2");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, key2_num, "key2");
		if (ret) {
            kfree(pgmydev);
            pgmydev = NULL;
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}

	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&pgmydev->mydev, &myops);

	/* 给struct cdev对象添加到内核对应的数据结构里 */
	pgmydev->mydev.owner = THIS_MODULE;
	cdev_add(&pgmydev->mydev, devno, key2_num);

	init_waitqueue_head(&pgmydev->rq);

	spin_lock_init(&pgmydev->lock);

    tasklet_init(&pgmydev->tsk, bottom_irq_func, (unsigned long)pgmydev);

    ret = request_irq(pgmydev->irqno, key2_irq_handle, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "key2", pgmydev);
    if (ret) {
        printk("request_irq failed\n");
        cdev_del(&pgmydev->mydev);
        kfree(pgmydev);
        pgmydev = NULL;
        unregister_chrdev_region(devno, key2_num);
        return -1;
    }

	return 0;
}

void __exit key2_exit(void)
{
	dev_t devno = MKDEV(major, minor);
    free_irq(pgmydev->irqno, pgmydev);
	cdev_del(&pgmydev->mydev);
	unregister_chrdev_region(devno, key2_num);

	kfree(pgmydev);
	pgmydev = NULL;
}


MODULE_LICENSE("GPL");

module_init(key2_init);
module_exit(key2_exit);

下半部机制之workqueue  --- 基于内核线程

结构体

typedef void (*work_func_t)(struct work_struct  *work)

struct work_struct {
​        atomic_long_t data;
​        struct list_head entry;
​        work_func_t func;

\#ifdef CONFIG_LOCKDEP
​            struct lockdep_map lockdep_map;
\#endif
};

定义工作队列底半部处理函数

void work_queue_func(struct work_struct *work);

初始化工作队列

struct work_struct work_queue;

初始化:绑定工作队列及工作队列的底半部处理函数

INIT_WORK(struct work_struct * pwork, _func);

参数:pwork:工作队列

           func:工作队列的底半部处理函数

工作队列的调度函数

bool schedule_work(struct work_struct *work);

示例代码(workqueue)

key_workqueue.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <asm/uaccess.h>

#include "key.h"

int major = 11;
int minor = 0;
int key2_num = 1;

struct key2_dev
{
	struct cdev mydev;

	int gpio;
    int irqno;

    struct keyvalue data;
    int newflag;
    spinlock_t lock;

	wait_queue_head_t rq;
    
    struct work_struct wk;
};

struct key2_dev *pgmydev = NULL;

int key2_open(struct inode* pnode, struct file* pfile)
{
	pfile->private_data = (void *)(container_of(pnode->i_cdev, struct key2_dev, mydev));
	return 0;
}

int key2_close(struct inode* pnode, struct file* pfile)
{
	return 0;
}

ssize_t key2_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
{
	struct key2_dev *pmydev = (struct key2_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

    if (count < sizeof(struct keyvalue)) {
        printk("expect read size is invalid\n");
        return -1;
    }

	spin_lock(&pmydev->lock);
	if (!pmydev->newflag) {
        if (pfile->f_flags & O_NONBLOCK) {
            // 非阻塞
            spin_unlock(&pmydev->lock);
            printk("O_NONBLOCK No Data Read\n");
            return -1;
        } else {
            // 阻塞
            spin_unlock(&pmydev->lock);
            ret = wait_event_interruptible(pmydev->rq, pmydev->newflag == 1);
            if (ret) {
                printk("Wake up by signal\n");
				return -ERESTARTSYS;
            }
            spin_lock(&pmydev->lock);
        }
    }

	if (count > sizeof(struct keyvalue)) {
		size = sizeof(struct keyvalue);
	} else {
		size = count;
	}

	ret = copy_to_user(puser, &pmydev->data, size);
	if (ret) {
		spin_unlock(&pmydev->lock);
		printk("copy_to_user failed\n");
		return -1;
	}

	pmydev->newflag = 0;

    spin_unlock(&pmydev->lock);

	return size;
}

unsigned int key2_poll(struct file* pfile, poll_table* ptb)
{
	struct key2_dev *pmydev = (struct key2_dev *)pfile->private_data;
	unsigned int mask = 0;

	poll_wait(pfile, &pmydev->rq, ptb);

	spin_lock(&pmydev->lock);
	if (pmydev->newflag) {
		mask |= POLLIN | POLLRDNORM;
	}
	spin_unlock(&pmydev->lock);

	return mask;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = key2_open,
	.release = key2_close,
	.read = key2_read,
	.poll = key2_poll,
};

irqreturn_t key2_irq_handle(int no, void *arg)
{
    struct key2_dev *pmydev = (struct key2_dev*)arg;
    
    schedule_work(&pmydev->wk);

    return IRQ_HANDLED;
}

void bottom_irq_func(struct work_struct *pwk)
{
    struct key2_dev *pmydev = container_of(pwk, struct key2_dev, wk);
    int status1 = 0;
    int status2 = 0;
    int status = 0;

    status1 = gpio_get_value(pmydev->gpio);
    mdelay(1);
    status2 = gpio_get_value(pmydev->gpio);

    if (status1 != status2) {
        return;
    }

    status = status1;

    spin_lock(&pmydev->lock);
    if (status == pmydev->data.status) {
        spin_unlock(&pmydev->lock);
        return;
    }

    pmydev->data.code = KEY2;
    pmydev->data.status = status;
    pmydev->newflag = 1;

    spin_unlock(&pmydev->lock);
    wake_up(&pmydev->rq);

    return;
}

int __init key2_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

    struct device_node *pnode = NULL;

    pnode = of_find_node_by_path("/mykey2_node");
    if (NULL == pnode) {
        printk("find node failed\n");
        return -1;
    }

    pgmydev = (struct key2_dev*)kmalloc(sizeof(struct key2_dev), GFP_KERNEL);
    if (NULL == pgmydev) {
        printk("kmalloc for struct key2_dev failed\n");
        return -1;
    }

    pgmydev->gpio = of_get_named_gpio(pnode, "key2-gpio", 0);
    pgmydev->irqno = irq_of_parse_and_map(pnode, 0);

	/* 申请设备号 */
	ret = register_chrdev_region(devno, key2_num, "key2");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, key2_num, "key2");
		if (ret) {
            kfree(pgmydev);
            pgmydev = NULL;
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}

	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&pgmydev->mydev, &myops);

	/* 给struct cdev对象添加到内核对应的数据结构里 */
	pgmydev->mydev.owner = THIS_MODULE;
	cdev_add(&pgmydev->mydev, devno, key2_num);

	init_waitqueue_head(&pgmydev->rq);

	spin_lock_init(&pgmydev->lock);

    INIT_WORK(&pgmydev->wk, bottom_irq_func);

    ret = request_irq(pgmydev->irqno, key2_irq_handle, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "key2", pgmydev);
    if (ret) {
        printk("request_irq failed\n");
        cdev_del(&pgmydev->mydev);
        kfree(pgmydev);
        pgmydev = NULL;
        unregister_chrdev_region(devno, key2_num);
        return -1;
    }

	return 0;
}

void __exit key2_exit(void)
{
	dev_t devno = MKDEV(major, minor);
    free_irq(pgmydev->irqno, pgmydev);
	cdev_del(&pgmydev->mydev);
	unregister_chrdev_region(devno, key2_num);

	kfree(pgmydev);
	pgmydev = NULL;
}


MODULE_LICENSE("GPL");

module_init(key2_init);
module_exit(key2_exit);

下半部机制比较

任务机制
​    
workqueue  ----- 内核线程  能睡眠  运行时间无限制

异常机制  -------  不能睡眠  下半部执行时间不宜太长( < 1s)
​    软中断  ----- 接口不方便
​    tasklet  ----- 无具体延后时间要求时
​    定时器  ----- 有具体延后时间要求时


设备模型

起源

仅devfs,导致开发不方便以及一些功能难以支持:

  1. 热插拔
  2. 不支持一些针对所有设备的统一操作(如电源管理)
  3. 不能自动mknod
  4. 用户查看不了设备信息
  5. 设备信息硬编码,导致驱动代码通用性差,即没有分离设备和驱动


新方案

uevent机制:sysfs + uevent + udevd(上层app)

sysfs: 一种用内存模拟的文件系统,系统启动时mount到/sys目录

sysfs用途:(类似于windows的设备管理器)

1. 建立系统中总线、驱动、设备三者之间的桥梁
2. 向用户空间展示内核中各种设备的拓扑图
3. 提供给用户空间对设备获取信息和操作的接口,部分取代ioctl功能

sysfs在内核中的组成要素在用户空间/sys下的显示
内核对象(kobject)目录
对象属性(attribute)文件
对象关系(relationship)链接(Symbolic Link)

四个基本结构

类型所包含的内容内核数据结构对应/sys项
设备(Devices)设备是此模型中最基本的类型,以设备本身的连接按层次组织struct device/sys/devices/?/?/…/
驱动(Drivers)在一个系统中安装多个相同设备,只需要一份驱动程序的支持struct device_driver/sys/bus/pci/drivers/?/
总线(Bus)在整个总线级别对此总线上连接的所有设备进行管理struct bus_type/sys/bus/?/
类别(Classes)这是按照功能进行分类组织的设备层次树;如 USB 接口和 PS/2 接口的鼠标都是输入设备,都会出现在/sys/class/input/下struct class/sys/class/?/

目录组织结构:

/sys下的子目录所包含的内容
/sys/devices这是内核对系统中所有设备的分层次表达模型,也是/sys文件系统管理设备的最重要的目录结构;
/sys/dev这个目录下维护一个按字符设备和块设备的主次号码(major:minor)链接到真实的设备(/sys/devices下)的符号链接文件;
/sys/bus这是内核设备按总线类型分层放置的目录结构, devices 中的所有设备都是连接于某种总线之下,在这里的每一种具体总线之下可以找到每一个具体设备的符号链接,它也是构成 Linux 统一设备模型的一部分;
/sys/class这是按照设备功能分类的设备模型,如系统所有输入设备都会出现在/sys/class/input 之下,而不论它们是以何种总线连接到系统。它也是构成 Linux 统一设备模型的一部分;
/sys/kernel这里是内核所有可调整参数的位置,目前只有 uevent_helper, kexec_loaded, mm, 和新式的slab 分配器等几项较新的设计在使用它,其它内核可调整参数仍然位于sysctl(/proc/sys/kernel) 接口中;
/sys/module这里有系统中所有模块的信息,不论这些模块是以内联(inlined)方式编译到内核映像文件(vmlinuz)中还是编译为外部模块(ko文件),都可能会出现在/sys/module 中
/sys/power这里是系统中电源选项,这个目录下有几个属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令让机器关机、重启等。

uevent


代码中自动mknod

struct class *class_create(struct module *owner, const char *name);
/*
 * 功能:在/sys/class生成一个目录,目录名由name指定
 * 参数:
    struct module *owner - THIS_MODULE
    const char *name - 目录名
 * 返回值  成功:class指针   失败:NULL
*/
/*
辅助接口:可以定义一个struct class 的指针变量cls来接受返回值,然后通过IS_ERR(cls)判断是否失败;
IS_ERR(cls);成功----------------->0
IS_ERR(cls);失败----------------->非0
PTR_ERR(cls);来获得失败的返回错误码;
*/
void class_destroy(struct class *cls)
/*
* 功能:删除class_create生成目录
* 参数:
     struct class *cls - class指针
* 返回值
*/
struct device *device_create(struct class *class, struct device *parent,
                 dev_t devt, void *drvdata, const char *fmt, ...)
/*
 * 功能:在/sys/class目录下class_create生成目录再生成一个子目录与该设备相对应,发uevent让应用程序udevd创建设备文件
 * 参数:
     struct class *class - class指针
     struct device *parent - 父对象,一般NULL
     dev_t devt - 设备号
     void *drvdata - 驱动私有数据,一般NULL
     const char *fmt - 字符串的格式
      ... - 不定参数
 * 返回值
     成功:device指针
     失败:NULL
 */
void device_destroy(struct class *class, dev_t devt)
/*
 * 功能:删除device_create生成目录
 * 参数:
     struct class *class - class指针
     dev_t devt - 设备号
 * 返回值
*/

示例代码(自动mknod,修改先前读写操作的代码)

mknod.c


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
 
#define BUF_LEN 100
 
int major = 11;
int minor = 0;
int mychar_num = 1;
 
struct mychar_dev
{
	struct cdev mydev;
 
	char mydev_buf[BUF_LEN];
	int curlen;

    struct class *pcls;
    struct device *pdev;
};
 
struct mychar_dev gmydev;
 
int mychar_open(struct inode* pnode, struct file* pfile)
{
	pfile->private_data = (void *)(container_of(pnode->i_cdev, struct mychar_dev, mydev));
	printk("mychar_open is called\n");
	return 0;
}
 
int mychar_close(struct inode* pnode, struct file* pfile)
{
	printk("mychar_close is called\n");
	return 0;
}
 
ssize_t mychar_read(struct file* pfile, char __user *puser, size_t count, loff_t* p_pos)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;
 
	if (count > pmydev->curlen) {
		size = pmydev->curlen;
	} else {
		size = count;
	}
 
	ret = copy_to_user(puser, pmydev->mydev_buf, size);
	if (ret) {
		printk("copy_to_user failed\n");
		return -1;
	}
 
	memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);
 
	pmydev->curlen -= size;
	return size;
}
 
ssize_t mychar_write(struct file* pfile, const char __user *puser, size_t count, loff_t* p_pos)
{
	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;
 
	if (count > BUF_LEN - pmydev->curlen) {
		size = BUF_LEN - pmydev->curlen;
	} else {
		size = count;
	}
 
	ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
	if (ret) {
		printk("copy_from_user failed\n");
		return -1;
	}
 
	pmydev->curlen += size;
	return size;
}
 
struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.release = mychar_close,
	.read = mychar_read,
	.write = mychar_write,
};
 
int __init mychar_init(void)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);
 
	/* 申请设备号 */
	ret = register_chrdev_region(devno, mychar_num, "mychar");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");
		if (ret) {
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}
 
	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&gmydev.mydev, &myops);
 
	/* 给struct cdev对象添加到内核对应的数据结构里 */
	gmydev.mydev.owner = THIS_MODULE;
	cdev_add(&gmydev.mydev, devno, mychar_num);

    gmydev.pcls = class_create(THIS_MODULE, "mymknod");
    if (IS_ERR(gmydev.pcls)) {
        printk("class_create failed\n");
        cdev_del(&gmydev.mydev);
	    unregister_chrdev_region(devno, mychar_num);
        return -1;
    }

    gmydev.pdev = device_create(gmydev.pcls, NULL, devno, NULL, "automknod");
    if (NULL == gmydev.pdev) {
        printk("device_create failed\n");
        class_destroy(gmydev.pcls);
        cdev_del(&gmydev.mydev);
	    unregister_chrdev_region(devno, mychar_num);
        return -1;
    }
 
	return 0;
}
 
void __exit mychar_exit(void)
{
	dev_t devno = MKDEV(major, minor);
    device_destroy(gmydev.pcls, devno);
    class_destroy(gmydev.pcls);
	cdev_del(&gmydev.mydev);
	unregister_chrdev_region(devno, mychar_num);
}
 
 
MODULE_LICENSE("GPL");
 
module_init(mychar_init);
module_exit(mychar_exit);

test.c


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
 
int main(int argc, char* argv[])
{
    int fd = -1;
    char buf[8] = "";
 
    if (argc < 2) {
        printf("The argument is too few\n");
        return 1;
    }
 
    fd = open(argv[1], O_RDWR);
    if (fd < 0) {
        printf("open %s failed\n", argv[1]);
        return 2;
    }
 
    write(fd, "hello", 6);
    read(fd, buf, 8);
    printf("buf = %s\n",buf);
 
    close(fd);
    fd = -1;
    return 0;
}

测试结果

shrek@ubuntu16:~/share/mydrivercode$ sudo insmod ./mknod.ko
shrek@ubuntu16:~/share/mydrivercode$ ls /dev/automknod -l
crw------- 1 root root 11, 0 Dec  6 21:07 /dev/automknod
shrek@ubuntu16:~/share/mydrivercode$ sudo chmod 777 /dev/automknod 
shrek@ubuntu16:~/share/mydrivercode$ ./a.out  /dev/automknod 
buf = hello

平台总线式驱动开发

总线、设备、驱动

硬编码式的驱动开发带来的问题:

1. 垃圾代码太多
2. 结构不清晰
3. 一些统一设备功能难以支持
4. 开发效率低下


初期解决思路:设备和驱动分离

​    struct device 来表示一个具体设备,主要提供具体设备相关的资源(如寄存器地址、GPIO、中断等等)

​    struct device_driver 来表示一个设备驱动,一个驱动可以支持多个操作逻辑相同的设备

​    带来的问题-------怎样将二者进行关联(匹配)?

​    硬件上同一总线上的设备遵循一致的时序通信,在其基础上增加管理设备和驱动的软件功能

​    于是引入总线(bus),各种总线的核心框架由内核来实现,通信时序一般由SOC供应商支持

​    内核中用 struct bus_type 来表示一种总线,总线可以是实际存在的总线,也可以是虚拟总线:

1. 实际总线:提供时序通信方式 + 管理设备和驱动
2. 虚拟总线:仅用来管理设备和驱动(最核心的作用之一就是完成设备和驱动的匹配)

理解方式:

设备:提供硬件资源——男方

驱动:提供驱动代码——女方

总线:匹配设备和驱动——婚介所


升级思路:根据设备树,在系统启动时自动产生每个节点对应的设备

初期方案,各种device需要编码方式注册进内核中的设备管理结构中,为了进一步减少这样的编码,引进设备树


基本数据类型

struct device 
{
    struct bus_type *bus;                // 总线类型
    dev_t devt;                          // 设备号
    struct device_driver *driver;        // 设备驱动
    struct device_node  *of_node;        // 设备树中的节点,重要
    void (*release)(struct device *dev); // 删除设备,重要
    //.......
};
struct device_driver 
{
    const char *name;                          // 驱动名称,匹配device用,重要
    struct bus_type *bus;                      // 总线类型
    struct module *owner;                      // 模块THIS_MODULE 
    const struct of_device_id *of_match_table; // 用于设备树匹配 of_match_ptr(某struct of_device_id对象地址) 重要
    //......
};
struct of_device_id
{
    char name[32];        // 设备名
    char type[32];        // 设备类型
    char compatible[128]; // 用于device和driver的match,重点
};
// 用到结构体数组,一般不指定大小,初始化时最后加{}表示数组结束

platform总线驱动

platform是一种虚拟总线,主要用来管理那些不需要时序通信的设备

基本结构图:


核心数据类型之platform_device

struct platform_device 
{
    const char *name;                          // 匹配用的名字
    int id;                                    // 设备id,用于在该总线上同名的设备进行编号,如果只有一个设备,则为-1
    struct device dev;                         // 设备模块必须包含该结构体
    struct resource *resource;                 // 资源结构体 指向资源数组
    u32 num_resources;                         // 资源的数量 资源数组的元素个数
    const struct platform_device_id *id_entry; // 设备id
};
struct platform_device_id
{
    char name[20];              // 匹配用名称
    kernel_ulong_t driver_data; // 需要向驱动传输的其它数据
};
struct resource 
{
    resource_size_t start;  // 资源起始位置   
    resource_size_t end;    // 资源结束位置
    const char *name;      
    unsigned long flags;    // 区分资源是什么类型的
};
 
#define IORESOURCE_MEM        0x00000200
#define IORESOURCE_IRQ        0x00000400 
/*
flags 指资源类型,我们常用的是 IORESOURCE_MEM、IORESOURCE_IRQ  这两种。start 和 end 的含义会随着 flags而变更,如

a -- flags为IORESOURCE_MEM 时,start 、end 分别表示该platform_device占据的内存的开始地址和结束值;注意不同MEM的地址值不能重叠

b -- flags为 IORESOURCE_IRQ   时,start 、end 分别表示该platform_device使用的中断号的开始地址和结束值
*/
/**
 * 注册:把指定设备添加到内核中平台总线的设备列表,等待匹配,匹配成功则回调驱动中probe;
 */
int platform_device_register(struct platform_device *);

/**
 * 注销:把指定设备从设备列表中删除,如果驱动已匹配则回调驱动方法和设备信息中的release;
 */
void platform_device_unregister(struct platform_device *);
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);
/*
    功能:获取设备资源
    参数:dev:平台驱动
        type:获取的资源类型
        num:对应类型资源的序号(如第0个MEM、第2个IRQ等,不是数组下标)
    返回值:成功:资源结构体首地址,失败:NULL
*/

核心数据类型之platform_driver

struct platform_driver 
{
    int (*probe)(struct platform_device *); // 设备和驱动匹配成功之后调用该函数
    int (*remove)(struct platform_device *); // 设备卸载了调用该函数
    
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver; // 内核里所有的驱动必须包含该结构体
    const struct platform_device_id *id_table; // 能够支持的设备八字数组,用到结构体数组,一般不指定大小,初始化时最后加{}表示数组结束
};
int platform_driver_register(struct platform_driver*pdrv);
/*
    功能:注册平台设备驱动
    参数:pdrv:平台设备驱动结构体
    返回值:成功:0
    失败:错误码
*/
void platform_driver_unregister(struct platform_driver*pdrv);

platform的三种匹配方式

2.1 名称匹配(如图中的name):一个驱动只对应一个设备 ----- 优先级最低

2.2 id匹配(如图中的platform_device_id结构体):一个驱动可以对应多个设备 ----- 优先级次低

     device模块中,id的name成员必须与struct platform_device中的name成员内容一致

     因此device模块中,struct platform_device中的name成员必须指定

     driver模块中,struct platform_driver成员driver的name成员必须指定,但与device模块中name可以不相同

2.3 设备树匹配:内核启动时根据设备树自动产生的设备 ----- 优先级最高

     使用compatible属性进行匹配,注意设备树中compatible属性值不要包含空白字符

     id_table可不设置,但struct platform_driver成员driver的name成员必须设置


名称匹配

基础框架

/* platform device框架 */
#include <linux/module.h> 
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>

// 定义资源数组

static void device_release(struct device *dev)
{
	printk("platform: device release\n");
}

struct platform_device test_device = {
	.id = -1,
	.name = "test_device", // 必须初始化
	.dev.release = device_release, 
};

static int __init platform_device_init(void)
{
	platform_device_register(&test_device);
	return 0;
}

static void __exit platform_device_exit(void)
{
	platform_device_unregister(&test_device);
}

module_init(platform_device_init);
module_exit(platform_device_exit);
MODULE_LICENSE("Dual BSD/GPL");

/* platform driver框架 */
#include <linux/module.h> 
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>

static int driver_probe(struct platform_device *dev)
{
	printk("platform: match ok!\n");
	return 0;
}

static int driver_remove(struct platform_device *dev)
{
	printk("platform: driver remove\n");
	return 0;
}

struct platform_driver test_driver = {
	.probe = driver_probe,
	.remove = driver_remove,
	.driver = {
		.name = "test_device", // 必须初始化
	},
};

static int __init platform_driver_init(void)
{
	platform_driver_register(&test_driver);
	return 0;
}

static void __exit platform_driver_exit(void)
{
	platform_driver_unregister(&test_driver);
}

module_init(platform_driver_init);
module_exit(platform_driver_exit);
MODULE_LICENSE("Dual BSD/GPL");

示例代码(名称匹配)

hello_device.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>

void hello_device_release(struct device *pdev)
{
    printk("hello_device_release is called\n");
}

struct resource hello_device_res [] =
{
	[0] = {.start = 0x1000, .end=0x1003, .name="reg1", .flags = IORESOURCE_MEM},
	[1] = {.start = 0x2000, .end=0x2003, .name="reg2", .flags = IORESOURCE_MEM},
	[2] = {.start = 10, .end=10, .name="irq1", .flags = IORESOURCE_IRQ},
	[3] = {.start = 0x3000, .end=0x3003, .name="reg3", .flags = IORESOURCE_MEM},
	[4] = {.start = 100, .end=100, .name="irq2", .flags = IORESOURCE_IRQ},
	[5] = {.start = 62, .end=62, .name="irq3", .flags = IORESOURCE_IRQ},
};

struct platform_device hello_device = {
    .name = "hello", // 匹配依据
    .dev.release = hello_device_release,
    .resource = hello_device_res,
    .num_resources = ARRAY_SIZE(hello_device_res),
};

int __init hello_device_init(void)
{
    platform_device_register(&hello_device);
    return 0;
}

void __exit hello_device_exit(void)
{
    platform_device_unregister(&hello_device);
}

MODULE_LICENSE("GPL");
module_init(hello_device_init);
module_exit(hello_device_exit);

hello_driver.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>

int hello_driver_probe(struct platform_device *p_pltdev)
{
    struct resource *pres = NULL;

    printk("hello_driver_probe is called\n");

    pres = platform_get_resource(p_pltdev, IORESOURCE_MEM, 2);
    printk("res.start = 0x%x\n",(unsigned int)pres->start);

    pres = platform_get_resource(p_pltdev, IORESOURCE_IRQ, 1);
	printk("res.start = %d\n", (int)pres->start);
	return 0;
}

int hello_driver_remove(struct platform_device *p_pltdev)
{
    printk("hello_driver_remove is called\n");
    return 0;
}

struct platform_driver hello_driver = {
    .driver.name = "hello", // 匹配依据
    .probe = hello_driver_probe,
    .remove = hello_driver_remove,
};

int __init hello_driver_init(void)
{
    platform_driver_register(&hello_driver);
    return 0;
}

void __exit hello_driver_exit(void)
{
    platform_driver_unregister(&hello_driver);
}

MODULE_LICENSE("GPL");
module_init(hello_driver_init);
module_exit(hello_driver_exit);

测试结果

插入两个ko之后匹配成功调用 driver 的 probe 函数
rmmod driver 会调用 driver 的 remove 函数
rmmod device 会调用 device 的 release 函数

shrek@ubuntu16:~/share/mydrivercode/platform$ sudo dmesg -C
shrek@ubuntu16:~/share/mydrivercode/platform$ sudo insmod ./hello_driver.ko
shrek@ubuntu16:~/share/mydrivercode/platform$ sudo insmod ./hello_device.ko
shrek@ubuntu16:~/share/mydrivercode/platform$ dmesg
[ 3477.288502] hello_driver_probe is called
[ 3477.288504] res.start = 0x3000
[ 3477.288504] res.start = 100

shrek@ubuntu16:~/share/mydrivercode/platform$ sudo rmmod hello_driver 
shrek@ubuntu16:~/share/mydrivercode/platform$ dmesg
[ 3477.288502] hello_driver_probe is called
[ 3477.288504] res.start = 0x3000
[ 3477.288504] res.start = 100
[ 3514.642304] hello_driver_remove is called

shrek@ubuntu16:~/share/mydrivercode/platform$ sudo rmmod hello_device 
shrek@ubuntu16:~/share/mydrivercode/platform$ dmesg
[ 3477.288502] hello_driver_probe is called
[ 3477.288504] res.start = 0x3000
[ 3477.288504] res.start = 100
[ 3514.642304] hello_driver_remove is called
[ 3526.339599] hello_device_release is called

示例代码(名称匹配之LED驱动)

leds_device.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>

#define GPX1CON 0x11000C20
#define GPX1DAT 0x11000C24

#define GPX2CON 0x11000C40
#define GPX2DAT 0x11000C44

#define GPF3CON 0x114001E0
#define GPF3DAT 0x114001E4

void leds_device_release(struct device *pdev)
{
    printk("leds_device_release is called\n");
}

struct resource leds_device_res [] =
{
	[0] = {.start = GPX1CON, .end=GPX1CON + 3, .name="GPX1CON", .flags = IORESOURCE_MEM},
	[1] = {.start = GPX1DAT, .end=GPX1DAT + 3, .name="GPX1DAT", .flags = IORESOURCE_MEM},

	[2] = {.start = GPX2CON, .end=GPX2CON + 3, .name="GPX2CON", .flags = IORESOURCE_MEM},
	[3] = {.start = GPX2DAT, .end=GPX2DAT + 3, .name="GPX2DAT", .flags = IORESOURCE_MEM},
	
	[4] = {.start = GPF3CON, .end=GPF3CON + 3, .name="GPF3CON", .flags = IORESOURCE_MEM},
	[5] = {.start = GPF3DAT, .end=GPF3DAT + 3, .name="GPF3DAT", .flags = IORESOURCE_MEM},
};

struct platform_device leds_device = {
    .name = "leds", // 匹配依据
    .dev.release = leds_device_release,
    .resource = leds_device_res,
    .num_resources = ARRAY_SIZE(leds_device_res),
};

int __init leds_device_init(void)
{
    platform_device_register(&leds_device);
    return 0;
}

void __exit leds_device_exit(void)
{
    platform_device_unregister(&leds_device);
}

MODULE_LICENSE("GPL");
module_init(leds_device_init);
module_exit(leds_device_exit);

leds_driver.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>

#include "leddrv.h"

int major = 11;
int minor = 0;
int myled_num = 1;

struct myled_dev
{
    struct cdev mydev;

    volatile unsigned long *pled2_con;
    volatile unsigned long *pled2_dat;

    volatile unsigned long *pled3_con;
	volatile unsigned long *pled3_dat;

	volatile unsigned long *pled4_con;
	volatile unsigned long *pled4_dat;

	volatile unsigned long *pled5_con;
	volatile unsigned long *pled5_dat;
};

struct myled_dev *pgmydev = NULL;

int myled_open(struct inode *pnode, struct file *pfile)
{
    pfile->private_data =(void*)(container_of(pnode->i_cdev,struct myled_dev,mydev));
    return 0;
}

int myled_close(struct inode *pnode, struct file *pfile)
{
    return 0;
}

void led_on(struct myled_dev *pmydev, int ledno)
{
    switch (ledno) {
        case 2:
            writel(readl(pmydev->pled2_dat) | (0x1 << 7), pmydev->pled2_dat);
            break;
        case 3:
            writel(readl(pmydev->pled3_dat) | (0x1 << 0), pmydev->pled3_dat);
			break;
		case 4:
			writel(readl(pmydev->pled4_dat) | (0x1 << 4), pmydev->pled4_dat);
			break;
		case 5:
			writel(readl(pmydev->pled5_dat) | (0x1 << 5), pmydev->pled5_dat);
			break;
    }
}

void led_off(struct myled_dev *pmydev, int ledno)
{
    switch (ledno) {
        case 2:
			writel(readl(pmydev->pled2_dat) & (~(0x1 << 7)), pmydev->pled2_dat);
			break;
		case 3:
			writel(readl(pmydev->pled3_dat) & (~(0x1 << 0)), pmydev->pled3_dat);
			break;
		case 4:
			writel(readl(pmydev->pled4_dat) & (~(0x1 << 4)), pmydev->pled4_dat);
			break;
		case 5:
			writel(readl(pmydev->pled5_dat) & (~(0x1 << 5)), pmydev->pled5_dat);
			break;
    }
}

long myled_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
    struct myled_dev *pmydev = (struct myled_dev*)pfile->private_data;

    if (arg < 2 || arg > 5) {
        return -1;
    }

    switch (cmd) {
        case MY_LED_ON:
            led_on(pmydev, arg);
            break;
        case MY_LED_OFF:
            led_off(pmydev, arg);
            break;
        default:
            return -1;
    }

    return 0;
}

struct file_operations myops = {
    .owner = THIS_MODULE,
    .open = myled_open,
    .release = myled_close,
    .unlocked_ioctl = myled_ioctl,
};

void ioremap_ledreg(struct myled_dev *pmydev, struct platform_device *p_pltdev)
{
	struct resource *pres = NULL;

    pres = platform_get_resource(p_pltdev, IORESOURCE_MEM, 2);
	pmydev->pled2_con = ioremap(pres->start, 4);

	pres = platform_get_resource(p_pltdev, IORESOURCE_MEM, 3);
	pmydev->pled2_dat = ioremap(pres->start, 4);


	pres = platform_get_resource(p_pltdev, IORESOURCE_MEM, 0);
	pmydev->pled3_con = ioremap(pres->start, 4);

	pres = platform_get_resource(p_pltdev, IORESOURCE_MEM, 1);
	pmydev->pled3_dat = ioremap(pres->start, 4);
	

	pres = platform_get_resource(p_pltdev, IORESOURCE_MEM, 4);
	pmydev->pled4_con = ioremap(pres->start, 4);

	pres = platform_get_resource(p_pltdev, IORESOURCE_MEM, 5);
	pmydev->pled4_dat = ioremap(pres->start, 4);
	
	pmydev->pled5_con = pmydev->pled4_con;
	pmydev->pled5_dat = pmydev->pled4_dat;
}

void set_output_ledconreg(struct myled_dev *pmydev)
{
    writel((readl(pmydev->pled2_con) & (~(0xF << 28))) | (0x1 << 28),pmydev->pled2_con);
	writel((readl(pmydev->pled3_con) & (~(0xF << 0 ))) | (0x1 << 0 ),pmydev->pled3_con);
	writel((readl(pmydev->pled4_con) & (~(0xF << 16))) | (0x1 << 16),pmydev->pled4_con);
	writel((readl(pmydev->pled5_con) & (~(0xF << 20))) | (0x1 << 20),pmydev->pled5_con);

	writel(readl(pmydev->pled2_dat) & (~(0x1 << 7)),pmydev->pled2_dat);
	writel(readl(pmydev->pled3_dat) & (~(0x1 << 0)),pmydev->pled3_dat);
	writel(readl(pmydev->pled4_dat) & (~(0x1 << 4)),pmydev->pled4_dat);
	writel(readl(pmydev->pled5_dat) & (~(0x1 << 5)),pmydev->pled5_dat);
}

void iounmap_ledreg(struct myled_dev *pmydev)
{
    iounmap(pmydev->pled2_con);
    pmydev->pled2_con = NULL;
    iounmap(pmydev->pled2_dat);
    pmydev->pled2_dat = NULL;

    iounmap(pmydev->pled3_con);
	pmydev->pled3_con = NULL;
	iounmap(pmydev->pled3_dat);
	pmydev->pled3_dat = NULL;
	
	iounmap(pmydev->pled4_con);
	pmydev->pled4_con = NULL;
	iounmap(pmydev->pled4_dat);
	pmydev->pled4_dat = NULL;
	
	pmydev->pled5_con = NULL;
	pmydev->pled5_dat = NULL;
}

int leds_driver_probe(struct platform_device *p_pltdev)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 申请设备号 */
	ret = register_chrdev_region(devno, myled_num, "myled");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, myled_num, "myled");
		if (ret) {
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}

    pgmydev = (struct myled_dev*)kmalloc(sizeof(struct myled_dev), GFP_KERNEL);
    if (NULL == pgmydev) {
        unregister_chrdev_region(devno, myled_num);
        printk("kmalloc failed\n");
        return -1;
    }
    memset(pgmydev, 0, sizeof(struct myled_dev));

	/* 给struct cdev对象指定操作函数集 */
	cdev_init(&pgmydev->mydev, &myops);

	/* 给struct cdev对象添加到内核对应的数据结构里 */
	pgmydev->mydev.owner = THIS_MODULE;
	cdev_add(&pgmydev->mydev, devno, myled_num);

	/* ioremap */
	ioremap_ledreg(pgmydev, p_pltdev);

	/* con-register set output */
    set_output_ledconreg(pgmydev);

	return 0;
}

int leds_driver_remove(struct platform_device *p_pltdev)
{
	dev_t devno = MKDEV(major, minor);

	/* iounmap */
    iounmap_ledreg(pgmydev);

	cdev_del(&pgmydev->mydev);
	unregister_chrdev_region(devno, myled_num);

    kfree(pgmydev);
    pgmydev = NULL;

	return 0;
}

struct platform_driver leds_driver = {
	.driver.name = "leds",
	.probe = leds_driver_probe,
	.remove = leds_driver_remove,
};

int __init myled_init(void)
{
	platform_driver_register(&leds_driver);
	return 0;
}

void __exit myled_exit(void)
{
	platform_driver_unregister(&leds_driver);
}

MODULE_LICENSE("GPL");

module_init(myled_init);
module_exit(myled_exit);

测试结果

[root@farsight]#insmod ./leds_driver.ko 
[root@farsight]#insmod ./leds_device.ko 
[root@farsight]#cat /proc/devices | grep led
 11 myled
[root@farsight]#mknod /dev/leddev c 11 0
[root@farsight]#./testled /dev/leddev 1 2
[root@farsight]#./testled /dev/leddev 1 3
[root@farsight]#./testled /dev/leddev 1 4
[root@farsight]#./testled /dev/leddev 1 5
[root@farsight]#./testled /dev/leddev 0 5
[root@farsight]#./testled /dev/leddev 0 4
[root@farsight]#./testled /dev/leddev 0 3
[root@farsight]#./testled /dev/leddev 0 2

ID匹配

Tips

1.  device 模块中,id 的 name 成员必须与 struct platform_device 中的 name 成员内容一致,因此 device 模块中,struct platform_device 中的 name 成员必须指定
2.  driver 模块中,struct platform_driver 成员 driver 的 name 成员必须指定,但与 device 模块中name可以不相同

基础框架

/* platform device框架 */
#include <linux/module.h> 
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>

// 定义资源数组

static void device_release(struct device *dev)
{
	printk("platform: device release\n");
}

struct platform_device_id test_id = {
    .name = "test_device",
};

struct platform_device test_device = {
	.name = "test_device", // 必须初始化
	.dev.release = device_release, 
    .id_entry = &test_id,
};

static int __init platform_device_init(void)
{
	platform_device_register(&test_device);
	return 0;
}

static void __exit platform_device_exit(void)
{
	platform_device_unregister(&test_device);
}

module_init(platform_device_init);
module_exit(platform_device_exit);
MODULE_LICENSE("Dual BSD/GPL");
/* platform driver框架 */
#include <linux/module.h> 
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>

static int driver_probe(struct platform_device *dev)
{
	printk("platform: match ok!\n");
	return 0;
}

static int driver_remove(struct platform_device *dev)
{
	printk("platform: driver remove\n");
	return 0;
}

struct platform_device_id testdrv_ids[] = 
{
	[0] = {.name = "test_device"},
    [1] = {.name = "abcxyz"},
    [2] = {}, // means ending
};

struct platform_driver test_driver = {
	.probe = driver_probe,
	.remove = driver_remove,
	.driver = {
		.name = "xxxxx", // 必须初始化
	},
    .id_table = testdrv_ids,
};

static int __init platform_driver_init(void)
{
	platform_driver_register(&test_driver);
	return 0;
}

static void __exit platform_driver_exit(void)
{
	platform_driver_unregister(&test_driver);
}

module_init(platform_driver_init);
module_exit(platform_driver_exit);
MODULE_LICENSE("Dual BSD/GPL");

用到结构体数组,一般不指定大小,初始化时最后加{}表示数组结束

设备中增加资源,驱动中访问资源


示例代码(ID匹配)

hello_device.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>

void hello_device_release(struct device *pdev)
{
    printk("hello_device_release is called\n");
}

struct resource hello_device_res [] =
{
	[0] = {.start = 0x1000, .end=0x1003, .name="reg1", .flags = IORESOURCE_MEM},
	[1] = {.start = 0x2000, .end=0x2003, .name="reg2", .flags = IORESOURCE_MEM},
	[2] = {.start = 10, .end=10, .name="irq1", .flags = IORESOURCE_IRQ},
	[3] = {.start = 0x3000, .end=0x3003, .name="reg3", .flags = IORESOURCE_MEM},
	[4] = {.start = 100, .end=100, .name="irq2", .flags = IORESOURCE_IRQ},
	[5] = {.start = 62, .end=62, .name="irq3", .flags = IORESOURCE_IRQ},
};

struct platform_device_id hello_id = {
    .name = "hello",
};

struct platform_device hello_device = {
    .name = "hello",
    .dev.release = hello_device_release,
    .resource = hello_device_res,
    .num_resources = ARRAY_SIZE(hello_device_res),

    .id_entry = &hello_id, // 匹配依据
};

int __init hello_device_init(void)
{
    platform_device_register(&hello_device);
    return 0;
}

void __exit hello_device_exit(void)
{
    platform_device_unregister(&hello_device);
}

MODULE_LICENSE("GPL");
module_init(hello_device_init);
module_exit(hello_device_exit);

hello_driver.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>

int hello_driver_probe(struct platform_device *p_pltdev)
{
    struct resource *pres = NULL;

    printk("hello_driver_probe is called\n");

    pres = platform_get_resource(p_pltdev, IORESOURCE_MEM, 2);
    printk("res.start = 0x%x\n",(unsigned int)pres->start);

    pres = platform_get_resource(p_pltdev, IORESOURCE_IRQ, 1);
	printk("res.start = %d\n", (int)pres->start);
	return 0;
}

int hello_driver_remove(struct platform_device *p_pltdev)
{
    printk("hello_driver_remove is called\n");
    return 0;
}

struct platform_device_id hellodrv_ids[] = {
    [0] = {.name = "hello"},
    [1] = {.name = "xyz"},
    [2] = {}
};

struct platform_driver hello_driver = {
    .driver.name = "abc", // 与device的name不同使之不能名字匹配
    .probe = hello_driver_probe,
    .remove = hello_driver_remove,

    .id_table = hellodrv_ids, // 匹配依据
};

int __init hello_driver_init(void)
{
    platform_driver_register(&hello_driver);
    return 0;
}

void __exit hello_driver_exit(void)
{
    platform_driver_unregister(&hello_driver);
}

MODULE_LICENSE("GPL");
module_init(hello_driver_init);
module_exit(hello_driver_exit);

测试结果

shrek@ubuntu16:~/share/mydrivercode/platform/hello_id$ sudo insmod ./hello_device.ko
shrek@ubuntu16:~/share/mydrivercode/platform/hello_id$ sudo insmod ./hello_driver.ko
shrek@ubuntu16:~/share/mydrivercode/platform/hello_id$ dmesg 
[  722.320042] hello_driver_probe is called
[  722.320043] res.start = 0x3000
[  722.320043] res.start = 100

设备树匹配

Tips

1. 无需编写 device 模块,只需编写 driver 模块
2. 使用 compatible 属性进行匹配,注意设备树中 compatible 属性值不要包含空白字符
3. id_table 可不设置,但 struct platform_driver 成员 driver 的 name 成员必须设置

/* platform driver框架 */
#include <linux/module.h> 
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>

static int driver_probe(struct platform_device *dev)
{
	printk("platform: match ok!\n");
	return 0;
}

static int driver_remove(struct platform_device *dev)
{
	printk("platform: driver remove\n");
	return 0;
}

struct platform_device_id testdrv_ids[] = 
{
	[0] = {.name = "test_device"},
    [1] = {.name = "abcxyz"},
    [2] = {}, // means ending
};

struct of_device_id test_of_ids[] = 
{
	[0] = {.compatible = "xyz,abc"},
    [1] = {.compatible = "qwe,opq"},
    [2] = {},
};

struct platform_driver test_driver = {
	.probe = driver_probe,
	.remove = driver_remove,
	.driver = {
		.name = "xxxxx", // 必须初始化
        .of_match_table = test_of_ids,
	},
};

static int __init platform_driver_init(void)
{
	platform_driver_register(&test_driver);
	return 0;
}

static void __exit platform_driver_exit(void)
{
	platform_driver_unregister(&test_driver);
}

module_init(platform_driver_init);
module_exit(platform_driver_exit);
MODULE_LICENSE("Dual BSD/GPL");

示例代码(设备树匹配之LED驱动)

设备树添加节点

4412-leds {
    compatible = "4412,led2-5";
    led2-gpio = <&gpx2 7 0>;
    led3-gpio = <&gpx1 0 0>;
    led4-gpio = <&gpf3 4 0>;
    led5-gpio = <&gpf3 5 0>;
};

leddrv_dt.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>

#include "leddrv.h"

int major = 11;
int minor = 0;
int myled_num = 1;

struct myled_dev
{
    struct cdev mydev;

    unsigned int led2gpio;
	unsigned int led3gpio;
	unsigned int led4gpio;
	unsigned int led5gpio;
};

struct myled_dev *pgmydev = NULL;

int myled_open(struct inode *pnode, struct file *pfile)
{
    pfile->private_data =(void*)(container_of(pnode->i_cdev,struct myled_dev,mydev));
    return 0;
}

int myled_close(struct inode *pnode, struct file *pfile)
{
    return 0;
}

void led_on(struct myled_dev *pmydev, int ledno)
{
    switch (ledno) {
        case 2:
            gpio_set_value(pmydev->led2gpio, 1);
            break;
        case 3:
            gpio_set_value(pmydev->led3gpio, 1);
			break;
		case 4:
			gpio_set_value(pmydev->led4gpio, 1);
			break;
		case 5:
			gpio_set_value(pmydev->led5gpio, 1);
			break;
    }
}

void led_off(struct myled_dev *pmydev, int ledno)
{
    switch (ledno) {
        case 2:
			gpio_set_value(pmydev->led2gpio, 0);
			break;
		case 3:
			gpio_set_value(pmydev->led3gpio, 0);
			break;
		case 4:
			gpio_set_value(pmydev->led4gpio, 0);
			break;
		case 5:
			gpio_set_value(pmydev->led5gpio, 0);
			break;
    }
}

long myled_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{
    struct myled_dev *pmydev = (struct myled_dev*)pfile->private_data;

    if (arg < 2 || arg > 5) {
        return -1;
    }

    switch (cmd) {
        case MY_LED_ON:
            led_on(pmydev, arg);
            break;
        case MY_LED_OFF:
            led_off(pmydev, arg);
            break;
        default:
            return -1;
    }

    return 0;
}

struct file_operations myops = {
    .owner = THIS_MODULE,
    .open = myled_open,
    .release = myled_close,
    .unlocked_ioctl = myled_ioctl,
};

void request_leds_gpio(struct myled_dev *pmydev, struct device_node *pnode) // 申请占用设备
{
	pmydev->led2gpio = of_get_named_gpio(pnode, "led2-gpio", 0);
	gpio_request(pmydev->led2gpio, "led2");

	pmydev->led3gpio = of_get_named_gpio(pnode, "led3-gpio", 0);
	gpio_request(pmydev->led3gpio, "led3");

	pmydev->led4gpio = of_get_named_gpio(pnode, "led4-gpio", 0);
	gpio_request(pmydev->led4gpio, "led4");

	pmydev->led5gpio = of_get_named_gpio(pnode, "led5-gpio", 0);
	gpio_request(pmydev->led5gpio, "led5");

}

void set_leds_gpio_output(struct myled_dev *pmydev) // 配置成输出
{
	gpio_direction_output(pmydev->led2gpio, 0);
	gpio_direction_output(pmydev->led3gpio, 0);
	gpio_direction_output(pmydev->led4gpio, 0);
	gpio_direction_output(pmydev->led5gpio, 0);
}

void free_leds_gpio(struct myled_dev *pmydev) // free设备
{
	gpio_free(pmydev->led2gpio);
	gpio_free(pmydev->led3gpio);
	gpio_free(pmydev->led4gpio);
	gpio_free(pmydev->led5gpio);
}

int myled_probe(struct platform_device *p_pltdev)
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);
	struct device_node *pnode = NULL;

	ret = register_chrdev_region(devno, myled_num, "myled");
	if (ret) {
		ret = alloc_chrdev_region(&devno, minor, myled_num, "myled");
		if (ret) {
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);
	}

    pgmydev = (struct myled_dev*)kmalloc(sizeof(struct myled_dev), GFP_KERNEL);
    if (NULL == pgmydev) {
        unregister_chrdev_region(devno, myled_num);
        printk("kmalloc failed\n");
        return -1;
    }
    memset(pgmydev, 0, sizeof(struct myled_dev));

	cdev_init(&pgmydev->mydev, &myops);

	pgmydev->mydev.owner = THIS_MODULE;
	cdev_add(&pgmydev->mydev, devno, myled_num);

	pnode = p_pltdev->dev.of_node;

	/* ioremap */
	request_leds_gpio(pgmydev, pnode);

	/* con-register set output */
    set_leds_gpio_output(pgmydev);

	return 0;
}

int myled_remove(struct platform_device *p_pltdev)
{
	dev_t devno = MKDEV(major, minor);

    /* iounmap */
	free_leds_gpio(pgmydev);

	cdev_del(&pgmydev->mydev);
	unregister_chrdev_region(devno, myled_num);

    kfree(pgmydev);
    pgmydev = NULL;

	return 0;
}

struct of_device_id myleddrv_of_ids[] = {
	[0] = {.compatible = "4412,led2-5"}, // 匹配依据
	[1] = {.compatible = "origen4412,led6-9"}, // 举例其他的
	[2] = {},
};

struct platform_driver myled_driver = {
	.driver = {
		.name = "leds",
		.of_match_table = myleddrv_of_ids,
	},
	.probe = myled_probe,
	.remove = myled_remove,
};

int __init myled_init(void)
{
	platform_driver_register(&myled_driver);
	return 0;
}

void __exit myled_exit(void)
{
	platform_driver_unregister(&myled_driver);
}


MODULE_LICENSE("GPL");

module_init(myled_init);
module_exit(myled_exit);

testled.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
 
#include "leddrv.h"
 
int main(int argc, char *argv[])
{
    int fd = -1;
    int onoff = 0;
    int no = 0;
 
    if (argc < 4) {
        printf("The argument is too few\n");
        return 1;
    }
 
    sscanf(argv[2], "%d", &onoff);
    sscanf(argv[3], "%d", &no);
 
    if (no < 2 || no > 5) {
        printf("led-no is invalid\n");
        return 2;
    }
 
    fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        printf("open %s failed\n", argv[1]);
        return 3;
    }
 
    if (onoff) {
        ioctl(fd, MY_LED_ON, no);
    } else {
        ioctl(fd, MY_LED_OFF, no);
    }
 
    close(fd);
    fd = -1;
    return 0;
}

测试结果

ubuntu上将 leddrv_dt.c 编译成 arm 架构的 ko 文件,将 testled.c 编译成 arm 架构的可执行文件,再将这两个文件拷贝到挂载的根文件系统目录下

shrek@ubuntu16:~/share/mydrivercode$ make ARCH=arm
shrek@ubuntu16:~/share/mydrivercode$ arm-none-linux-gnueabi-gcc ./testled.c -o testled
shrek@ubuntu16:~/share/mydrivercode$ cp leddrv_dt.ko /opt/4412/rootfs/
shrek@ubuntu16:~/share/mydrivercode$ cp testled /opt/4412/rootfs/

开发板上挂载好根文件系统,执行如下命令

[root@farsight]#insmod leddrv_dt.ko
[root@farsight]#cat /proc/devices | grep led
 11 myled
[root@farsight]#mknod /dev/leddev c 11 0
[root@farsight]#./testled /dev/leddev 1 2
[root@farsight]#./testled /dev/leddev 1 3
[root@farsight]#./testled /dev/leddev 1 4
[root@farsight]#./testled /dev/leddev 1 5
[root@farsight]#./testled /dev/leddev 0 5
[root@farsight]#./testled /dev/leddev 0 4
[root@farsight]#./testled /dev/leddev 0 3
[root@farsight]#./testled /dev/leddev 0 2

看到开发板上的LED灯可以控制亮灭


一个编写驱动用的宏

struct platform_driver xxx = {  
    ...
};
module_platform_driver(xxx);

//最终展开后就是如下形式:
static int __init xxx_init(void)
{
        return platform_driver_register(&xxx);
}
module_init(xxx_init);

static void __exit xxx_init(void)
{
        return platform_driver_unregister(&xxx);
}
module_exit(xxx_exit)

I2C总线式驱动开发

Linux内核对I2C总线的支持

I2C设备驱动:即挂接在I2C总线上的二级外设的驱动,也称客户(client)驱动,实现对二级外设的各种操作,二级外设的几乎所有操作全部依赖于对其自身内部寄存器的读写,对这些二级外设寄存器的读写又依赖于I2C总线的发送和接收

I2C总线驱动:即对I2C总线自身控制器的驱动,一般SOC芯片都会提供多个I2C总线控制器,每个I2C总线控制器提供一组I2C总线(SDA一根+SCL一根),每一组被称为一个I2C通道,Linux内核里将I2C总线控制器叫做适配器(adapter),适配器驱动主要工作就是提供通过本组I2C总线与二级外设进行数据传输的接口,每个二级外设驱动里必须能够获得其对应的adapter对象才能实现数据传输

I2C核心:承上启下,为I2C设备驱动和I2C总线驱动开发提供接口,为I2C设备驱动层提供管理多个i2c_driver、i2c_client对象的数据结构,为I2C总线驱动层提供多个i2c_algorithm、i2c_adapter对象的数据结构


四大核心对象之间的关系图


i2c二级外设驱动开发涉及到核心结构体及其相关接口函数

struct i2c_board_info {
    char        type[I2C_NAME_SIZE];
    unsigned short  flags;
    unsigned short  addr;
    void        *platform_data;
    struct dev_archdata *archdata;
    struct device_node *of_node;
    int     irq;
};
/*用来协助创建i2c_client对象
重要成员
type:用来初始化i2c_client结构中的name成员
flags:用来初始化i2c_client结构中的flags成员
addr:用来初始化i2c_client结构中的addr成员
platform_data:用来初始化i2c_client结构中的.dev.platform_data成员
archdata:用来初始化i2c_client结构中的.dev.archdata成员
irq:用来初始化i2c_client结构中的irq成员

关键就是记住该结构和i2c_client结构成员的对应关系。在i2c子系统不直接创建i2c_client结构,只是提供struct i2c_board_info结构信息,让子系统动态创建,并且注册。
*/
struct i2c_client {
    unsigned short flags;
    unsigned short addr;
    char name[I2C_NAME_SIZE];
    struct i2c_adapter *adapter;
    struct i2c_driver *driver;
    struct device dev;
    int irq;
    struct list_head detected;
};
/*重要成员:
flags:地址长度,如是10位还是7位地址,默认是7位地址。如果是10位地址器件,则设置为I2C_CLIENT_TEN
addr:具体I2C器件如(at24c02),设备地址,低7位
name:设备名,用于和i2c_driver层匹配使用的,可以和平台模型中的平台设备层platform_driver中的name作用是一样的。
adapter:本设备所绑定的适配器结构(CPU有很多I2C适配器,类似单片机有串口1、串口2等等,在linux中每个适配器都用一个结构描述)
driver:指向匹配的i2c_driver结构,不需要自己填充,匹配上后内核会完成这个赋值操作
dev:内嵌的设备模型,可以使用其中的platform_data成员传递给任何数据给i2c_driver使用。
irq:设备需要使用到中断时,把中断编号传递给i2c_driver进行注册中断,如果没有就不需要填充。(有的I2C器件有中断引脚编号,与CPU相连)
*/

/* 获得/释放 i2c_adapter 路径:i2c-core.c linux-3.5\drivers\i2c */
/*功能:通过i2c总线编号获得内核中的i2c_adapter结构地址,然后用户可以使用这个结构地址就可以给i2c_client结构使用,从而实现i2c_client进行总线绑定,从而增加适配器引用计数。
返回值:
NULL:没有找到指定总线编号适配器结构
非NULL:指定nr的适配器结构内存地址*/
struct i2c_adapter *i2c_get_adapter(int nr);


/*减少引用计数:当使用·i2c_get_adapter·后,需要使用该函数减少引用计数。(如果你的适配器驱动不需要卸载,可以不使用)*/
void i2c_put_adapter(struct i2c_adapter *adap);

/*
功能:根据参数adap,info,addr,addr_list动态创建i2c_client并且进行注册
参数:
adap:i2c_client所依附的适配器结构地址
info:i2c_client基本信息
addt_list: i2c_client的地址(地址定义形式是固定的,一般是定义一个数组,数组必须以I2C_CLIENT_END结束,示例:unsigned short ft5x0x_i2c[]={0x38,I2C_CLIENT_END};
probe:回调函数指针,当创建好i2c_client后,会调用该函数,一般没有什么特殊需求传递NULL。
返回值:
非NULL:创建成功,返回创建好的i2c_client结构地址
NULL:创建失败
*/
struct i2c_client * i2c_new_probed_device
(
 struct i2c_adapter *adap,
 struct i2c_board_info *info,
 unsigned short const *addr_list,
 int (*probe)(struct i2c_adapter *, unsigned short addr)
);
/*示例:
struct i2c_adapter *ad;
struct i2c_board_info info={""};

unsigned short addr_list[]={0x38,0x39,I2C_CLIENT_END};

//假设设备挂在i2c-2总线上
ad=i2c_get_adapter(2);

//自己填充board_info 
strcpy(inf.type,"xxxxx");
info.flags=0;
//动态创建i2c_client并且注册
i2c_new_probed_device(ad,&info,addr_list,NULL);

i2c_put_adapter(ad);
*/

/*注销*/
void i2c_unregister_device(struct i2c_client *pclt)


 struct i2c_client * i2c_new_device
 (
     struct i2c_adapter *padap,
     struct i2c_board_info const *pinfo
 );
/*示例:
struct i2c_adapter *ad;
struct i2c_board_info info={
	I2C_BOARD_INFO(name,二级外设地址)
};
//假设设备挂在i2c-2总线上
ad=i2c_get_adapter(2);

//动态创建i2c_client并且注册
i2c_new_device(ad,&info);

i2c_put_adapter(ad);
*/
struct i2c_driver {
    unsigned int class;

    /* Standard driver model interfaces */
    int (*probe)(struct i2c_client *, const struct i2c_device_id *);
    int (*remove)(struct i2c_client *);

    /* driver model interfaces that don't relate to enumeration  */
    void (*shutdown)(struct i2c_client *);
    int (*suspend)(struct i2c_client *, pm_message_t mesg);
    int (*resume)(struct i2c_client *);
	void (*alert)(struct i2c_client *, unsigned int data);

    /* a ioctl like command that can be used to perform specific functions
     * with the device.
     */
    int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

    struct device_driver driver;
    const struct i2c_device_id *id_table;

    /* Device detection callback for automatic device creation */
    int (*detect)(struct i2c_client *, struct i2c_board_info *);
    const unsigned short *address_list;
    struct list_head clients;
};
/*重要成员:
probe:在i2c_client与i2c_driver匹配后执行该函数
remove:在取消i2c_client与i2c_driver匹配绑定后后执行该函数
driver:这个成员类型在平台设备驱动层中也有,而且使用其中的name成员来实现平台设备匹配,但是i2c子系统中不使用其中的name进行匹配,这也是i2c设备驱动模型和平台设备模型匹配方法的一点区别
id_table:用来实现i2c_client与i2c_driver匹配绑定,当i2c_client中的name成员和i2c_driver中id_table中name成员相同的时候,就匹配上了。

补充:i2c_client与i2c_driver匹配问题
- i2c_client中的name成员和i2c_driver中id_table中name成员相同的时候
- i2c_client指定的信息在物理上真实存放对应的硬件,并且工作是正常的才会绑定上,并执行其中的probe接口函数这第二点要求和平台模型匹配有区别,平台模型不要求设备层指定信息在物理上真实存在就能匹配
*/

/*功能:向内核注册一个i2c_driver对象
返回值:0成功,负数 失败*/
#define i2c_add_driver(driver)     i2c_register_driver(THIS_MODULE, driver)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);

/*功能:从内核注销一个i2c_driver对象
返回值:无 */
void i2c_del_driver(struct i2c_driver *driver);
struct i2c_msg {
    __u16 addr; /* slave address            */
    __u16 flags;
#define I2C_M_TEN       0x0010  /* this is a ten bit chip address */
#define I2C_M_RD        0x0001  /* read data, from slave to master */
    __u16 len;      /* msg length               */
    __u8 *buf;      /* pointer to msg data          */
};
/* 重要成员:
addr:要读写的二级外设地址
flags:表示地址的长度,读写功能。如果是10位地址必须设置I2C_M_TEN,如果是读操作必须设置有I2C_M_RD······,可以使用或运算合成。
buf:要读写的数据指针。写操作:数据源 读操作:指定存放数据的缓存区
len:读写数据的数据长度
*/

/*i2c收发一体化函数,收还是发由参数msgs的成员flags决定*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
/*
功能:根据msgs进行手法控制
参数:
adap:使用哪一个适配器发送信息,一般是取i2c_client结构中的adapter指针作为参数
msgs:具体发送消息指针,一般情况下是一个数组
num:表示前一个参数msgs数组有多少个消息要发送的
返回值:
负数:失败
> 0 表示成功发送i2c_msg数量
*/

/*I2C读取数据函数*/
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
/*功能:实现标准的I2C读时序,数据可以是N个数据,这个函数调用时候默认已经包含发送从机地址+读方向这一环节了
参数:
client:设备结构
buf:读取数据存放缓冲区
count:读取数据大小 不大于64k
返回值:
失败:负数
成功:成功读取的字节数
*/
    
/*I2C发送数据函数*/
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
/*功能:实现标准的I2C写时序,数据可以是N个数据,这个函数调用时候默认已经包含发送从机地址+写方向这一环节了
参数:
client:设备结构地址
buf:发送数据存放缓冲区
count:发送数据大小 不大于64k
返回值:
失败:负数
成功:成功发送的字节数
*/

  • 24
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值