Linux驱动基础、内核模块(二)

10】
字符设备驱动:
user:
	/dev/led
	fd=open("/dev/led",O_RDWR);
	read=(fd,buf,sizeof(buf));
	write(fd,buf,sizeof(buf));
	close(fd);
	open-->inode号-->c /dev/led (设备号32=主设备号12+次设备号20)
	
	//操作方法结构体
	struct file_operations fops{          <==>  struct file_operations fops;
		.open = led_open,				  <==>	fops.open =led_open;
		.read = led_read,                       
		.write = led_write,
		.release = led_close,
	};//在定义的时候直接赋值,内核常用这种方式
	
************************************************11】 字符设备驱动的API
函数原型:int register_chrdev(unsigned int major, const char *name,
								const struct file_operations *fops)
功能:  向内核申请注册设备号

参数:	@major :主设备号。如果你填写的major>0,它认为这个大于0的值就是主设备号
		如果major=0,系统就会自动分配主设备号。
	   
   	    @name :字符设备驱动的名字
				cat /proc/devices
		@fops : 操作方法结构体
返回值:major>0,成功返回0,失败返回错误码
		major=0,成功返回主设备号,失败返回错误码

函数原型:void unregister_chrdev(unsigned int major, const char *name)
功能:注销字符设备驱动
参数:
		@major :主设备号。
		@name :字符设备驱动的名字		

追寻源码
	make tags 或者 ctags -R *//创建tags
	:set tags=/home/hsw/HQ/kernel-3.4.39/tags

【12】创建设备文件
		手动创建:(/dev 没有mycdev设备文件)
		先加载驱动模块,然后查看
		
		[root@iTOP-4418]# cat /proc/devices 
		243 my_cdev
		
		sudo mknod /dev/mycdev{设备文件的名字} c/b 主设备号 次设备号
		sudo mknod /dev/mycdev c 243 0
	编写应用层程序:

		if((fd = open("/dev/mycdev",O_RDWR))<0)
		{
			perror("open");
			return -1;
		}
		read(fd,buf,sizeof(buf));
		write(fd,buf,sizeof(buf));
		close(fd);
	查看现象
		sudo ./test
		sudo /chmod 777 /dev/mycdev
		./test 
		[ 2556.987000] /home/cdev/my_cdev.c:mydev_open:19
		[ 2556.988000] /home/cdev/my_cdev.c:mydev_read:24
		[ 2556.994000] /home/cdev/my_cdev.c:mydev_write:29
		[ 2557.000000] /home/cdev/my_cdev.c:mydev_close:3513】用户空间和内核空间数据传递
	//“__”是告诉编译器对地址进行检查
	
	unsigned long __must_check copy_from_user(void *to,
			const void __user *from,unsigned long n)	
	功能:将用户空间数据拷贝到内核空间
	参数:
		@to  :内核空间内存的首地址
		@form:用户空间内存的首地址
		@n   :拷贝的字节的个数
	返回值:  成功返回0,失败返回未拷贝的字节个数
			
	unsigned long __must_check copy_to_user(void __user *to,
			const void *from, unsigned long n);
	功能:将内核空间数据拷贝到用户空间
	参数:
		@to  :用户空间内存的首地址
		@form:内核空间内存的首地址
		@n   :拷贝的字节的个数
	返回值:  成功返回0,失败返回未拷贝的字节个数
		
【14】内存映射的函数		  
	void *ioremap(phys_addr_t offset, unsigned long size)
	功能:将物联地址映射成虚拟地址
	参数:
		 @off:物理地址
		 @size:映射的字节的
	返回值:成功返回虚拟地址,失败返回NULL

	
	void iounmap(void  *addr)
	功能:取消映射
	参数:
		 @addr:虚拟地址
	返回值:成功返回虚拟地址,失败返回NULL
	
	#define LED_GPIOO			(PAD_GPIO_C + 1)
	Base Address: C001_C000h (GPIOC)15】ioctl函数
 user: #include <sys/ioctl.h> int ioctl(int fd, unsigned long request, ...); 
 功能:调用驱动的ioctl 
参数: 
@fd :文件描述符 
@request:通过_IO _IOR _IOW _IOWR来封装的命令码
@... :可变参数,可以传递也可以不传递

kernel:
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	
	参数:@cmd : 命令码
		  @args :传递的数据或数组,结构体,字符串的首地址
		  
	
	32位命令码的组成如下:
	
	 bits    meaning
	 31-30	00 - no parameters: uses _IO macro
		10 - read: _IOR
		01 - write: _IOW
		11 - read/write: _IOWR

	 29-16	size of arguments

	 15-8	ascii character supposedly
		unique to each driver

	 7-0	function #

	31-30  29-16  15-8  7-0
	方向   大小   类型  序号
	21488位
	
	命令码宏的内核封装格式如下:
	#define _IO(type,nr)		_IOC(_IOC_NONE,(type),(nr),0)
	#define _IOR(type,nr,size)	_IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
	#define _IOW(type,nr,size)	_IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
	#define _IOWR(type,nr,size)	_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size))
	
	#define _IOC(dir,type,nr,size) \
	(((dir)  << _IOC_DIRSHIFT) | \
	 ((type) << _IOC_TYPESHIFT) | \
	 ((nr)   << _IOC_NRSHIFT) | \
	 ((size) << _IOC_SIZESHIFT))
	 
	 如何使用内核的宏封装去封装命令码
	 #define type 'b'
	 #define LED_RED_ON _IO(type,0)
	 #define LED_RED_OFF _IO(type,1)
	 
	如果不想和内核中已有的ioctl的命令码冲突,请查看
	kernel/Documentation/ioctl$ vi ioctl-number.txt 文档
	
【16】自动创建设备节点
user:

hotplug---------->udev/mdev /sys/class/目录/文件 |---->/dev/设备节点

kernel:
		1.提交目录(目录名)
		2.提交信息(设备号,文件名)
		
	#define class_create(owner, name)
	功能:提交目录
	参数:
		@owner: THIS MODULE
		@name:  目录名
	返回值:成功返回struct class *结构体指针
			失败返回错误码指针
注:如何判断失败,内核预留4k错误码信息

	struct class *cls = class_create(THIS MODULE, "hello");
	判断错误:
		IS_ERR(const void *ptr)   如果是错误返回真,否则返回假
		ERR_PTR(long error)       将错误码转化为错误指针码
		PTR_ERR(const void *ptr)  将错误码指针转化为错误码
	反向操作函数:void class_destroy(struct class *cls
	
	
	struct device *device_create(struct class *cls, struct device *parent,
			     dev_t devt, void *drvdata,
			     const char *fmt, ...);
	反向操作函数:void device_destroy(struct class *class, dev_t devt)
	功能:提交信息(设备名,文件名)
	参数:
		@cls	:class结构体指针
		@parent : NULL
		@devt   : 设备号
				MKDEV(major,minor) ==>根据主设备号和次设备号合成设备号
				MAJOP(dev)         ==>根据设备号的到主设备号
				MINOR(dev)         ==>根据设备号的到此设备号
		@drvdata: NULL
		@fmt    : 文件名字
	返回值:成功返回device的结构体指针
			失败返回错误码指针
			
【17】字符设备驱动的框架
	open("/dev/myadc",O_RDWR) read write close
	文件系统 ----ls -i ------->inode号
								描述文件的结构体
								struct inode {
									umode_t			i_mode;         //文件的权限
									unsigned short		i_opflags;  //
									uid_t			i_uid;			//用户id
									gid_t			i_gid;			//组的id
									unsigned int		i_flags;	//文件系统标志
	
	1.字符设备的结构体
	
	struct cdev {
		struct kobject kobj;
		struct module *owner;
		const struct file_operations *ops;//操作方法结构体
		struct list_head list; //在内核中构成链表
		dev_t dev;             //设备号
		unsigned int count;    //设备的个数
	};
	
	2.字符设备驱动创建的流程
		1>分配对象
			struct cdev cdev;
			struct cdev *cdev = cdev_alloc();
		2>结构体成员的初始化
			void cdev_init(struct cdev *cdev, 
					const struct file_operations *fops)
			功能:只初始化了fops成员
		3>申请设备号
			主设备号:哪一类设备
			次设备号:同类中的那个设备
			
			静态指定:
			int register_chrdev_region(dev_t from, 
				unsigned count, const char *name)
			功能:静态指定
			参数:
				@from :设备号的起始的值
				@count: 个数
				@name : 名字 /cat proc/devices
			返回值:成功为0,失败返回错误码
		
			自动分配:
			int alloc_chrdev_region(dev_t *dev, 
					unsigned baseminor, unsigned count,
					const char *name)
			功能:自动分配主设备号
			参数:
				@dev :申请到设备号
				@baseminor: 个数
				@count:次设备号的起始的值
				@name : 名字 /cat proc/devices
			返回值:成功为0,失败返回错误码
			
		4>字符设备驱动的注册	
		
		int cdev_add(struct cdev *p, dev_t dev, unsigned count)
		功能:字符设备驱动的注册
		参数:
			@p :cdev指针
			@dev: 设备号
			@count:个数
		返回值:成功为0,失败返回错误码
	-----------------------------------------------------------------
	注销函数:
	void cdev_del(struct cdev *p)
	功能:字符设备驱动的注销
	
	void unregister_chrdev_region(dev_t from, unsigned count)
	功能:释放设备号
	
	void kfree(void *p)
	功能:释放kzalloc分配的内存
	
	---------------公司一般都这样做,带有下划线的一般是给内核使用,
	---------------这种方法可以设置次设备号大于256,而内核只申请这
	---------------么多,若设备号要大于256往往用这种方式
	
	面试注意:#define COUNT 3const int count = 3;
			  宏定义编译器只做替换不做检查,而const修
			  饰一个只读的变量,会对格式进行检查,const
			  可不是修饰一个常量
			  常量和变量有本质的区别
			  
			  
【18】fd如何找到驱动的操作方法结构体中的函数
	read(fd,buf,sizeof(buf));
	fd:文件描述符,它是大于0的整数
	open,read,write...函数是在进程中执行,fd是咋执行这个进程的时候产生的,
	在进程结构体中绝对会有fd的描述
	
	进程的结构体
	struct task_struct {
		volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
		void *stack;
		int prio, static_prio, normal_prio; //进程优先级
		pid_t pid;  //进程号
		struct task_struct *parent; //父进程
		struct list_head children;  //子进程
		struct list_head sibling;   //兄弟进程
		struct files_struct *files; /* open file information */
	};
	
	struct files_struct {
		struct file * fd_array[NR_OPEN_DEFAULT];
	};
	fd文件描述符就是fd_arrary数组的下标
	fd--fd_array[fd]-->struct file *
	
	struct file:只要在进程中打开一次,就会产生一个file结构体,这个结构体用来
				记录你打开的文件时候的各种信息,比如打开文件的方式,读,写等

	struct file {
		struct path		f_path; //路径
		const struct file_operations	*f_op; //操作方法结构体
											   //它是从inode结构体中获取到的
		unsigned int 		f_flags; //记录打开文件的方式
		void			*private_data; //私有数据,驱动的函数间参数
								   //传递,只能进程值传递
};19】设备号如何存储的,如何分配的?
	在linux内核中通过哈希表来管理设备号
	数字下标 = 主设备号%255(取余)
	0   ==> -------0----------255......>
	1
	2
	.
	.
	.
	254
	所以通过以上的的表我们能解释为什么500 600 在中间的位置,反而255在开头位置
	
	设备号动态分配的方法?
	动态申请的设备号只能是254-1之间的值,其他的申请不到(只能自己指定)。
	从后向前申请。
	
【20】并发和竞态的解决方法
	有多个程序访问同一个驱动程序中的临界资源的时候,竞态就产生了
	竞态产生的根本原因:
		1.对于单核CPU,内核支持抢占
		2.对于多核CPU,核与核之间会产生竞态
		3.中断和进程也会产生竞态
		4.中断和中断间产生竞态(arm架构不支持嵌套)
		
	解决方法:
		1.中断屏蔽(了解)
			只适合在单核CPU屏蔽,中断屏蔽保护的临界资源的时间尽可能的短,
			如果屏蔽的时间较长,可能导致内核的崩溃,或者用户数据的丢失。
			local_irq_disable();
			//临界资源
			local_irq_enable();
			
		2.自旋锁(重点)
			针对多核CPU设计的,在一个进程中获得到这把锁之后,另一个进程也想获取
			这把锁,此时第二进程就处于自旋状态。
			1>并不是休眠,消耗CPU资源,不断询问
			2>自旋锁保护的临界资源尽可能的短,在上锁区间不能做延时,耗时,
			  copy_from_user/copy_to_user,不能够让进程状态切换。
			3>自旋锁会导致死锁(在同一个进程内想多次获取同一把未解锁的锁)
			4>自旋锁在上锁的时候会关闭抢占
			5>自旋锁工作在中断上下文
			API:
				定义自旋锁    spinlock_t lock
				初始化自旋锁  spin_lock_init(&lock)
				上锁          spin_lock(&lock)
				解锁          spin_unlock(&lock)
				
			
			全局:spinlock_t lock;	
			flags = 0;
			static int __init my_cdev_init(void)
			{
				spin_lock_init(&lock);
			}
			int mycdev_open (struct inode * node, struct file * filp)
			{
				 spin_lock(&lock);
				 if(flags == 1)
				 {
					spin_unlock(&lock)
					return -EBUSY;
				 }
				 flags = 0;
				
				return 0; 
			}
			
			int mycdev_close (struct inode * node, struct file * filp)
			{
				spin_lock(&lock);
				flags = 0;
				spin_unlock(&lock);
				
				return 0; 
			}
		
		3.信号量(重点)
			信号量:当一个进程获取到锁之后,另一个进程也想获取这个锁,此时第二个
			进程处于休眠状态。
			1> 休眠状态的进程不消耗CPU
			2> 信号量保护的临界区,可以有延时,耗时,休眠,甚至进程状态切换代码
			3> 信号量工作在进程上下文,(不能在中断上下文中使用)
			4> 信号量的开销比较大(指进程的状态切换)
			
			API:
			定义信号量:
				struct semaphore sem;
			初始化信号量:
				void sema_init(struct semaphore *sem, int val);
				@val:设置的是信号量可以被获取的次数,但一般在驱动中都是设置为1,实现互斥效果
				val;0表示同步,在调用down的时候就休眠了,必须通过up之后才能获取信号量
			上锁:
				void down(struct semaphore *sem);
				int down_trylock(struct semaphore *sem); //成功返回0,失败返回1
			解锁:
				void up(struct semaphore *sem);
				
				
			struct semaphore sem;
			static int __init my_cdev_init(void)
			{
				sema_init(&sem);
			}
			int mycdev_open (struct inode * node, struct file * filp)
			{
				//down(&sem);
				if(down_trylock(&sem))
				{
					printk("get sema error\n");
					return -EBUSY;
				}
				return 0; 
			}
			
			int mycdev_close (struct inode * node, struct file * filp)
			{
				spin_lock(&lock);
				flags = 0;
				spin_unlock(&lock);
				
				return 0; 
			}
		4.互斥体(会用)
		
		定义互斥体:
			struct mutex lock;
		初始化:
			mutex_init(&lock);
		上锁:
			void mutex_lock(&lock);
			void mutex_trylock(&lock);//成功返回1,失败返回0
		解锁:
			void mutex_unlock(&lock);
			
		5.原子操作(会用)(将整个操作看为一个整体)
		typedef struct {
			int counter;
		} atomic_t;
		使用内联汇编
		定义及初始化
			atomic_t lock = ATOMIC_INIT(1);
		//上锁
			atomic_dec_and_test(v) 
			//减去1之后和0比较,如果结果为0,表示获取锁成功,否则失败
		//解锁
			atomic_inc(v)
		
		定义及初始化
			atomic_t lock = ATOMIC_INIT(-1);	
		//上锁
			atomic_inc_and_test(v);
			//加1之后和0比较,如果结果为0,表示获取锁成功,否则失败
		//解锁
			atomic_dec(v);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值