天天看點

Linux驅動學習5(詳細分析字元裝置驅動信号量實作互斥)

/*======================================================================
    A mem_pool driver as an example of char device drivers  

======================================================================*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include "mem_pool.h"

static mem_pool_major = mem_pool_MAJOR;
/*裝置結構體指針*/
struct mem_pool_dev *mem_pool_devp; 
/*檔案打開函數*/
int mem_pool_open(struct inode *inode, struct file *filp)
{
	struct mem_pool_dev *dev;
	dev = container_of(inode->i_cdev,struct mem_pool_dev,cdev);
  /*将裝置結構體指針指派給檔案私有資料指針*/
  filp->private_data = dev;
  
	printk("mem_pool_open func \n");
	printk("dev->sem.count = %d \n",dev->sem.count);
  return 0;
}
/*檔案釋放函數*/
int mem_pool_release(struct inode *inode, struct file *filp)
{
  return 0;
}

/* ioctl裝置控制函數 */
static int mem_pool_ioctl(struct inode *inodep, struct file *filp, unsigned
  int cmd, unsigned long arg)
{
	/*獲得裝置結構體指針*/
  struct mem_pool_dev *dev = filp->private_data;

  switch (cmd)
  {
    case MEM_CLEAR:
   		/* 增加并發機制 由信号量控制*/
			if(down_interruptible(&dev->sem))
			{	
			return -ERESTARTSYS;	
			}
      memset(dev->mem, 0, mem_pool_SIZE);      
      printk( "mem_pool is set to zero\n");
     	/* 釋放信号量 */
  		up(&dev->sem);   
      break;

    default:
      return  - EINVAL;
  }
  return 0;
}

/*讀函數*/
static ssize_t mem_pool_read(struct file *filp, char __user *buf, size_t size,
  loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  /*1. 獲得裝置結構體指針*/
  struct mem_pool_dev *dev = filp->private_data; 

  /*2. 分析和擷取有效的寫長度*/
  if (p >= mem_pool_SIZE)
    return count ?  - ENXIO: 0;
  if (count > mem_pool_SIZE - p)
    count = mem_pool_SIZE - p;
	/* 增加并發機制 由信号量控制*/
	printk("read dev->sem = %d \n",dev->sem.count);
	if(down_interruptible(&dev->sem))
	{
		return -ERESTARTSYS;	
	}
  /*3. 核心空間->使用者空間 */
  if (copy_to_user(buf, (void*)(dev->mem + p), count))
  {
    ret =  - EFAULT;
  }
  else
  {
    *ppos += count;
    ret = count;
    
    printk( "read %d bytes(s) from %d\n", count, p);
  }
  /* 釋放信号量 */
  up(&dev->sem);
  
  return ret;
}

/*寫函數*/
static ssize_t mem_pool_write(struct file *filp, const char __user *buf,
  size_t size, loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  /*1. 獲得裝置結構體指針*/
  struct mem_pool_dev *dev = filp->private_data; 

  /*2. 分析和擷取有效的寫長度*/
  if (p >= mem_pool_SIZE)
    return count ?  - ENXIO: 0;

  if (count > mem_pool_SIZE - p)
    count = mem_pool_SIZE - p;
    
	/* 增加并發機制 由信号量控制*/
	printk("write dev->sem = %d \n",dev->sem.count);
	/* 正常情況下(擷取到信号量,sem > 0),down_interruptible傳回0 */
	if(down_interruptible(&dev->sem))
	{
		return -ERESTARTSYS;	
	}    
  /*3. 使用者空間->核心空間*/
  printk("copy_from_user  \n");
  printk("dev->mem + p = %x  \n",dev->mem + p);
  if (copy_from_user(dev->mem + p, buf, count))
    ret =  - EFAULT;
  else
  {
    *ppos += count;
    ret = count;
    
    printk( "written %d bytes(s) from %d\n", count, p);
  }
  printk("dev->sem = %d \n",dev->sem.count);
  /* 釋放信号量 */
  up(&dev->sem);
  printk("  up(&dev->sem);  \n");
  printk("dev->sem = %d \n",dev->sem.count);
  return ret;
}

/* seek檔案定位函數 */
static loff_t mem_pool_llseek(struct file *filp, loff_t offset, int orig)
{
  loff_t ret = 0;
  switch (orig)
  {
    case 0:   /*相對檔案開始位置偏移*/
      if (offset < 0)
      {
        ret =  - EINVAL;
        break;
      }
      if ((unsigned int)offset > mem_pool_SIZE)
      {
        ret =  - EINVAL;
        break;
      }
      filp->f_pos = (unsigned int)offset;
      ret = filp->f_pos;
      break;
    case 1:   /*相對檔案目前位置偏移*/
      if ((filp->f_pos + offset) > mem_pool_SIZE)
      {
        ret =  - EINVAL;
        break;
      }
      if ((filp->f_pos + offset) < 0)
      {
        ret =  - EINVAL;
        break;
      }
      filp->f_pos += offset;
      ret = filp->f_pos;
      break;
    default:
      ret =  - EINVAL;
      break;
  }
  return ret;
}

/*檔案操作結構體*/
static const struct file_operations mem_pool_fops =
{
  .owner = THIS_MODULE,
  .llseek = mem_pool_llseek,
  .read = mem_pool_read,
  .write = mem_pool_write,
  .ioctl = mem_pool_ioctl,
  .open = mem_pool_open,
  .release = mem_pool_release,
};

/*初始化并注冊cdev*/
static void mem_pool_setup_cdev(struct mem_pool_dev *dev, int index)
{
  int err, devno = MKDEV(mem_pool_MAJOR, index);
  /*1. 初始化cdev,綁定裝置和檔案操作函數*/
  cdev_init(&dev->cdev, &mem_pool_fops);
  dev->cdev.owner = THIS_MODULE;
  /*2. 為檔案操作提供具體實作方法*/
  dev->cdev.ops = &mem_pool_fops;
  /*3. 添加該cdev至核心*/
  err = cdev_add(&dev->cdev, devno, 1);
  if (err)
    printk( "Error %d adding LED%d", err, devno);
}

/*裝置驅動子產品加載函數*/
int mem_pool_init(void)
{
  int result;
  dev_t devno = MKDEV(mem_pool_major, 0);

  printk( "mem_pool_init !\n");
  /*1. 申請裝置号*/
  if (mem_pool_major)
    result = register_chrdev_region(devno, 2, "mem_pool");		//申請兩個裝置号
  else  
  {
	/*2. 動态申請裝置号 */  	
    result = alloc_chrdev_region(&devno, 0, 2, "mem_pool");		//申請兩個裝置号
    mem_pool_major = MAJOR(devno);
  }  
  if (result < 0)
    return result;
    
  /*3. 動态申請裝置結構體的記憶體*/
  mem_pool_devp = kmalloc(2 * sizeof(struct mem_pool_dev), GFP_KERNEL);
  /*4. 申請失敗*/
  if (!mem_pool_devp)    
  {
    result =  - ENOMEM;
    goto fail_malloc;
  }
  /*5. 記憶體初始化*/
  memset(mem_pool_devp, 0, 2 * sizeof(struct mem_pool_dev));
  /*6. 注冊初始化裝置*/
  mem_pool_setup_cdev(&mem_pool_devp[0] , 0);
  mem_pool_setup_cdev(&mem_pool_devp[1] , 1);
  /*7. 初始化信号量*/
  init_MUTEX(&mem_pool_devp[0].sem);
  /*8. 初始化信号量*/
 // init_MUTEX(&mem_pool_devp[1].sem);

  return 0;

  fail_malloc: unregister_chrdev_region(devno, 2);
  return result;
}

/*子產品解除安裝函數*/
void mem_pool_exit(void)
{
  printk( "mem_pool_exit !\n");
	/*1. 登出cdev*/
  cdev_del(&(mem_pool_devp[0].cdev));
  cdev_del(&(mem_pool_devp[1].cdev));
	/*2. 釋放裝置結構體記憶體*/   
  kfree(mem_pool_devp);     
  /*3. 釋放裝置号*/
  unregister_chrdev_region(MKDEV(mem_pool_major, 0), 2); 
}

MODULE_AUTHOR("xb Deng");
MODULE_LICENSE("Dual BSD/GPL");

module_param(mem_pool_major, int, S_IRUGO);

module_init(mem_pool_init);
module_exit(mem_pool_exit);




           

1.inode是如何來的,這個應該和mknod這個指令有關mknod /dev/mem_pool c 250 0 這個指令生成了一個mem_pool的裝置節點,

然後當我們在應用程式中打開這個裝置檔案的時候,系統自動賦予了inode結構體的值并且在inode結構體中,儲存着一些重要的資訊,

如:裝置号,cdev的指針位址。為什麼要建立裝置節點,就是為了能夠建立這些相關的資訊,這裡不讨論,file和inode的資料結構,

在網上随便一搜,能夠搜到一大把。

2.在使用printk列印調試的時候,碰到很多愚昧的調試語句,比如

printk("down_interruptible(&dev->sem) = %d \n",down_interruptible(&dev->sem));
           

試想一下。執行了這句之後不就相當于擷取了一次信号量麼,難怪運作程式的時候總是會停在那裡。

3.在多個裝置的時候,記憶體塊也要申請多個,同時别忘記了,信号量的申請也需要多個,具體如果你不這麼做,會産生什麼結果,可以

自己試試

4. 建立兩個裝置節點就可以進行測試兩個裝置了

mknod /dev/mem_pool0 c 249 0

mknod /dev/mem_pool1 c 249 1

5.使用者空間的測試程式如下所示:

#include <stdio.h>

int main()
{
	FILE *fp0 = NULL;
	char Buf[4096];
	
	/*初始化Buf*/
	strcpy(Buf,"mem_pool is char dev!");
	printf("BUF: %s\n",Buf);
	
	/*打開裝置檔案*/
	fp0 = fopen("/dev/mem_pool1","r+");
	if (fp0 == NULL)
	{
		printf("Open mem_pool Error!\n");
		return -1;
	}
	printf("Open mem_pool Success!\n");
	/*寫入裝置*/
	fwrite(Buf, sizeof(Buf), 1, fp0);
	printf("Write mem_pool Success!\n");	
	/*重新定位檔案位置(思考沒有該指令,會有何後果)*/
	fseek(fp0,0,SEEK_SET);
	printf("seek mem_pool Success!\n");	
	/*清除Buf*/
	strcpy(Buf,"Buf is NULL!");
	printf("BUF: %s\n",Buf);
	
	
	/*讀出裝置*/
	fread(Buf, sizeof(Buf), 1, fp0);
	printf("Read mem_pool Success!\n");	
	/*檢測結果*/
	printf("BUF: %s\n",Buf);

	fclose(fp0);
	
	return 0;	

}
           

最後可以看看這個頭檔案裡面的内容

mem_pool.h

#define mem_pool_SIZE	0x1000	/*全局記憶體最大8K位元組*/
#define MEM_CLEAR 	0x1  	/*清0全局記憶體*/
#define mem_pool_MAJOR 	249    	/*預設的mem_pool的主裝置号*/

struct mem_pool_dev                                     
{                                                        
  struct cdev cdev; 		    /*cdev結構體*/                       
  unsigned char mem[mem_pool_SIZE]; /*全局記憶體*/   
  struct semaphore sem;     
};/*mem_pool裝置結構體*/

int mem_pool_open(struct inode *inode, struct file *filp);
int mem_pool_release(struct inode *inode, struct file *filp);
           

這種封裝的方法還不是很好,有機會的話繼續改進一下。

繼續閱讀