Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1261815
  • 博文数量: 548
  • 博客积分: 7597
  • 博客等级: 少将
  • 技术积分: 4224
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-15 13:21
个人简介

嵌入式软件工程师&&太极拳

文章分类

全部博文(548)

文章存档

2014年(10)

2013年(76)

2012年(175)

2011年(287)

分类: LINUX

2011-02-20 20:23:33

我们一般open一个文件,其实就是在打开硬盘这个设备,因为文件存在硬盘当中,要有硬盘驱动的支持,所有软件都要有硬件的支持,怎么支持就要用驱动

ls -l看文件时,在文件组后面,为主设备次设备号,主设备号对做驱动最重要了,主设备号和内核驱动设备号相对应的

内核也用了面向对象思想,看到结构体就要想到这是一个类或对象

include/linux/fs.h
struct file   描述上层文件的对象    
struct inode  更详细的上层文件的对象  
struct file_operations    对于驱动开发这个最重要的结构体,字符设备  ,描述上层文件的操作

函数主要看:功能,参数和返回值
这两个API是老的,不推荐用了
注册驱动到内核里去:
int  register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
			major  主设备号,  name 驱动名字   fops  一个函数集,为驱动的操作函数指针
对应注册的卸载函数:
int  unregister_chrdev(unsigned int major, const char *name);


新的注册驱动到内核里的函数:
初始化函数:
void cdev_init(struct cdev *cdev, struct file_operations *fops);
	struct cdev   描述一个字符设备  在include/linux/cdev.h
注册函数:
int cdev_add(struct cdev *, dev_t, unsigned);
	dev_t   设备号,不叫主设备号,怎么得到 用MKDEV宏,MKDEV(主设备号,次设备号);返回一个设备号
	主设备号是描述某一类设备
	最后一个参数unsigned 表示一个注册几个设备,一般为1

卸载函数:
void cdev_del(struct cdev *);

主设备号: Documentation/devices.txt   看哪些主设备号用了   
	主设备号具有唯一号,内核有保留给我们用的主设备号240到254

只要注册了就可以用了,但是为了方便内核管理等所以得申请
申请内核我要注册一个什么设备号:
void register_chrdev_region(dev_t from, unsigned count,  const char name);
		dev_t from   设备号, unsigned count 要注册几个这类的设备,  const char name  设备名,给文件系统看的,没什么事
释放:和申请配对
void unregister_chrdev_region(dev_t from, unsigned count);

申请和注册一般都是一起跟着走

自动申请一个内核没有在用的设备号:
int  alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
	dev_t *dev 传进去给变量赋值,unsigned baseminor次设备号,一般为0,  unsigned count 要注册几个这类的设备, const char name 设备名  
对应的释放函数还是 void unregister_chrdev_region(dev_t from, unsigned count);

mknod /dev/mydev c 254 0     创建一个设备文件,c为字符设备,如果写p为管道文件,254主设备号 0为次设备号

cat /proc/devices   查看注册了多少设备驱动

内核函数,驱动编程来调用内核函数,
asm-- 内核函数--驱动编程--posix-- 系统调用 ---C库--APP
应用程序->C库函数->系统调用(POSIX)->驱动编程的函数->内核函数->硬件相关代码(汇编)
驱动编程主要关心内核函数


include/asm_generic/errno_base.h   内核错误码
容错处理包含: include/linux/errno.h
在注册时判断容错,一般在Linux内核源码中错误处理用的最多的是goto结构

file_openations:
	int open(struct inode *, struct file *);
		try_module_get(THIS_MODULE);   尝试看看模块能不能操作,一般不用
	int release(struct inode *, struct file *);  相当于close    open和close一般没有什么实现代码,重要是返回值,为0则返回正常
		module_put(THIS_MODULE);  告诉 内核这模块现在不用了,和上面try_module_get相对应
	ssize_t read(struct file *, char __user *, size_t, loff_t *);   
	ssize_t write(struct file *, char __user *, size_t, loff_t *);


带检查的拷贝函数:	 /include/asm/uaccess.h   返回值为还有多少没有拷贝,一般为0
	unsigned long copy_to_user(void __user *to, const void *form, usnigned long n);   对应read
	unsigned long copy_from_user(void *to, const void __user *form, unsigned long n);  对应write
 

	int ioctl(struct inode *node, struct file *filp, unsigned int cmd, unsigned long args);  来一个命令然后执行一段代码  
				unsigned int cmd  命令,unsigned long args  看作指针,做为参数

struct file_operations 结构注册函数时
	owner = THIS_MODULE;   这句为固定


同步通知:
实现阻塞I/O:
生产者消费者模型: read 为消费者, write 生产者
睡眠机制:
	init_waitqueue_head(wait_queue_head_t *q);
	wait_event_interruptible(wait_queue_head_t rq, condition);
			rq  队列    condition  条件
	wake_up(wait_queue_head_t *rq);   唤醒整个等待队列

阻塞和轮询的区别:
	都是为了有事件发生再执行,不同为轮询为忙等待,即一直占用CPU,而阻塞为在等待着会睡眠,这样就不会占用CPU
	最常用的轮询为select()   ,做驱动网卡方面才用的最多,字符设备一般不会用轮询

实现select :    include 
	 unsigned int poll(struct file * filp, struct poll_table_struct *table);   
	poll_wait(struct file *filp, wait_queue_head_t *, struct poll_table table);  注册队列
	返回mask即下面的宏:
	POLLIN       read   不空就读
	POLLOUT   write	空的话提示写

看看file_operations里有没有相对应的函数指针,如果有把函数指针拷贝过来,再在file_operations结构体里加入函数,再把头文件加进来

异步通知:
signal   工作中不要用这个函数,是个老函数,可重入性很差,用sigaction()函数 
	int sigaction(int signum, const struct sigaction * act, struct sigaction *oldact);
		signum  信号号, act  要一个结构体   oldact 要上一操作,可重入性
		memset(&act, 0, sizeof(struct sigaction));
		act.sa_handler = 信号调用的函数;
		act.flag = 0 ;设置标志

	fcntl(fd, F_SETOWN, getpid());   设置属注 对应内核file_operations的owner成员赋值
	fcntl(fd, F_SETL, (fcntl(fd, F_GETFL) | FASYNC)); 设置标志  对应内核file_operations的fasync函数指针

应用层:注册信号,设置属注,设置标志
驱动层:一个数据结构两个函数

fs/fcntl.c
	int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp);    相对应于注册函数,注册到内核,并且把file->owner注册进去
	int fasync(int, struct file *, int);       file_operations 里的函数指针
	void kill_fasync(struct fasync_struct **fapp, int signum, POLLIN);  发送信号
	调用自己的fasync函数
	test_fasync(-1, filp, 0);   在release 函数里移出自己的注册的信号


MMC  是SD卡




test: version magic '2.6.13-Qt2440 ARMv4 gcc-3.4' should be '2.6.13-Qt2440 preempt ARMv4 gcc-3.4'
insmod: cannot insert `test.ko': Invalid module format (-1): Exec format error
 用抢占式内核程序插入到不支持抢占式内核,就会出错误,解决办法用不支持抢占式内核编译程序就可以了

并发和竞态(互斥):
	竞态就是在两个进程同时修改一个数据,所得数据不是用户想要的
	会发生竞态:
		单核:
			进程与进程之间   用的最多 
				preempt_disable();
				preempt_enable();
			进程和中断之间
				local_irq_disable();
				local_irq_enable();
			中断和中断之间   可重入性好,会保护现场
				local_irq_save(flag);
				local_irq_restore(flag);  flag为一 个int值

		多核:
			进程与进程之间
				抢占时间       CPU轮询空转,没有睡眠:自旋锁

		  头文件:	include/splock.h
				spin_lock(lock); 干的事就是调用 下面函数     参数lock为spinlock_t 的结构体指针
					preempt_disable;   先防止CPU1抢占时间片
					_raw_write_lock(lock); 先CPU2自旋轮询

				spin_unlock();	解锁
					_raw_spin_lock(lock);	
					preempt_enable;

			进程和中断之间  
				spin_lock_irq(lock); 	
					local_irq_disable;
					spin_lock();			
				spin_unlock_irq(lock);
					local_irq_enable;
					spin_unlock();

			中断和中断之间  
				spin_lock_irqsave(lock)
					local_irq_save();   关中断,用这个可重入性好
					spin_lock();	防止时间片和让CPU自旋
				spin_lock_irqrestore()
					local_irq_restore();
					spin_unlock();

spin_lock:   占CPU资源,比较危险,时实性要求比较高的才用
	spinlock_t  结构体为spin_lock的一个结构体
	spin_lock_init(spinlock_t *);   初始化spin_lock

	注意:  单核 就是 preempt
		     在多核  spin_lock 和 spin_unlock  之间千万不要睡眠, 因为CPU自旋用了睡眠就没有其它唤醒它,会引起睡眠的函数: copy_to_user和copy_from_user  sleep等, 慢I/O就是用户态和内核态之间的I/O, 还有kmalloc  


semaphore:
	头文件: include/asm/semaphore.h
		struct  semaphore sem;     结构体
		init_MUTEX(struct semaphore *sem);  初始化
	用法 :
		down(struct semaphore *sem);   上锁   不可被信号中断,比较少用	
		down_interruptible(struct semaphore *sem);  可被信号中断,为了被打断后能回来,所以一般都用下面这句判断
			if (down_ifterruptible(struct semaphore *sem)) return -ERESTARTSYS;

		up(struct semaphore *sem);   解锁


semaphore和spin_lock  
	在spin_lock和spin_unlock里不能用semaphore, 因为semaphore会引起睡眠
	spin_lock效率高,占CPU资源

要互斥就要用semaphore


内核知识,理论: (书)
	Linux内核设计与实现第二版   陈莉君  (总括,内核里的框架)
	深入理解Linux内核第三版      陈莉君    (深入细看,要有操作系统的概念)
 	Linux内核源码情景分析		毛德操    (当字典,很细,缺点讲2.4内核)
	操作系统	

驱动:
	Linux设备驱动 第三版    <ldd3>  魏永明  (实践性强, x86, 标准)
	edd3     纯英文的
	Linux设备驱动详解		宋宝华	(实践,arm)

硬件:
	嵌入式硬件设计		O‘Relly

模块
字符设备
并发与竞态
分配内存
操作硬件
中断
时间延时,底半部,推迟执行
Linux设备模型
驱动实例的讲解




分配内存:
	一个进程跑在操作系统有4G地址空间,不是内存空间,用户态在底地址3G空间,内核态在高地址占1G空间
在内核态的1G中,分两种地址:
一种叫逻辑地址 又叫线性地址,和内存地址一一对应    kmalloc   分配小空间用,一定要判断返回值,经常会申请失败,可能会睡眠要注意
一种叫虚拟地址,在内存中零碎分配再对应到虚拟地址    vmalloc   分配大空间用
mmu 粗小页为4K,linux内核里是以页管理内存,用的是粗小页,所以用的时候要以4K对齐(是和内核打交道时要4K对齐)
	#include 
	struct page

kmalloc的头文件:	#include 
	void * kmalloc(size_t size, unsigned int __nocast flags);
		size  分配的空间的大小  
		 flags 
			  GFP_ATOMIC     不会睡眠,告诉kmalloc如果没有那么大的空间,不要等待,直接返回失败,所以绝对要判断返回值,这样就不会睡眠,可以在中断里用了
			 GFP_KERNEL    最常用的分配空间的标志
		判断返回值为NULL失败
配对释放空间函数:  kfree();

设计程序时全局变量少用,全局结构体也要少用,可以全局指针
		
I/O 访问:
	头文件 : #include 
	拿到物理地址映射的虚拟地址:
		unsigned long ioremap(unsigned long phs, int size);   映射物理地址到虚拟地址
			phs 为要映射的物理地址,  size  为要映射多大,返回值为一个虚拟地址

		iounmap()  和ioremap 相配对,参数要ioremap的返回值

	主要是可以在/proc/iomem里看到自己映射物理地址情况:	
		struct resource * request_mem_region(struct resource *parent, unsigned long start, unsigned ing size, unsigned long name);
			parent        没有什么大太用处,可以不写
			start   硬件物理地址 ,  size  ioremap申请时的大小
			name   可以在/proc/iomem   看到自己映射的情况
		要判断返回值是否为NULL,为NULL失败
		release_resource(struct resource *parent);  相对应的,退出时告诉内核内存地址不用了	

	静态映射: 就是内核一起来就已经映射好了,ioremap是自己的程序运行起来才映射
		arch/arm/mach-s3c2410/mach-bit2440.c
				static struct map_desc bit2440_iodesc[]  __initdata  找到这个结构,加上一组映射关系,虚拟地址,物理地址,长度,固定设备类型 
	

	操作I/O:
		ioread8(p);
		ioread16(p);
		ioread32(p);

		iowrite8(v, p);   v 为值, P 为地址 
		iowrite16(v, p);
		iowrite32(v, p);  
	

cat /proc/cpuinfo	查看CPU信息
cat /proc/devices	查看哪些设备注册了
cat /proc/iomem    查看内存映射情况
cat /proc/interrupts     查看注册了哪些中断                                                                                                                                                                                                                                               

arch/arm/common/rtctime.c    分层结构,回调函数


cdev_add这个函数要放在init函数的最后


中断:
	中断处理函数,要注册进内核, 中断号跟板相关,头文件在 include/asm-arm/arch-s3c2410/irqs.h
		头文件 : include asm/irq.h   linux/interrupt.h
			int  request_irq(IRQ_EINT0, do_irq, unsigned long  irq_flags, const char * devname, void *dev_id);
				IRQ_ENIT0  为中断源,  do_irq为函数指针, devname 为中断名,可以在/proc/下看到   dev_id为给函数指针传参
			函数指针原型:  irqreturn_t do_irq(int irq, void *dev_id, struct pt_regs *regs);
					int irq 为中断号   struct pt_regs *reg 为调试用的, dev_id为注册中断时接参数
				返回值为 IRQ_HANDLED或0 因为0不正规

			unsigned long irq_flags     有几个flags
				最常用的SA_INTERRUPT 
				SA_SHIRQ   共享中断flag   必须要传参
	
			注册中断一定要判断返回值

			free_irq(int irq, void *dev_id); 注册中断与之对应释放函数


			set_irq_type(unsigned int irq, unsigned int type);  设置按键类型, 在includeasm/irq.h里有宏

	注册时要判断返回时,看注册成功没,如果没有成功,看看/proc/interrputs 里有没有被占用,然后搜索占用的是不是模块,然后移出来


oops
中断
	start_kernel() 是内核第一个执行的函数:
		上锁	初始化中断等等,接收U-boot的参数
		最后干的事:rest_init();  启动守护进程init,是文件系统的	

第一条线: 找到异常向量表  执行的过程
	start_kernel -> trap_init->vector_start(arch/arm/kernel/entry-arm.S)->__stubs_start(arch/arm/kernel/entry-armv.S)->
	_irq_svc ->irq_handler(本文件里的 .macro irq_handler)->asm_do_IRQ(C语言的)里的desc->handle(irq, desc, regs);(跟irq处理相关的)

	然后找到struct irqdesc *desc; 这个结构体数组全局的,是arm linux的中断体系最重要的结构体	  成员:   以中断号为索引的数组
		irq_handler_t handler;    这个是中断来了的处理函数
		struct irqchip  *chip;        和清中断和开关中断
		struct irqaction  *action;     存我们的函数等 ,指向一个链表 

第二条线:  找到了struct irqdest r handler成员,看看哪修改了它   初始化过程 
	start_kernel-> init_IRQ->init_arch_irq();(全局的函数指针,要全局查找)->setup.c(给init_arm_irq赋值)->找到mdesc这个结构体,再找这个在哪初始化->setup_machine()这个函数返回的结构体->lookup_machine_type这个函数的返回值,这个函数在汇编实现的,在Head.S里-> 找到MACHINFO_TYPE这个段,跟CPU的machine相关,在arm/mach-s3c2410/mach-bit2440.c里MACHINE_START这个宏:看它的定义可以看到 machine_desc这个结构体,然后这个宏把这个结构体初始化,  再看 init_irq初始化成什么 (bit2440_init_irq)这个函数,    即init_arch_irq这个函数执行就是bit2440_init_irq这个函数  
	然后看bit2440_init_irq是什么,调用了s3c24xx_init_irq
		 这个函数清中断
			跟全局的struct irqdesc desc对应并赋值, 找到 do_edge_IRQ->__do_irq() 会调到action的handler
			最后找 struct irqaction *action 在哪被初始化就能找到我们注册的handler

第三条线:注册的过程 从request_irq()
		最后找到request_irq() 初始化了action
		再调用setup_irq函数    把我们的do_irq函数加进一个链表(这一步是将我们的函数加到链表里,这样就可以发生一个中断,可以触发这整个链表的函数)

共享标志中断:
	就是换个标志,可以注册多个函数,一般共享中断传参,有多少个request_irq就要有对应的多少个free_irq

中断资源比较珍贵,所以中断处理函数不能太长,就引出了顶半部和底半部之说,即顶半部开出另一个进程来处理,处理的部分就叫做底半部
	tasklet_init(struct tasklet_struct *, void (* func)(unsigned long val), unsigned long data);  	初始化,注册函数和传参
	中断里常用的 kill   wake up    compltion  不能睡眠和kmalloc;
	tasklet_schedule(struct tasklet_struct *);   用tasklet,就是调用tasklet_init注册的函数,像多线程,不在中断的上下文里执行, 这个就叫底半部, 用的比较少

	INT_WORK(struct work_struct *, void (*func)(void *val), void  *data);  初始化的宏,和tasklet一样
	schedule_work(struct work_struct *work);  调度用,这个最常用了						

重点:
tasklet_init 不可以睡眠,在软中断上下文	
work_struct 可以睡眠,在进程的下下文,最常用


kernel_thread(int (*fn)(void *), void *arg, unsigned long flags);  内核态多线程函数	



内核时间:
	Hz  频率  ,一秒多少次
	jiffies	滴答数, 多少次一秒,内核独有的
		内核一运行jiffies为0,开始累加,1HZ运行多少次,jiffies就加多少
	HZ和jiffies是内核全局宏,可以直接用

	kernel/time.c  
		do_gettimeofday();  可以获取时间
	延时:
		linux/delay.h 里面有延时函数: 到这里来查
			mdelay()	延时微秒,mdelay(1000)为延时一秒
			ndelay()   纳秒级
			msleep()  等 等 
	定时:
		linux/timer.h  一个结构体两个函数
			struct timer_list;  结构体为主角
				struct list_head entry;   内核管理用的,一般我们不用管




阅读(678) | 评论(0) | 转发(0) |
0

上一篇:2010_3_11.kernel

下一篇:2010_3_18.kernel

给主人留下些什么吧!~~