【struct inode结构体的功能】
只要文件存在于文件系统中,那么在内核中就会存在一个inode对象用来保存当前文件的相关信息
【struct file结构体的功能】
1.应用程序中系统调用函数通过文件描述符回调到驱动中操作方法的方式
open函数的第一个参数是文件路径,通过文件路径可以在内核中索引到文件的inode对象,进而找到驱动对象回调起具体的操作方法,而read/write等系统调用函数的参数就不再是文件路径了,而是文件描述符,那么文件描述符是如何找到具体的驱动中的操作方法的呢?
1.1文件描述符
1.2struct task_struct分析
只要一个进程加载在操作系统中,在内核中就会存在task_struct类型的对象描述当前进程的相关信息
2.struct file结构体的作用
只要在一个进程中打开了一个文件,在系统内核中就会存在一个struct file结构体,在这个对象中存放的是打开的文件相关信息
3.通过文件描述符回调到驱动中操作方法的路线
fd->fd_array[fd]->struct file对象->struct file_operations对象->具体的操作方法
【将设备文件和一个具体的设备绑定的方法】
open(){
unsigned int minor =MINOR(inode -> i_rdev);//获取操作文件的次设备号
file->private_data =(void *)minor;将次设备号当作私有数据存放}
ioctl()
{
unsigned int which = (unsigned int)file -> private_open()获取open函数中的次设备号
}
【linux内核中的并发和竞态以及竞态的解决方法】
1.linux内核中产生的原因
1.1表面原因
多个进程访问同一个驱动对驱动的资源产生争抢
1.2本质原因
对于单核处理器,如果支持抢占,那么就会存在竞态
对于多核处理器,核与核本身就会出现状态
对于进程和中断,也会产生竞态
2.竞态的解决方法
2.1中断屏蔽
2.2自旋锁
概述
进程在访问临界资源之前先进行上锁操作,另一个进程也想访问临界资源但是解不开锁,此时该进程进入自旋状态等待锁被解开
特点
进入自旋状态的进程是处于运行态,需要消耗cpu的资源
自旋锁锁住的临界区尽可能小,里面尽量不要有延时,耗时,甚至休眠的操作,也不可以有copy_to_user和copy_from_user
自旋锁会导致死锁
自旋锁可以用于进程访问临界资源的过程中,也可以用于中断访问临界资源的过程
自旋锁在上锁时会关闭抢占
API
定义一把自旋锁
初始化自旋锁
上锁
解锁
2.3信号量
概述
一个进程访问临界资源之前先去申请信号量,如果申请到信号量就去访问临界资源,如果访问不到临界资源,此时进程切换到休眠状态,直到可以申请到信号量
特点
申请不到信号量的进程会进入休眠状态,休眠状态不需要消耗CPU资源,但是进程之间的状态的切换需要消耗CPU资源
信号量可以用于临界资源很大的场景,临界区内部也可以有延时,耗时甚至休眠的操作
信号量不会出现死锁
信号量只能用于进程访问临界资源过程中,不可以用于中断访问临界资源过程
信号量也不会关闭抢占
API
1.定义一个信号量
2.初始化信号量
3.上锁
4.解锁
2.4互斥体
概述
当一个进程想要访问临界资源时,首先会申请互斥体,如果申请到了就访问临界资源,申请不到进程就切换到休眠状态
特点
申请不到互斥体的进程会进入休眠状态,休眠状态不需要消耗CPU资源,但是进程之间的状态的切换需要消耗CPU资源
互斥体可以用于临界资源很大的场景,临界区内部也可以有延时,耗时甚至休眠的操作
互斥体不会出现死锁
互斥体只能用于进程访问临界资源过程中,不可以用于中断访问临界资源过程
互斥体也不会关闭抢占
相比于信号量,互斥体解决竞态时如果进程无法访问资源,那么此进程不会立即进入休眠状态,而是稍微等一段时间,如果这一段时间内此进程可以申请到互斥体了,那么进程就不需要进入休眠,而是直接访问临界资源,所以再使用效率方面,互斥体的效率要高于信号量
API
1.定义互斥体
2.初始化互斥体
3.上锁
4.解锁
2.5原子操作
概述
将进程访问临界资源的过程变为一种不可分割的原子状态,当进程访问临界资源结束后,原子状态被打破,别的进程可以访问临界资源。原子操作是通过对原子变量的数值的修改来实现,原子变量数值的修改通过内联汇编来实现。
1.定义原子变量并初始化atmoic_t atm=ATOMIC_INIT(1);//给原子变量一个初始值1
2.int atomic_dec_and_test(atmotic* v)将原子变量的数值-1并且和0比较
3.void atomic_inc(atmotic* v)将原子变量的数值+1
【IO模型】
1.概述
当前对于设备文件的读写需要根据情况的不同使用不同的方式进行
2.关于IO模型的分类
根据情况的不同设制了四种不同IO模型:阻塞IO,非阻塞IO,IO多路复用,信号驱动IO
3.非阻塞IO
概述:当我们需要读取硬件数据时,不管硬件数据是否准备好,read函数不会阻塞,而是立即返回
实现:
/***********应用程序************/
int fd = open("/dev/mycdev",O_RDWR|O_NONBLOCK);
read(fd,buf,sizeof(buf));
/*************驱动程序*********/
if(file->f_flags&O_NONBLOCK)
4.阻塞IO
4.1概述
当使用阻塞IO去读取硬件数据时,如果硬件数据没有准备好,此时进程阻塞在read函数的位置,直到硬件数据准备好,read函数返回,程序继续向下执行。进程处于阻塞时是休眠态,而进程的休眠态又分两种,如下:
S:可中断的休眠态
D:不可中断的休眠态
4.2基本实现
1.等待队列头
2.初始化等待队列头
3.进程休眠
4.进程唤醒
【实现设备文件和设备的绑定,实现LED灯驱动】
驱动代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include "head.h"
struct cdev *cdev;
unsigned int major = 0;
unsigned int minor = 0;
dev_t devno;
gpio_t *vir_led1;
gpio_t *vir_led2;
gpio_t *vir_led3;
unsigned int *vir_rcc;
struct class *cls;
struct device *dev;
int mycdev_open(struct inode *inode, struct file *file)
{
unsigned int minor = MINOR(inode->i_rdev);//获取操作文件的次设备号
file -> private_data = (void *)minor; //将次设备号当作file对象的私有数据成员
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
long mycdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
unsigned int which =(unsigned int)file->private_data; //获取到open中得到的次设备号
switch(which)
{
case 0://LED1控制
switch (cmd)
{
case LED_ON:
vir_led1->ODR |= (0x1 << 10);
break;
case LED_OFF:
vir_led1->ODR &= (~(0x1 << 10));
default:
break;
}
break;
case 1://LED2控制
switch (cmd)
{
case LED_ON:
vir_led2->ODR |= (0x1 << 10);
break;
case LED_OFF:
vir_led2->ODR &= (~(0x1 << 10));
default:
break;
}
break;
case 2://LED3控制
switch (cmd)
{
case LED_ON:
vir_led1->ODR |= (0x1 << 8);
break;
case LED_OFF:
vir_led1->ODR &= (~(0x1 << 8));
default:
break;
}
break;
}
return 0;
}
int mycdev_close(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
// 定义操作方法结构体变量并赋值
struct file_operations fops = {
.open = mycdev_open,
.unlocked_ioctl = mycdev_ioctl,
.release = mycdev_close,
};
int all_led_init(void)
{
// 寄存器地址的映射
vir_led1 = ioremap(PHY_LED1_ADDR, sizeof(gpio_t));
if (vir_led1 == NULL)
{
printk("ioremap filed:%d\n", __LINE__);
return -ENOMEM;
}
vir_led2 = ioremap(PHY_LED2_ADDR, sizeof(gpio_t));
if (vir_led2 == NULL)
{
printk("ioremap filed:%d\n", __LINE__);
return -ENOMEM;
}
vir_led3 = vir_led1;
vir_rcc = ioremap(PHY_RCC_ADDR, 4);
if (vir_rcc == NULL)
{
printk("ioremap filed:%d\n", __LINE__);
return -ENOMEM;
}
printk("物理地址映射成功\n");
// 寄存器的初始化
// rcc
(*vir_rcc) |= (3 << 4);
// led1
vir_led1->MODER &= (~(3 << 20));
vir_led1->MODER |= (1 << 20);
vir_led1->ODR &= (~(1 << 10));
// led2
vir_led2->MODER &= (~(3 << 20));
vir_led2->MODER |= (1 << 20);
vir_led2->ODR &= (~(1 << 10));
// led3
vir_led3->MODER &= (~(3 << 16));
vir_led1->MODER |= (1 << 16);
vir_led1->ODR &= (~(1 << 8));
printk("寄存器初始化成功\n");
return 0;
}
static int __init mycdev_init(void)
{
int ret;
// 1.分配字符设备驱动对象
cdev = cdev_alloc();
if (cdev == NULL)
{
printk("申请字符设备驱动对象失败\n");
ret = -EFAULT;
goto OUT1;
}
printk("申请字符设备驱动对象成功\n");
// 2.初始化字符设备驱动对象
cdev_init(cdev, &fops);
// 3.申请设备号
if (major > 0) // 静态指定
{
ret = register_chrdev_region(MKDEV(major, minor), 3, "mycdev");
if (ret)
{
printk("静态指定设备号失败\n");
goto OUT2;
}
}
else
{
ret = alloc_chrdev_region(&devno, minor, 3, "mycdev");
if (ret)
{
printk("动态指定设备号失败\n");
goto OUT2;
}
minor = MINOR(devno);
major = MAJOR(devno);
}
printk("申请设备号成功\n");
// 4.注册字符设备驱动对象
ret = cdev_add(cdev, MKDEV(major, minor), 3);
if (ret)
{
printk("注册字符设备驱动对象失败\n");
goto OUT3;
}
printk("注册字符设备驱动对象成功\n");
// 向上提交目录
cls = class_create(THIS_MODULE, "Mycdev");
if (IS_ERR(cls))
{
printk("向上提交目录信息失败\n");
ret = -PTR_ERR(cls);
goto OUT4;
}
printk("向上提交目录信息成功\n");
// 向上提交节点信息
int i;
for (i = 0; i < 3; i++)
{
dev = device_create(cls, NULL, MKDEV(major, i), NULL, "Mycdev%d", i);
if (IS_ERR(dev))
{
printk("向上提交设备节点失败\n");
ret = -PTR_ERR(dev);
goto OUT5;
}
}
printk("向上提交设备节点成功\n");
all_led_init();
return 0;
OUT5:
for (--i; i >= 0; i--)
{
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
OUT4:
cdev_del(cdev);
OUT3:
unregister_chrdev_region(MKDEV(major, minor), 3);
OUT2:
kfree(cdev);
OUT1:
return ret;
return 0;
}
static void __exit mycdev_exit(void)
{
// 取消物理内存的映射
iounmap(vir_led1);
iounmap(vir_led2);
iounmap(vir_rcc);
// 销毁设备节点信息
int i;
for (i = 0; i < 3; i++)
{
device_destroy(cls, MKDEV(major, i));
}
// 销毁目录
class_destroy(cls);
// 注销字符设备驱动对象
cdev_del(cdev);
// 释放设备号
unregister_chrdev_region(MKDEV(major, minor), 3);
// 释放对象空间
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
头文件
#ifndef __HEAD_H__
#define __HEAD_H__
typedef struct
{
unsigned int MODER;
unsigned int OTYPER;
unsigned int OSPEEDR;
unsigned int PUPDR;
unsigned int IDR;
unsigned int ODR;
}gpio_t;
#define PHY_LED1_ADDR 0X50006000
#define PHY_LED2_ADDR 0X50007000
#define PHY_LED3_ADDR 0X50006000
#define PHY_RCC_ADDR 0X50000A28
#define LED_ON _IOW('l',1,int) //开灯
#define LED_OFF _IOW('l',0,int) //关灯
#endif
应用代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "head.h"
#include <sys/ioctl.h>
int main(int argc, const char *argv[])
{
char buf[128] = {};
int a, b;
int fd1 = open("/dev/Mycdev0", O_RDWR);
if (fd1 < 0)
{
printf("打开设备文件失败\n");
exit(-1);
}
int fd2 = open("/dev/Mycdev1", O_RDWR);
if (fd2 < 0)
{
printf("打开设备文件失败\n");
exit(-1);
}
int fd3 = open("/dev/Mycdev2", O_RDWR);
if (fd3 < 0)
{
printf("打开设备文件失败\n");
exit(-1);
}
printf("成功打开设备文件\n");
while (1)
{
printf("请选择控制几号灯:1/2/3(开关灯)>");
scanf("%d", &b);
printf("请选择灯控制方式:0/1(开关灯)>");
scanf("%d", &a);
switch (b)
{
case 1:
switch (a)
{
case 0:
ioctl(fd1, LED_OFF);
break;
case 1:
ioctl(fd1, LED_ON);
break;
}
break;
case 2:
switch (a)
{
case 0:
ioctl(fd2, LED_OFF);
break;
case 1:
ioctl(fd2, LED_ON);
break;
}
break;
case 3:
switch (a)
{
case 0:
ioctl(fd3, LED_OFF);
break;
case 1:
ioctl(fd3, LED_ON);
break;
}
break;
default:
break;
}
}
close(fd1);
close(fd2);
close(fd3);
return 0;
}
运行结果: