linux内核驱动入门之阻塞操作实验:glob

      首先,先来了解一下设备的阻塞与非阻塞操作以及实现阻塞操作的方法:

    1.设备的阻塞与非阻塞操作:

    阻塞操作是指,在执行设备操作时,若不能获得资源,则进程被挂起直到满足可操作的条件再进行操作。非阻塞操作是指,当进程不能进行设备操作时,并不挂起,它或者放弃,或者不停地查询,直到可以进行操作为止。

    2.实现阻塞操作的方法:

    在linux驱动程序中,可以使用等待队列(wait queue)来实现阻塞访问。


一,glob字符设备驱动程序的编写,把文件名命名为glob.c,源代码如下:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h> 
#include <asm/uaccess.h> 
#include <linux/wait.h>  //有关等待队列的头文件
#include <linux/semaphore.h> //有关信号量的头文件
#include <linux/sched.h> 
MODULE_LICENSE("GPL");
#define MAJOR_NUM 1400
#define DEVICE_NAME "glob" 
static int glob_var = 0;
static struct semaphore sem; //定义信号量
static wait_queue_head_t outq;  //定义一个等待队列头   
static int flag = 0;

//*******************定义read方法****************************
static ssize_t glob_read(struct file *filp, char *buf, ssize_t len, loff_t *off)
{
      //等待数据可获得
      //wait_event_interruptible的返回一个整数值,非零值表示休眠被某个信号中断
      //wait_event_interruptible中第一个参数是等待队列头,第二个参数是一个布尔表达式,在条件为真之前,进程会保持休眠 
     if (wait_event_interruptible(outq, flag != 0)) 
     {
          return - ERESTARTSYS;
     }
     //down_interruptible 函数返回非零值,表示操作被中断,调用者拥有信号量失败
     if (down_interruptible(&sem))
     {
          return - ERESTARTSYS;
     }
     flag = 0;
     //将内核空间中的数据移动到用户空间
     if (copy_to_user(buf, &glob_var, sizeof(int)))
     {
          up(&sem); //移动数据的操作不完全成功也需要释放信号量
          return - EFAULT;
     }
     up(&sem);//移动数据成功,释放信号量
     return sizeof(int);
}

//************************定义write方法******************************
//glob_write函数中,flip是文件指针,buf是指向用户空间的缓冲区,len表示请求传输数据的长度,
//off指向一个长偏移量类型对象的指针,这个对象指明用户在文件中进行存储操作的位置   
static ssize_t glob_write(struct file *filp, const char *buf, ssize_t len,loff_t *off)
{
     if (down_interruptible(&sem))
     {
          return - ERESTARTSYS;
     }
     //将用户空间的数据移动到内核空间 
     if (copy_from_user(&glob_var, buf, sizeof(int)))
    {
        up(&sem); //移动数据不完全成功也需要释放信号量 
        return - EFAULT;
    }
    up(&sem); //移动数据成功,释放信号量 
    flag = 1;
    //通知数据可获得
    wake_up_interruptible(&outq); //唤醒休眠进程        
    return sizeof(int);
}

//************初始化file_operations结构体*************
struct file_operations glob_fops =
{
    .owner = THIS_MODULE,
    .read = glob_read, 
    .write = glob_write,
};

//*******模块初始化函数*********
static int __init glob_init(void)
{
     int ret;
     ret = register_chrdev(MAJOR_NUM, DEVICE_NAME, &glob_fops); 
     if (ret)
     {
          printk("glob register failure");
     }
     else
    {
         printk("glob register success");
         //init_MUTEX(&sem);
         sema_init(&sem,1); //初始化一个互斥锁,把信号量sem的值设置为1
         init_waitqueue_head(&outq); //初始化等候队列头      
    }
     return ret;
}

//************模块卸载函数**************
static void __exit glob_exit(void)
{
     unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
     printk("glob unregister success!\n");
}

module_init(glob_init);
module_exit(glob_exit);


二,Makefile文件的编写,源代码如下:

obj-m:=glob.o
default:
$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean


三,编译模块:

    把上面的glob.c和Makefile两个文件放在同一个文件夹下,我这里的文件夹是“glob阻塞操作实验”,然后进入文件夹,打开终端,登录root,输入指令make,便开始进行模块的编译了,遇到编译错误,多百度,积累经验。


四,加载模块:

    在root权限下,在终端输入指令 insmod glob.ko 加载模块,没有任何错误提示的话,加载就成功了,如果遇到设备正忙的错误提示,请更改你的主设备号,然后重新加载便可。输入指令 cat /proc/devices 可以看到里面多了这么一行 :1400 glob  ,这说明我们的模块确实是已经成功加载进了我们的系统内核。


五,编写测试程序测试我们的内核模块:

    因为我们需要实现对glob虚拟设备的阻塞访问,所以我们这里编写两个测试程序:glob_read_test.c和glob_write_test.c 。我们最终要实现的效果是,当这两个程序同时访问我们的glob虚拟设备时,只有当成功执行glob_write_test这个进程,往设备里写入了数据,另一个进程glob_read_test,才能读取glob虚拟设备里面的数据。因为最开始glob设备里是不存在数据的,不满足执行进程glob_read_test的条件,这个进程被挂起了直到设备glob里面存在数据,glob_read_test这个进程才会被唤醒,否则一直会被挂起。

    glob_read_test.c的源代码如下:

//#include <sys/types.h>
//#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
int main(void)
{
    int fd, num;
    fd = open("/dev/glob", O_RDWR, S_IRUSR | S_IWUSR);
    if (fd != - 1)
   {
       while (1)
       {
             read(fd, &num, sizeof(int)); //程序将阻塞在此语句,除非有针对 glob 的输入
             printf("The glob is %d\n", num);
             //如果输入是 0,则退出
             if (num == 0)
              {
                   close(fd);
                   break;
              }
      }
   }
   else
   {
       printf("device open failure\n");
   }
   return 0;
}


    glob_write_test.c的源码如下:

//#include <sys/types.h>
//#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
int main(void)
{
     int fd, num;
     fd = open("/dev/glob", O_RDWR, S_IRUSR | S_IWUSR);
     if (fd != - 1)
     {
          while (1)
          {
                printf("Please input the glob:\n");
                scanf("%d", &num);
                write(fd, &num, sizeof(int));
                //如果输入 0,退出
                if (num == 0)
                {
                     close(fd);
                     break;
                }
           }
    }
     else
     {
           printf("device open failure\n");
     }
     return 0;
}


六,编译我们前面的测试程序:

    在终端输入: gcc -o glob_read_test.o glob_read_test.c 

                    gcc -o glob_write_test.o glob_write_test.c 

    编译我们的测试程序。 


七,创建设备文件:

    在终端输入命令:mknod /dev/glob c 1400 0 ,前面第一个参数是创建的目录;第二个参数是指设备的类型,c代表字符设备;第三个参数是主设备号;第四个参数是次设备号。


八,测试设备glob:

    打开一个终端 输入 ./glob_write_test.o 

    打开另一个终端 输入 ./glob_read_test.o 

    这时候你会发现,只有当执行线程glob_write_test,往glob写入数据后,另一个线程glob_read_test才能读取数据,实现了glob_read_test对字符设备glob的阻塞访问,这和我们第五步中的设想完全一致! 效果如下图:











本文出自 “止不住的思考” 博客,请务必保留此出处http://9110091.blog.51cto.com/9100091/1546959

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。