Linux的一些学习笔记

/*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.根据当前设备的状态,返回相应的状态信息

猜你喜欢

转载自blog.csdn.net/dummkopfer/article/details/80472494