linux驱动学习笔记1

驱动程序(工作于内核态)

头文件:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

安装模块(使之成为内核的一部分) insmod  (.ko文件)
卸载模块 rmmod

模块安装与卸载框架
int __init first_drv_init() //模块加载函数,加载时才调用
{
 printk("init\n"); //内核的打印函数
 return 0;
}

void __exit first_drv_exit()  //模块卸载函数,卸载时才调用
{
 printk("exit\n");
}

模块加载与卸载的修饰: 单独写最后面
module_init(first_drv_init);
module_exit(first_drv_exit);
MODULE_LICENSE("GPL");

makefile基本框架:
KERN_DIR = /work/system/linux-2.6.22.6 //内核目录(编译好的)
all:
 make -C $(KERN_DIR) M=`pwd` modules
clean:
 make -C $(KERN_DIR) M=`pwd` modules clean
 rm -rf modules.order
obj-m += first_drv.o

===内核导出符号:
void myprint() //例如某个导出函数
{
 printk("myprint!\n");
}
EXPORT_SYMBOL(myprint); //声明为导出符号
之后在写其他模块时可以调用它



===模块参数:在用户空间可以修改内核模块中全局变量的值
在模块中申明静态全局变量
static short mpshort=1;
module_param(mpshort,short,S_IRWXU);//模块参数声明宏,S_IRWXU为读写可执行权限
在安装模块时使用模块参数
insmod modparam.ko mpshort=100; //在安装时调用
在安装后会在 /sys/module/生成文件夹modparam里面有子文件夹paramters
存在几个文件都是由模块参数命名的
里面的全局变量还可以进行修改
cd /sys/module/modparam/paramters/
cat mpshort  得到100
如果有写的权限就可以 echo 55 > mpshort (更改)



===内存管理
 物理地址:出现在地址总线上的值
 虚拟地址:每个进程都有0~4G独立空间
 逻辑地址:汇编文件中使用的偏移地址
头文件
#include<linux/fs.h>
#include<linux/slab.h>
#include<linux/vmalloc.h>

unsigned char *kernelkmalloc=NULL;
unsigned long *kernelpagemem=NULL;
unsigned char *kernelvmalloc=NULL;
在函数中
kernelkmalloc=(unsigned char *)kmalloc(100,GFP_KERNEL)
申请100字节
参二:常用取值
GFP_KERNEL:代表分配内存,分配过程中可能导致睡眠(如果找不到连续空间)
GFP_ATOMIC:分配过程中不会导致睡眠(内核没有找到连续空间时会得到一个错误值)
GFP_DMA:申请到的内存通常情况下位于0~16M之间(0~16M肯定可以用于DMA传输)
__GFP_HIGHMEM:申请高端内存,896M以上的物理地址
if(IS_ERR(kernelkmalloc)) //如果出错
{
 printk("kmalloc failed!\n");
 ret=PTR_ERR(kernelkmalloc); //获取错误编号
 kfree(kernelkmalloc);
 return ret;
}
kfree(kernelkmalloc); //释放
kernelpagemem=__get_free_pages(GFP_KERNEL,4); //按页申请
申请2^4页也就是16页
if(IS_ERR(kernelpagemem))
{
 ret=PTR_ERR(kernelpagemem); //获取错误编号
 return ret;
}
free_pages(kernelpagemem); //释放页
kernelvmalloc=(unsigned char *)vmalloc(1024*1024); //一般申请大空间内存不连续
if(IS_ERR(kernelvmalloc))
{
 ret=PTR_ERR(kernelvmalloc); //获取错误编号
 return ret;
}
vfree(kernelvmalloc); //释放
注意写驱动需要关心内存泄漏问题

内核链表
例子:两个数据结构,如果分别写操作函数比较麻烦,内核提供一种对链表的支持
struct student
{
 char name[20];
 int age;
 int sex;
 int id;
 struct student *next;
}
struct employee
{
 char name[20];
 int age;
 int sex;
 int id;
 int salary;
 struct student *next;
}
内核链表操作(增删改查)代码对于任何数据结构只有一个
头文件
#include<linux/list.h>
struct student
{
 char name[20];
 int age;
 int sex;
 int id;
 struct list_head list;
}
初始化节点
struct list_head student_list; //创建头节点
struct student *studentp=NULL;
INIT_LIST_HEAD(&student_list); //内核初始化头节点函数
studentp=kmalloc(sizeof(struct student)*10,GFP_KERNEL);
//申请10个空间,因为没有在中断上下文中所以无所谓睡眠
memset(studentp,0,sizeof(struct student)*10); //注意这里的memset和用户空间中不是一套代码
for(i=0;i<10;i++) //初始化
{
 sprintf(studentp[i].name,"student%d",i);
 ...
 list_add(&(studentp[i].list),&student_list); //添加
}
/* 链表节点的遍历 */
struct list_head *pos=NULL; //用来保存查找结点地址
struct student *student_tmp=NULL; //临时变量
list_for_each(pos,&student_list)
{
 student_tmp=list_entry(pos,struct student,list);
 //在已知pos时得到的(某个尾节点地址)得到头节点地址
 printk("%s",student_tmp->name);
}
链表析构就只要
kfree(studentp);
还有(不必要) list_del(&(studentp[i].list));
节点删除
list_del



===内核定时器
 时钟中断
 HZ:常数,决定了时钟中断发生的频率
 tick:发生时钟中断的时间间隔=1/HZ
 jiffies:核心变数,纪录开机以来经历了多少tick
 struct timer_list //重要结构体
头文件
#include <linux/timer.h>
具体用例
MODULE_LICENSE("GPL");
struct timer_list bigdog_timer;
void bigdog_timer_handler(unsigned long data) //固定定义格式,此例中内核每次调用传进100
{
 static int bark_count=0;
 printk("wangwang!!--%ld\n",data);
 if(bark_count<10)
 {
  bigdog_timer.expires=jiffies+HZ*5;
  bigdog_timer.data++;
  add_timer(&bigdog_timer);
  bark_count++;
 }
}
int __init kerneltimer_init(void)
{
 //初始化定时器变量
 init_timer(&bigdog_timer); //内核提供
 bigdog_timer.expires=jiffies+HZ*5;
 //从安装后开始计时一定时间后(这里是5s)开始,发生一个超时
 bigdog_timer.function=bigdog_timer_handler;//处理函数
 bigdog_timer.data=100;//定时时间到了可以传个参数进来也可以事先指定
 add_timer(&bigdog_timer); //添加定时器到内核中
 return 0;
}
void __exit kerneltimer_exit(void)
{
 del_timer(&bigdog_timer); //卸载定时器
}

module_init(kerneltimer_init);
module_exit(kerneltimer_exit);



===系统调用
 UNIX C: open
 ANSI C: fopen
linux下的驱动程序是为应用程序提供服务的,驱动程序在内核态执行
应用程序首先使用适当的值填充寄存器:
arch/arm/asm/unistd.h
然后调用特殊的指令,跳转到内核某个固定的位置,根据应用程序填充的
固定的值来找到相应的函数执行。
用户空间编程fd=open("ss.s"...);首先用5(open)->寄存器,然后调用
sys_call_table[5](就是sys_open内核函数)
增加一个新的系统调用
vi arch/arm/kernel/sys_arm.c
//asmlinkage表示当用汇编语言调用函数时传递参数不使用寄存器用栈
asmlinkage int sys_add(int x,int y)
{
 printk("add\n");
 return x+y;
}
更新头文件
vi arch/arm/include/asm/unistd.h
#define __NR_add (__NR_SYSCALL_BASE+366) //366个系统调用
更新系统调用表
vi arch/arm/kernel/calls.S //不能随便放药严格遵循顺序
CALL(sys_add) //放到365个之后一个
重新编译内核make加载新内核
应用程序系统调用
syscall(366,12,13);//得到sys_add(12,13)
硬件设备分类:
字符设备:顺序读写,不带缓冲
块设备:读写顺序不固定,带有读写缓冲 (sync强制将缓冲区写到硬件设备)
网络设备

字符设备框架
硬件上有一个字符设备,内核就有一个cdev结构与之对应
struct cdev
{
 dev_t dev;//设备号
/*
设备号(32bit)=主设备号(高12bit)+次设备号低(20bit)
主设备号:代表一个类型的设备
次设备号:用于区分相同设备中不同个体
如查看串口主、次设备号ls /dev/s3c2410_serial* -l其中204为主,64.为次
主设备号选取:
静态分配
cat /proc/devices //查看设备和主设备号
或者看文件 Documentation/device.txt
动态分配
*/
 const struct file_operations *ops;
}
例子
MODULE_LICENSE("GPL");
dev_t dev=0;
int __init cdd_init(void)
{
 int ret=0;
 //注册设备号
 dev=MKDEV(CDD_MAJOR,CDD_MINOR);//前面是主后面是次,生成设备号
 ret=register_chrdev_region(dev,10,"cdd_demp");//连续注册10个设备号
 if(ret<0) return ret;
 
 return 0;
}
void __exit cdd_exit(void)
{
 unregister_chrdev_region(dev,10);//注销设备号
}
module_init(cdd_init);
module_exit(cdd_exit);

cdev的操作
头文件
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs/h>
cdev_init()//初始化cdev
cdev_add()//向内核注册cdev
定义cdev类型变量
dev_t dev=0;
struct cdev cdd_cdev;
int cdd_open(struct inode *inode,struct file *filp)
{
 printk("open\n");
 return 0;
}
int cdd_read(struct file *filp,char __user *buf,size_t count,
loff_t *offset)
{
 printk("read\n");
 return 0;
}
int cdd_write(struct file *filp,const char __user *buf,
size_t count,loff_t *offset)
{
 printk("write\n");
 return 0;
}
int cdd_ioctl(struct inode *inode,struct file *filp,
unsigned int cmd,unsigned long data)
{
 printk("ioctl\n");
 return 0;
}
int cdd_release(struct inode *inode,struct file *filp)
{
 printk("release\n");
 return 0;
}
struct file_operations cdd_fops=
{
 .owner=THIS_MODULE,
 .open=cdd_open,
 .read=cdd_read,
 .write=cdd_write,
 .ioctl=cdd_ioctl,
 .release=cdd_release,
};
u32 cdd_major=0;'
u32 cdd_minor=0;
struct class *dev_class=NULL;
struct device *dev_device=NULL;
int __init cdd_init(void)
{
 int ret=0;
 //注册设备号
 ret=alloc_chrdev_region(&dev,cdd_minior,1,"cdd_demp");//自动获取主设备号
 if(ret<0)
 {
  return ret;
 }
 cdd_major=MAJOR(dev);//获取主设备号
 cdev_init(&cdd_cdev,&cdd_fops); //初始化cdev
 ret=cdev_add(&cdd_cdev,dev,1); //向内核中添加cdev
 if(ret<0){
  unregister_chrdev_region(dev,1);
  return ret;
 }
 //自动创建设备节点文件
 dev_class=class_create(THIS_MODULE,"cdd_class");//注册设备类
 //多一个/sys/class/cdd_class文件夹
 if(IS_ERR(dev_class))
 {
  ret=PTR_ERR(dev_class);
  cdev_del(&cdd_cdev);
  unregister_chrdev_region(dev,1);
  return ret;
 }
 dev_device=device_create(dev_class.NULL,dev,NULL,"cdd%d",cdd_minor);
 //注册设备,最后一个参数为可变参数
 //会多一个/sys/class/cdd_class/cdd0,多一个/dev/cdd0
 if(IS_ERR(dev_device))
 {
  ret=PTR_ERR(dev_device);
  class_destroy(dev_class);
  cdev_del(&cdd_cdev);
  unregister_chrdev_region(dev,1);
  return ret;
 }
 return 0;
}
void __exit cdd_exit(void)
{
 //逆序消除影响
 device_destroy(dev_class,dev);
 class_destroy(dev_class);
 cdev_del(&cdd_cdev);
 unregister_chrdev_region(dev,1);//注销设备号
}
module_init(cdd_init);
module_exit(cdd_exit);
测试程序
#include<stdio.h>
#include<fcntl.h>
#include<stdlib.h>
int fd=0;
int main()
{
 sfd=open("/dev/cdd",O_RDWR);
}
安装设备
手工创建设备节点文件,主设备号经查证得248,次设备号为0
mknod /dev/cdd c 248 0
或者自动创建设备节点文件
设备节点文件是由udev或者mdev机制创建的可以在/sbin里面看到
udev,后台守护进程,可以接收uevent事件

一个多设备操作的例子:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/timer.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/uaccess.h>

MODULE_LICENSE("GPL");

struct cdd_cdev
{
 struct cdev cdev;
 struct class_device *dev_device;
 u8 led;
 char kbuf[100];
};

struct cdd_cdev *cdd_cdevp=NULL; //用来动态申请空间

int cdd_open(struct inode *inode,struct file *filp)
{
 struct cdd_cdev *pcdevp=NULL;
 printk("open\n");
 pcdevp=container_of(inode->i_cdev,struct cdd_cdev ,cdev);
 printk("led=%d\n",pcdevp->led);
 filp->private_data=pcdevp; //想放什么放什么
 return 0;
}
int cdd_read(struct file *filp,char __user *buf,size_t count,loff_t *offset)
{
 int ret=0;
 struct cdd_cdev *pcdevp=filp->private_data;
 printk("read\n");
 printk("LED=%d\n",pcdevp->led);
 ret=copy_to_user(buf,pcdevp->kbuf,count);//read(fd,buf,10)
 return ret;
}
int cdd_write(struct file *filp,const char __user *buf,size_t count,loff_t *offset)
{
 int ret=0;
 struct cdd_cdev *pcdevp=filp->private_data;
 printk("write\n");
 ret=copy_from_user(pcdevp->kbuf,buf,count);
 return ret;
}
int cdd_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long data)
{
 printk("ioctl\n");
 return 0;
}
int cdd_release(struct inode *inode,struct file *filp)
{
 printk("release\n");
 return 0;
}
struct file_operations cdd_fops=
{
 .owner=THIS_MODULE,
 .open=cdd_open,
 .read=cdd_read,
 .write=cdd_write,
 .ioctl=cdd_ioctl,
 .release=cdd_release,
};

#define CDD_COUNT 10
dev_t dev=0;
u32 cdd_major=0;
u32 cdd_minor=0;
struct class *dev_class=NULL;

int __init cdd_init(void)
{
 int ret=0;
 int i=0;
 ret=alloc_chrdev_region(&dev,cdd_minor,CDD_COUNT,"cdd_demo");
 if(ret<0)
 {
  goto failure_register_chrdev;
 }
 printk("register succeed %d\n",dev);
 cdd_major=MAJOR(dev);
 cdd_cdevp=kzalloc(sizeof(struct cdd_cdev)*CDD_COUNT, GFP_KERNEL);//动态分配
 if(IS_ERR(cdd_cdevp))
 {
  goto failure_kzalloc;
 }
 printk("kzalloc succeed\n");
 dev_class=class_create(THIS_MODULE,"cdd_class");
 if(IS_ERR(dev_class))
 {
  ret=PTR_ERR(dev_class);
  goto failure_dev_class;
 }
 printk("class succeed\n");
 for(i=0;i<CDD_COUNT;i++)
 {
  /*初始化cdev*/
  cdev_init(&(cdd_cdevp[i].cdev),&cdd_fops);
  printk("cdev_init%d succeed\n",i);
  /*添加cdev到核*/
  cdev_add(&(cdd_cdevp[i].cdev),dev+i,1);
  printk("cdev_add%d succeed\n",i);
  /*创建节点*/
  cdd_cdevp[i].dev_device=class_device_create(dev_class,NULL,dev+i,NULL,"cdd%d",i);
  printk("device_create%d succeed\n",i);
  cdd_cdevp[i].led=i;
 }
 
 return 0;
failure_dev_class:
 kfree(cdd_cdevp);
failure_kzalloc:
 unregister_chrdev_region(dev,CDD_COUNT);
failure_register_chrdev:
 return ret;
}

void __exit cdd_exit(void)
{
 int i=0;
 for(i=0;i<CDD_COUNT;i++)
 {
  class_device_unregister(cdd_cdevp[i].dev_device);
  cdev_del(&(cdd_cdevp[i].cdev));
 }
 class_destroy(dev_class);
 kfree(cdd_cdevp);
 unregister_chrdev_region(dev,CDD_COUNT);
}
module_init(cdd_init);
module_exit(cdd_exit);

进一步完整驱动程序使之更像GNU C

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/timer.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/uaccess.h>

MODULE_LICENSE("GPL");

#define BUF_SIZE 100

struct cdd_cdev
{
 struct cdev cdev;
 struct class_device *dev_device;
 u8 led;
 char kbuf[BUF_SIZE];
 u32 data_len;
};

struct cdd_cdev *cdd_cdevp=NULL; //用来动态申请空间

int cdd_open(struct inode *inode,struct file *filp)
{
 struct cdd_cdev *pcdevp=NULL;
 printk("open\n");
 pcdevp=container_of(inode->i_cdev,struct cdd_cdev ,cdev);
 printk("led=%d\n",pcdevp->led);
 filp->private_data=pcdevp; //想放什么放什么
 return 0;
}
int cdd_read(struct file *filp,char __user *buf,size_t count,loff_t *offset)
{
 int ret=0;
 u32 pos=*offset; //从哪个空间开始读
 u32 cnt=count; //读取字节数
 struct cdd_cdev *pcdevp=filp->private_data;
 //printk("read\n");
 //printk("LED=%d\n",pcdevp->led);
 if(cnt>(pcdevp->data_len-pos))
 {
  cnt=pcdevp->data_len-pos;
 }
 ret=copy_to_user(buf,pcdevp->kbuf+pos,cnt);//read(fd,buf,10)
 *offset+=cnt;
 return ret;
}
int cdd_write(struct file *filp,const char __user *buf,size_t count,loff_t *offset)
{
 int ret=0;
 struct cdd_cdev *pcdevp=filp->private_data;
 u32 pos=*offset;
 u32 cnt=count;
 //printk("write\n");
 if(cnt>(BUF_SIZE-pos))
 {
  cnt=BUF_SIZE-pos;
 }
 ret=copy_from_user(pcdevp->kbuf+pos,buf,cnt);
 *offset+=cnt;
 if(*offset>pcdevp->data_len)
 {
  pcdevp->data_len=*offset;
 }
 return ret;
}
int cdd_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long data)
{
 //printk("ioctl\n");
 return 0;
}
int cdd_release(struct inode *inode,struct file *filp)
{
 //printk("release\n");
 return 0;
}
loff_t cdd_llseek(struct file *filp, loff_t offset, int whence)
{
 struct cdd_cdev *pcdevp=filp->private_data;
 loff_t newpos=0;
 switch(whence)
 {
  case SEEK_SET:
   newpos=offset;
   break;
  case SEEK_CUR:
   newpos=filp->f_pos+offset;
   break;
  case SEEK_END:
   newpos=pcdevp->data_len+offset;
   break;
  default:
   return -EINVAL;
 }
 if(newpos<0||newpos>=BUF_SIZE)
 {
  return -EINVAL;
 }
 filp->f_pos=newpos;
 return newpos;
}

struct file_operations cdd_fops=
{
 .owner=THIS_MODULE,
 .open=cdd_open,
 .read=cdd_read,
 .write=cdd_write,
 .ioctl=cdd_ioctl,
 .release=cdd_release,
 .llseek=cdd_llseek,
};

#define CDD_COUNT 10
dev_t dev=0;
u32 cdd_major=0;
u32 cdd_minor=0;
struct class *dev_class=NULL;

int __init cdd_init(void)
{
 int ret=0;
 int i=0;
 ret=alloc_chrdev_region(&dev,cdd_minor,CDD_COUNT,"cdd_demo");
 if(ret<0)
 {
  goto failure_register_chrdev;
 }
 //printk("register succeed %d\n",dev);
 cdd_major=MAJOR(dev);
 cdd_cdevp=kzalloc(sizeof(struct cdd_cdev)*CDD_COUNT, GFP_KERNEL);//动态分配
 if(IS_ERR(cdd_cdevp))
 {
  goto failure_kzalloc;
 }
 //printk("kzalloc succeed\n");
 dev_class=class_create(THIS_MODULE,"cdd_class");
 if(IS_ERR(dev_class))
 {
  ret=PTR_ERR(dev_class);
  goto failure_dev_class;
 }
 //printk("class succeed\n");
 for(i=0;i<CDD_COUNT;i++)
 {
  /*初始化cdev*/
  cdev_init(&(cdd_cdevp[i].cdev),&cdd_fops);
  //printk("cdev_init%d succeed\n",i);
  /*添加cdev到核*/
  cdev_add(&(cdd_cdevp[i].cdev),dev+i,1);
  //printk("cdev_add%d succeed\n",i);
  /*创建节点*/
  cdd_cdevp[i].dev_device=class_device_create(dev_class,NULL,dev+i,NULL,"cdd%d",i);
  //printk("device_create%d succeed\n",i);
  cdd_cdevp[i].led=i;
 }
 
 return 0;
failure_dev_class:
 kfree(cdd_cdevp);
failure_kzalloc:
 unregister_chrdev_region(dev,CDD_COUNT);
failure_register_chrdev:
 return ret;
}

void __exit cdd_exit(void)
{
 int i=0;
 for(i=0;i<CDD_COUNT;i++)
 {
  class_device_unregister(cdd_cdevp[i].dev_device);
  cdev_del(&(cdd_cdevp[i].cdev));
 }
 class_destroy(dev_class);
 kfree(cdd_cdevp);
 unregister_chrdev_region(dev,CDD_COUNT);
}
module_init(cdd_init);
module_exit(cdd_exit);

如果是控制led灯
在用户空间有两种方式控制led亮灭
1: /dev/led0 /dev/led1
 fd0=open("/dev/led0",O_RDWR);
 ioctl(fd0,1,0); //亮,第三个参数废弃ioctl(fd,cmd,data)
 ioctl(fd0,0,0); //灭



===中断
内核中找到gpio对应的中断编号函数
gpio_to_irq()






























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值