国嵌--linux字符设备驱动学习之memdev设备

https://blog.csdn.net/li4850729/article/details/7561708

https://blog.csdn.net/wangrunmin/article/details/7571555


字符设备驱动

驱动分类

——字符设备驱动

       字符设备:字符设备是一种按字节来访问的设备,字符驱动则负责驱动字符设备,这样的驱动通常实现open,close,read,write系统调用

——网络接口驱动

       网络接口:任何网络事务都通过一个接口来进行,一个接口通常是一个硬件设备(eth0),但是它也可以是一个纯粹的软件设备,比如回环接口(lo)。一个网络接口负责发送和接收数据报文。

——块设备驱动

       块设备:——在大部分的unix系统,块设备不能按字节处理数据,只能一次传送一个活多个长度是512字节(或一个更大的2次幂的数)的整块数据

                     ——而linux则允许块设备传送任意数目的字节。因此,块和字符设备的区别仅仅是驱动的与内核的接口不同。


字符设备与块设备的区别:块设备是可以进行随机访问的。而字符设备不能。在linux系统中,块设备也可以进行字节访问

 

驱动程序安装

——模块方式

——直接编译进内核:修改Kconfig、修改Makefile,即可。

                                   将要编译进内核的代码(比如hello.c)cp进内核源码树的/kernel/drivers/char。在char目录下改写Kconfig。然后再make menuconfig的时候便能看见hello world项(Kconfig是用来在menuconfig中增加菜单的,menuconfig配置后的结果保存在.config中);再修改/char目录下的Makefile添加obj-$(CONFIG_HELLO_WORLD)       +=hello.o(Makefile根据配置去选择CONFIG_HELLO_WORLD的值)。如此之后便能编译内核了(进入源码树编译)。编译好的内核位于arch/arm/boot/uImage


A:linux用户程序通过设备文件(又名:设备节点)来使用驱动程序操作字符设备和块设备

Q:设备(字符、块)文件在何处?——在/dev/目录下

 

字符设备驱动程序设计

主次设备号

字符设备通过字符设备文件来存取。字符设备文件由使用ls –l的输出的第一列的“c”标识。如果使用ls –l命令,会看到在设备文件项中有2个数(由一个逗号分隔)这些数字就是设备文件的主次设备编号(举例说明,进入/dev/目录,ls –l)

 

Q:内核中如何描述设备号?

A:dev_t   其实质为unsigned int 32位整数,其中高12位(4K)为主设备号,低20位(64K)为次设备号

 

Q:如何从dev_t中分解出主设备号?

A:MAJOR(dev_t dev)

 

Q:如何从dev_t中分解出此设备号?
A:MINOR(dev_t dev)

设备号

每个设备文件对应有自己的设备号

驱动程序也有自己的设备号

如果两者的设备号对应相同,那么设备文件便和设备驱动建立关联


设备号作用

——主设备号用来标识与设备文件相连的驱动程序。次编号被驱动程序用来辨别操作的是哪个设备

 

       主设备号用来反映设备类型

       此设备号用来区分同类型的设备

 

 

分配主设备号

Linux内核如何给设备分配主设备号?
——静态申请和动态分配两种方法

 

静态申请

——方法:1.根据documentation/devices.txt,确定一个没有使用的主设备号

                2.使用register_chrdev_region函数注册设备号

——优点:简单

——缺点:一旦驱动被广泛使用,这个随机选定的主设备号可能会导致设备号冲突,而使驱动程序无法注册。

       Intregister_chrdev_region(dev_t from, unsigned count, const char *name)

功能——申请使用从from开始的count个设备号(主设备号不变,次设备号增加)

参数——from:希望申请使用的设备号

       ——count:希望申请使用设备号数目

       ——name:设备名(体现在/proc/devices)

 

动态分配(让内核自动来分)

——方法:使用alloc_chrdev_region分配设备号

——有点:简单,易于驱动推广(因为内核知道哪些驱动有没使用)

——缺点:无法在安装驱动前创建设备文件(因为安装前还没有分配到主设备号)

——解决办法:安装驱动后,从/proc/devices中查询设备号

 

       Intalloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char*name)

——功能:请求内核动态分配count个设备号,且次设备号从baseminor开始

——参数:dev:分配到的设备号

                Baseminor:起始设备号

                Count:需要分配的设备号数目

                Name:设备名(体现在/proc/devices)

 

注销设备号

不论使用何种方法分配设备号,都应该在不再使用它们时释放这些设备号

       Voidunregister_chrdev_region(dev_t from, unsigned count)

——功能:释放从from开始的count个设备号

 

创建设备文件

2种方法:

——1.使用mknod命令手工创建

 

Mknod用法:       mknod filename typemajor minor

——filename:设备文件名

——type:设备文件类型(b/c)

——major:主设备号

——minor:次设备号

例:       mknod serial0 c 100 0

 

——2.自动创建

 

重要结构

在linux字符设备驱动程序设计中,有3种非常重要的数据结构:struct file ; struct inode ; struct file_operations

 

Structfile

代表一个打开的文件。系统中每个打开的文件在内核空间都有一个关联的struct file。它由内核在打开文件时创建,在文件关闭后释放。(如果有3个程序打开同一个文件,那么也有3个struct file)

——重要成员:loff_tf_pos //文件读写位置,loff_t其实是个整形

                       Struct file_operations *f_op

 

Structinode

用来记录文件的物理上的信息。因此,它和代表打开文件的file结构是不同的。一个文件可以对应多个file结构,但只有一个inode结构

——重要成员:dev_ti_rdev:设备号

 

Structfile_operations

一个函数指针的集合(更像是一个对应关系表,把应用程序中对文件的操作转化为驱动程序中相应的函数),定义能在设备上进行的操作。结构中的成员指向驱动中的函数,这些函数实现一个特别的操作,对于不支持的操作保留为NULL

例:              mem_fops

       Structfile_operations mem_fops={

       .owner= THIS_MODULE;

       .llseek= mem_seek;

       .read= mem_read;

       .write= mem_write;

       .ioctl= mem_ioctl;

       .open= mem_open;

       .release= mem_release;

       };

 

内核代码导读

 

设备注册

在linux2.6内核中,字符设备使用struct cdev来描述

字符设备的注册可分为如下3个步骤:

1.分配cdev

              Structcdev的分配可使用cdev_alloc函数来完成

              Structcdev *cdev_alloc(void)

——2.初始化cdev

              Structcdev的初始化使用cdev_init函数来完成

              Voidcdev_init(struct cdev*cdev, const structfile_operations *fops)

              ——参数:cdev:待初始化的cdev结构

                            :fops:设备对应的操作函数集

——3.添加cdev

              Structcdev的注册使用cdev_add函数来完成

              Intcdev_add(struct cdev *p, dev_t dev, unsigned count)

              ——参数:p:待添加到内核的字符设备结构

                              dev:设备号

                              count:添加的设备个数、

 

设备操作实现

完成了驱动程序的注册,下一步该做什么呢?——实现设备所支持的操作(即是file_operations中的函数指针集)

——int (*open)(struct inode *, struct file *)

在设备文件上的第一个操作,并不要求驱动程序一定要实现这个方法。如果该项为NULL,设备的打开操作永远成功。Open这个函数指针名可以改,比如改为上述mem_fops中的mem_open,但是其参数的类型是固定的,不能更改的。下同。

——void (*release)(struct inode *, struct file *)

当设备文件被关闭时调用这个操作。与open相仿,release也可以没有

——ssize_t (*read)(struct file*, char __user *, size_tloff_t)

从设备中读取数据

——ssize_t(*write)(struct file *, const char __user *,size_t loff_t)

向设备发送数据

——unsigned int (*poll)(struct file *, structpoll_table_struct *)

对应select系统调用

——int (*ioctl)(struct inode *, struct file *, unsignedint , unsigned long)

控制设备

——int (*mmap)(struct file *, struct vm_area_struct *)

将设备映射到进程虚拟地址空间中

——off_t (*llseek)(struct file *, loff_t, int)

修改文件的当前读写位置,并将新位置作为返回值

——参数:要操作的文件,移动的偏移量,移动的起始位置(有三种取值,头、当前位置、尾)

 

那么如何实现上述函数的呢?

 

OPEN方法

OPEN方法是驱动程序用来为以后的操作完成初始化准备工作的。在大部分驱动程序中,open完成如下工作:

——初始化设备

——标明次设备号

RELEASE方法

RELEASE方法的作用正好与open相反。这个设备方法有时也称为close,它应该:——关闭设备

 

读和写

读和写方法都完成类似的工作:从设备中读取数据到用户空间;将数据传递给驱动程序,它们的原型也相当相似:

ssize_t xxx_read(struct file *filp, char__user * buff, size_t count , loff_t * offp);

 

ssize_t xxx_write(struct file *filp, char__user *buff, size_t count , loff_t *offp);

对于2个方法,filp是文件指针,count是请求传输的数据量。buff参数指向数据缓存。最后,offp指出文件当前的访问位置(buff和count来自用户空间,filp和offp来自内核)

 

Read和write方法的buff参数是用户空间指针。因此,它不能被内核代码直接引用(而应由内核提供的专门函数来引用),理由如下:用户空间指针在内核空间时可能根本是无效的——没有那个地址的映射

 

内核提供了专门的函数用于访问用户空间的指针,例如:

——intcopy_from_user(void *to, const void __user *from, int n)

对应写操作,为真则是写失败

——intcopy_to_user(void __user *to , const void *from, int n)

对应读操作,为真则是读失败


设备注销

字符设备的注销使用cdev_del函数来完成

       Int cdev_del(struct cdev *p)

——参数:p:要注销的字符设备结构

 

:字符设备驱动程序:memdev.c

(分析驱动程序不像应用程序那样从头到尾看,应该看入口module_init())

(分析一个字符设备驱动程序,首先分析初始化、分析file operations的各函数(open、read、write、seek))


memdev.h 

[plain]  view plain  copy
  1. #ifndef _MEMDEV_H_  
  2. #define _MEMDEV_H_  
  3.   
  4. #ifndef MEMDEV_MAJOR  
  5. #define MEMDEV_MAJOR 254    //预设的mem的主设备号  
  6. #endif  
  7.   
  8. #ifndef MEMDEV_NR_DEVS  
  9. #define MEMDEV_NR_DEVS 2        //设备数  
  10. #endif  
  11.   
  12. #ifndef MEMDEV_SIZE  
  13. #define MEMDEV_SIZE 4096    //4K,申请的用于模拟字符设备的内存是4K  
  14.   
  15. //mem设备描述结构体  
  16. Struct mem_dev  
  17. {  
  18.     Char *data;     //由于是用内存模拟字符设备,所以需要记录那块内存的地址,用data保存  
  19.     Unsigned long size;  
  20. };  
  21.   
  22. #endif      /*_MEMDEV_H_*/  
[plain]  view plain  copy
  1.   

memdev.c

[plain]  view plain  copy
  1. //此函数是用内存中的某一段数据来模拟一个字符设备  
  2. #include <linux/module.h>  
  3. #include <linux/types.h>  
  4. #include <linux/fs.h>  
  5. #include <linux/errno.h>  
  6. #include <linux/mm.h>  
  7. #include <linux/sched.h>  
  8. #include <linux/init.h>  
  9. #include <linux/cdev..h>  
  10. #include <asm/io.h>  
  11. #include <asm/system.h>  
  12. #include <asm/uaccess.h>  
  13.   
  14. #include “memdev..h”  
  15.   
  16. static mem_major = MEMDEV_MAJOR;  
  17. module_param(mem_major, int, S_IRUGO);  
  18.   
  19. struct mem_dev *mem_devp ; //设备结构体指针  
  20.   
  21. struct cdev cdev;  
  22.   
  23. /*文件打开函数*/  
  24. int mem_open(struct inode *inode, struct file *filp)  
  25. {  
  26.     struct mem_dev *dev;  
  27.       
  28.     /*获取次设备号*/  
  29.     int num = MINOR(inode->i_rdev);  
  30.   
  31.     if (num >= MEMDEV_NR_DEVS)  
  32.         return -ENODEV;  
  33.     dev = &mem_devp[num];  
  34.   
  35.     /*将设备描述结构指针赋值给文件私有数据指针*/  
  36.     filp->private_data = dev;  
  37.   
  38.     return 0;  
  39. }  
  40.   
  41. /*文件释放函数*/  
  42. int mem_release(struct inode * inode , struct file * filp)  
  43. {  
  44.     return 0;  
  45. }  
  46.   
  47. /*读函数*/  
  48. static ssize_t mem_read(struct file * filp, char __user *buf, size_t size, loff_t *ppos)  
  49. {  
  50.     unsigned long p = *ppos;  
  51.     unsigned int count = size;  
  52.     int ret = 0;  
  53.     struct mem_dev *dev = filp->private_data;    //获得设备结构体指针  
  54.       
  55.     /*判断读位置是否有效*/  
  56.     if (p >= MEMDEV_SIZE)  
  57.      return 0;  
  58.     if (count > MEMDEV_SIZE -p)  
  59.      count = MEMDEV_SIZE -p;  
  60.       
  61.     /*读数据到用户空间*/  
  62.     if(copy_to_user(buf, (void *)(dev->data + p), count))  
  63.      {  
  64.        ret = -EFAULT;  
  65.     }  
  66.     else  
  67.      {  
  68.        *ppos += count;  
  69.        ret = count;  
  70.       
  71.        printk(KERN_INFO, "read %d bytes(s) from %d \n", count, p);  
  72.      }  
  73.     return ret;   
  74.       
  75. }  
  76. /*写函数*/  
  77. static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)  
  78. {  
  79.     unsigned long p = *ppos;  
  80.     unsigned int count = size;  
  81.     int ret = 0;  
  82.     struct mem_dev *dev = filp->private_data;//获得设备结构体指针  
  83.   
  84.     /*分析和获取有效的写长度*/  
  85.     if (p >= MEMDEV_SIZE)  
  86.      return 0;  
  87.     if (count > MEMDEV_SIZE -p)  
  88.      count = MEMDEV_SIZE -p;  
  89.       
  90.     /*从用户空间写入数据*/  
  91.     if (copy_from_user(dev->data +p , buf, count))  
  92.      ret = -EFAULT;  
  93.     else  
  94.      {  
  95.        *ppos += count;  
  96.        ret = count;  
  97.   
  98.        printk(KERN_INFO "written %d bytes(s) from %d \n", count, p);      
  99.     }  
  100.     return ret;  
  101. }  
  102.   
  103. /*seek文件定位函数*/  
  104. static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)  
  105. {  
  106.     loff_t newpos;  
  107.       
  108.     switch(whence){  
  109.       case 0:   /*SEEK_SET*/  
  110.         newpos = offset;  
  111.         break;  
  112.   
  113.       case 1:   /*SEEK_CUR*/  
  114.         newpos = filp->f_ops + offset;  
  115.         break;  
  116.   
  117.       case 2:   /*SEEK_END*/  
  118.         newpos = MEMDEV_SIZE - 1 + offset;//此时offset应为负数  
  119.         break;  
  120.       
  121.       default:  /*can't happen*/  
  122.         return -EINVAL;   
  123.     }  
  124.     if ((newpos < 0) || (newpos > MEMDEV_SIZE))  
  125.         return -EINVAL;  
  126.       
  127.     filp->f_pos = newpos;  
  128.     return newpos;   
  129. }  
  130.   
  131. /*文件操作结构体*/  
  132. static const struct file_operations mem_fops =   
  133. {  
  134.     .owner = THIS_MODULE,  
  135.     .llseek = mem_llseek,  
  136.     .read = mem_read,  
  137.     .write = mem_write,  
  138.     .open = mem_open,  
  139.     .release = mem_release,  
  140. };  
  141.   
  142. /*设备驱动模块加载函数*/  
  143. static int memdev_init(void)  
  144. {  
  145.     int result;  
  146.     int i;  
  147.       
  148.     dev_t devno = MKDEV(mem_major, 0);  
  149.   
  150.     /*静态申请设备号*/  
  151.     if (mem_major)  
  152.      result = register_chrdev_region(devno, 2, "memdev");  
  153.     else    /*动态分配设备号*/  
  154.      {  
  155.         result = alloc_chrdev_region(&devno, 0, 2, "memdev");  
  156.         mem_major = MAJOR(devno);     
  157.     }  
  158.       
  159.     if (result < 0)  
  160.      return result;  
  161.   
  162.     /*初始化cdev结构*/  
  163.     cdev_init(&cdev, &mem_fops);    //因为cdev是之前定义好了的struct cdev cdev,所以不需要分配,而直接进行初始化  
  164.     cdev.owner = THIS_MODULE;  
  165.     cdev.ops = &mem_fops;  
  166.   
  167.     /*注册字符设备*/  
  168.     cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);  
  169.       
  170.     /*为设备描述结构分配内存*/  
  171.     mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);  
  172.     if(!mem_devp)   //申请失败  
  173.     {  
  174.      result = -ENOMEM;  
  175.      goto fail_malloc;  
  176.     }  
  177.     memset(mem_devp, 0, sizeof(struct mem_dev));  
  178.   
  179.     /*为设备分配内存*/  
  180.     for (i=0; i<MEMDEV_NR_DEVS; i++)  
  181.      {  
  182.         mem_devp[i].size = MEMDEV_SIZE;  
  183.         mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);  
  184.         memset(mem_devp[i].data, 0, MEMDEV_SIZE);  
  185.     }  
  186.       
  187.     return 0;  
  188.   
  189.     fail_malloc:  
  190.     unregister_chrdev_region(devno, 1);  
  191.   
  192.     return result;  
  193. }  
  194.   
  195. /*模块卸载函数*/  
  196. static void memdev_exit(void)  
  197. {  
  198.     cdev_del(&cdev);    //注销设备  
  199.     kfree(mem_devp);    //释放设备结构体内存  
  200.     unregister_chrdev_region(MKDEV(mem_major, 0), 2);   //释放设备号  
  201. }  
  202.   
  203. MODULE_AUTHOR("David Jason");  
  204. MODULE_LICENCE("GPL");  
  205.   
  206. module_init(memdev_init);  
  207. module_exit(memdev_exit);  


main.c


[plain]  view plain  copy
  1. //此为应用程序。用来测试驱动程序的读、写、重定位等功能  
  2. //当写入的内容和读出的内容一样,那么即是说读写功能成功。  
  3. #include <stdio.h>  
  4.   
  5. int main()  
  6. {  
  7.     FILE *fp0 = NULL;  
  8.     char Buf[4096];  
  9.   
  10.     /*初始化Buf*/  
  11.     strcpy(Buf, "Mem is char dev!");  
  12.     printf("BUF:%s \n", Buf);  
  13.       
  14.     /*打开设备文件*/  
  15.     fp0 = fopen("/dev/memdev0", "r+");//用fopen打开/dev/memdev0这个设备文件,这个设备文件是由我们自己去创建的:安装驱动程序之后,再去创建这个设备文件。  
  16.     if (fp0 == NULL)  
  17.      {  
  18.         printf("Open Memdev0 Error!\n");  
  19.         return -1;  
  20.     }     
  21.   
  22.     /*写入设备*/  
  23.     fwrite(Buf, sizeof(Buf), 1, fp0);  
  24.   
  25.     /*重新定位文件位置(思考没有该指令,会有何后果)*/  
  26.     fseek(fp0, 0, SEEK_SET);//如果没有SEEK_SET,那么会因为每次写入之后seek指针的位置总是在跟随变化的,所以,当读的时候,便不是从文件开头读起!  
  27.   
  28.     /*清除Buf*/  
  29.     strcpy(Buf, "Buf os NULL!\n");  
  30.     printf("Buf:%s \n ", Buf);  
  31.   
  32.     /*读出设备*/  
  33.     fread(Buf, sizeof(Buf), 1, fp0);  
  34.   
  35.     /*检测结果*/  
  36.     printf("Buf :%s \n", Buf);  
  37.   
  38.     return 0;  
  39. }  


竞争与互斥

调试技术分类

对于驱动程序设计来说,核心问题之一就是如何完成调试。当前常用的驱动调试技术科分为:

——打印调试(printk)

       在调试应用程序时,最常用的调试技术是打印,就是在应用程序中合适的点调用printf。当调试内核代码的时候,可以用printk完成类似任务

       合理使用printk

       在驱动开发时,printk非常有助于调试。但当正式发行驱动程序时,应当去掉这些打印语句。但你有可能很快又发现,你又需要在驱动程序中实现一个新功能(或者修复一个bug),这时你又要用到那些被删除的打印语句。这里介绍一种是用printk的合理方法,可以全局地打开或关闭它们,而不是简单地删除:

       #ifdefPDEBUG

       #define PLOG(fmt, args…) printk(KERN_DEBUG”scull:”fmt,##args)

#else

#define PLOG(fmt,args..)           //do nothing

#endif

 

       Makefile作如下修改:

       ——DEBUG=y

              ifeq ($(DEBUG),y)

              DEBFLAGS=-O2 –g –DPDEBUG        //D的作用是相当于#define

              else

              DEBFLAGS=-O2

              endif

              CFLAGS +=$(DEBFLAGS)

      

——调试器调试(kgdb)

——查询调试(/proc文件系统)

 

并发与竞态

——并发:多个执行单元同时被执行

——竞态:并发的执行单元对共享资源(硬件资源和软件上的全局变量等)的访问导致的竞争状态

 

例:

If(copy_from_user(&(dev->data[pos]), buf, count))

Ret = -EFAULT;

Goto out;

 

假设有2个进程试图同时向一个设备的相同位置写入数据,就会造成数据混乱(对应于多核情况)

 

处理并发的常用技术是加锁或者互斥,即确保在任何时间只有一个执行单元可以操作共享资源。在Linux内核中主要通过semaphore(信号量)机制和spin_lock(自旋锁)机制实现

 

信号量

Linux内核的信号量在概念和原理上与用户态的信号量是一样的,但是它不能在内核之外使用,它是一种睡眠锁。如果有一个任务想要获得已经被占用的信号量时,信号量会将这个进程放入一个等待队列,然后让其睡眠。当持有信号量的进程将信号释放后,处于等待队列中的任务将被唤醒,并让其获得信号量

 

——信号量在创建时需要设置一个初始值,表示允许有几个任务同时访问该信号量保护的共享资源,初始值为1就变成互斥锁(Mutex),即同时只能有一个任务可以访问信号量保护的共享资源。

 

——当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,如果释放后信号量的值为非正数,表明有任务等待当前信号量,因此要唤醒等待该信号量的任务。

 

信号量的实现也是与体系结构相关的,定义在<asm/semaphore.h>中,struct semaphore类型用来表示信号量。

1.定义信号量        struct semaphore sem;

2.初始化信号量   

       voidsema_init(struct semaphore *sem, int vall)该函数用户初始化设置信号量的初值,它设置信号量sem的值为val

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

       voidinit_MUTEX_LOCKED(struct semaphore *sem)该函数用于初始化一个互斥锁,但它把信号量sem的值设置为0,即一开始就处在已锁状态。

 

定义及初始化的工作可由如下宏一步完成:

DECLARE_MUTEX(name):定义一个信号量name,并初始化它的值为1

DECLARE_MUTEX_LOCKED(name):定义一个信号量name,但它把它的初始值设置为0,即锁在创建时就处在已锁状态。

 

3.获取信号量

       voiddown(struct semaphore * sem)获取信号量sem,可能会导致进程睡眠,因此不能在中断上下文使用该函数。该函数将把sem的值减1,如果信号量sem 的值非负,就直接返回,否则调用者将被挂起,直到别的任务释放该信号量才能继续运行(此时处于TASK_UNINTERRUPTIBLE的状态)。

 

——intdown_interruptible(struct semaphore * sem):获取信号量sem。如果信号量不可用,进程将被设置为TASK_INTERRUPTIBLE(可被信号和中断唤醒)类型的睡眠状态。该函数由返回值来区分是正常返回还是被信号中断返回,如果返回0,表示获得信号量正常返回,如果被信号打断,返回-EINTR

 

——down_killable(structsemaphore * sem):获取信号量sem。如果信号量不可用,进程将被置为TASK_KILLABLE类型的睡眠状态

 

注:down()函数(linux 2.4)现已不建议继续使用。建议使用down_killable()或down_interruptible()函数

 

4.释放信号量

       voidup(struct semaphore * sem):该函数释放信号量sem,即把sem 的值加1,如果sem的值为非正数,表明有任务等待该信号量,因此唤醒这些等待者。

 

自旋锁

自旋锁最多只能被一个可执行单元持有。自旋锁不会引起调用者睡眠,如果一个执行线程试图获得一个已经被持有的自旋锁,那么线程就会一直进行忙循环(一直占有CPU),一直等待下去,在那里看是否该自旋锁的保持者已经释放了锁,“自旋”就是这个意思。

 

——spin_lock_init(x):该宏用于初始化自旋锁x,自旋锁在使用前必须先初始化

——spin_lock(lock):获取自旋锁lock,如果成功,立即获得锁,并马上返回,否则它将一直自旋在那里,直到该自旋锁的保持者释放。

——spin_trylock(lock):试图获取自旋锁lock,如果能立即获得锁,并返回真,否则立即返回假。它不会一直等待被释放

——spin_unlock(lock):释放自旋锁lock,它与spin_trylock或spin_lock配对使用

 

信号量PK自旋锁

——信号量可能允许有多个持有者,而自旋锁在任何时候只能允许一个持有者。当然也有信号量叫互斥信号量(只能一个持有者),允许有多个持有者的信号量叫计数信号量

——信号量适合于保持时间较长的情况;而自旋锁适合于保持时间非常短的情况,在实际应用中自旋锁控制的代码只有几行,而持有自旋锁的时间也一般不会超过两次上下文切换的时间,因为线程一旦要进行切换,就至少花费切出切入两次,自旋锁的占用时间如果远远长于两次上下文切换,我们就应该选择信号量。


实验1: 1.在mini2440平台编写实现了读、写,定位功能的字符设备驱动程序

2.编写应用程序,测试驱动


实验2: 基于实验1设计的驱动程序,加入竞争控制

猜你喜欢

转载自blog.csdn.net/xiaodingqq/article/details/80149966