(七)linux函数接口的使用

前面我们讲解了字符设备的驱动模型,有了前面的基础后,今天学习函数接口就比较容易了

思考一个问题:当我们应用层调用open、read、write、close的时候,内核层是如何实现的呢?
前面学习字符设备驱动模型中有一个file_operation结构体,当我们调用open函数的时候,内核会调用file_operation结构体的open函数指针指向的函数。

我们来看一下file_operation结构体的样子:

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);  //llseek对应了系统提供的lseek接口,实现函数指针位置的定位
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//当用户层调用系统层提供的read接口的时候需要通过此函数指针所指向的接口来实现对应的操作
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//当用户层调用系统层提供的write接口的时候需要通过此函数指针所指向的接口来实现对应的操作
	unsigned int (*poll) (struct file *, struct poll_table_struct *);//  当需要进行轮询操作的时候调用的底层接口,对应了系统层的select和poll
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);//struct inode *:内核内部用来标识文件的数据结构  
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*fasync) (int, struct file *, int);
};
(一)open函数接口

系统层接口:

int open(const char *pathname, int flags, mode_t mode);
 //O_CREAT
 //O_NONBLOCK or O_NDELAY

内核层接口:

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

struct inode :内核中用来标识文件的数据结构,此数据结构的成员无需程序员手动赋值,而是内核中已经赋予了与文件相对应的操作值
struct file *:该结构体标识了一个打开的文件,系统会为每一个打开的文件关联一个struct file 数据结构,是在内核打开文件的同时,将该参数传递到和文件操作相关的所有需要该参数的接口中

(二)read函数接口

系统层:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

内核层:

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

read接口可以直接将内核空间的数据传递到用户空间,但是一般在开发驱动的过程中不会直接采用这种方式,原因是本操作需要两个空间地址,即用户空间和内核空间,用户空间直接操作内核地址是非常危险的,常用copy_to_user和copy_from_user进行用户空间和内核空间交换数据。

(三)lseek函数接口

系统层:

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

内核层:

loff_t (*llseek) (struct file *, loff_t, int);

struct file *:文件结构体
loff_t:上层传递的偏移量
int :文件光标定位状态

SEEK_SET:将光标定位在文件的开头,此时loff_t的值为正数
SEEK_CUR:将光标定位在当前位置,此时loff_t的值为可正可负
SEEK_EDN:将光标定位在文件的结尾,此时loff_t的值为负数

在这里面可以实现文件偏移操作,例如:

loff_t cdev_lseek(struct file *fp, loff_t offset, int whence)
{
   //获取偏移量需要offset和whence结合
   loff_t newoff=0;
   switch(whence)
   	{
   	  case SEEK_SET: newoff=offset;break;
	  case SEEK_CUR: newoff=fp->f_pos+offset;break;
	  case SEEK_END: newoff=offset+4;break;
   	}
   if(newoff >4)newoff=4;
   if(newoff<0)newoff=0;
   fp->f_pos = newoff;
   return newoff;

}
(四)用户空间和用户空间交换数据

copy_to_user:将内核空间的数据拷贝到用户空间

static inline long copy_to_user(void __user *to,const void *from, unsigned long n)
{
   to:用户空间的地址
   from:内核空间的地址
   n:传递数据的大小 
	might_sleep();
    #define VERIFY_WRITE 1
	if (access_ok(VERIFY_WRITE, to, n))
		return __copy_to_user(to, from, n);
	else
		return n;
}

copy_from_user:将用户空间的数据拷贝到内核空间

static inline long copy_from_user(void *to,const void __user * from, unsigned long n)
{
	might_sleep();
	if (access_ok(VERIFY_READ, from, n))
		return __copy_from_user(to, from, n);
	else
		return n;
}
(五)通过设备节点提取设备号
//通过设备节点提取次设备号
static inline unsigned iminor(const struct inode *inode)
{
	return MINOR(inode->i_rdev);
}
//通过设备节点提取次主设备号
static inline unsigned imajor(const struct inode *inode)
{
	return MAJOR(inode->i_rdev);
}
(六)映射ioremap

程序中在操作物理硬件地址的时候不要直接操作对应的地址,需要先进行映射操作

static inline void __iomem *ioremap(phys_addr_t offset, unsigned long size)
{
	return (void __iomem*) (unsigned long)offset;
}
typedef u32 phys_addr_t;
phys_addr_t offset:指的是映射的物理地址
unsigned long size:映射空间的大小
void __iomem *:接收映射后的起始地址

解除映射:

void iounmap (volatile void __iomem *addr)
(七)实例:LED驱动编程

思路:
首先把需要操作的寄存器物理地址进行映射,然后在open函数中做初始化工作,最后在read/write函数中调用copy_to/from_user函数将用户空间(内核空间)的数据拷贝到内核空间(用户空间),对数据进行操作
led.c

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>

int i=0;
dev_t dev=0;
#define CDEVCOUNT 5
#define CDEVNAME "cdevdevice"
#define CDEVCLASS "myclass"
#define INODENAME "mycdev"

#define ADDRSZIE 8

unsigned int  phy_addr = 0x110002E0;//映射的起始地址为GPM4CON
unsigned int * virt_addr = NULL;//用来接收映射后的起始地址

struct cdev * cdev=NULL;
struct class * cdevclass=NULL;

#define GPM4CON (*(volatile unsigned int * )virt_addr)
#define GPM4DAT (*(volatile unsigned int * )(virt_addr +1))	

int cdev_open (struct inode *node, struct file *file)
{
   //清空配置寄存器
   GPM4CON &= ~(0XFFFF<<0);
   //设置引脚为输出状态
   GPM4CON |= (0x1111<<0);
   //给指定寄存器初始化
   GPM4DAT |= (0x0F<<0);
	printk("cdev_open is install\n");
	return 0;
}
ssize_t cdev_read (struct file *fp, char __user *buf, size_t size, loff_t *offset)
{
	printk("cdev_read is install\n");
	return 0;
}
ssize_t cdev_write (struct file *fp, const char __user * buf, size_t size, loff_t *offset)
{
    int i=0;
	char str[4]={-1,-1,-1,-1};

	int ret =copy_from_user(str,buf,4);
	for(i=0;i<4;i++)
		{
 			if(str[i]=='0')
				GPM4DAT |= (1<<i);
			else 
				if(str[i]=='1')
					GPM4DAT &=~(1<<i);
		}
	printk("cdev_write is install\n");
	return 0;
}
int cdev_release (struct inode *node, struct file *fp)
{
	printk("cdev_release is install\n");
	return 0;
}
struct file_operations fop={
	.open=cdev_open,
	.read=cdev_read,
	.write=cdev_write,
	.release=cdev_release,
};
static int __init cdev_module_init(void)
{
	int ret=alloc_chrdev_region(&dev, 0, CDEVCOUNT, CDEVNAME);
	if(ret)
	{
		return -1;
	}
	cdev=cdev_alloc();
	if (!cdev)
		goto out;
	cdev_init(cdev, &fop);
	if(cdev_add(cdev, dev, CDEVCOUNT))
	{
		goto out1;
	}
  printk("cdev_add success\n");
	cdevclass=class_create(THIS_MODULE,CDEVCLASS);
	if (IS_ERR(cdevclass))
	{
		goto out2;
	}
  printk("class_create success\n");
  for(i=0;i<5;i++)
	  device_create(cdevclass,NULL, dev+i, NULL, "mycdev%d",i );
  printk("device_create success\n");
  virt_addr = ioremap(phy_addr, ADDRSZIE);
	return 0;
	out:
		unregister_chrdev_region(dev,CDEVCOUNT);
		return -2;
	out1:
		unregister_chrdev_region(dev,CDEVCOUNT);
		kfree(cdev);
	out2:
		cdev_del(cdev);
		unregister_chrdev_region(dev,CDEVCOUNT);
		kfree(cdev);
		return PTR_ERR(cdevclass);
}
static void __exit cdev_module_cleanup(void)
{
  for(--i;i>=0;i--)
	  device_destroy(cdevclass,dev+i);
  printk("device_destroy success\n");
	class_destroy(cdevclass);
    cdev_del(cdev);
	unregister_chrdev_region(dev,CDEVCOUNT);
	kfree(cdev);
	iounmap(virt_addr);
  printk("kfree success\n");
}
module_init(cdev_module_init);
module_exit(cdev_module_cleanup);
MODULE_LICENSE("GPL");

led_app.c

#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
  char str[]={'1','1','1','0'};
  int fd= open(argv[1],O_RDWR);
  if(fd== -1)
  {
    perror("open");
    return -1;
  }
  write(fd,str,4);
  close(fd);
  return 0;
}

Makefile

CFLAG =-C
TARGET = led
TARGET1 = led_app
KERNEL = /mydriver/linux-3.5
obj-m += $(TARGET).o

all:
	make $(CFLAG)  $(KERNEL) M=$(PWD)
	arm-linux-gcc -o $(TARGET1) $(TARGET1).c
clean:
	make $(CFLAG)  $(KERNEL) M=$(PWD) clean

本文章仅供学习交流用禁止用作商业用途,文中内容来水枂编辑,如需转载请告知,谢谢合作

微信公众号:zhjj0729

微博:文艺to青年

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值