1 裸机驱动和linux驱动的区别
相同:都要进行硬件实现层的设计(硬件裸机驱动)
不同:
- linux操作系统中加了众多接口,从系统API到硬件实现层调用过程中的系统框架
- 同步,互斥,POLL,并发, 自旋锁,信号量 ,等待队列 standby,;linux机制同样要体现在内核驱动中
- 驱动要服从linux内核管理:设备树,电源管理的全局构架
- 不同CPU或不同linux版本的移植
2 linux的架构
2.1 linux0.11 字符驱动
下面是从用户层的系统调用,到虚拟文件系统,到真是驱动函数的过程
2.2 linux2.6 字符驱动
__register_chardev_region
把创建传进来的file_operations打包成char_device_struct
并且把这个变量放到 全面变量 chrdevs数组中 设备号 指定的位置中
一个设备(major,minor)最多对应256个major
并且每一个major只能对应一个fops
使用:
与0.11结构完全一样,不一样的是堆结构体对象进行的封装和扩充
static struct file_opeations Tchardrv_fops = {
/* data */
.owner = THIS_MODULE,
.read = ReadChar,
};
//init前加__, 会把这部分代码加载到指定的段中 #define __init __attribute__ ((__section__ (".text.init"))), 运行完后,系统会自动把这个段释放掉
static int major
static __init InitChardrv(void)
{
// int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
major = register_chrdev(major, "char_devfisrt", &Tchardrv_fops)
}
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
//帧缓冲设备中,如果使用定义了MMAP,则可以在应用程序中进行对应的帧缓冲读写,不需要做用户到操作系统内核的内存拷贝
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);//打开设备,对应insmod
int (*flush) (struct file *, fl_owner_t id);//冲刷帧
int (*release) (struct inode *, struct file *);//释放设备,对应rmmod
//同步函数
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*dir_notify)(struct file *filp, unsigned long arg);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
};
经过系统调用,chardevs在这个全局结构体数组找到对应major的char_device_struct
static struct char_device_struct {
struct char_device_struct *next;
unsigned int major; //设备号
unsigned int baseminor;
int minorct;
char name[64];
struct file_operations *fops;//设备的各种操作结构函数
struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
__register_chrdev_region
函数会把char_device_struct
添加到chrdevs
中去
用到__register_chrdev_region
函数的有
编写示例驱动
在自己的驱动中使用register_chrdev来进行注册,来添加char_device_struct
//字符设备,GPIO LED设置
static struct class *first_chardev_class;
static struct class_device *first_chardev_class_dev_0;
static struct class_device *first_chardev_class_dev_1;
static int major;
//配置寄存器
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
ssize_t read_fisrt (struct file *file, char __user * buff, size_t count, loff_t *pos)
{
}
//字符设备的写函数,用于控制GPIO LED的开关,1-灯亮 0-灯灭
ssize_t write_fisrt (struct file *file, char __user * buff, size_t count, loff_t *pos)
{
int val;
copy_from_user(&val, buff, count);
if (1 == val)
{
*gpfdata &= ~(0x1<<(4*2) | 0x1<<(5*2) | 0x1<<(6*2));
}
else
{
*gpfdata |= (0x1<<(4*2) | 0x1<<(5*2) | 0x1<<(6*2));
}
return 0;
}
static int open_first(struct inode *inode, struct file *file)
{
printk("inode->irdev = %d\n", inode->i_rdev);
*gpfcon &= ~(0x3<<(4*2) | 0x3<<(5*2) | 0x3<<(6*2));
*gpfcon |= (0x1<<(4*2) | 0x1<<(5*2) | 0x1<<(6*2));
printk("open is ok\n");
}
struct file_operations fisrt_char
{
.owner = THIS_MODULE,
.read = read_fisrt,
.write = write_fisrt,
.open = open_first,
};
static int __init InitChardrv_first(void) //加上__init,放到.init_text段中 运行完后会这个段会被释放掉。不加的话,这个函数会放在其他位置,运行后也不释放
{
//int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops);
//注册一个fops
major = register_chrdev(major, "fisrt_char_dev", &fisrt_char); //这里name是设备驱动的名字,和/dev中名字不一样,dev下名字是文件节点
//可以手动使用mknod在/dev下创建一个节点,然后挂接这个设备。也可以直接用下面函数自动创建节点
//创建一个主设备号对应的设备节点
first_chardev_class = class_create(THIS_MODULE, "first_chrdev");
//创建多个设备节点的子设备 minor, 这里多个从设备对应同一个fops,就是操作同一个设备
first_chardev_class_dev_0 = class_device_create(first_chardev_class, NULL, MKDEV(major, 0), NULL, "firstchar_0");//dev_t devt是major和minor的综合体MKDEV(major, minor)
first_chardev_class_dev_1 = class_device_create(first_chardev_class, NULL, MKDEV(major, 1), NULL, "firstchar_1");
//硬件相关操作
//把物理地址映射到线性地址上去
gpfcon = ioremap(0x56000000, 32);
gpfdat = gpfcon + 1;
return 1;
}
static int __exit ExitChardrv_first(void)//加上__exit, 放到.exit_text段中 运行完后会这个段会被释放掉。
{
unregister_chrdev(major, "fisrt_char_dev");
class_device_destroy(first_chardev_class_dev_0, MKDEV(major, 0));
class_device_destroy(first_chardev_class_dev_1, MKDEV(major, 1));
class_destroy(first_chardev_class);
iounmap(gpfcon);
return 1;
}
module_init(InitChardrv_first); //把函数映射到指定的表中, 在insmod命令时通过表找这个函数
module_exit(ExitChardrv_first); //把函数映射到指定的表中, 在rmmod命令时通过表找这个函数
代码中创建class和class_device类似于创建了下面的结构:
主设备号对应class,次设备号对应class_device。
通过这个结构可以方便的通过设备号来找到对应的fops,一个主设备号通过class指定一个fops,剩下所有的class_device都指向这一个fops
2.3 linux3.4 字符驱动
上面会导致资源浪费,为了解决这个问题,使用cdev
cdev_region(major, minor, size, fops1)
一个major下的minor到minor+size的所有从设备对应fops1
其中chrdevs数组中每个成员都的内容改变了,添加了cdev,会指定某个范围的子设备号指向一个ops,而不再是一个主设备号下所有的子设备号都指向一个ops。增加子设备号了使用率。
上层再调用的时候,从根据major
在数组中找到成员,在根据次设备号minor
获取指定cdev
,再从cdev
中找到fops
添加cdev的方式
alloc_chrdev_region() ; //还没有分配任何cdev,用这个函数来分配。(major = 0 说明是第一次分配)
register_chrdev_region()//前面分配了,就调用这个函数来添加新的cdev。(major!= 0,说明已经分配了)
下面是示例代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/stat.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define MEM_MINORCNT 10
#define KINDLEMEM_SIZE 1024
//命令是一个32位数,由方向,类型,数据,大小组成
//#define _IOC(dir,type,nr,size) \
// (((dir) << _IOC_DIRSHIFT) | \
// ((type) << _IOC_TYPESHIFT) | \
// ((nr) << _IOC_NRSHIFT) | \
// ((size) << _IOC_SIZESHIFT))
#define KINDLE_MAGIC 'k'
#define KINDLE_MEM_CLEAR _IOC(1, KINDLE_MAGIC, 0, 0)
#define OFFSET_HEAD 0
#define OFFSET_CURRENT 1
static int major;
struct kindlemem {
struct cdev cdev;
unsigned char mem[KINDLEMEM_SIZE];
};
struct kindlemem *kindlemem_p;
static ssize_t read_kindlemem(struct file *file, char __user *buf, size_t count, loff_t *off)
{
int ret = 0;
unsigned long pos = *off;
unsigned long size = count;
//获取文件结构体中私有数据
struct kindlemem *kindlemem_tmp = (struct kindlemem *)file->private_data;
if (pos >= KINDLEMEM_SIZE)
return 0;
if (size > KINDLEMEM_SIZE - pos)
size = KINDLEMEM_SIZE - pos;
if (copy_to_user(buf, (kindlemem_tmp->mem) + pos, size))
{
return -EFAULT;
}
else
{
*off += size;
ret = size;
printk(KERN_INFO,"read size = %d frome %d\n", size, *off);
}
return ret;
}
static ssize_t write_kindlemem(struct file *file, const char __user *userbuf, size_t bytes, loff_t *off)
{
int ret = 0;
unsigned long pos = *off;
unsigned long size = count;
//获取文件结构体中私有数据
struct kindlemem *kindlemem_tmp = (struct kindlemem *)file->private_data;
if (pos >= KINDLEMEM_SIZE)
return 0;
if (size > KINDLEMEM_SIZE - pos)
size = KINDLEMEM_SIZE - pos;
if (copy_from_user(kindlemem_tmp->mem + pos, userbuf, size))
{
return -EFAULT;
}
else
{
*off += size;
ret = size;
printk(KERN_INFO,"read size = %d frome %d\n", size, *off);
}
}
static long ioctl_kindlemem (struct file *file, unsigned int cmd, unsigned long arg)
{
//获取文件结构体中私有数据
struct kindlemem *kindlemem_tmp = (struct kindlemem *)file->private_data;
switch (cmd)
{
case KINDLE_MEM_CLEAR:
memset(kindlemem_tmp->mem, 0, KINDLEMEM_SIZE);
printk(KERN_INFO, "clear mem\n");
break;
default:
return -EINIT;
}
}
static loff_t llseek_kindlemem (struct file *file, loff_t offset, int origin)
{
loff_t ret = 0;
switch (origin)
{
case OFFSET_HEAD: //从开始移动
if(offset < 0 || (unsigned int)offset > KINDLEMEM_SIZE)
{
ret = -EINVAL;
break;
}
file->f_pos = (unsigned int)offset;
ret = file->f_pos;
break;
case OFFSET_CURRENT:
if((file->f_pos + offset) < 0 || (unsigned int)(file->f_pos + offset) > KINDLEMEM_SIZE))
{
ret = -EINVAL;
break;
}
file->f_pos += (unsigned int)offset;
ret = file->f_pos;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int open_kindlemem(struct inode *inode, sturct file *file)
{
file->private_data = kindlemem_p;
return 0;
}
int release_kindlemem (struct inode *inode, struct file *file)
{
return 0;
}
static struct file_operations kindlemem_fops = {
.owner = THIS_MODULE,
.read = read_kindlemem,
.write = write_kindlemem,
.llseek = llseek_kindlemem,
.open = open_kindlemem,
.release =release_kindlemem,
.unlock_ioctl = ioctl_kindlemem,
};
//进行cdev的初始化配置
void setup_kindle_cdev(struct kindlemem *dev, int minor)
{
int error;
dev_t devno = MKDEV(major, minor);
cdev_init(&dev->cdev, &kindlemem_fops);//初始化一个cdev,对内部成员ops进行赋值
dev->cdev.owner = THIS_MODULE;
error = cdev_add(&dev->cdev, devno, MEM_MINORCNT);//将初始化完成的cdev根据设备号devno添加到相应的位置
if (error)
{
printk(KERN_NOTICE "ERROR %d %d", devno, minor);
}
memset(dev->mem, 0, KINDLEMEM_SIZE);
}
static int __init init_kindlemem(void )
{
int ret;
dev_t devno = MKDEV(major, 0);
if(major)
{
//register_chrdev_region(dev_t from 从哪开始, unsigned count区域个数, const char * name)
//给major下minor0-10创建一个cdev
ret = register_chrdev_region(devno, MEM_MINORCNT, "kindlemem");
}
else
{
//baseminor:起始位置
// alloc_chrdev_region(dev_t * dev, unsigned baseminor, unsigned count, const char * name)
ret = alloc_chrdev_region(&devno, 0, MEM_MINORCNT, "kindlemem");
major = MAJOR(devno);//上面函数会堆major复制,这里用这个函数来提取
}
kindlemem_p = kzalloc(sizeof(struct kindlemem), GFP_KERNEL);
if (!kindlemem_p)
{
ret = -1;
goto fail_kzalloc;
}
setup_kindle_cdev(kindlemem_p, 0);
if (ret < 0)
goto fail;
return ret;
fail:
printk(KERN_ERR "kindlmem: failed to register chrdev region\n");
return ret;
fail_kzalloc:
printk(KERN_ERR "kindlmem: failed to alloc memory\n");
unregister_chrdev_region(devno, MEM_MINORCNT);
return ret;
}
static int __exit exit_kindlemem(void )
{
cdev_del(&kindlemem_p->cdev);
kfree(kindlemem_p);
unregister_chrdev_region(devno, MEM_MINORCNT);
}
module_init(init_kindlemem);
module_exit(exit_kindlemem);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("David Howells <dak@qq.com>");
当将注册多个cdev时,需要做初始化进行一些修改
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/stat.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define MEM_MINORCNT 10
#define KINDLEMEM_SIZE 1024
//命令是一个32位数,由方向,类型,数据,大小组成
//#define _IOC(dir,type,nr,size) \
// (((dir) << _IOC_DIRSHIFT) | \
// ((type) << _IOC_TYPESHIFT) | \
// ((nr) << _IOC_NRSHIFT) | \
// ((size) << _IOC_SIZESHIFT))
#define KINDLE_MAGIC 'k'
#define KINDLE_MEM_CLEAR _IOC(1, KINDLE_MAGIC, 0, 0)
#define OFFSET_HEAD 0
#define OFFSET_CURRENT 1
static int major;
struct kindlemem {
struct cdev cdev;
unsigned char mem[KINDLEMEM_SIZE];
};
struct kindlemem *kindlemem_p;
static ssize_t read_kindlemem(struct file *file, char __user *buf, size_t count, loff_t *off)
{
int ret = 0;
unsigned long pos = *off;
unsigned long size = count;
//获取文件结构体中私有数据
struct kindlemem *kindlemem_tmp = (struct kindlemem *)file->private_data;
if (pos >= KINDLEMEM_SIZE)
return 0;
if (size > KINDLEMEM_SIZE - pos)
size = KINDLEMEM_SIZE - pos;
if (copy_to_user(buf, (kindlemem_tmp->mem) + pos, size))
{
return -EFAULT;
}
else
{
*off += size;
ret = size;
printk(KERN_INFO,"read size = %d frome %d\n", size, *off);
}
return ret;
}
static ssize_t write_kindlemem(struct file *file, const char __user *userbuf, size_t bytes, loff_t *off)
{
int ret = 0;
unsigned long pos = *off;
unsigned long size = count;
//获取文件结构体中私有数据
struct kindlemem *kindlemem_tmp = (struct kindlemem *)file->private_data;
if (pos >= KINDLEMEM_SIZE)
return 0;
if (size > KINDLEMEM_SIZE - pos)
size = KINDLEMEM_SIZE - pos;
if (copy_from_user(kindlemem_tmp->mem + pos, userbuf, size))
{
return -EFAULT;
}
else
{
*off += size;
ret = size;
printk(KERN_INFO,"read size = %d frome %d\n", size, *off);
}
}
static long ioctl_kindlemem (struct file *file, unsigned int cmd, unsigned long arg)
{
//获取文件结构体中私有数据
struct kindlemem *kindlemem_tmp = (struct kindlemem *)file->private_data;
switch (cmd)
{
case KINDLE_MEM_CLEAR:
memset(kindlemem_tmp->mem, 0, KINDLEMEM_SIZE);
printk(KERN_INFO, "clear mem\n");
break;
default:
return -EINIT;
}
}
static loff_t llseek_kindlemem (struct file *file, loff_t offset, int origin)
{
loff_t ret = 0;
switch (origin)
{
case OFFSET_HEAD: //从开始移动
if(offset < 0 || (unsigned int)offset > KINDLEMEM_SIZE)
{
ret = -EINVAL;
break;
}
file->f_pos = (unsigned int)offset;
ret = file->f_pos;
break;
case OFFSET_CURRENT:
if((file->f_pos + offset) < 0 || (unsigned int)(file->f_pos + offset) > KINDLEMEM_SIZE))
{
ret = -EINVAL;
break;
}
file->f_pos += (unsigned int)offset;
ret = file->f_pos;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int open_kindlemem(struct inode *inode, sturct file *file)
{
struct kindlemem *kindlemem_tmp = container_of(inode->i_cdev, struct kindlemem, cdev);//通过结构体中cdev的位置,找的结构体的地址
file->private_data = kindlemem_tmp;
return 0;
}
int release_kindlemem (struct inode *inode, struct file *file)
{
return 0;
}
static struct file_operations kindlemem_fops = {
.owner = THIS_MODULE,
.read = read_kindlemem,
.write = write_kindlemem,
.llseek = llseek_kindlemem,
.open = open_kindlemem,
.release =release_kindlemem,
.unlock_ioctl = ioctl_kindlemem,
};
//新加开始
static ssize_t read_kindlemem1(struct file *file, char __user *buf, size_t count, loff_t *off)
{
printk(KERN_INFO,"read_kindlemem1 read s\n");
}
static ssize_t write_kindlemem1(struct file *file, const char __user *userbuf, size_t bytes, loff_t *off)
{
printk(KERN_INFO,"write_kindlemem write s\n");
}
static struct file_operations kindlemem_fops1 = {
.owner = THIS_MODULE,
.read = read_kindlemem1,
.write = write_kindlemem1,
.open = open_kindlemem,
};
//新加结束
//进行cdev的初始化配置
#if 0
//多个cdev共用一个fops
void setup_kindle_cdev(struct kindlemem *dev, int minor)
{
int error;
dev_t devno = MKDEV(major, minor);
cdev_init(&dev->cdev, &kindlemem_fops);//初始化一个cdev,对内部成员ops进行赋值
dev->cdev.owner = THIS_MODULE;
error = cdev_add(&dev->cdev, devno, MEM_MINORCNT);//将初始化完成的cdev根据设备号devno添加到相应的位置,MEM_MINORCNT是共同使用cdev的此设备数
if (error)
{
printk(KERN_NOTICE "ERROR %d %d", devno, minor);
}
memset(dev->mem, 0, KINDLEMEM_SIZE);
}
#else
//修改,cdev使用不同的fops
void setup_kindle_cdev(struct kindlemem *dev, int minor)
{
int error;
dev_t devno = MKDEV(major, minor);
switch(minor)
{
case 0:
cdev_init(&dev->cdev, &kindlemem_fops);//初始化一个cdev,对内部成员ops进行赋值
break;
case 1:
cdev_init(&dev->cdev, &kindlemem_fops1);//初始化一个cdev,对内部成员ops进行赋值
break;
default:
cdev_init(&dev->cdev, &kindlemem_fops);//初始化一个cdev,对内部成员ops进行赋值
break;
}
dev->cdev.owner = THIS_MODULE;
error = cdev_add(&dev->cdev, devno, 1);//将初始化完成的cdev根据设备号devno添加到相应的位置
if (error)
{
printk(KERN_NOTICE "ERROR %d %d", devno, minor);
}
memset(dev->mem, 0, KINDLEMEM_SIZE);
}
#endif
static int __init init_kindlemem(void )
{
int ret, i;
dev_t devno = MKDEV(major, 0);
if(major)
{
//register_chrdev_region(dev_t from 从哪开始, unsigned count区域个数, const char * name)
//给major下minor0-10创建一个cdev
ret = register_chrdev_region(devno, MEM_MINORCNT, "kindlemem");
}
else
{
//baseminor:起始位置
// alloc_chrdev_region(dev_t * dev, unsigned baseminor, unsigned count, const char * name)
ret = alloc_chrdev_region(&devno, 0, MEM_MINORCNT, "kindlemem");
major = MAJOR(devno);//上面函数会堆major复制,这里用这个函数来提取
}
kindlemem_p = kzalloc(sizeof(struct kindlemem) * MEM_MINORCNT, GFP_KERNEL);
if (!kindlemem_p)
{
ret = -1;
goto fail_kzalloc;
}
//注册多个cdev实现多个ops,通过循环,并且minor修改成i
for (i = 0; i < MEM_MINORCNT; i++)
{
setup_kindle_cdev(kindlemem_p[i], i);
}
if (ret < 0)
goto fail;
return ret;
fail:
printk(KERN_ERR "kindlmem: failed to register chrdev region\n");
return ret;
fail_kzalloc:
printk(KERN_ERR "kindlmem: failed to alloc memory\n");
unregister_chrdev_region(devno, MEM_MINORCNT);
return ret;
}
static int __exit exit_kindlemem(void )
{
int i;
//修改
//注销多个cdev实现多个ops
for (i = 0; i < MEM_MINORCNT; i++)
{
cdev_del(&(kindlemem_p[i]->cdev));
}
kfree(kindlemem_p);
unregister_chrdev_region(devno, MEM_MINORCNT);
}
module_init(init_kindlemem);
module_exit(exit_kindlemem);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("David Howells <dak@qq.com>");
3 驱动中基本结构
3.1 module
#include <linux/module.h>
// #ifndef _ASM_ARM_MODULE_H
// #define _ASM_ARM_MODULE_H
// struct mod_arch_specific
// {
// int foo;
// };
// #define Elf_Shdr Elf32_Shdr
// #define Elf_Sym Elf32_Sym
// #define Elf_Ehdr Elf32_Ehdr
// /*
// * Include the ARM architecture version.
// */
// #define MODULE_ARCH_VERMAGIC "ARMv" __stringify(__LINUX_ARM_ARCH__) " "
// #endif /* _ASM_ARM_MODULE_H */
// 为了给insmod rmmod lsmod 指定函数int request_module(const char *fmt, ...)
static int __init InitChardrv_first(void) //加上__init,放到.init_text段中 运行完后会这个段会被释放掉。不加的话,这个函数会放在其他位置,运行后也不释放
{
return 1;
}
static int __exit ExitChardrv_first(void)//加上__exit, 放到.exit_text段中 运行完后会这个段会被释放掉。
{
return 1;
}
module_init(InitChardrv_first); //把函数映射到指定的表中, 在insmod命令时通过表找这个函数
module_exit(ExitChardrv_first); //把函数映射到指定的表中, 在rmmod命令时通过表找这个函数
modprobe 比 insmode高级,一次加载所有相关模块(有依赖关系的模块,依赖关系在modules.dep中)
makefie
这个makefile算是linux中makefile的子makefile。
只是利用linux中内核来编译自己的代码
obj-m表示以模块的形式进行编译,需要进行insmod等命令进行操作
还有 obi-y表示编译到内核 obj-n表示不做任何操作
KERN_DIR = /work/system/linux-2.6.22
all:
make -C $(KERN_DIR) M = 'pwd' modules
clean:
make -C $(KERN_DIR) M = 'pwd' modules clean
rm -rf modules.order
obj-m += char_drv.o