【Linux驱动】普通字符设备驱动程序框架

简介: 【Linux驱动】普通字符设备驱动程序框架

一、普通字符设备驱动设计流程

------------------------定义一个普通字符设备---------------------------

1)定义一个普通字符设备

2)定义普通字符设备所对应的文件操作集

3)给普通字符设备申请一个设备号

4)初始化普通字符设备

5)将普通字符设备加入到内核中

--------------------------给定一个设备文件,给应用程序提供访问---------

6)创建一个class

7) 创建一个device,并且该device要属于class ------》指定一个设备文件名,比如:led_dev/gec6818led

------------------------申请物理内存,并得到物理地址对应的虚拟地址---------

8)申请物理内存区

9)完成io动态映射—>得到物理地址与虚拟地址之间关系

10)在驱动程序中,通过虚拟地址来访问硬件

二、分析

1、定义一个普通字符设备

一个驱动程序是可以由内核模块来体现出来,所以在驱动设计时,它的载体就是一个内核模块

头文件:

kernel\include\linux\cdev.h

1 struct cdev {
2 struct kobject kobj;
3 struct module *owner;
4 const struct file_operations *ops;
5 struct list_head list;
6 dev_t dev;
7 unsigned int count;
8 };

每一个字符设备都可以 看作是一个struct cdev 变量,比如:

struct cdev gec6818led_cdev;

对该结构体关键成员进行说明:

1)struct kobject kobj; —> 内核对象管理者,使用object,该成员由内核自己来实现

2)struct module *owner; ----> 内核模块对象,说明当前驱动程序是属于哪一个内核模块,固定写法:THIS_MODULE

3) const struct file_operations *ops;----->文件操作集,字符设备都一个对应的文件操作集接口

struct file_operations gec6818led_fops;

gec6818led_cdev.ops = &gec6818led_fops;---->给一个字符设备设置一个文件操作

4)struct list_head list; —>管理字符设备的链表,由内核自己实现

5)dev_t dev; ---->设备号---->本质上是一个无 符号整型数,由程序员完成

6)unsigned int count; ----->一个字符设备的次设备号的数目 ,程序员完成

2、定义普通字符设备所对应的文件操作集

kernel\include\linux\fs.h

1 struct file_operations {
2 struct module *owner;
3 loff_t (*llseek) (struct file *, loff_t, int);
4 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
5 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
6 ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
7 ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
8 int (*readdir) (struct file *, void *, filldir_t);
9 unsigned int (*poll) (struct file *, struct poll_table_struct *);
10 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
11 long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
12 int (*mmap) (struct file *, struct vm_area_struct *);
13 int (*open) (struct inode *, struct file *);
14 int (*flush) (struct file *, fl_owner_t id);
15 int (*release) (struct inode *, struct file *);
16 int (*fsync) (struct file *, loff_t, loff_t, int datasync);
17 int (*aio_fsync) (struct kiocb *, int datasync);
18 int (*fasync) (int, struct file *, int);
19 int (*lock) (struct file *, int, struct file_lock *);
20 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
21 unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned l
ong);
22 int (*check_flags)(int);
23 int (*flock) (struct file *, int, struct file_lock *);
24 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
25 ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
26 int (*setlease)(struct file *, long, struct file_lock **);
27 long (*fallocate)(struct file *file, int mode, loff_t offset,
28 loff_t len);
29 };

比如:

//定义的接口,要根据应用程序中,接口对应

int gec6818led_open (struct inode *node, struct file *file)
{
return 0;
}
static struct file_operations gec6818led_fops = {
.owner = THIS_MODULE,
.open = gec6818led_open,
};

3、给普通字符设备申请一个设备号

设备号: dev_t dev

设备号是由主设备和次设备组合而成

设备号 = 主设备<<20,再与次设备号进行位运算得到设备号

3.1 产一个设备号 —>用宏函数实现

#define MINORBITS 20

#define MINORMASK ((1U << MINORBITS) - 1)

1)由主设备号和次设备号得到设备号

#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

dev_t gec6818led_dev = MKDEV(major,minor);

2)由设备号得到主设备号和次设备号

#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) —>

#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))

static unsigned int major = MAJOR(gec6818led_dev);

static unsigned int minor = MINOR(gec6818led_dev);


3.2 将设备号注册到内核中

1)静态注册

1 /**
2 * register_chrdev_region() ‐ register a range of device numbers
3 * @from: the first in the desired range of device numbers; must include
4 * the major number.
5 * @count: the number of consecutive device numbers required
6 * @name: the name of the device or driver.
7 *
8 * Return value is zero on success, a negative error code on failure.
9 */
10 int register_chrdev_region(dev_t from, unsigned count, const char *name)

功能:向内核中注册一个设备号,由程序员提供一个设备号,向内核进行申请,并不一定成功

参数说明:

参数一:dev_t from —>要向内核申请的设备号,做为一个传入参数

参数二: unsigned count —>连续编号的次设备号数目

参数三:const char *name —> 设备的名字

返回值:

成功:0

失败:负数

2)动态注册

1 /**
2 * alloc_chrdev_region() ‐ register a range of char device numbers
3 * @dev: output parameter for first assigned number
4 * @baseminor: first of the requested range of minor numbers
5 * @count: the number of minor numbers required
6 * @name: the name of the associated device or driver
7 *
8 * Allocates a range of char device numbers. The major number will be
9 * chosen dynamically, and returned (along with the first minor number)
10 * in @dev. Returns zero or a negative error code.
11 */
12 int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
13 const char *name)

功能:由内核动态分配一个设备号

参数说明:

参数一:dev_t *dev ---->内核动态分配的设备号,----输出参数

参数二:unsigned baseminor ---->第一个次设备号

参数三: unsigned count —>连续编号的次设备号数目

参数四:const char *name —> 设备的名字

返回值:

成功:0

失败:负数

备注:对于内核的资源中,当申请的设备号使用完时,要及时还回给系统,所以资源申请和释放在内核中要保持成对 使用

3)释放设备号

1 /**
2 * unregister_chrdev_region() ‐ return a range of device numbers
3 * @from: the first in the range of numbers to unregister
4 * @count: the number of device numbers to unregister
5 *
6 * This function will unregister a range of @count device numbers,
7 * starting with @from. The caller should normally be the one who
8 * allocated those numbers in the first place...
9 */
10 void unregister_chrdev_region(dev_t from, unsigned count)

功能:将设备号返回给内核

参数说明:

参数一:dev_t from----要释放的设备号

参数二:unsigned count ---- 要释放次设备号的数目

对于程序设计而言,使用一个兼容的方式,可以提供静态或者动态注册

/*
* Register a single major with a specified minor range.
*
* If major == 0 this functions will dynamically allocate a major and return
* its number.
*
* If major > 0 this function will attempt to reserve the passed range of
* minors and will return zero on success.
*
* Returns a -ve errno on failure.
*/

主设备号为0 ------》则说明为动态注册,否则,就是静态注册

4、 初始化普通字符设备

函数原型:

1 void cdev_init(struct cdev *, const struct file_operations *);

参数说明:

参数一:struct cdev * ----> 要初始化的字符设备

参数二: const struct file_operations * ----->要初始化字符设备的文件操作集

5、 将字符设备加入到内核中

5.1 增加字符设备到内核中

函数原型:

1 /**
2 * cdev_add() ‐ add a char device to the system
3 * @p: the cdev structure for the device
4 * @dev: the first device number for which this device is responsible
5 * @count: the number of consecutive minor numbers corresponding to this
6 * device
7 *
8 * cdev_add() adds the device represented by @p to the system, making it
9 * live immediately. A negative error code is returned on failure.
10 */
11 int cdev_add(struct cdev *p, dev_t dev, unsigned count)

参数说明:

参数一:struct cdev *p ---->要增加的字符设备

参数二:dev_t dev ----->字符设备的设备号

参数三:unsigned count ----> 次设备号的数目

返回值:

成功:0

失败:负数

5.2 从内核中删除字符设备

函数原型

1 /**
2 * cdev_del() ‐ remove a cdev from the system
3 * @p: the cdev structure to be removed
4 *
5 * cdev_del() removes @p from the system, possibly freeing the structure
6 * itself.
7 */
8 void cdev_del(struct cdev *p)

参数说明:

参数一:struct cdev *p ---->要删除的字符设备

6、 创建一个class

头文件:kernel\include\linux\device.h

#include <linux/device.h>

6.1 创建class

函数原型:

1 /* This is a #define to keep the compiler from merging different
2 * instances of the __key variable */
3 #define class_create(owner, name) \
4 ({ \
5 static struct lock_class_key __key; \
6 __class_create(owner, name, &__key); \
7 })
8 owner, name参数的类型要根据__class_create,除此之外,__class_create函数的返回值
9 也是class_create函数返回值
10 class_create函数原型如下:
11 /**
12 * class_create ‐ create a struct class structure
13 * @owner: pointer to the module that is to "own" this struct class
14 * @name: pointer to a string for the name of this class.
15 * @key: the lock_class_key for this class; used by mutex lock debugging
16 *
17 * This is used to create a struct class pointer that can then be used
18 * in calls to device_create().
19 *
20 * Returns &struct class pointer on success, or ERR_PTR() on error.
21 *
22 * Note, the pointer created here is to be destroyed when finished by
23 * making a call to class_destroy().
24 */
25 struct class *__class_create(struct module *owner, const char *name,
26 struct lock_class_key *key)

综上所述,最张得到可使用的接口原型如下:

1 struct class * class_create(struct module *owner, const char *name)

功能:创建一个class

参数说明:

参数一:struct module *owner ----> 说明创建的class是属于哪一个模块---->固定写法:THIS_MODULE

参数二: const char *name ----> class名字 ---->在开发板系统中可以查看

返回值:

成功:struct class *

失败:NULL

6.2 销毁class

函数原型:

1 /**
2 * class_destroy ‐ destroys a struct class structure
3 * @cls: pointer to the struct class that is to be destroyed
4 *
5 * Note, the pointer to be destroyed must have been created with a call
6 * to class_create().
7 */
8 void class_destroy(struct class *cls)

参数说明:

struct class *cls ---->要销毁的class

7、创建device

----这一步的目的是为了得到一个设备文件的名字,在开发板上的/dev目录下可以查看

7.1 创建一个device

函数原型:

1 /**
2 * device_create ‐ creates a device and registers it with sysfs
3 * @class: pointer to the struct class that this device should be registered to
4 * @parent: pointer to the parent struct device of this new device, if any
5 * @devt: the dev_t for the char device to be added
6 * @drvdata: the data to be added to the device for callbacks
7 * @fmt: string for the device's name
8 *
9 * This function can be used by char device classes. A struct device
10 * will be created in sysfs, registered to the specified class.
11 *
12 * A "dev" file will be created, showing the dev_t for the device, if
13 * the dev_t is not 0,0.
14 * If a pointer to a parent struct device is passed in, the newly created
15 * struct device will be a child of that device in sysfs.
16 * The pointer to the struct device will be returned from the call.
17 * Any further sysfs files that might be required can be created using this
18 * pointer.
19 *
20 * Returns &struct device pointer on success, or ERR_PTR() on error.
21 *
22 * Note: the struct class passed to this function must have previously
23 * been created with a call to class_create().
24 */
25 struct device *device_create(struct class *class, struct device *parent,
26 dev_t devt, void *drvdata, const char *fmt, ...)

功能:创建一个device,并且注册到系统的文件系统中

参数说明:

参数一:struct class *class ---->说明device是属于哪 一个class的,class_create的函数返回值

参数二:struct device *parent —>当前创建设备的父设备,一般设置NULL

参数三:dev_t devt —> 设备的设备号,查看设备文件时,查看设备号

参数四:void *drvdata —> 创建device时,系统回调的数据,不需时,也使用NULL

参数五: const char *fmt ----> 设备文件的名字,可以在/dev目录下查看

返回值:

成功:struct device *

失败:NULL

7.2 销毁device

函数原型:

1 /**
2 * device_destroy ‐ removes a device that was created with device_create()
3 * @class: pointer to the struct class that this device was registered with
4 * @devt: the dev_t of the device that was previously registered
5 *
6 * This call unregisters and cleans up a device that was created with a
7 * call to device_create().
8 */
9 void device_destroy(struct class *class, dev_t devt)

参数说明:

参数一:struct class *class -----device的class

参数二: dev_t devt ---- 设备的设备号

8、 申请物理内存区

头文件:#include <linux/ioport.h>

8.1 申请物理内存区

宏原型如下:

1 #define request_mem_region(start,n,name)
2 __request_region(&iomem_resource, (start), (n), (name), 0)
3
4 start,n,name 三个参数的类型源于__request_region函数
5 /**
6 * __request_region ‐ create a new busy resource region
7 * @parent: parent resource descriptor
8 * @start: resource start address
9 * @n: resource region size
10 * @name: reserving caller's ID string
11 * @flags: IO resource flags
12 */
13 struct resource * __request_region(struct resource *parent,
14 resource_size_t start, resource_size_t n,
15 const char *name, int flags)

综上所述,可得request_mem_region的原型:

1 struct resource *request_mem_region(resource_size_t start, resource_size_t n,
2 const char *name)

参数说明:

参数一:resource_size_t -----> 要申请的物理内存的首地址

#ifdef CONFIG_PHYS_ADDR_T_64BIT

typedef u64 phys_addr_t;

#else

typedef u32 phys_addr_t;

#endif

typedef phys_addr_t resource_size_t;

参数二:resource_size_t n —> 要申请物理内存区的大小

参数三: const char *name ---->申请成功后,该物理内存区的名字,在开发板的/proc/iomem查看

返回值:

成功:struct resource *

失败:NULL

8.2 释放物理内存区

宏的原型:

1 #define release_mem_region(start,n)
2 __release_region(&iomem_resource, (start), (n))
3 /**
4 * __release_region ‐ release a previously reserved resource region
5 * @parent: parent resource descriptor
6 * @start: resource start address
7 * @n: resource region size
8 *
9 * The described resource region must match a currently busy region.
10 */
11 void __release_region(struct resource *parent, resource_size_t start,
12 resource_size_t n)

综上所述,宏函数的原型如下:

1 void release_mem_region(resource_size_t start,
2 resource_size_t n)

参数说明:

参数一:resource_size_t -----> 要申请的物理内存的首地址

参数二:resource_size_t n —> 要申请物理内存区的大小


9、IO映射 ----- 目的是为了建立物理地址与虚拟地址的映射关系

头文件:#include <linux/io.h>

9.1 IO动态映射

1 #define ioremap(cookie,size)
2 __arm_ioremap((cookie), (size), MT_DEVICE)
3 cookie,size参数的类型源自于函数__arm_ioremap
4 void __iomem *__arm_ioremap(unsigned long phys_addr, size_t size,
5 unsigned int mtype)

综上所述,宏函数的原型如下:

1 void __iomem *ioremap(unsigned long phys_addr, size_t size)

参数说明:

参数一:unsigned long phys_addr ---->要进行映射的物理首地址(芯片手册)

参数二:size_t size ----> 要映射地址的大小

返回值:

成功:void __iomem * ----虚拟地址的首地址

失败:NULL

9.2 解映射

1 #define iounmap __arm_iounmap
2 void __arm_iounmap(volatile void __iomem *addr)


三、驱动代码

#include <linux/module.h> //内核模块接口头文件
#include <linux/printk.h> //printk函数头文件
#include <linux/cdev.h> // struct cdev
#include <linux/fs.h>  //struct file_operations
#include <linux/device.h> //struct class
#include <linux/err.h> //#define  ENOMEM    12
#include <linux/io.h> //ioremap
#include <linux/ioport.h> //request_mem_region
#include <linux/uaccess.h> //copy_from_user

static dev_t gec6818led_dev;//定义一个设备号
static unsigned int major = 0; //主设备号表示该设备是哪一类设备
static unsigned int minor = 0; //次设备号表示这类设备中哪一个设备

static struct class *gec6818led_class = NULL;
static struct device *gec6818led_device = NULL;
static struct resource *gec6818led_res = NULL;

//虚拟地址:
static void __iomem *gec6818led_va_base = NULL;
//GPIOEOUT ----0xC001E000
static void __iomem *gec6818led_va_eout = NULL;
//GPIOEOUTENB --- 0xC001E004
static void __iomem *gec6818led_va_eoutenb = NULL;
//GPIOEALTFN0 ---- 0xC001E020
static void __iomem *gec6818led_va_ealtfn0 = NULL;

int gec6818led_open (struct inode *node, struct file *file)
{

  return 0;
}

/*
驱动程序与应用程序之间的数据传输:
问题一:
  数据协议:系统调用(open write ) ----> struct file_operations --- write
问题二:
    数据格式:控灯:一个灯  定义一个数组:char kbuf[1]  --- kbuf[0] --->表示控制灯的命令:1 ---开灯 0 --- 关灯 
                   多个灯  定义一个数组:char kbuf[2] ---- kbuf[0] --->表示控制灯的命令,kbuf[1] --- 表示灯的编号
  驱动程序和应用程序之间的数据协议+数据格式要保持 一致
  
  对于驱动程序使用的内核空间,而应用程序使用的是用户空间,两个空间之间不能直接相互访问,只能通过特定的接口:
  copy_to_user
  copy_from_user
*/
ssize_t gec6818led_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
   printk(KERN_WARNING "gec6818led_write\n");
  //[1]定义数据格式
  char kbuf[1];
  int ret;
  //int copy_from_user(void *to, const void __user *from, int n)
  ret = copy_from_user(kbuf,buf,size); //从用户空间把数据获取出来存放在kbuf中
  //[2] 根据数据的命令来实现控制硬件
  if(kbuf[0] == 1) //开灯
  {
     *((volatile unsigned int*)gec6818led_va_eout) &= ~(1<<13);
  }else if(kbuf[0] == 0)//关灯
  {
     *((volatile unsigned int*)gec6818led_va_eout) |= (1<<13);
  }
  return size;
}
//2.定义一个普通字符设备的文件操作集对象
static struct file_operations gec6818led_fops = {
  .owner = THIS_MODULE,
  .open = gec6818led_open,
  .write = gec6818led_write,
};

//1.定义一个普通字符设备对象
static struct cdev  gec6818led_cdev;





static int __init gec6818led_init(void)
{
  int ret;
    printk(KERN_WARNING "gec6818led_init\n");
  
  //3.申请一个设备号,设备号就相当于一个人的身份证号码一样,在Linux它是唯一,设备号还要注册到linux内核中,
  //如果内核中没有,则说明设备号可用,否则,不行
  if(major == 0)//动态注册
  {
    //int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
      //const char *name)
    ret = alloc_chrdev_region(&gec6818led_dev,minor,1,"gec6818led_number");
  }else //静态注册
  {
    //先由主设备号和次设备号生成一个设备号
    gec6818led_dev = MKDEV(major,minor);
    //再将设备号注册到内核中 int register_chrdev_region(dev_t from, unsigned count, const char *name)
    ret = register_chrdev_region(gec6818led_dev,1,"gec6818led_number");
  }
  
    if(ret != 0)
    {
      printk(KERN_WARNING "register_device_number error\n");
      goto register_device_number_error;
    }
  //4.初始化普通字符设备 void cdev_init(struct cdev *, const struct file_operations *);
  cdev_init(&gec6818led_cdev,&gec6818led_fops); //在初始化函数中,对普通字符设备与文件操作集进行了关联
  
  //5. 增加字符设备到内核中 int cdev_add(struct cdev *p, dev_t dev, unsigned count)
  ret = cdev_add(&gec6818led_cdev,gec6818led_dev,1);
   if(ret != 0)
    {
      printk(KERN_WARNING "cdev_add error\n");
      goto cdev_add_error;
    }
    //6.创建一个class struct class * class_create(struct module *owner, const char *name)
    gec6818led_class = class_create(THIS_MODULE,"gec6818led_class");
    if(gec6818led_class == NULL)
    {
       printk(KERN_WARNING "class_create error\n");
       //如果函数返回的值不是整型,但是,总函数中的返回值为整型 ,所以,要重新给ret设置一个返回值
       ret =  -ENOMEM; // #define ENOMEM    12  /* Out of memory */ 这个宏常量的头文件引用进来
       goto class_create_error;
    }
    //7.创建device struct device *device_create(struct class *class, struct device *parent,
         //  dev_t devt, void *drvdata, const char *fmt, ...)
    gec6818led_device = device_create(gec6818led_class,NULL,gec6818led_dev,NULL,"gz1or2_led_drv");
    if(gec6818led_device == NULL)
    {
       printk(KERN_WARNING "device_create error\n");
       ret = -ENOMEM;
       goto device_create_error;
    }
    
    //8.申请物理内存区
    //struct resource *request_mem_region(resource_size_t start, resource_size_t n,
    //       const char *name)
    //该函数的参数的传递,要根据具体的硬件的地址来确定,以D7 --- GPIOE13  0XC001E000
    //申请物理内存区的大小,最好跟GPIO口分组:A组的大小:0XC001A000 B:0xc001b000 ---> B-A --0x1000--4kb
    gec6818led_res = request_mem_region(0XC001E000,0x1000,"GPIOE_MEM");
    if(gec6818led_res == NULL)
    {
       printk(KERN_WARNING "request_mem_region error\n");
       ret =  -EBUSY; //#define EBUSY   16  /* Device or resource busy */
       goto request_mem_region_error;
    }
    //9.Io动态映射 void __iomem *ioremap(unsigned long phys_addr, size_t size)
    gec6818led_va_base = ioremap(0XC001E000,0x1000);
    
       if(gec6818led_va_base == NULL)
    {
       printk(KERN_WARNING "ioremap error\n");
       ret =  -EBUSY; //#define EBUSY   16  /* Device or resource busy */
       goto ioremap_error;
    }
    gec6818led_va_eout = gec6818led_va_base + 0x00;
    gec6818led_va_eoutenb = gec6818led_va_base + 0x04;
    gec6818led_va_ealtfn0 = gec6818led_va_base + 0x20;
    
    //[1] 设置引脚的功能
     *((volatile unsigned int*)gec6818led_va_ealtfn0) &= ~(3<<26);
     //[2]设置引脚的工作模式:输出
      *((volatile unsigned int*)gec6818led_va_eoutenb) |= (1<<13);
       //[3] 默认设置LED灯为亮
       *((volatile unsigned int*)gec6818led_va_eout) &= ~(1<<13);
     
  return 0;
  ioremap_error:
  release_mem_region(0XC001E000,0x1000);
  request_mem_region_error:
  device_destroy(gec6818led_class,gec6818led_dev);
  device_create_error:
  class_destroy(gec6818led_class);
   gec6818led_class = NULL;
  class_create_error:
  cdev_del(&gec6818led_cdev);
  cdev_add_error:
  unregister_chrdev_region(gec6818led_dev,1);
  register_device_number_error:
  return ret;
  
}

static void __exit gec6818led_exit(void)
{
    printk(KERN_WARNING "gec6818led_exit\n");
  //进行资源的释放 
  iounmap(gec6818led_va_base);
  gec6818led_va_base = NULL;
  release_mem_region(0XC001E000,0x1000);
  //void device_destroy(struct class *class, dev_t devt)
  device_destroy(gec6818led_class,gec6818led_dev);
  //void class_destroy(struct class *cls)
   class_destroy(gec6818led_class);
   gec6818led_class = NULL;
  //void cdev_del(struct cdev *p)
  cdev_del(&gec6818led_cdev);
  
  //void unregister_chrdev_region(dev_t from, unsigned count)
  
  unregister_chrdev_region(gec6818led_dev,1);
}

module_init(gec6818led_init);
module_exit(gec6818led_exit);

MODULE_AUTHOR("gec.zhang3");//驱动作者
MODULE_DESCRIPTION("GEC6818 Led driver");//驱动的描述
MODULE_LICENSE("GPL"); //module所遵守的协议许可,GPL---->公共通用许可证


四、应用代码

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



int main(void)
{
  char buf[4096];
  //1.打开文件
  int fd = open("/udata/rfid_test.c",O_RDWR);
  if(fd == -1)
  {
    perror("open");
    return -1;
  }
  
  //2.读数据
  read(fd,buf,sizeof(buf));
  printf("data = %s\n",buf);
  
  
  //3.关闭文件
   close(fd);
  return 0;
}
相关文章
|
1天前
|
Oracle 关系型数据库 Linux
讲解linux下的Qt如何编译oracle的驱动库libqsqloci.so
通过这一连串的步骤,可以专业且有效地在Linux下为Qt编译Oracle驱动库 `libqsqloci.so`,使得Qt应用能够通过OCI与Oracle数据库进行交互。这些步骤适用于具备一定Linux和Qt经验的开发者,并且能够为需要使用Qt开发数据库应用的专业人士提供指导。
10 1
讲解linux下的Qt如何编译oracle的驱动库libqsqloci.so
|
14天前
|
安全 小程序 Linux
Linux中信号是什么?Ctrl + c后到底为什么会中断程序?
信号在进程的学习中是一个非常好用的存在,它是软件层次上对中断机制的一种模拟,是异步通信方式,同时也可以用来检测用户空间到底发生了什么情况,然后系统知道后就可以做出相应的对策。
|
14天前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
|
17天前
|
JavaScript Linux
【详细讲解】Linux grep命令用法大全 片尾有示例搜索指定目录中指定文件后缀的指定字符
【详细讲解】Linux grep命令用法大全 片尾有示例搜索指定目录中指定文件后缀的指定字符
34 1
|
13天前
|
Linux
Linux02---命令基础 Linux命令基础, ls命令入门,ls命令参数和选项,命令行是一种以纯字符操作系统的方式,command命令本身,options命令的细节行为,parameter命令的
Linux02---命令基础 Linux命令基础, ls命令入门,ls命令参数和选项,命令行是一种以纯字符操作系统的方式,command命令本身,options命令的细节行为,parameter命令的
|
13天前
|
运维 监控 大数据
部署-Linux01,后端开发,运维开发,大数据开发,测试开发,后端软件,大数据系统,运维监控,测试程序,网页服务都要在Linux中进行部署
部署-Linux01,后端开发,运维开发,大数据开发,测试开发,后端软件,大数据系统,运维监控,测试程序,网页服务都要在Linux中进行部署
|
14天前
|
Linux 开发者
Linux底层驱动社区饮水机系统详解
在Linux驱动开发中,入门时通常会关注驱动程序的三大核心步骤:入口函数、出口函数和声明许可证。这些步骤构成了驱动程序的基本结构,是驱动与内核交互的基础。下面是对这三个步骤的简要说明:
|
21天前
|
存储 Linux
深入了解Linux设备管理:字符、块和网络设备文件
深入了解Linux设备管理:字符、块和网络设备文件
24 0
|
21天前
|
缓存 Linux 编译器
技术笔记:Linux程序包管理
技术笔记:Linux程序包管理
|
21天前
|
Java 编译器 Linux
程序技术好文:详解Linux安装GCC方法
程序技术好文:详解Linux安装GCC方法
30 0