歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

Linux應用程序訪問字符設備驅動詳細過程解析

下面先通過一個編寫好的內核驅動模塊來體驗以下字符設備驅動

可以暫時先忽略下面的代碼實現!

memdev.c

 

#include 
#include 
#include 
#include 
#include 

int dev1_registers[5];
int dev2_registers[5];

struct cdev cdev; 
dev_t devno;

/*文件打開函數*/
int mem_open(struct inode *inode, struct file *filp)
{
    
    /*獲取次設備號*/
    int num = MINOR(inode->i_rdev);
    
    if (num==0)
        filp->private_data = dev1_registers;
    else if(num == 1)
        filp->private_data = dev2_registers;
    else
        return -ENODEV;  //無效的次設備號
    
    return 0; 
}

/*文件釋放函數*/
int mem_release(struct inode *inode, struct file *filp)
{
  return 0;
}

/*讀函數*/
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
  unsigned long p =  *ppos;
  unsigned int count = size;
  int ret = 0;
  int *register_addr = filp->private_data; /*獲取設備的寄存器基地址*/

  /*判斷讀位置是否有效*/
  if (p >= 5*sizeof(int))
    return 0;
  if (count > 5*sizeof(int) - p)
    count = 5*sizeof(int) - p;

  /*讀數據到用戶空間*/
  if (copy_to_user(buf, register_addr+p, count))
  {
    ret = -EFAULT;
  }
  else
  {
    *ppos += count;
    ret = count;
  }

  return ret;
}

/*寫函數*/
static ssize_t mem_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;
  int *register_addr = filp->private_data; /*獲取設備的寄存器地址*/
  
  /*分析和獲取有效的寫長度*/
  if (p >= 5*sizeof(int))
    return 0;
  if (count > 5*sizeof(int) - p)
    count = 5*sizeof(int) - p;
    
  /*從用戶空間寫入數據*/
  if (copy_from_user(register_addr + p, buf, count))
    ret = -EFAULT;
  else
  {
    *ppos += count;
    ret = count;
  }

  return ret;
}

/* seek文件定位函數 */
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{ 
    loff_t newpos;

    switch(whence) {
      case SEEK_SET: 
        newpos = offset;
        break;

      case SEEK_CUR: 
        newpos = filp->f_pos + offset;
        break;

      case SEEK_END: 
        newpos = 5*sizeof(int)-1 + offset;
        break;

      default: 
        return -EINVAL;
    }
    if ((newpos<0) || (newpos>5*sizeof(int)))
    	return -EINVAL;
    	
    filp->f_pos = newpos;
    return newpos;

}

/*文件操作結構體*/
static const struct file_operations mem_fops =
{
  .llseek = mem_llseek,
  .read = mem_read,
  .write = mem_write,
  .open = mem_open,
  .release = mem_release,
};

/*設備驅動模塊加載函數*/
static int memdev_init(void)
{
  /*初始化cdev結構*/
  cdev_init(&cdev, &mem_fops);
  
  /* 注冊字符設備 */
  alloc_chrdev_region(&devno, 0, 2, "memdev");
  cdev_add(&cdev, devno, 2);
}

/*模塊卸載函數*/
static void memdev_exit(void)
{
  cdev_del(&cdev);   /*注銷設備*/
  unregister_chrdev_region(devno, 2); /*釋放設備號*/
}

MODULE_LICENSE("GPL");

module_init(memdev_init);
module_exit(memdev_exit);


 

1. 編譯/安裝驅動:在Linux系統中,驅動程序通常采用內核模塊的程序結構來進行編碼,因此編譯、安裝一個驅動程序,其實質就是編譯/安裝一個內核模塊。

2. 創建設備文件

\

應用程序如何通過字符設備文件來訪問設備驅動接口,即字符設備驅動程序訪問大揭秘,下面先來看個應用程序的小例子:(應用程序是如何通過系統調用找到設備驅動程序入口的然後讓設備驅動程序work)

read-mem.c

 

#include 
#include 
#include 
#include 

int main()
{
	int fd = 0;
	int dst = 0;
	
	/*打開設備文件*/
	fd = open("/dev/memdev0",O_RDWR);
	
	/*寫入數據*/
	read(fd, &dst, sizeof(int));
	
	printf("dst is %d\n",dst);
	
	/*關閉設備*/
	close(fd);
	
	return 0;	

}

\

 

在linux下對上面的文件進行靜態編譯(考慮到前面開發板上移植的某些庫還沒有添加進去)生成read-mem目標文件,然後進行反匯編並將反匯編生成的文件導入到當前目錄下的dump上去。

VIM打開dump文件,搜索/main 可以看到這一段匯編代碼

\

可以看到 應用程序中的read()函數實際是調用了__libc_read函數, 然後繼續在dump中搜索__libc_read函數

\

這裡紅箭頭指向的兩行是比較重要的兩行,將3傳給r7,然後使用了SVC系統調用指令,這時PC指針會從用戶空間進入到內核空間(通過一個固定的入口),第二步會取r7寄存器裡面的值3, 然後根據這個值查一個表確定要調用那個系統調用(即對於3的系統調用內核代碼)。這裡打開內核源碼工程,打開一個文件:

entry-comon.S(/arch/arm/kernel目錄下)

\

上面的內核代碼部分vector_swi就是那個固定的入口,第二個箭頭部分就是上面所說的第二步,第三步在這部分的代碼下面這裡截圖截不了這麼多(也就是根據number查表)

 

enable_irq

	get_thread_info tsk
	adr	tbl, sys_call_table		@ load syscall table pointer
搜索sys_call_table,來看看這張表是什麼?

 

\
查看calls.S文件

\

系統就是通過固定入口進入內核空間,然後取出系統調用編號,在利用編號查找上面的這張表,然後取出內核中對於上面用戶空間的read的實現函數!(這裡就分析了用戶空間的read是如何找到內核空間的sys_read的過程)

這裡順便看一下sys_read的內核代碼實現:/fs 目錄下的 Read_write.c文件中

 

SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
	struct file *file;
	ssize_t ret = -EBADF;
	int fput_needed;

	file = fget_light(fd, &fput_needed);
	if (file) {
		loff_t pos = file_pos_read(file);
		ret = vfs_read(file, buf, count, &pos);
		file_pos_write(file, pos);
		fput_light(file, fput_needed);
	}

	return ret;
}
每個打開的文件都會有一個struct file與之對應!從上面的函數可以看到通過傳入的fd參數 能找到與之對應的struct file.

 

然後通過file 調用了vfs_read()函數.下面先看看該函數的內部實現。

\
 

紅色箭頭部分!f_op部分是驅動程序裡面的自定義結構,通過f_op結構找到設備讀取方法!

就是這個:

/*文件操作結構體*/
static const struct file_operations mem_fops =
{
  .llseek = mem_llseek,
  .read = mem_read,
  .write = mem_write,
  .open = mem_open,
  .release = mem_release,
};

 

 

 

 

Copyright © Linux教程網 All Rights Reserved