驱动

一、linux内核模块

1 课程大纲

1.linux内核模块
2.字符设备驱动
3.内核中的并发和竞态的解决方法
在应用层中的方法是加锁等
驱动中有五种解决方法
4.IO模型
阻塞
非阻塞
IO多路复用
异步通知(又叫信号驱动IO)
5.linux内核中的中断
和ARM中的中断原理一样,只是内核中有一个中断子系统,基于中断子系统驱动调用就可以了,中断产生的4大步3小步都是一样的
6.platform总线驱动
7.i2c总线驱动/spi总线驱动
8.块设备驱动
9.网卡设备驱动
10.摄像头驱动

2 ARM裸机驱动和驱动的区别

共同点:都可以操作硬件
不同点:
ARM裸机驱动:不需要依赖linux内核,可以直接通过裸机代码操作硬件。无多进程多线程函数库等,所有的操作硬件的代码都在main函数中完成。
驱动:需要依赖linux内核才可以完成。驱动代码不能够单独执行,也不可以主动执行,必要有应用层调用的时候才可以执行。在内核中多个驱动是可以并行执行的。

3 linux层次

应用层:
APP   glibc(c库)
-------------------接口-------------------
内核层:5大功能:
		1.内存管理:内存的申请和释放
		2.文件管理:通过文件系统对文件进行组织管理(如ntfs)
		3.网络管理:通过网络协议栈(除了应用层和驱动,为网络协议栈)负责网络数据的收发
		4.进程管理:进程的创建,销毁和调度
		5.设备管理:设备驱动管理
			1.字符设备驱动:按照字节流来访问的硬件,只能顺序访问,不能无序访问(LED,LCD(帧缓存),触摸屏,camera,鼠标,键盘)
			2.块设备驱动:按照block来访问的(最小为512字节,1024,2048,4096等),可以顺序访问也可以无序访问的设备(emmc,u盘,硬盘)。
			3.网卡设备驱动:对网络的数据进行收发。(dm9000,cs8900)网络协议栈把数据交给网卡设备驱动,网卡设备驱动将数据给硬件(网卡驱动没有设备节点)
------------------------------------------
硬件层:LED  LCD(帧缓存)  触摸屏 camera  鼠标 键盘
		emmc u盘 硬盘  dm9000 cs8900

设备节点放在/dev下
驱动是内核中五大管理中的一个,如果是内核开发,则文件管理/内存管理等等都要涉及

4 驱动的使用流程

假如有一个demo.c的驱动源码,如何把它配置到内核中?
1.Kconfig:生成选项菜单的文件
(在kernel/kernel-3.4.39/drivers/char/Kconfig)

config FARSIGHT_DEMO
		#	bool               (Y N)
			tristate  "test test test"    (Y M N)
执行make menuconfig在字符设备驱动中找到test test test
		 <Y> test test test
		 <M> test test test

2…config:保存选配好的信息的文件
(在kernel/.config)

CONFIG_FARSIGHT_DEMO=y
CONFIG_FARSIGHT_DEMO=m

3.Makefile
(在kernel/kernel-3.4.39/drivers/char/Makefile中)

		obj-$(CONFIG_FARSIGHT_DEMO) += demo.o
		obj-y ->表示驱动要编译到内核中
		obj-m ->编译成一个内核模块

可以直接做第三步不做第一,二步
4.编译
make uImage 将驱动编译到内核中,在内核启动的时候,驱动将被自动安装
make modules 编译完成之后将生成一个demo.ko的文件

模块安装:
	sudo insmod demo.ko   安装
	sudo rmmod demo       卸载
	lsmod                 查看

5 linux内核模块

linux内核模块的三要素:
入口:资源的申请
出口:资源的释放
许可证:遵从开源的协议GPL

#include 
#include 
//入口
//注意返回值是int,参数为void,固定写法
static int __init demo_init(void)
{
	//在编译的时候,编译遇到__init就会把这个函数放入到.init.text这个段中。
	return 0;
}
//出口
//注意返回值是void,参数为void,固定写法
static void __exit demo_exit(void)
{
	//在编译的时候,编译遇到__exit就会把这个函数放入到.exit.text这个段中。
}
//告诉内核入口,出口的地址
module_init(demo_init);
module_exit(demo_exit);
//许可证,注意GPL是大写的
MODULE_LICENSE("GPL");
解析:
查找内核的源码:~/kernel/kernel-3.4.39目录中:用ctags -R创建一个tags索引目录
vi -t __init
vi -t printk
vi -t module_init
等等

找驱动的头文件:用vi -t ***来找,且include后的为头文件
内核的链接脚本叫vmlinux.lds(存放的是编译的时候各个代码存放的位置)
查找文件:find -name vmlinux.lds
为了方便起见,不采用【4】流程,自行写一个makefile

6 驱动模块的外部编译的方法

在内核源码树外进行编译,就是外部编译

KERNELDIR:= /home/linux/kernel/kernel-3.4.39/
//以此路径进行编译:生成的是ARM格式的可执行文件(file demo.ko) 不可以在ubuntu上安装执行,ubuntu是X86架构
//在内核的顶层目录Makefile中指定的
#内核的路径
# KERNELDIR:=/lib/modules/$(shell uname -r)/build
// 把此路径指定为ubuntu的内核路径即可编译成在X86的架构下可以执行的
// 查看ubuntu的版本:uname -r
PWD:= $(shell pwd) //在makefile中使用shell命令
#当前路径
all:                                                                            
	make -C $(KERNELDIR) M=$(PWD) modules
	@-C:进入指定的目录并执行make 
	@进入内核的顶层目录执行make modules,并通过M指定要编译的模块的路径在当前的路径下。
clean:
	make -C $(KERNELDIR) M=$(PWD) clean
obj-m:=demo.o
#指定的模块的名字

在内核顶层目录下执行make modules会将所有的m选项的驱动都模块化编译到内核中
传了M路径以后,只编译该路径下的模块demo.c

7 linux内核中打印函数的使用

printk(打印级别 (空格)“控制格式”);
printk(“控制格式”); —>在使用printk的时候不写打印级别就使用默认的消息的级别(4)

#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         */
在内核的打印级别中数值越小级别越高,打印级别用来过滤打印信息。上述的打印级别和消息级别的关系如下:只有当消息的级别大于终端的级别的时候,消息才会在终端上显示。
cat /proc/sys/kernel/printk //查看级别
4	           4	          1	                7
终端的级别   默认的消息的级别  最大的终端的级别  最小的终端的级别

ubuntu的开发者对普通终端进行了处理,不会显示任何级别的信息
虚拟终端是没有被修改的,默认为4

进入虚拟终端的方法:
	ctrl+alt+[F1~F6]
退出虚拟终端的方法
	ctrl+alt+F7

修改默认的消息的级别(Ubuntu)

		su root
		echo 4 3 1 7 > /proc/sys/kernel/printk

修改默认的消息的级别(开发板)

//在根文件系统中(~/nfs/rootfs/etc/init.d/rcS)
		在rootfs/etc/init.d/rcS文件中添加
		echo 4 3 1 7 > /proc/sys/kernel/printk
#include 
#include 
//入口,安装的时候执行
static int __init demo_init(void)
{
	printk(KERN_WARNING "this is a warning message 1\n");
	printk(KERN_ERR "this is a warning message 2\n");
	printk("this is a warning message 3\n");								   printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
//出口,卸载的时候执行
static void __exit demo_exit(void)
{
	printk(KERN_INFO "this is demo_exit 1\n");
	printk(KERN_ERR "this is demo_exit 2\n");
	printk("this is demo_exit 3\n");
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
}
//告诉内核入口,出口的地址
module_init(demo_init);
module_exit(demo_exit);
//许可证
MODULE_LICENSE("GPL");

如果在Ubuntu上想查看打印信息(不关心打印级别)就可以使用dmesg
dmesg 查看所有级别的打印信息
sudo dmesg -C 清除所有的打印信息(直接清除)
sudo dmesg -c 清除打印信息(先回显后清除)

8 内核模块的传参

应用程序可以通过argc和argv进行命令行传参
内核模块是否可以传参?
内核模块的传参:
module_param(name, type, perm)

功能:在内核模块接收命令行传递的参数
参数:
@name :变量名
@type :变量的类型
		 * Standard types are:(没有char)
		 * byte, short, ushort, int, uint, long, ulong
		 * charp: a character pointer
		 * bool: a bool, values 0/1, y/n, Y/N.
		 * invbool: the above, only sense-reversed (N = true).
@perm:权限,以变量命名的文件的权限
	  其他人的权限不能有写(0662 即为×)
	  需要安装上demo驱动以后才会在下面的目录中产生demo文件
	  写完以后在目录/sys/module/demo/parameters/中产生一个文件 a(若为int a=100)

MODULE_PARM_DESC(_parm, desc)

功能:对传参的变量进行描述
参数:
@_parm:被描述的变量
@desc:描述的字符串

如何传参数?
1.在安装驱动的时候传参
sudo insmod demo.ko a=500 b=22 c=…
2.使用上述变量命名的文件进行传参
/sys/module/demo/parameters/ (在所有的sys目录中,断电数据会全部丢失,修改只能通过echo >)
cat a
su root
echo 300 > a
注意点:安装完demo驱动以后,init中的a不会被改变,安装时已经执行过了,但是卸载时会打印已经改变的值
如何查看一个模块中有哪些可传递的变量?
modinfo demo.ko

#include 
#include 
int backlight = 127;
module_param(backlight, int, 0775);
MODULE_PARM_DESC(backlight,"this is backlight val range(0-255)default:127");
//入口,安装的时候执行
static int __init demo_init(void)
{
	printk("backlight = %d\n",backlight);
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
//出口,卸载的时候执行
static void __exit demo_exit(void)
{
	printk("backlight = %d\n",backlight);
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
}
//告诉内核入口,出口的地址
module_init(demo_init);
module_exit(demo_exit);
//许可证
MODULE_LICENSE("GPL");

练习:
内核模块传参(byte,charp)
char -> byte 不能够传递字符
charp -> 在传递字符串的时候不能够有空格
通过模块传参传递数组:
module_param_array(name, type, nump, perm)

功能:接收一个数组
参数:
@name:数组名
@type:数组成员的类型
@nump:传递一个变量的地址,这个变量代表的是传递的数组的成员的个数
@perm:权限
#include 
#include 
int backlight = 127;
module_param(backlight, int, 0775);
MODULE_PARM_DESC(backlight,"this is backlight val range(0-255)default:127");
unsigned char tt = 100;
module_param(tt,byte,0664);
MODULE_PARM_DESC(tt, "this is a char var");
char *p = "hello world";
module_param(p,charp,0775);
MODULE_PARM_DESC(p,"this is a charp var");
int num;
unsigned int ww[20] = {0};
module_param_array(ww,uint,&num, 0775);
MODULE_PARM_DESC(ww,"this is uint array [20]");
//入口,安装的时候执行
static int __init demo_init(void)
{
	int i;
	printk("backlight = %d\n",backlight);
	printk("tt = %d\n",tt);
	printk("p = %s\n",p);
	for(i=0;i<num;i++)
	printk("ww[%d] = %d\n",i,ww[i]);
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
//出口,卸载的时候执行
static void __exit demo_exit(void)
{
	printk("backlight = %d\n",backlight);
	printk("tt = %d\n",tt);
	printk("p = %s\n",p);
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
}
//告诉内核入口,出口的地址
module_init(demo_init);
module_exit(demo_exit);
//许可证
MODULE_LICENSE("GPL");
linux@ubuntu:~/dc10-11/day1/module1$ sudo insmod demo.ko backlight=10 tt=5 p=hel ww=11,22,33,44,55,66
linux@ubuntu:~/dc10-11/day1/module1$ dmesg
		[16977.159899] backlight = 10
		[16977.159902] tt = 5
		[16977.159903] p = hel
		[16977.159904] ww[0] = 11
		[16977.159905] ww[1] = 22
		[16977.159906] ww[2] = 33
		[16977.159906] ww[3] = 44
		[16977.159907] ww[4] = 55
		[16977.159907] ww[5] = 66
在文件/sys/module/demo/parameters/中 echo 11,22,3344>ww

9 模块导出符号表

在一个内核模块中调用另外一个内核模块中的函数的时候,需要将这个函数的符号表导出,让调用者通过这个符号表能过找到并调用这个函数的过程。

好处如下:
		1.可以解决代码冗余的问题
		2.可以让驱动开发者的工作更为简单
		等等

EXPORT_SYMBOL_GPL(sym)

	功能:导出符号表
	参数:sym:函数的名字

编译:先编译提供者,将提供者编译好之后就会在当前文件下生成一个Module.symvers,这个文件中就是函数的符号表

0x72f367e8  add /home/linux/dc10-11/day1/export/A/demoA EXPORT_SYMBOL_GPL

在编译调用者模块前需要将这个文件拷贝到调用者的目录下然后在执行编译,否则会提示undefined
注:如果提供者模块被编译到内核中了,此时在编译调用者模块的时候就不需要拷贝Module.symvers文件了。
安装:先安装提供者,再安装调用者
卸载:先卸载调用者,再卸载提供者
A文件中 demoA.c

#include 
#include 
int add(int a,int b)
{
	return (a+b);
}
EXPORT_SYMBOL_GPL(add); 
//入口,安装的时候执行
static int __init demo_init(void)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
//出口,卸载的时候执行
static void __exit demo_exit(void)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
}
//告诉内核入口,出口的地址
module_init(demo_init);
module_exit(demo_exit);
//许可证
MODULE_LICENSE("GPL");

B文件中 demoB.c

#include 
#include 
extern int add(int a,int b);
//入口,安装的时候执行
static int __init demo_init(void)
{
	printk("sum = %d\n",add(100,200));
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
//出口,卸载的时候执行
static void __exit demo_exit(void)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
}
//告诉内核入口,出口的地址
module_init(demo_init);
module_exit(demo_exit);
//许可证
MODULE_LICENSE("GPL");

二、字符设备

驱动程序和应用程序的区别


功能 应用程序 内核模块
运行的地址 0-3G 用户空间(每个进程都有1个) 3-4G内核空间 (共有1个)
执行 ./a.out sudo insmod demo.ko
停止 ctrl+c sudo rmmod demo
入口 main static int __init demo_init
出口 return exit _exit static void __exit demo_exit
许可证 不需要遵从GPL协议 遵从GPL MODULE_LICENSE(“GPL”)
传参 argc /argv module_param()/module_param_array()
导出符号表 不可以导出符号表 可以 EXPORT_SYMBOL_GPL
打印 printf/perror/strerror/ fprintf printk (8个级别)
头文件查找 man手册 通过内核源码查找
拓展:
1./sys目录下存放的是在内存上运行的程序(0,1),内核驱动相关的内容,(内核停止,掉电即会丢失)
如module:当前内核运行下的驱动模块
	class:对应的设备节点的信息
2./proc存放的是内核运行下的内存的相关进程信息,掉电也是会丢失

1. 字符设备驱动

驱动:承上启下(驱动操作硬件,向上往应用层提供操作接口)

用户空间:
	open("/dev/myled",O_RDWR)   read   write  close
*******************************************************
/dev/字符设备驱动的设备文件
/dev/myled
crw-r----- 1 root root 13, 33 Feb 22 09:56 myled
	                   13主设备号 33次设备号
设备号(32位)=主设备号(高12位)+次设备号(低20位)
主设备号:标识它是哪一类设备
次设备号: 同类中的哪一个设备
-------------|-----------------------------------------
内核空间:   |
		LED的驱动
		(字符设备驱动)
		(每一个驱动都有一个设备号)
		#############################################
		#struct file_operations  <===操作方法结构体 #
		#{                                          #
		#	函数指针open                            #
		#	函数指针read                            #
		#	函数指针write                           #
		#	函数指针release                         #
		#}                                          #
		# 函数指针open-->myled_open :打开灯        #
		# 函数指针read-->myled_read :读灯          #
		# 函数指针write-->myled_write :写灯        #
		# 函数指针release-->myled_close :关灯      #
		#                                           #
		#############################################
-------------------------------------------------------
硬件 :   LED

2. 字符设备驱动的API

2.1 register_chrdev

int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)

功能:注册字符设备驱动
参数: 
@major:主设备号
		major > 0 用户指定主设备号
		major = 0 系统自动分配主设备号,并通过返回值将分配到的主设备号返回给你
@name:字符设备驱动的名字
	    cat /proc/devices 
@fops:操作方法结构体的指针
	struct file_operations {
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*open) (struct inode *, struct file *);
	int (*release) (struct inode *, struct file *);   
	....
	}
返回值:
	major > 0 :成功返回0,失败返回错误码
	major = 0 :成功返回主设备号,失败返回错误码 

2.2 unregister_chrdev

void unregister_chrdev(unsigned int major, const char *name)

功能:注销字符设备驱动
参数:
@major:主设备号
@name:名字
返回值:无

练习:字符设备驱动的实例

chrdev.c

#include 
#include 
#include 
#define CNAME "myled"
int major=0;
int myled_open(struct inode *inode, struct file *filp)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
ssize_t myled_read(struct file * filp, char __user *ubuf, size_t size, loff_t *offs)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
ssize_t myled_write(struct file *filp, const char __user *ubuf, size_t size, loff_t *offs)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
int myled_close(struct inode *inode, struct file *filp)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
const struct file_operations fops = {
	.open  = myled_open,
	.read  = myled_read,
	.write = myled_write,
	.release = myled_close,
};
//入口
static int __init myled_init(void)
{
	//注册字符设备驱动
	major = register_chrdev(0,CNAME,&fops);
	if(major < 0){
		printk("register LED char device driver error\n");
		return major;
	}
	return 0;
}
//出口
static void __exit myled_exit(void)
{
	//注销字符设备驱动
	unregister_chrdev(major,CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yqq [email protected]");

test.c

#include 
#include 
#include 
#include 
#include 
char buf[128] = {0};
int main(int argc, const char *argv[])
{
	int fd;
	if((fd = open("./hello",O_RDWR))<0){
		perror("open error");
		return -1;
	}
	read(fd,buf,sizeof(buf));
	write(fd,buf,sizeof(buf));
	close(fd);
	return 0;
}
在linux中,在内核下用ctags -R可以创建tags索引文件,这样在~/kernel/kernel-3.4.39/下可以通过vi -t 函数名追踪底层源码,但是在别的目录下无法使用vi -t 函数名,可通过在底行模式中:set tags=~/kernel/kernel-3.4.39/tags (但退出后又无效了)

3. 驱动的测试流程

1.编译驱动
make
2.安装驱动
sudo insmod myled.ko
3.查看驱动是否安装成功了
lsmod ===>查看模块是否安装了

cat /proc/devices ======>字符设备驱动已经创建成功了
		 250                    myled
          |                      |
		 自动分配的主设备号     字符设备驱动的名字

4.查看/dev/是否有设备节点
上述编写的字符设备驱动不会自动创建设备节点
手动创建设备节点

mknod pathname  b/c major minor	
mknod:创建设备节点的命令
pathname:设备节点的路径+节点名
	        |           |
	 路径是任意的  不一定和设备驱动的名字相同
b/c:  b:块设备驱动
      c:字符设备驱动
	  网卡没有设备节点
major:主设备号
minor:次设备号
创建设备文件
sudo mknod /dev/hello c 250  3
查看设备文件
crw-r--r-- 1 root root 250, 3 Feb 23 11:25 hello

5.看应用程序是否能访问到字符设备驱动

编写应用程序让它调用驱动
#include 
char buf[128] = {0};
int main(int argc, const char *argv[])
{
	int fd; 
	if((fd = open("./hello",O_RDWR))<0){
			perro("open error");
			return -1; 
	}   
	read(fd,buf,sizeof(buf));
	write(fd,buf,sizeof(buf));
	close(fd);                                                                        
	return 0;
}

6.编译执行应用程序

gcc test.c =======>a.out
./a.out 
open error: Permission denied
		
sudo ./a.out  ===>执行成功
dmesg 如果看到下面这四句话,就说明应用程序
已经能够调用到驱动了。	
/home/linux/dc10-11/day2/mycdev/myled.c:myled_open:10
/home/linux/dc10-11/day2/mycdev/myled.c:myled_read:17
/home/linux/dc10-11/day2/mycdev/myled.c:myled_write:24
/home/linux/dc10-11/day2/mycdev/myled.c:myled_close:30

vim的补全的修改:/home/linux/.vimrc 在139行左右
/home/linux/.vim/snippets/c.snippets 修改补全的内容

4. 用户空间和内核空间的数据传输问题

4.1 copy_from_user

函数的使用站在用户的角度来考虑
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)

#include 
功能:将数据从用户空间拷贝到内核空间,用在驱动的write
参数:
@to:内核空间的首地址
@from:用户空间的首地址
@n:拷贝的大小,单位是字节
返回值:成功返回0,失败返回未拷贝的字节的个数

4.2 copy_to_user

unsigned long copy_to_user(void __user *to,const void *from, unsigned long n)   
功能:将数据从内核空间拷贝到用户空间,用在驱动的read
参数:
@to:用户空间的首地址
@from:内核空间的首地址
@n:拷贝的大小,单位是字节
返回值:成功返回0,失败返回未拷贝的字节的个数

练习

myled.c

#include 
#include 
#include 
#include 
#define CNAME "myled"
int major=0;
char kbuf[128] = {0};
int myled_open(struct inode *inode, struct file *filp)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
ssize_t myled_read(struct file * filp, 
		char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_to_user(ubuf,kbuf,size);
	if(ret){
		printk("copy data to user error\n");
		return -EINVAL; //返回一个错误码
	}
	memset(kbuf,0,sizeof(kbuf));
	return size;
}
ssize_t myled_write(struct file *filp, 
		const char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_from_user(kbuf,ubuf,size);
	if(ret){
		printk("copy data from user error\n");
		return -EINVAL; //返回一个错误码
	}
	return size;
}
int myled_close(struct inode *inode, struct file *filp)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
const struct file_operations fops = {
	.open  = myled_open,
	.read  = myled_read,
	.write = myled_write,
	.release = myled_close,
};
//入口
static int __init myled_init(void)
{
	//注册字符设备驱动
	major = register_chrdev(0,CNAME,&fops);
	if(major < 0){
		printk("register LED char device driver error\n");
		return major;
	}
	return 0;
}
//出口
static void __exit myled_exit(void)
{
	//注销字符设备驱动
	unregister_chrdev(major,CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
//许可证
MODULE_LICENSE("GPL");
MODULE_AUTHOR("farsight [email protected]");

test.c

#include 
#include 
#include 
#include 
#include 
#include 
char buf[128] = {0};
int main(int argc, const char *argv[])
{
	int fd;
	if((fd = open("./hello",O_RDWR))<0){
		perror("open error");
		return -1;
	}
	printf("input string > ");
	fgets(buf,sizeof(buf),stdin);
	buf[strlen(buf)-1] = '\0';
	write(fd,buf,strlen(buf));
	//将用户空间的数据写入到内核空间
	memset(buf,0,sizeof(buf));
	read(fd,buf,sizeof(buf));
	//将内核空间的数据读取到用户空间
	printf("user:%s\n",buf);
	close(fd);
	return 0;
}

5. 在内核空间如何访问到LED的物理地址

驱动运行在3-4G内核空间,内核空间是一个虚拟的地址,但是LED的寄存器是一个物理地址,在内核中不能够直接操作物理地址,需要用到物理地址向虚拟地址映射的过程。

5.1 ioremap

void *ioremap(phys_addr_t offset, unsigned long size)

#include 
功能:将物理地址映射成虚拟地址
参数:
@offset:物理地址
@size:映射的大小,单位是字节
返回值:成功返回虚拟地址,失败返回NULL

5.2 iounmap

void iounmap(void *addr)

功能:取消映射
参数:
@addr:虚拟地址
返回值:

6. 使用字符设备驱动点灯

红灯   GPIOA28   0xc001a000
绿灯   GPIOE13   0xc001e000
蓝灯   GPIOB12   0xc001b000

myled.c

#include 
#include 
#include 
#include 
#include 
#define CNAME "myled"
#define RED 0
#define GREEN 1
#define BLUE 2
#define ON 1
#define OFF 0
#define RED_PHY_BASE 0xc001a000
#define BLUE_PHY_BASE 0xc001b000
#define GREEN_PHY_BASE 0xc001e000
unsigned int PHY_ADDR[] = {RED_PHY_BASE,GREEN_PHY_BASE,BLUE_PHY_BASE};
unsigned int * virt_addr[3];
int major=0;
char kbuf[128] = {0};
int myled_open(struct inode *inode, struct file *filp)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
ssize_t myled_read(struct file * filp, 
		char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_to_user(ubuf,kbuf,size);
	if(ret){
		printk("copy data to user error\n");
		return -EINVAL; //返回一个错误码
	}
	memset(kbuf,0,sizeof(kbuf));
	return size;
}
//ubuf[2]
//ubuf[0]  0 红  1 绿 2 蓝
//ubuf[1]  0 OFF   1 ON
ssize_t myled_write(struct file *filp, 
		const char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_from_user(kbuf,ubuf,size);
	if(ret){
		printk("copy data from user error\n");
		return -EINVAL; //返回一个错误码
	}
	switch(kbuf[0]){
		case RED:
			kbuf[1]?(*(virt_addr[0]) |= (1<<28)):\
				(*(virt_addr[0]) &= ~(1<<28));
			break;
		case GREEN:
			kbuf[1]?(*(virt_addr[1]) |= (1<<13)):\
				(*(virt_addr[1]) &= ~(1<<13));
			break;
		case BLUE:
			kbuf[1]?(*(virt_addr[2]) |= (1<<12)):\
				(*(virt_addr[2]) &= ~(1<<12));
			break;
	}
	return size;
}
int myled_close(struct inode *inode, struct file *filp)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
const struct file_operations fops = {
	.open  = myled_open,
	.read  = myled_read,
	.write = myled_write,
	.release = myled_close,
};
//入口
static int __init myled_init(void)
{
	int i;
	//注册字符设备驱动
	major = register_chrdev(0,CNAME,&fops);
	if(major < 0){
		printk("register LED char device driver error\n");
		return major;
	}
	for(i=0;i<ARRAY_SIZE(PHY_ADDR);i++){
		virt_addr[i] = ioremap(PHY_ADDR[i],40);
		if(virt_addr[i] == NULL){
			printk("ioremap error\n");
			return -ENOMEM;
		}
	}
	//virt_addr[0]  红灯的虚拟的基地址
	*(virt_addr[0] + 9) &= ~(3<<24);
	*(virt_addr[0] + 1) |= (1<<28);
	*(virt_addr[0] + 0) &= ~(1<<28);
	//virt_addr[1]  绿灯的虚拟的基地址
	*(virt_addr[1] + 8) &= ~(3<<26);
	*(virt_addr[1] + 1) |= (1<<13);
	*(virt_addr[1] + 0) &= ~(1<<13);
	//virt_addr[2]  蓝灯的虚拟的基地址
	*(virt_addr[2] + 8) &= ~(3<<24);
	*(virt_addr[2] + 8) |= (1<<25);
	*(virt_addr[2] + 1) |= (1<<12);
	*(virt_addr[2] + 0) &= ~(1<<12);
	return 0;
}
//出口
static void __exit myled_exit(void)
{
	int i;
	//注销字符设备驱动
	unregister_chrdev(major,CNAME);
	for(i=0;i<ARRAY_SIZE(virt_addr);i++){
		iounmap(virt_addr[i]);
	}
}
module_init(myled_init);
module_exit(myled_exit);
//许可证
MODULE_LICENSE("GPL");
MODULE_AUTHOR("farsight [email protected]");

test.c

#include 
#include 
#include 
#include 
#include 
#include 
char buf[128] = {0};
int main(int argc, const char *argv[])
{
	int fd;
	if((fd = open("./hello",O_RDWR))<0){
		perror("open error");
		return -1;
	}
	while(1){
		scanf("%d,%d",&buf[0],&buf[1]);

		write(fd,buf,sizeof(buf));
		//将用户空间的数据写入到内核空间
	}
	close(fd);
	return 0;
}

7. ADC字符设备驱动的编写

myadc.c

#include 
#include 
#include 
#include 
#include 
typedef struct ADC_REG{
	unsigned int  ADCCON;
	unsigned int  ADCDAT;
	unsigned int  ADCINTENB; 
	unsigned int  ADCINTCLR;
	unsigned int  PRESCALERCON;
}adc_t;
#define ADC_PHY_BASE 0xc0053000
#define ADCNAME "myadc"
int major;
volatile adc_t *adc;
int myadc_open(struct inode *inode, struct file *file)
{
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
ssize_t myadc_read (struct file *file,
	char __user *ubuf, size_t size, loff_t *offs)
{
	unsigned int data;
	int ret;
	//1.启动adc
	adc->ADCCON |= (1<<0);
	//2.等待adc转化完成
	while(adc->ADCCON & 0x1);
	//3.读数据
	data = (adc->ADCDAT)&0xfff;
	//4.将数据拷贝到用户空间
	if(size > sizeof(data))size = sizeof(data);
	ret = copy_to_user(ubuf,&data,size);
	if(ret){
		printk("copy data to user error\n");
		return -EINVAL;
	}
	return size;
}
int myadc_close(struct inode *inode, struct file *file)
{
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
const struct file_operations fops = {
	.open = myadc_open,
	.read = myadc_read,
	.release = myadc_close,
};
static int __init myadc_init(void)
{
	//1.注册字符设备驱动
	major = register_chrdev(0,ADCNAME,&fops);
	if(major < 0){
		printk("register char device driver error\n");
		return major;
	}
	//2.adc地址映射
	adc = (adc_t *)ioremap(ADC_PHY_BASE,20);
	if(adc == NULL){
		printk("adc addr ioreamp error\n");
		return -ENOMEM;
	}
	//3.adc的初始化
	adc->ADCCON = 0;
	adc->ADCCON |= 6<<6;
	adc->PRESCALERCON = 0;
	adc->PRESCALERCON |= 149<<0;
	adc->PRESCALERCON |= (1<<15);
	return 0;
}
static void __exit myadc_exit(void)
{
	iounmap(adc);
	unregister_chrdev(major,ADCNAME);
}
module_init(myadc_init);
module_exit(myadc_exit);
MODULE_LICENSE("GPL");

test.c

#include 
#include 
#include 
#include 
#include 
unsigned int data;
float r_v;
int main(int argc, const char *argv[])
{
	int fd;
	if((fd = open("/dev/myadc",O_RDWR))<0){
		perror("open error");
		return -1;
	}
	while(1){
		read(fd,&data,sizeof(data));
		
		r_v = data/4096.0 *1.8;
		printf("V = %.2f\n",r_v);
		sleep(1);
	}
	close(fd);
	return 0;
}

8. 自动创建设备节点

udev/mdev(mdev就是一个轻量级的udev,一般在嵌入式设备上用)
	|
	|-->udev就是一个用户空间的应用程序
	
user:	 
		/dev/xxx	     <--------udev/mdev(恋爱顾问)
											   |
											   |
		hotplug(前台)(/sys/class/目录/节点信息)|
		   |
-----------|-------------------------------
kernel:    |
		1.提交目录
		2.在目录中提交设备节点的信息

#include

  1. struct class * class_create(owner, name)
    //void class_destroy(struct class *cls) 销毁
功能:在sys目录下去创建一个目录
参数:
	@owner: THIS_MODULE 
	@name : 目录的名字
功能:成功返回struct class的结构体指针,这个指针就是指向这个目录的句柄。struct class*cls;
	 失败返回错误码指针(void *)(-5)
struct class *cls;  
cls = class_create(THIS_MODULE,"hello");
if(IS_ERR(cls)){ //IS_ERR判断地址是否在内核顶端的4K区间
			    //如果在内核顶端的4K那它就是出错了
	return  PTR_ERR(cls);//将错误码指针强转外错误码
}
  1. struct device *device_create(struct class *class, struct device *parent,dev_t devt,void *drvdata, const char *fmt, …)
    //void device_destroy(struct class *class, dev_t devt)
功能:去提交设备的信息
参数:
	@class:class_create返回的句柄
	@parent:父设备  NULL
	@devt  :设备号
	@drvdata:驱动的数据 NULL
	@fmt    :设备节点的名字
返回值:成功返回device的结构体指针,失败返回错误码指针

myadc.c

#include 
#include 
#include 
#include 
#include 
#include 
typedef struct ADC_REG{
	unsigned int  ADCCON;
	unsigned int  ADCDAT;
	unsigned int  ADCINTENB; 
	unsigned int  ADCINTCLR;
	unsigned int  PRESCALERCON;
}adc_t;
#define ADC_PHY_BASE 0xc0053000
#define ADCNAME "myadc"
int major;
volatile adc_t *adc;
struct class *cls;
struct device *dev;
int myadc_open(struct inode *inode, struct file *file)
{
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
ssize_t myadc_read (struct file *file,
	char __user *ubuf, size_t size, loff_t *offs)
{
	unsigned int data;
	int ret;
	//1.启动adc
	adc->ADCCON |= (1<<0);
	//2.等待adc转化完成
	while(adc->ADCCON & 0x1);
	//3.读数据
	data = (adc->ADCDAT)&0xfff;
	//4.将数据拷贝到用户空间
	if(size > sizeof(data))size = sizeof(data);
	ret = copy_to_user(ubuf,&data,size);
	if(ret){
		printk("copy data to user error\n");
		return -EINVAL;
	}
	return size;
}
int myadc_close(struct inode *inode, struct file *file)
{
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
const struct file_operations fops = {
	.open = myadc_open,
	.read = myadc_read,
	.release = myadc_close,
};
static int __init myadc_init(void)
{
	//1.注册字符设备驱动
	major = register_chrdev(0,ADCNAME,&fops);
	if(major < 0){
		printk("register char device driver error\n");
		return major;
	}
	//2.adc地址映射
	adc = (adc_t *)ioremap(ADC_PHY_BASE,20);
	if(adc == NULL){
		printk("adc addr ioreamp error\n");
		return -ENOMEM;
	}
	//3.adc的初始化
	adc->ADCCON = 0;
	adc->ADCCON |= 6<<6;
	adc->PRESCALERCON = 0;
	adc->PRESCALERCON |= 149<<0;
	adc->PRESCALERCON |= (1<<15);
	//4.自动创建设备节点
	cls = class_create(THIS_MODULE,"hello");
	if(IS_ERR(cls)){
		printk("class create error\n");
		return PTR_ERR(cls);
	}
	dev = device_create(cls,NULL,MKDEV(major,0),NULL,"myadc0");
	if(IS_ERR(dev)){
		printk("device create error\n");
		return PTR_ERR(dev);
	}
	return 0;
}
static void __exit myadc_exit(void)
{
	device_destroy(cls,MKDEV(major,0));
	class_destroy(cls);
	iounmap(adc);
	unregister_chrdev(major,ADCNAME);
}
module_init(myadc_init);
module_exit(myadc_exit);
MODULE_LICENSE("GPL");

test.c

#include 
#include 
#include 
#include 
#include 
unsigned int data;
float r_v;
int main(int argc, const char *argv[])
{
	int fd;
	if((fd = open("/dev/myadc0",O_RDWR))<0){
		perror("open error");
		return -1;
	}
	while(1){
		read(fd,&data,sizeof(data));
		r_v = data/4096.0 *1.8;
		printf("V = %.2f\n",r_v);
		sleep(1);
	}
	close(fd);
	return 0;
}

9. ioctl函数

ioctl函数的使用
user:
	#include 
    int ioctl(int d, int request, ...);
	功能:通过ioctl函数可以去控制低层的设备(io)
	参数:
	@d:文件描述符
	@request:一个请求码,是一个32位的数,这个32位的数中有表示输入或者输出方向的位,还有表示第三参数所传递的字节大小的位。
	31-30 (dir)
		00 - no parameters: uses _IO macro
		10 - read: _IOR
		01 - write: _IOW
		11 - read/write: _IOWR                                                            
	29-16 size(第三个参数传递的字节的大小)size of arguments
    15-8  type(用一个字符唯一标识一个驱动)ascii character supposedly unique to each driver 
    7-0   nr 功能 function #
通过_IO _IOR  _IOW _IOWR这四个宏中的其中一个来封装成一个具备特定功能的命令码
	@...  :这是一个可变参数,可以传递也可以缺省(传值通过此参数)
返回值:成功返回0,失败返回-1并置位错误码
----------------------------------------------------------------
kernel:fops:
long (*unlocked_ioctl) (struct file *,unsigned int cmd, unsigned long args);
功能:应用层的ioctl调用的时候,这个unlocked_ioctl就会被执行,应用的第二个参数和第三个参数原封不动的传递给unlocked_ioctl函数。
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)	_IOC(_IOC_READ,(type),(nr),(sizeof(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(sizeof(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(sizeof(size)))
#define _IOC(dir,type,nr,size) \ 这个宏就是合成一个32位的数的
	(((dir)  << 30) | \
	 ((type) << 8) | \
	 ((nr)   << 0) | \
	 ((size) << 16))
#define RGB_LED_RED_ON _IO('k',1)
#define RGB_LED_RED_OFF _IO('k',0)
#define ACCESS_WRITE_INT_DATA _IOW('k',0,int)

myled.h

#ifndef __HEAD_H__
#define __HEAD_H__
#define type 'k'
#define RGB_LED_RED_ON _IO(type,1)
#define RGB_LED_RED_OFF _IO(type,0)
#endif

myled_ioctl.c

#include 
#include 
#include 
#include 
#include 
#include 
#include "head.h"
#define CNAME "myled"
#define RED 0
#define GREEN 1
#define BLUE 2
#define ON 1
#define OFF 0
#define RED_PHY_BASE 0xc001a000
#define BLUE_PHY_BASE 0xc001b000
#define GREEN_PHY_BASE 0xc001e000
unsigned int PHY_ADDR[] = {RED_PHY_BASE,GREEN_PHY_BASE,BLUE_PHY_BASE};
unsigned int * virt_addr[3];
int major=0;
char kbuf[128] = {0};
struct class *cls;
struct device *dev;
int myled_open(struct inode *inode, struct file *filp)
{
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
ssize_t myled_read(struct file * filp, char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_to_user(ubuf,kbuf,size);
	if(ret){
		printk("copy data to user error\n");
		return -EINVAL; 
	}
	memset(kbuf,0,sizeof(kbuf));
	return size;
}
ssize_t myled_write(struct file *filp, const char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_from_user(kbuf,ubuf,size);
	if(ret){
		printk("copy data from user error\n");
		return -EINVAL; 
	}
	switch(kbuf[0]){
		case RED:
			kbuf[1]?(*(virt_addr[0]) |= (1<<28)):\
				(*(virt_addr[0]) &= ~(1<<28));
			break;
		case GREEN:
			kbuf[1]?(*(virt_addr[1]) |= (1<<13)):\
				(*(virt_addr[1]) &= ~(1<<13));
			break;
		case BLUE:
			kbuf[1]?(*(virt_addr[2]) |= (1<<12)):\
				(*(virt_addr[2]) &= ~(1<<12));
			break;
	}
	return size;
}
long myled_ioctl(struct file *file,unsigned int cmd, unsigned long args)
{	
	switch(cmd){
		case RGB_LED_RED_ON:
			*(virt_addr[0]) |= (1<<28);
			break;
		case RGB_LED_RED_OFF:
			*(virt_addr[0]) &= ~(1<<28);
			break;
	}
	return 0;
}
int myled_close(struct inode *inode, struct file *filp)
{
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
const struct file_operations fops = {
	.open  = myled_open,
	.read  = myled_read,
	.write = myled_write,
	.unlocked_ioctl=myled_ioctl,
	.release = myled_close,
};
static int __init myled_init(void)
{
	int i;
	major = register_chrdev(0,CNAME,&fops);
	if(major < 0){
		printk("register LED char device driver error\n");
		return major;
	}
	for(i=0;i<ARRAY_SIZE(PHY_ADDR);i++){
		virt_addr[i] = ioremap(PHY_ADDR[i],40);
		if(virt_addr[i] == NULL){
			printk("ioremap error\n");
			return -ENOMEM;
		}
	}
	*(virt_addr[0] + 9) &= ~(3<<24);
	*(virt_addr[0] + 1) |= (1<<28);
	*(virt_addr[0] + 0) &= ~(1<<28);
	*(virt_addr[1] + 8) &= ~(3<<26);
	*(virt_addr[1] + 1) |= (1<<13);
	*(virt_addr[1] + 0) &= ~(1<<13);
	*(virt_addr[2] + 8) &= ~(3<<24);
	*(virt_addr[2] + 8) |= (1<<25);
	*(virt_addr[2] + 1) |= (1<<12);
	*(virt_addr[2] + 0) &= ~(1<<12);
	//自动创建设备节点
	cls = class_create(THIS_MODULE,"hello");
	if(IS_ERR(cls)){
		printk("class create error\n");
		return PTR_ERR(cls);
	}
	dev = device_create(cls,NULL,MKDEV(major,0),NULL,"myled");
	if(IS_ERR(dev)){
		printk("device create error\n");
		return PTR_ERR(dev);
	}
	return 0;
}
static void __exit myled_exit(void)
{
	int i;
	device_destroy(cls,MKDEV(major,0));
	class_destroy(cls);
	unregister_chrdev(major,CNAME);
	for(i=0;i<ARRAY_SIZE(virt_addr);i++){
		iounmap(virt_addr[i]);
	}
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("farsight [email protected]");

test.c

#include 
#include 
#include 
#include 
#include 
#include 
#include "head.h"
int main(int argc, const char *argv[])
{
	int fd;
	if((fd = open("/dev/myled",O_RDWR))<0){
		perror("open error");
		return -1;
	}
	while(1){
		ioctl(fd,RGB_LED_RED_ON);
		sleep(1);
		ioctl(fd,RGB_LED_RED_OFF);
		sleep(1);
	}
	close(fd);
	return 0;
}

练习:
1.通过ioctl读写一个int类型的数据
2.通过ioctl写一个数组到内核空间 char buf[128]
3.通过ioctl读写一个结构体
struct aa{
char name[50];
char sex;
int age;
}
myled_ioctl.c

#include 
#include 
#include 
#include 
#include 
#include 
#include "head.h"
#define CNAME "myled"
#define RED 0
#define GREEN 1
#define BLUE 2
#define ON 1
#define OFF 0
#define RED_PHY_BASE 0xc001a000
#define BLUE_PHY_BASE 0xc001b000
#define GREEN_PHY_BASE 0xc001e000
#define CMD_SIZE(cmd) ((cmd>>16) & 0x3fff)
unsigned int PHY_ADDR[] = {RED_PHY_BASE,GREEN_PHY_BASE,BLUE_PHY_BASE};
unsigned int * virt_addr[3];
int major=0;
int data;
char kbuf[128] = {0};
struct class *cls;
struct device *dev;
int myled_open(struct inode *inode, struct file *filp)
{
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
ssize_t myled_read(struct file * filp, char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_to_user(ubuf,kbuf,size);
	if(ret){
		printk("copy data to user error\n");
		return -EINVAL; //返回一个错误码
	}
	memset(kbuf,0,sizeof(kbuf));
	return size;
}
ssize_t myled_write(struct file *filp,const char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_from_user(kbuf,ubuf,size);
	if(ret){
		printk("copy data from user error\n");
		return -EINVAL; //返回一个错误码
	}
	switch(kbuf[0]){
		case RED:
			kbuf[1]?(*(virt_addr[0]) |= (1<<28)):\
				(*(virt_addr[0]) &= ~(1<<28));
			break;
		case GREEN:
			kbuf[1]?(*(virt_addr[1]) |= (1<<13)):\
				(*(virt_addr[1]) &= ~(1<<13));
			break;
		case BLUE:
			kbuf[1]?(*(virt_addr[2]) |= (1<<12)):\
				(*(virt_addr[2]) &= ~(1<<12));
			break;
	}
	return size;
}
long myled_ioctl(struct file *file, unsigned int cmd, unsigned long args)
{	
	int ret;
	switch(cmd){
		case RGB_LED_RED_ON:
			*(virt_addr[0]) |= (1<<28);
			break;
		case RGB_LED_RED_OFF:
			*(virt_addr[0]) &= ~(1<<28);
			break;
		case ACCESS_WRITE_INT_DATA:
			ret = copy_from_user((void *)&data,(void *)args,CMD_SIZE(ACCESS_WRITE_INT_DATA));
			if(ret){
				printk("copy data from user error\n");
				return -EINVAL;
			}
			printk("kernel:int:%d\n",data++);
			break;
		case ACCESS_READ_INT_DATA:
			ret = copy_to_user((void *)args,(void *)&data,CMD_SIZE(ACCESS_READ_INT_DATA));
			if(ret){
				printk("copy data to user error\n");
				return -EINVAL;
			}
			break;
		case ACCESS_WRITE_CHAR_ARRAY:
			ret = copy_from_user(kbuf,(void *)args,CMD_SIZE(ACCESS_WRITE_CHAR_ARRAY));
			if(ret){
				printk("copy data from user error\n");
				return -EINVAL;
			}
			printk("kernel:array:%s\n",kbuf);
			break;
	}
	return 0;
}
int myled_close(struct inode *inode, struct file *filp)
{
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
const struct file_operations fops = {
	.open  = myled_open,
	.read  = myled_read,
	.write = myled_write,
	.unlocked_ioctl=myled_ioctl,
	.release = myled_close,
};
//入口
static int __init myled_init(void)
{
	int i;
	//注册字符设备驱动
	major = register_chrdev(0,CNAME,&fops);
	if(major < 0){
		printk("register LED char device driver error\n");
		return major;
	}
	for(i=0;i<ARRAY_SIZE(PHY_ADDR);i++){
		virt_addr[i] = ioremap(PHY_ADDR[i],40);
		if(virt_addr[i] == NULL){
			printk("ioremap error\n");
			return -ENOMEM;
		}
	}

	//virt_addr[0]  红灯的虚拟的基地址
	*(virt_addr[0] + 9) &= ~(3<<24);
	*(virt_addr[0] + 1) |= (1<<28);
	*(virt_addr[0] + 0) &= ~(1<<28);

	//virt_addr[1]  绿灯的虚拟的基地址
	*(virt_addr[1] + 8) &= ~(3<<26);
	*(virt_addr[1] + 1) |= (1<<13);
	*(virt_addr[1] + 0) &= ~(1<<13);
	
	//virt_addr[2]  蓝灯的虚拟的基地址
	*(virt_addr[2] + 8) &= ~(3<<24);
	*(virt_addr[2] + 8) |= (1<<25);
	*(virt_addr[2] + 1) |= (1<<12);
	*(virt_addr[2] + 0) &= ~(1<<12);

	cls = class_create(THIS_MODULE,"hello");
	if(IS_ERR(cls)){
		printk("class create error\n");
		return PTR_ERR(cls);
	}
	dev = device_create(cls,NULL,MKDEV(major,0),NULL,"myled");
	if(IS_ERR(dev)){
		printk("device create error\n");
		return PTR_ERR(dev);
	}
	return 0;
}
//出口
static void __exit myled_exit(void)
{
	int i;
	device_destroy(cls,MKDEV(major,0));
	class_destroy(cls);
	//注销字符设备驱动
	unregister_chrdev(major,CNAME);

	for(i=0;i<ARRAY_SIZE(virt_addr);i++){
		iounmap(virt_addr[i]);
	}
}
module_init(myled_init);
module_exit(myled_exit);
//许可证
MODULE_LICENSE("GPL");
MODULE_AUTHOR("farsight [email protected]");

test.c

#include 
#include 
#include 
#include 
#include 
#include 
#include "head.h"
char buf[128] = "i am user array data......";
int main(int argc, const char *argv[])
{
	int fd;
	int data = 1234;
	if((fd = open("/dev/myled",O_RDWR))<0){
		perror("open error");
		return -1;
	}
	ioctl(fd,ACCESS_WRITE_INT_DATA,&data);
	ioctl(fd,ACCESS_READ_INT_DATA,&data);
	printf("user:%d\n",data);
	ioctl(fd,ACCESS_WRITE_CHAR_ARRAY,buf);
	while(1){
		ioctl(fd,RGB_LED_RED_ON);
		sleep(1);
		ioctl(fd,RGB_LED_RED_OFF);
		sleep(1);
	}
	close(fd);
	return 0;
}

head.h

#ifndef __HEAD_H__
#define __HEAD_H__
#define type 'k'
#define RGB_LED_RED_ON _IO(type,1)
#define RGB_LED_RED_OFF _IO(type,0)
#define ACCESS_WRITE_INT_DATA _IOW(type,0,int)
#define ACCESS_READ_INT_DATA _IOR(type,0,int)
#define ACCESS_WRITE_CHAR_ARRAY _IOW(type,0,char [128])
#endif

问:在驱动开发时,什么时候适合使用ioctl函数?

比如对于摄像头驱动来说,摄像头采集的图像的大小,图像的格式就可以使用ioctl来控制完成设置,在比如ADC的使用的分频率可以通过ioctl留个用户来设置,ADC的启动与否也可以通过ioctl就给用户选择设置。等等这些时候就适合通过ioctl来完成。

作业:
1.将ADC的代码修改支持ioctl设置硬件的一些属性
2.对ADC寄存器的操作使用readl和writel完成

readl(addr) 	
功能:读取一个32位的数据
参数:
	@addr:虚拟地址
返回值:读取到的值
writel(v, r)	
功能:向一个地址中写23位的数据
参数:
	@v:要写的值
	@r:虚拟地址
eg:
	writel(readl(virt_addr[0]+9) &(~(3<<24)),virt_addr[0]+9);

10. 字符设备驱动的框架

user:
	fd=open("/dev/myled",);  read(fd,);   write(fd,);  close(fd,);
		   |--ls -i----->inode号<---文件系统识别文件的标号
---------------------------|--------------
kernel:                    |
					struct inode{
						umode_t i_mode; //文件权限
						uid_t	i_uid;  //用户的id
						gid_t	i_gid;  //组的id
						unsigned long	i_ino; //inode号
						dev_t	i_rdev; //设备号
						union {
							struct block_device	*i_bdev;//块设备驱动的结构体
							struct cdev		*i_cdev;//字符设备驱动的结构体
						};
					}	
		字符设备驱动
		struct cdev *i_cdev;
		dev_t  i_rdev;
		---------			
	   |		 |
	   |         |
	   |         |
	   -----------
	  struct cdev {
		const struct file_operations *ops;//操作方法结构体
		struct list_head list;//内核链表(cdev放入到了内核的链表中)
		dev_t dev;//设备号
		unsigned int count;//设备的个数
	 };
------------------------------------------
hardware:   LED

问:inode是干什么的?什么时候产生的inode?

只要一个文件存在,那它就对应一个inode结构体,这个inode结构体就是用来描述这个文件的性信息的。
通过inode就可以唯一找到一个inode结构体

问:字符设备驱动的编写流程(分步)
1.分配对象

struct cdev cdev;//这个不用分配空间
struct cdev *cdev;//这个需要分配空间

struct cdev *cdev_alloc(void)
功能:为字符设备驱动的对象分配内存(如果内存申请成功会初始化cdev中的链表)
参数:@无
返回值:成功返回结构体指针,失败返回NULL
		
void kfree(void *p)
功能:释放cdev结构体指针的内存
参数:@p:cdev的结构体的首地址
返回值:无

2.对象的初始化

void cdev_init(struct cdev *cdev,const struct file_operations *fops)
功能:初始化cdev结构体(fops)
参数:@cdev:cdev的结构体指针
	 @fops:操作方法结构体指针
返回值:无
------------------------------------------------------------		
申请设备号:
功能:静态指定想要注册的设备号
int register_chrdev_region(dev_t from,unsigned count, const char *name)
参数:@from:设备号的起始的值
	 @count:个数
	 @name:设备驱动的名字
返回值:成功返回0,失败返回错误码

功能:动态申请设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
参数:@dev:申请到的设备号
	 @baseminor:次设备号的起始的值
	 @count:个数
	 @name:名字
返回值:成功返回0,失败返回错误码			
------------------------------------------------------------			
void unregister_chrdev_region(dev_t from, unsigned count)
功能:向系统归还设备号
参数:@from:设备号的起始的值
	 @count:个数
返回值:无

3.对象的注册

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
功能:字符设备驱动的注册
参数:@p:cdev的结构体指针
	 @dev:设备号
	 @count:个数
返回值:成功返回0,失败返回错误码

4.对象的注销

void cdev_del(struct cdev *p)
功能:注销一个字符设备驱动
参数:@p:cdev的结构体指针
返回值:无

练习:字符设备驱动分步实现的流程
mycdev.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define CNAME "mycdev"
#define COUNT 3
struct cdev *cdev; 
int major = 500;
int minor = 0;
struct class*cls;
struct device *dev;
char kbuf[128] = {0};
int mycdev_open(struct inode *inode, struct file *filp)
{
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
ssize_t  mycdev_read(struct file * filp,char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_to_user(ubuf,kbuf,size);
	if(ret){
		printk("copy data to user error\n");
		return -EINVAL; 
	}
	return size;
}
ssize_t  mycdev_write(struct file *filp, const char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_from_user(kbuf,ubuf,size);
	if(ret){
		printk("copy data from user error\n");
		return -EINVAL; 
	}
	return size;
}
int  mycdev_close(struct inode *inode, struct file *filp)
{
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
const struct file_operations fops = {
	.open  = mycdev_open,
	.read  =  mycdev_read,
	.write =  mycdev_write,
	.release =  mycdev_close,
};
static int __init mycdev_init(void)
{
	int ret,i;
	dev_t devno;//1.分配对象
	cdev = cdev_alloc();
	if(cdev == NULL){
		printk("alloc cdev memory error\n");
		ret = -ENOMEM;
		goto ERR1;
	}
	//2.对象的初始化
	cdev_init(cdev,&fops);
	//3.申请设备号
	if(major > 0){
		ret = register_chrdev_region(MKDEV(major,minor),COUNT,CNAME);
		if(ret){
			printk("static: alloc device number error\n");
			goto ERR2;
		}
	}else{
		ret = alloc_chrdev_region(&devno,minor,COUNT,CNAME);
		if(ret){
			printk("dynamic: alloc device number error\n");
			goto ERR2;
		}
		major = MAJOR(devno);
		minor = MINOR(devno);
	}
	//4.注册
	ret = cdev_add(cdev,MKDEV(major,minor),COUNT);
	if(ret){
		printk("register char device driver error\n");
		goto ERR3;
	}
	//5.自动创建设备节点
	cls = class_create(THIS_MODULE,CNAME);
	if(IS_ERR(cls)){
		printk("class create error\n");
		ret = PTR_ERR(cls);
		goto ERR4;
	}
	for(i=0;i<COUNT;i++){
		dev = device_create(cls,NULL,MKDEV(major,minor+i),NULL,"mycdev%d",i);
		if(IS_ERR(dev)){
			printk("device create error\n");
			ret = PTR_ERR(dev);
			goto ERR5;
		}
	}
	return 0; //这里的return千万不要忘记写!!!!
ERR5:
	for(--i;i>=0;i--){
		device_destroy(cls,MKDEV(major,minor+i));
	}
	class_destroy(cls);
ERR4:
	cdev_del(cdev);
ERR3:
	unregister_chrdev_region(MKDEV(major,minor),COUNT);
ERR2:
	kfree(cdev);
ERR1:
	return ret;
}
static void __exit mycdev_exit(void)
{
	int i;
	for(i=0;i<COUNT;i++){
		device_destroy(cls,MKDEV(major,minor+i));
	}
	class_destroy(cls);
	cdev_del(cdev);
	unregister_chrdev_region(MKDEV(major,minor),COUNT);
	kfree(cdev);	
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

text.c

#include 
#include 
#include 
#include 
#include 
char buf[128] = {0};
int main(int argc, const char *argv[])
{
	int fd;
	if((fd = open("/dev/mycdev1",O_RDWR))<0){
		perror("open error");
		return -1;
	}
	read(fd,buf,sizeof(buf));
	write(fd,buf,sizeof(buf));
	close(fd);
	return 0;
}

问:file结构体的功能是什么?什么时候产生的?

在一个进程中调用open函数的时候就会产生一个文件描述符fd。
在调用open函数的时候会产生一个file结构体的对象,这个file结构体就是用来记录本次打开文件时候的各种信息的对象。
这个file结构体就被放到fd_array[fd]数组中,这个数组的下标就是文件描述符。
fd--->fd_array[fd]--->struct file-->f_op-->read write relase
struct file {
		unsigned int 	f_flags; //打开文件的读写方式
		fmode_t			f_mode;  //权限
		loff_t			f_pos;   //文件的流置针(lseek)
		const struct file_operations	*f_op; //这个就是操作方法结构体
		void		 *private_data;//私有数据,它的作用就是在驱动的open read write close函数的相互传递参数使用的
	}

作业:(根据设备文件识别设备)

设备文件:mycdev0   mycdev1   mycdev2
	       500,0     500,1    500,2
		--------------------------------------
		 mycdev字符设备驱动(在驱动中只要能拿到当前文件
		 的次设备号,那么就知道用户正在访问哪一个文件,
		 如果访问的是文件mycdev0,并且写入到内核的值是1,
		 那么在驱动中就将红灯点亮即可。)
		---------------------------------------
			 RED     GREEN      BLUE
		echo 1 > /dev/mycdev0   红灯亮
		echo 0 > /dev/mycdev0   红灯灭
		echo 1 > /dev/mycdev1   绿灯亮
		echo 0 > /dev/mycdev1   绿灯灭	
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define CNAME "mycdev"
#define COUNT 3
#define RED 0
#define GREEN 1
#define BLUE 2
struct cdev *cdev; 
int major = 0;
int minor = 0;
struct class*cls;
struct device *dev;
char kbuf[128] = {0};
int mycdev_open(struct inode *inode, struct file *filp)
{
	int cur_minor_no;
	cur_minor_no = MINOR(inode->i_rdev);
	filp->private_data = (void *)cur_minor_no;
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
ssize_t  mycdev_read(struct file * filp, char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_to_user(ubuf,kbuf,size);
	if(ret){
		printk("copy data to user error\n");
		return -EINVAL; 
	}
	return size;
}
ssize_t  mycdev_write(struct file *filp, const char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
	int cur_minor_no = (int)filp->private_data;
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_from_user(kbuf,ubuf,size);
	if(ret){
		printk("copy data from user error\n");
		return -EINVAL; 
	}
	switch(cur_minor_no){
		case RED:
			if(kbuf[0] == '1'){
				printk("red light on\n");
			}else{
				printk("red ligt off\n");
			}
			break;
		case GREEN:
			if(kbuf[0] == '1'){
				printk("green light on\n");
			}else{
				printk("green ligt off\n");
			}
			break;
		case BLUE:
			if(kbuf[0] == '1'){
				printk("blue light on\n");
			}else{
				printk("blue ligt off\n");
			}

			break;
	}
	return size;
}
int  mycdev_close(struct inode *inode, struct file *filp)
{
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
const struct file_operations fops = {
	.open  = mycdev_open,
	.read  =  mycdev_read,
	.write =  mycdev_write,
	.release =  mycdev_close,
};
static int __init mycdev_init(void)
{
	int ret,i;
	dev_t devno;
	//1.分配对象
	cdev = cdev_alloc();
	if(cdev == NULL){
		printk("alloc cdev memory error\n");
		ret = -ENOMEM;
		goto ERR1;
	}
	//2.对象的初始化
	cdev_init(cdev,&fops);
	//3.申请设备号
	if(major > 0){
		ret = register_chrdev_region(MKDEV(major,minor),
			COUNT,CNAME);
		if(ret){
			printk("static: alloc device number error\n");
			goto ERR2;
		}
	}else{
		ret = alloc_chrdev_region(&devno,minor,COUNT,CNAME);
		if(ret){
			printk("dynamic: alloc device number error\n");
			goto ERR2;
		}
		major = MAJOR(devno);
		minor = MINOR(devno);
	}
	//4.注册
	ret = cdev_add(cdev,MKDEV(major,minor),COUNT);
	if(ret){
		printk("register char device driver error\n");
		goto ERR3;
	}
	//5.自动创建设备节点
	cls = class_create(THIS_MODULE,CNAME);
	if(IS_ERR(cls)){
		printk("class create error\n");
		ret = PTR_ERR(cls);
		goto ERR4;
	}
	for(i=0;i<COUNT;i++){
		dev = device_create(cls,NULL,MKDEV(major,minor+i),
			NULL,"mycdev%d",i);
		if(IS_ERR(dev)){
			printk("device create error\n");
			ret = PTR_ERR(dev);
			goto ERR5;
		}
	}
	return 0; //这里的return千万不要忘记写!!!!
ERR5:
	for(--i;i>=0;i--){
		device_destroy(cls,MKDEV(major,minor+i));
	}
	class_destroy(cls);
ERR4:
	cdev_del(cdev);
ERR3:
	unregister_chrdev_region(MKDEV(major,minor),COUNT);
ERR2:
	kfree(cdev);
ERR1:
	return ret;
}
static void __exit mycdev_exit(void)
{
	int i;
	for(i=0;i<COUNT;i++){
		device_destroy(cls,MKDEV(major,minor+i));
	}
	class_destroy(cls);
	cdev_del(cdev);
	unregister_chrdev_region(MKDEV(major,minor),COUNT);
	kfree(cdev);	
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

三、linux内核中的并发和竞态的解决方法

有多个进程同时访问同一个驱动临界资源,这时候竞态就会产生。
竞态产生的根本原因?
1.对于单核cpu来说,如果内核支持抢占就会产生竞态。比如正在访问临界资源的进程被更高优先级的进程打断,并使用临界资源
2.对于多核cpu来说,核与核之间可以产生竞态
3.中断和进程间也会产生竞态
4.中断和更高优先级的中断间会产生竞态(arm架构下这句话是错误的,因为arm中不支持中断嵌套,但是单片机等支持中断嵌套的就是对的)
竞态的解决方法如下?
方法一:顺序执行(在应用层时,使用多线程间可以顺序执行,信号和信号量)
方法二:互斥执行(互斥锁)
在内核模块间无法保证顺序执行,所以从互斥方面入手
1.中断屏蔽(了解)
中断屏蔽只针对单核cpu有效,不管是进程间的切换或者是中断和进程的切换都是需要中断参与的,所以将中断关闭掉之后,就可以解决竞态。

1.只能在单核cpu中使用
2.有可能导致用户数据的丢失
3.如果中断屏蔽的时间比较长还可能会导致内核的崩溃
local_irq_disable();
//临界区(临界区所占用的时间尽可能的短)
local_irq_enable();

2.自旋锁(重点掌握)(忙等锁)
自旋锁是针对多核CPU设计的,当一个进程占用自旋锁之后,另外一个进程也想或者这把锁,此时后一个进程处于自旋状态(原地打转)。

特点:
	1.自旋锁多核cpu有效
	2.在忙等的期间是需要消耗cpu资源的
	3.自旋锁工作在中断上下文 
	4.自旋锁保护的临界区尽可能的短小,不能够有延时,耗时,甚至休眠的操作,也不能够有copy_to_user/copy_from_user函数,也不能够执行进程调度的函数(schedule())。
	5.自旋锁会产生死锁(在同一个进程中多次获取同一把未解锁的锁)或者未解锁,另一进程不断去获取锁,也会产生死锁
	6.自旋锁在上锁的时候会关闭抢占
API:
	spinlock_t lock; //定义自旋锁
	spin_lock_init(&lock)//初始化自旋锁
	void spin_lock(spinlock_t *lock) //上锁
	void spin_unlock(spinlock_t *lock)//解锁

mydev.c

#include 
#include 
#include 
#include 
#include 
#define CNAME "mycdev"
#define COUNT 3
struct cdev *cdev; 
int major = 0;
int minor = 0;
struct class*cls;
struct device *dev;
char kbuf[128] = {0};
spinlock_t lock; //定义自旋锁
int flags = 0;
int mycdev_open(struct inode *inode, struct file *filp)
{
	spin_lock(&lock);
	if(flags != 0){
		spin_unlock(&lock);
		return -EBUSY;
	}
	flags=1;
	spin_unlock(&lock);
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
ssize_t  mycdev_read(struct file * filp,char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_to_user(ubuf,kbuf,size);
	if(ret){
		printk("copy data to user error\n");
		return -EINVAL; 
	}
	return size;
}
ssize_t  mycdev_write(struct file *filp, const char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_from_user(kbuf,ubuf,size);
	if(ret){
		printk("copy data from user error\n");
		return -EINVAL; 
	}
	return size;
}
int  mycdev_close(struct inode *inode, struct file *filp)
{
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	spin_lock(&lock);
	flags=0;
	spin_unlock(&lock);
	return 0;
}
const struct file_operations fops = {
	.open  = mycdev_open,
	.read  =  mycdev_read,
	.write =  mycdev_write,
	.release =  mycdev_close,
};
static int __init mycdev_init(void)
{
	int ret,i;
	dev_t devno;
	//1.分配对象
	cdev = cdev_alloc();
	if(cdev == NULL){
		printk("alloc cdev memory error\n");
		ret = -ENOMEM;
		goto ERR1;
	}
	//2.对象的初始化
	cdev_init(cdev,&fops);
	//3.申请设备号
	if(major > 0){
		ret = register_chrdev_region(MKDEV(major,minor),
			COUNT,CNAME);
		if(ret){
			printk("static: alloc device number error\n");
			goto ERR2;
		}
	}else{
		ret = alloc_chrdev_region(&devno,minor,COUNT,CNAME);
		if(ret){
			printk("dynamic: alloc device number error\n");
			goto ERR2;
		}
		major = MAJOR(devno);
		minor = MINOR(devno);
	}
	//4.注册
	ret = cdev_add(cdev,MKDEV(major,minor),COUNT);
	if(ret){
		printk("register char device driver error\n");
		goto ERR3;
	}
	//5.自动创建设备节点
	cls = class_create(THIS_MODULE,CNAME);
	if(IS_ERR(cls)){
		printk("class create error\n");
		ret = PTR_ERR(cls);
		goto ERR4;
	}
	for(i=0;i<COUNT;i++){
		dev = device_create(cls,NULL,MKDEV(major,minor+i),
			NULL,"mycdev%d",i);
		if(IS_ERR(dev)){
			printk("device create error\n");
			ret = PTR_ERR(dev);
			goto ERR5;
		}
	}
	//初始化自旋锁
	spin_lock_init(&lock);
	return 0; //这里的return千万不要忘记写!!!!
ERR5:
	for(--i;i>=0;i--){
		device_destroy(cls,MKDEV(major,minor+i));
	}
	class_destroy(cls);
ERR4:
	cdev_del(cdev);
ERR3:
	unregister_chrdev_region(MKDEV(major,minor),COUNT);
ERR2:
	kfree(cdev);
ERR1:
	return ret;}

static void __exit mycdev_exit(void)
{
	int i;
	for(i=0;i<COUNT;i++){
		device_destroy(cls,MKDEV(major,minor+i));
	}
	class_destroy(cls);
	cdev_del(cdev);
	unregister_chrdev_region(MKDEV(major,minor),COUNT);
	kfree(cdev);	
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

test.c

#include 
#include 
#include 
#include 
#include 
char buf[128] = {0};
int main(int argc, const char *argv[])
{
	int fd;
	if((fd = open("/dev/mycdev1",O_RDWR))<0){
		perror("open error");
		return -1;
	}
	read(fd,buf,sizeof(buf));
	sleep(5);
	write(fd,buf,sizeof(buf));
	close(fd);
	return 0;
}

3.信号量(重点掌握)
当一个进程获取到信号量之后,后一个进程也想获取到这个信号量,此时后一个进程就处于休眠的状态。

特点:
		1.获取不到信号量的时候进程处于休眠的状态此时不消耗cpu资源
		2.信号量工作在进程上下文
		3.在信号量保护的临界区中可以有延时,耗时甚至休眠的操作。
API:
	struct semaphore sem;//定义信号量
	void sema_init(struct semaphore *sem, int val)//信号量的初始化
	//信号量本身是一个同步的机制,只有当val被设置为1的时候才有互斥的效果。如果把val设置为0它是一种同步的机制。
	void down(struct semaphore *sem);//上锁,如果获取不到锁就会休眠
	int  down_trylock(struct semaphore *sem);//尝试获取锁,获取锁成功返回0,不成功返回1
	//如果获取不到锁是不会休眠的
	void up(struct semaphore *sem);
	//解锁

mycdev.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define CNAME "mycdev"
#define COUNT 3
#define RED 0
#define GREEN 1
#define BLUE 2
struct cdev *cdev; 
int major = 0;
int minor = 0;
struct class*cls;
struct device *dev;
char kbuf[128] = {0};
struct semaphore sem; //定义信号量
int mycdev_open(struct inode *inode, struct file *filp)
{
	//down(&sem);
	if(down_trylock(&sem)){
		return -EBUSY;
	}
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
ssize_t  mycdev_read(struct file * filp,char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_to_user(ubuf,kbuf,size);
	if(ret){
		printk("copy data to user error\n");
		return -EINVAL; 
	}
	return size;
}
ssize_t  mycdev_write(struct file *filp,const char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_from_user(kbuf,ubuf,size);
	if(ret){
		printk("copy data from user error\n");
		return -EINVAL; 
	}
	return size;
}
int  mycdev_close(struct inode *inode, struct file *filp)
{
	
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	up(&sem);
	return 0;
}
const struct file_operations fops = {
	.open  = mycdev_open,
	.read  =  mycdev_read,
	.write =  mycdev_write,
	.release =  mycdev_close,
};
static int __init mycdev_init(void)
{
	int ret,i;
	dev_t devno;
	//1.分配对象
	cdev = cdev_alloc();
	if(cdev == NULL){
		printk("alloc cdev memory error\n");
		ret = -ENOMEM;
		goto ERR1;
	}
	//2.对象的初始化
	cdev_init(cdev,&fops);
	//3.申请设备号
	if(major > 0){
		ret = register_chrdev_region(MKDEV(major,minor),
			COUNT,CNAME);
		if(ret){
			printk("static: alloc device number error\n");
			goto ERR2;
		}
	}else{
		ret = alloc_chrdev_region(&devno,minor,
			COUNT,CNAME);
		if(ret){
			printk("dynamic: alloc device number error\n");
			goto ERR2;
		}
		major = MAJOR(devno);
		minor = MINOR(devno);
	}
	//4.注册
	ret = cdev_add(cdev,MKDEV(major,minor),COUNT);
	if(ret){
		printk("register char device driver error\n");
		goto ERR3;
	}
	//5.自动创建设备节点
	cls = class_create(THIS_MODULE,CNAME);
	if(IS_ERR(cls)){
		printk("class create error\n");
		ret = PTR_ERR(cls);
		goto ERR4;
	}
	for(i=0;i<COUNT;i++){
		dev = device_create(cls,NULL,MKDEV(major,minor+i),
			NULL,"mycdev%d",i);
		if(IS_ERR(dev)){
			printk("device create error\n");
			ret = PTR_ERR(dev);
			goto ERR5;
		}
	}
	sema_init(&sem,1);
	return 0; //这里的return千万不要忘记写!!!!
ERR5:
	for(--i;i>=0;i--){
		device_destroy(cls,MKDEV(major,minor+i));
	}
	class_destroy(cls);
ERR4:
	cdev_del(cdev);
ERR3:
	unregister_chrdev_region(MKDEV(major,minor),COUNT);
ERR2:
	kfree(cdev);
ERR1:
	return ret;
}
static void __exit mycdev_exit(void)
{
	int i;
	for(i=0;i<COUNT;i++){
		device_destroy(cls,MKDEV(major,minor+i));
	}
	class_destroy(cls);
	cdev_del(cdev);
	unregister_chrdev_region(MKDEV(major,minor),COUNT);
	kfree(cdev);	
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

test.c

#include 
#include 
#include 
#include 
#include 
char buf[128] = {0};
int main(int argc, const char *argv[])
{
	int fd;
	if((fd = open("/dev/mycdev1",O_RDWR))<0){
		perror("open error");
		return -1;
	}
	read(fd,buf,sizeof(buf));
	sleep(5);
	write(fd,buf,sizeof(buf));
	close(fd);
	return 0;
}

4.互斥体(掌握)
互斥体跟信号的特点类似,当一个进程获取到互斥体之后,另外一个进程也想获取这个互斥体,此时后一个进程处于休眠状态。获取不到互斥体资源的时候,进程在休眠前会稍等一会儿。所以互斥体使用用在时间很短的反复
进程状态切换的代码中。

API:
struct mutex mutex;//定义互斥体
mutex_init(&mutex);//互斥体的初始化
void  mutex_lock(struct mutex *lock)//上锁
int mutex_trylock(struct mutex *lock)//尝试获取锁,如果返回值是1表示获取锁成功了,如果返回0表示获取锁失败了。
void  mutex_unlock(struct mutex *lock)//解锁

mycdev.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define CNAME "mycdev"
#define COUNT 3
#define RED 0
#define GREEN 1
#define BLUE 2
struct cdev *cdev; 
int major = 0;
int minor = 0;
struct class*cls;
struct device *dev;
char kbuf[128] = {0};
struct mutex lock;
int mycdev_open(struct inode *inode, struct file *filp)
{
	if(!mutex_trylock(&lock)){
		return -EBUSY;
	}
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
ssize_t  mycdev_read(struct file * filp, char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_to_user(ubuf,kbuf,size);
	if(ret){
		printk("copy data to user error\n");
		return -EINVAL; 
	}
	return size;
}
ssize_t  mycdev_write(struct file *filp, const char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_from_user(kbuf,ubuf,size);
	if(ret){
		printk("copy data from user error\n");
		return -EINVAL; 
	}
	return size;
}
int  mycdev_close(struct inode *inode, struct file *filp)
{
	
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	mutex_unlock(&lock);
	return 0;
}
const struct file_operations fops = {
	.open  = mycdev_open,
	.read  =  mycdev_read,
	.write =  mycdev_write,
	.release =  mycdev_close,
};
static int __init mycdev_init(void)
{
	int ret,i;
	dev_t devno;
	//1.分配对象
	cdev = cdev_alloc();
	if(cdev == NULL){
		printk("alloc cdev memory error\n");
		ret = -ENOMEM;
		goto ERR1;
	}
	//2.对象的初始化
	cdev_init(cdev,&fops);
	//3.申请设备号
	if(major > 0){
		ret = register_chrdev_region(MKDEV(major,minor),
			COUNT,CNAME);
		if(ret){
			printk("static: alloc device number error\n");
			goto ERR2;
		}
	}else{
		ret = alloc_chrdev_region(&devno,minor,
			COUNT,CNAME);
		if(ret){
			printk("dynamic: alloc device number error\n");
			goto ERR2;
		}
		major = MAJOR(devno);
		minor = MINOR(devno);
	}
	//4.注册
	ret = cdev_add(cdev,MKDEV(major,minor),COUNT);
	if(ret){
		printk("register char device driver error\n");
		goto ERR3;
	}
	//5.自动创建设备节点
	cls = class_create(THIS_MODULE,CNAME);
	if(IS_ERR(cls)){
		printk("class create error\n");
		ret = PTR_ERR(cls);
		goto ERR4;
	}
	for(i=0;i<COUNT;i++){
		dev = device_create(cls,NULL,MKDEV(major,minor+i),
			NULL,"mycdev%d",i);
		if(IS_ERR(dev)){
			printk("device create error\n");
			ret = PTR_ERR(dev);
			goto ERR5;
		}
	}
	mutex_init(&lock);
	return 0; //这里的return千万不要忘记写!!!!
ERR5:
	for(--i;i>=0;i--){
		device_destroy(cls,MKDEV(major,minor+i));
	}
	class_destroy(cls);
ERR4:
	cdev_del(cdev);
ERR3:
	unregister_chrdev_region(MKDEV(major,minor),COUNT);
ERR2:
	kfree(cdev);
ERR1:
	return ret;
}
static void __exit mycdev_exit(void)
{
	int i;
	for(i=0;i<COUNT;i++){
		device_destroy(cls,MKDEV(major,minor+i));
	}
	class_destroy(cls);
	cdev_del(cdev);
	unregister_chrdev_region(MKDEV(major,minor),COUNT);
	kfree(cdev);	
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

5.原子操作(掌握)
原子操作的含义是把本次操作看成一个整体,在本次操作的时候是不可以被打断的。原因这个原子变量的赋值是通过内联汇编实现了。

typedef struct {
			int counter;
		} atomic_t; 
API:
	atomic_t atm = ATOMIC_INIT(1);//定义并初始化原子变量
	上锁:
		atomic_inc_and_test(&atm)
	//让原子变量的值加1和0比较,如果结果为0,表示
	//获取锁成功了,返回真,如果结果不为0,表示
	//获取锁失败了,返回假
			
		atomic_dec_and_test(&atm)
	//让原子变量的值减1和0比较,如果结果为0,表示
	//获取锁成功了,返回真,如果结果不为0,表示
	//获取锁失败了,返回假
			
	解锁:
	atomic_inc(&atm)
	//让原子变量的值加1
	atomic_dec(&atm)
	//让原子变量的值减1

mycdev.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define CNAME "mycdev"
#define COUNT 3
#define RED 0
#define GREEN 1
#define BLUE 2
struct cdev *cdev; 
int major = 0;
int minor = 0;
struct class*cls;
struct device *dev;
char kbuf[128] = {0};
atomic_t atm = ATOMIC_INIT(1);
int mycdev_open(struct inode *inode, struct file *filp)
{
	if(!atomic_dec_and_test(&atm)){
		atomic_inc(&atm);
		return -EBUSY;
	}
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	return 0;
}
ssize_t  mycdev_read(struct file * filp, char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_to_user(ubuf,kbuf,size);
	if(ret){
		printk("copy data to user error\n");
		return -EINVAL; 
	}
	return size;
}
ssize_t  mycdev_write(struct file *filp, const char __user *ubuf, size_t size, loff_t *offs)
{
	int ret;
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	if(size > sizeof(kbuf)) size = sizeof(kbuf);
	ret = copy_from_user(kbuf,ubuf,size);
	if(ret){
		printk("copy data from user error\n");
		return -EINVAL; 
	}
	return size;
}
int  mycdev_close(struct inode *inode, struct file *filp)
{
	printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
	atomic_inc(&atm);
	return 0;
}
const struct file_operations fops = {
	.open  = mycdev_open,
	.read  =  mycdev_read,
	.write =  mycdev_write,
	.release =  mycdev_close,
};
static int __init mycdev_init(void)
{
	int ret,i;
	dev_t devno;
	//1.分配对象
	cdev = cdev_alloc();
	if(cdev == NULL){
		printk("alloc cdev memory error\n");
		ret = -ENOMEM;
		goto ERR1;
	}
	//2.对象的初始化
	cdev_init(cdev,&fops);
	//3.申请设备号
	if(major > 0){
		ret = register_chrdev_region(MKDEV(major,minor),
			COUNT,CNAME);
		if(ret){
			printk("static: alloc device number error\n");
			goto ERR2;
		}
	}else{
		ret = alloc_chrdev_region(&devno,minor,
			COUNT,CNAME);
		if(ret){
			printk("dynamic: alloc device number error\n");
			goto ERR2;
		}
		major = MAJOR(devno);
		minor = MINOR(devno);
	}
	//4.注册
	ret = cdev_add(cdev,MKDEV(major,minor),COUNT);
	if(ret){
		printk("register char device driver error\n");
		goto ERR3;
	}
	//5.自动创建设备节点
	cls = class_create(THIS_MODULE,CNAME);
	if(IS_ERR(cls)){
		printk("class create error\n");
		ret = PTR_ERR(cls);
		goto ERR4;
	}
	for(i=0;i<COUNT;i++){
		dev = device_create(cls,NULL,MKDEV(major,minor+i),
			NULL,"mycdev%d",i);
		if(IS_ERR(dev)){
			printk("device create error\n");
			ret = PTR_ERR(dev);
			goto ERR5;
		}
	}
	return 0; //这里的return千万不要忘记写!!!!
ERR5:
	for(--i;i>=0;i--){
		device_destroy(cls,MKDEV(major,minor+i));
	}
	class_destroy(cls);
ERR4:
	cdev_del(cdev);
ERR3:
	unregister_chrdev_region(MKDEV(major,minor),COUNT);
ERR2:
	kfree(cdev);
ERR1:
	return ret;
}

static void __exit mycdev_exit(void)
{
	int i;
	for(i=0;i<COUNT;i++){
		device_destroy(cls,MKDEV(major,minor+i));
	}
	class_destroy(cls);
	cdev_del(cdev);
	unregister_chrdev_region(MKDEV(major,minor),COUNT);
	kfree(cdev);	
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

8. source insight软件的使用

1.软件下载的地址
https://github.com/daizhansheng/source-insight-tools
sourceInsight.exe :安装的软件
sn.txt :激活的秘钥
GLOBAL.CF3 :这是一个配色文件
2.双击安装即可
3.使用source insight对linux内核创建索引
3.1在source insight中创建工程
project->new project->工程的名字和工程的路径->
3.2将系统移植课程中的linux内核在windows中解压一份
如果解压的时候遇错误或者警告直接全部选择“是”
3.3将刚才解压的内核代码添加到上述的工程中
选择解压后的内核源码的路径,然后点击add all
大概会向工程中添加2万多个文件,添加完之后点击close
3.4为上述的内核源码创建索引
project–>同步文件
大概10分钟左右就能够创建成功了
3.5如何验证索引是否创建成功了
在source insight中新建一个.c文件,然后
输入register_chr,如果能够source insight能够
将这个函数补全,就说明索引创建成功了。
4.如何查找代码
查找 :点击R图标->输入要搜索的内核->在所有到内容后,按下空格停止->点击左侧的<>图标->将光标停在要查找的函数上–>ctrl+鼠标左键就可以查找到函数的定义的位置了
回退:直接点击回退的图标即可
5.配色修改
options->load configuration->load->选择从github下载到的GLOBAL.CF3->打开 配色即可完成

你可能感兴趣的:(linux,内核,嵌入式)