1,Linux设备驱动属于内核的一部分,Linux内核的一个模块可以以两种方式被编译和加载:
(1)直接编译进Linux内核,随同Linux启动时加载;
(2)编译成一个可加载和删除的模块,使用insmod加载(modprobe和insmod命令类似,但依赖于相关的配置文件),rmmod删除。
内存

  在Linux内核模式下,我们不能使用用户态的malloc()和free()函数申请和释放内存。进行内核编程时,最常用的内存申请和释放函数为在include/linux/kernel.h文件中声明的kmalloc()和kfree(),其原型为:

void *kmalloc(unsigned int len, int priority);
void kfree(void *__ptr);

  kmalloc的priority参数通常设置为GFP_KERNEL,如果在中断服务程序里申请内存则要用GFP_ATOMIC参数,因为使用GFP_KERNEL参数可能会引起睡眠,不能用于非进程上下文中(在中断中是不允许睡眠的)。

  由于内核态和用户态使用不同的内存定义,所以二者之间不能直接访问对方的内存。而应该使用Linux中的用户和内核态内存交互函数(这些函数在include/asm/uaccess.h中被声明):

unsigned long copy_from_user(void *to, const void *from, unsigned long n);
unsigned long copy_to_user (void * to, void * from, unsigned long len);

  copy_from_user、copy_to_user函数返回不能被复制的字节数,因此,如果完全复制成功,返回值为0。

  include/asm/uaccess.h中定义的put_user和get_user用于内核空间和用户空间的单值交互(如char、int、long)。

一个简单的gobalvar模块:
设备"gobalvar"的驱动程序的这些函数应分别命名为gobalvar_open、gobalvar_ release、gobalvar_read、gobalvar_write、gobalvar_ioctl,因此设备"gobalvar"的基本入口点 结构变量gobalvar_fops 赋值如下:

struct file_operations gobalvar_fops = {
 read: gobalvar_read,
 write: gobalvar_write,
};

  上述代码中对gobalvar_fops的初始化方法并不是标准C所支持的,属于GNU扩展语法。
完整的globalvar.c文件源代码如下:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
MODULE_LICENSE("GPL");

#define MAJOR_NUM 254 //主设备号

static ssize_t globalvar_read(struct file *, char *, size_t, loff_t*);
static ssize_t globalvar_write(struct file *, const char *, size_t, loff_t*);

//初始化字符设备驱动的file_operations结构体
struct file_operations globalvar_fops =
{
 read: globalvar_read, write: globalvar_write,
};
static int global_var = 0; //"globalvar"设备的全局变量

static int __init globalvar_init(void)
{
 int ret;

 //注册设备驱动
 ret = register_chrdev(MAJOR_NUM, "globalvar", &globalvar_fops);
 if (ret)
 {
  printk("globalvar register failure");
 }
 else
 {
  printk("globalvar register success");
 }
 return ret;
}

static void __exit globalvar_exit(void)
{
 int ret;

 //注销设备驱动
 ret = unregister_chrdev(MAJOR_NUM, "globalvar");
 if (ret)
 {
  printk("globalvar unregister failure");
 }
 else
 {
  printk("globalvar unregister success");
 }
}

static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)
{
 //将global_var从内核空间复制到用户空间
 if (copy_to_user(buf, &global_var, sizeof(int)))
 {
  return - EFAULT;
 }
 return sizeof(int);
}

static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off)
{
 //将用户空间的数据复制到内核空间的global_var
 if (copy_from_user(&global_var, buf, sizeof(int)))
 {
  return - EFAULT;
 }
 return sizeof(int);
}

module_init(globalvar_init);
module_exit(globalvar_exit);
make file 内容:
obj-m += globalvar.o
all:
    make -C  /lib/modules/$(shell uname -r)/build M=$(pwd) modules
clean:
    make -C  /lib/modules/$(shell uname -r)/build M=$(pwd) clean
2, 并发控制
在驱动程序中,当多个线程同时访问相同的资源时(驱动程序中的全局变量是一种典型的共享资源),可能会引发"竞态",因此我们必须对共享资源进行并发控制。Linux内核中解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用)。

  自旋锁与信号量"类似而不类",类似说的是它们功能上的相似性,"不类"指代它们在本质和实现机理上完全不一样,不属于一类。

  自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环查看是否该自旋锁的保持者已经释放了锁,"自旋"就是"在原地打转"。而信号量则引起调用者睡眠,它把进程从运行队列上拖出去,除非获得锁。这就是它们的"不类"。

  但是,无论是信号量,还是自旋锁,在任何时刻,最多只能有一个保持者,即在任何时刻最多只能有一个执行单元获得锁。这就是它们的"类似"。

与信号量相关的API主要有:

  定义信号量

struct semaphore sem;

  初始化信号量

void sema_init (struct semaphore *sem, int val);

  该函数初始化信号量,并设置信号量sem的值为val

void init_MUTEX (struct semaphore *sem);

  该函数用于初始化一个互斥锁,即它把信号量sem的值设置为1,等同于sema_init (struct semaphore *sem, 1);

void init_MUTEX_LOCKED (struct semaphore *sem);

  该函数也用于初始化一个互斥锁,但它把信号量sem的值设置为0,等同于sema_init (struct semaphore *sem, 0);

  获得信号量

void down(struct semaphore * sem);

  该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文使用;

int down_interruptible(struct semaphore * sem);

  该函数功能与down类似,不同之处为,down不能被信号打断,但down_interruptible能被信号打断;

int down_trylock(struct semaphore * sem);

  该函数尝试获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,返回非0值。它不会导致调用者睡眠,可以在中断上下文使用。

  释放信号量

void up(struct semaphore * sem);

  该函数释放信号量sem,唤醒等待者。

  与自旋锁相关的API主要有:

  定义自旋锁

spinlock_t spin;

  初始化自旋锁

spin_lock_init(lock)

  该宏用于动态初始化自旋锁lock

  获得自旋锁

spin_lock(lock)

  该宏用于获得自旋锁lock,如果能够立即获得锁,它就马上返回,否则,它将自旋在那里,直到该自旋锁的保持者释放;

spin_trylock(lock)

  该宏尝试获得自旋锁lock,如果能立即获得锁,它获得锁并返回真,否则立即返回假,实际上不再"在原地打转";

  释放自旋锁

spin_unlock(lock)

  该宏释放自旋锁lock,它与spin_trylock或spin_lock配对使用;

3,阻塞与非阻塞实现
阻塞操作是指,在执行设备操作时,若不能获得资源,则进程挂起直到满足可操作的条件再进行操作。
非阻塞操作的进程在不能进行设备操作时,并不挂起,而是被CPU调度出进入睡眠。
阻塞,应用层:read  or write ,内核层:等待队列
非阻塞,应用层:select ,内核层:等待队列+设备poll函数实现
poll函数原型为
static unsigned int device_poll(struct file *filp, poll_table *wait)
该函数中最主要用到的一个API是poll_wait,其原型如下:
void poll_wait(struct file *filp, wait_queue_heat_t *queue, poll_table * wait);
poll_wait函数所做的工作是把当前进程添加到wait参数指定的等待列表(poll_table)中。