/*Linux内存管理*/
物理地址是虚拟地址的子集(64位CPU寻址64TB)
MMU将CPU发出的虚拟地址变为物理地址,交给内存总线(段式地址转换,页式地址转换)
内存申请和释放,kmalloc和kfree
static void *malloc(int size)//要申请size字节大小的内存空间
{
void *p;
if (size < 0)
error("Malloc error");
if (!malloc_ptr)
malloc_ptr = free_mem_ptr;
malloc_ptr = (malloc_ptr + 3) & ~3; /* Align */
p = (void *)malloc_ptr;
malloc_ptr += size;
if (free_mem_end_ptr && malloc_ptr >= free_mem_end_ptr)
error("Out of memory");
malloc_count++;
return p;
}
static void free(void *where)//释放内存空间
{
malloc_count--;
if (!malloc_count)
malloc_ptr = free_mem_ptr;
}
/*中断*/
Linux系统规定了中断服务处理函数的参数和返回类型
三部曲:编服务,报名,允许中断
irqreturn_t myfun_isr(int irq,void* dev_id)
int request_irq(unsigned int irq , irq_handler_t handler,
unsigned long irqflags, const char *devname , void *dev_id)
enable_irq(irq)
/*定时*/
硬件定时器:操作arm的寄存器,类似TCFGx,TCON,TCNT,TCMP
软件定时器:
static struct timer_list my_timer;//定义一个软件定时器
struct timer_list {
struct list_head entry; //定时器链表的入口
unsigned long expires; //定时器超时时的节拍数
void (*function)(unsigned long); //【重要】定时器处理函数
unsigned long data; //传给定时器处理函数的长整型参数
struct tvec_t_base_s *base; //定时器内部值,用户不要使用
#ifdef CONFIG_TIMER_STATS
void *start_site;
char start_comm[16];
int start_pid;
#endif
};
void my_timer_function(...);//定义自己的软件定时器函数
init_timer(&my_timer);
my_timer.function=my_timer_function;
add_timer(my_timer);
/*并发*/(站在线程的角度)
并发-多个线程同时访问同一个设备驱动程序的共享资源
并发控制方式:信号量 和 自旋锁
信号量:(互斥信号量,计数信号量)
struct semaphore sem;//定义一个信号量
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
void sema_init(struct semaphore * sem,int val);//初始化一个信号量,val=1,则变成互斥锁Mutex
void down(struct semaphore * sem);//获取信号量,可能导致进程睡眠,sem减1
void up (struct semaphore * sem);//释放信号量,唤醒等待者,sem加1
自旋锁:(自旋意味着原地打转,只能一个持有者)
spin_lock_init(x)//该宏用于初始化自旋锁x。自旋锁在真正使用前必须先初始化。该宏用于动态初始化。
spin_lock(lock)//该宏用于获得自旋锁lock,如果能够立即获得锁,它就马上返回,
//否则,它将自旋在那里,直到该自旋锁的保持者释放,这时,它获得锁并返回。
//总之,只有它获得锁才返回。
spin_trylock(lock)//该宏尽力获得自旋锁lock,如果能立即获得锁,它获得锁并返回真,
//否则不能立即获得锁,立即返回假。它不会自旋等待lock被释放。
spin_unlock(lock)//该宏释放自旋锁lock,它与spin_trylock或spin_lock配对使用。
【区别】:信号量试用于保持时间较长的情况,自旋锁试用于较短的情况
信号量可能会引起调用者睡眠,所以不能在中断服务函数中使用信号量
/*阻塞与非阻塞*/(站在应用程序的角度)
应用程序阻塞访问设备,设备没准备好,则应用程序进入挂起后的睡眠状态
应用程序非阻塞访问设备,设备没准备好,则应用程序直接返回
每个设备驱动都有一个进程等待队列(wait_queue),阻塞访问时,若是设备还没有准备好,
就通过wait_event(为访问设备的进程而创建等待队列的节点),将该进程加入到wait_queue中,
若是一会儿设备准备好了,就调用wake_up唤醒阻塞的进程(唤醒哪个全靠mode?)。
//__wake_up(wait_queue_head_t *q, unsigned int mode,
// int nr_exclusive , void *key)
【Linux设备驱动程序的阻塞编程】
1.定义一个与驱动程序配对的进程等待队列,并初始化(实质上只定义一个等待队列头)
static wait_queue_head_t wait_queue;
init_waitqueue_head(&wait_queue);
2.在设备读操作中,调用 wait_event 实现阻塞访问
3.在设备写操作中,调用 wake_up 唤醒该设备等待队列上的进程
/*异步通知*/(桥梁)
异步的出现是为了解决设备驱动程序某个事件发生时,能够及时通知应用程序,在内核空间(设备驱动)
和用户空间(应用程序)架起了通信的桥梁。
struct fasync_struct { //异步通知进程队列
int magic;
int fa_fd; //设备文件描述符
struct fasync_struct *fa_next; //链表
struct file *fa_file; //进程打开的文件
};
//用fasync_helper创建一个fasync_struct的结构体变量,将其初始化
//实际上是对使用该设备的当前进程创建一个节点,并把这个节点挂接到该设备的异步通知队列上
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
{
if (!on)
return fasync_remove_entry(filp, fapp);
return fasync_add_entry(fd, filp, fapp);
}
//设备状态信息改变时,设备驱动通过 kill_fasync 告知异步通知队列中的进程
//本质是遍历 fasync_struct 队列,调用send_sigio给每个进程发送异步I/O通知消息和SIGIO信号
void kill_fasync(struct fasync_struct **fp, int sig, int band)
{
/* First a quick test without locking: usually
* the list is empty.
*/
if (*fp) {
rcu_read_lock();
kill_fasync_rcu(rcu_dereference(*fp), sig, band);
rcu_read_unlock();
}
}
/*轮询*/(站在设备的角度)
轮询的目的:应用程序通过轮询设备,根据设备返回的信息判断是否可以对该设备进行[无阻塞]的访问
轮询的实现:通过等待队列机制来实现,在设备驱动程序中定义了一个进程轮询等待队列
wait_queue_head_t poll_wq_write;//仅一个头部,该队列的每个元素代表轮询设备的进程
轮询的实质:应用程序调用poll函数轮询一个设备文件描述符,实质上会调用设备驱动的poll函数
然后 1.将该进程添加到设备的进程轮询等待队列中,通过 poll_wait 实现
2.根据当前设备的状态,返回相应的状态信息
物理地址是虚拟地址的子集(64位CPU寻址64TB)
MMU将CPU发出的虚拟地址变为物理地址,交给内存总线(段式地址转换,页式地址转换)
内存申请和释放,kmalloc和kfree
static void *malloc(int size)//要申请size字节大小的内存空间
{
void *p;
if (size < 0)
error("Malloc error");
if (!malloc_ptr)
malloc_ptr = free_mem_ptr;
malloc_ptr = (malloc_ptr + 3) & ~3; /* Align */
p = (void *)malloc_ptr;
malloc_ptr += size;
if (free_mem_end_ptr && malloc_ptr >= free_mem_end_ptr)
error("Out of memory");
malloc_count++;
return p;
}
static void free(void *where)//释放内存空间
{
malloc_count--;
if (!malloc_count)
malloc_ptr = free_mem_ptr;
}
/*中断*/
Linux系统规定了中断服务处理函数的参数和返回类型
三部曲:编服务,报名,允许中断
irqreturn_t myfun_isr(int irq,void* dev_id)
int request_irq(unsigned int irq , irq_handler_t handler,
unsigned long irqflags, const char *devname , void *dev_id)
enable_irq(irq)
/*定时*/
硬件定时器:操作arm的寄存器,类似TCFGx,TCON,TCNT,TCMP
软件定时器:
static struct timer_list my_timer;//定义一个软件定时器
struct timer_list {
struct list_head entry; //定时器链表的入口
unsigned long expires; //定时器超时时的节拍数
void (*function)(unsigned long); //【重要】定时器处理函数
unsigned long data; //传给定时器处理函数的长整型参数
struct tvec_t_base_s *base; //定时器内部值,用户不要使用
#ifdef CONFIG_TIMER_STATS
void *start_site;
char start_comm[16];
int start_pid;
#endif
};
void my_timer_function(...);//定义自己的软件定时器函数
init_timer(&my_timer);
my_timer.function=my_timer_function;
add_timer(my_timer);
/*并发*/(站在线程的角度)
并发-多个线程同时访问同一个设备驱动程序的共享资源
并发控制方式:信号量 和 自旋锁
信号量:(互斥信号量,计数信号量)
struct semaphore sem;//定义一个信号量
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
void sema_init(struct semaphore * sem,int val);//初始化一个信号量,val=1,则变成互斥锁Mutex
void down(struct semaphore * sem);//获取信号量,可能导致进程睡眠,sem减1
void up (struct semaphore * sem);//释放信号量,唤醒等待者,sem加1
自旋锁:(自旋意味着原地打转,只能一个持有者)
spin_lock_init(x)//该宏用于初始化自旋锁x。自旋锁在真正使用前必须先初始化。该宏用于动态初始化。
spin_lock(lock)//该宏用于获得自旋锁lock,如果能够立即获得锁,它就马上返回,
//否则,它将自旋在那里,直到该自旋锁的保持者释放,这时,它获得锁并返回。
//总之,只有它获得锁才返回。
spin_trylock(lock)//该宏尽力获得自旋锁lock,如果能立即获得锁,它获得锁并返回真,
//否则不能立即获得锁,立即返回假。它不会自旋等待lock被释放。
spin_unlock(lock)//该宏释放自旋锁lock,它与spin_trylock或spin_lock配对使用。
【区别】:信号量试用于保持时间较长的情况,自旋锁试用于较短的情况
信号量可能会引起调用者睡眠,所以不能在中断服务函数中使用信号量
/*阻塞与非阻塞*/(站在应用程序的角度)
应用程序阻塞访问设备,设备没准备好,则应用程序进入挂起后的睡眠状态
应用程序非阻塞访问设备,设备没准备好,则应用程序直接返回
每个设备驱动都有一个进程等待队列(wait_queue),阻塞访问时,若是设备还没有准备好,
就通过wait_event(为访问设备的进程而创建等待队列的节点),将该进程加入到wait_queue中,
若是一会儿设备准备好了,就调用wake_up唤醒阻塞的进程(唤醒哪个全靠mode?)。
//__wake_up(wait_queue_head_t *q, unsigned int mode,
// int nr_exclusive , void *key)
【Linux设备驱动程序的阻塞编程】
1.定义一个与驱动程序配对的进程等待队列,并初始化(实质上只定义一个等待队列头)
static wait_queue_head_t wait_queue;
init_waitqueue_head(&wait_queue);
2.在设备读操作中,调用 wait_event 实现阻塞访问
3.在设备写操作中,调用 wake_up 唤醒该设备等待队列上的进程
/*异步通知*/(桥梁)
异步的出现是为了解决设备驱动程序某个事件发生时,能够及时通知应用程序,在内核空间(设备驱动)
和用户空间(应用程序)架起了通信的桥梁。
struct fasync_struct { //异步通知进程队列
int magic;
int fa_fd; //设备文件描述符
struct fasync_struct *fa_next; //链表
struct file *fa_file; //进程打开的文件
};
//用fasync_helper创建一个fasync_struct的结构体变量,将其初始化
//实际上是对使用该设备的当前进程创建一个节点,并把这个节点挂接到该设备的异步通知队列上
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
{
if (!on)
return fasync_remove_entry(filp, fapp);
return fasync_add_entry(fd, filp, fapp);
}
//设备状态信息改变时,设备驱动通过 kill_fasync 告知异步通知队列中的进程
//本质是遍历 fasync_struct 队列,调用send_sigio给每个进程发送异步I/O通知消息和SIGIO信号
void kill_fasync(struct fasync_struct **fp, int sig, int band)
{
/* First a quick test without locking: usually
* the list is empty.
*/
if (*fp) {
rcu_read_lock();
kill_fasync_rcu(rcu_dereference(*fp), sig, band);
rcu_read_unlock();
}
}
/*轮询*/(站在设备的角度)
轮询的目的:应用程序通过轮询设备,根据设备返回的信息判断是否可以对该设备进行[无阻塞]的访问
轮询的实现:通过等待队列机制来实现,在设备驱动程序中定义了一个进程轮询等待队列
wait_queue_head_t poll_wq_write;//仅一个头部,该队列的每个元素代表轮询设备的进程
轮询的实质:应用程序调用poll函数轮询一个设备文件描述符,实质上会调用设备驱动的poll函数
然后 1.将该进程添加到设备的进程轮询等待队列中,通过 poll_wait 实现
2.根据当前设备的状态,返回相应的状态信息