常用字符设备驱动函数总结 ----- 记录总结tool

2023-11-07

      打印等级:
        #define KERN_EMERG  "<0>"   /* system is unusable           */
        #define KERN_ALERT  "<1>"   /* action must be taken immediately */
        #define KERN_CRIT   "<2>"   /* critical conditions          */
        #define KERN_ERR    "<3>"   /* error conditions         */
        #define KERN_WARNING"<4>"   /* warning conditions           */
        #define KERN_NOTICE "<5>"   /* normal but significant condition */
        #define KERN_INFO   "<6>"   /* informational            */
        #define KERN_DEBUG  "<7>"   /* debug-level messages         */ 

    控制台相关的打印级别:
        #define console_loglevel (console_printk[0])
        #define default_message_loglevel (console_printk[1])
        #define minimum_console_loglevel (console_printk[2])
        #define default_console_loglevel (console_printk[3])

cat /proc/sys/kernel/printk  查看当前打印级别

/lib/modules ls  查看当前加载的是哪个内核  并找到makefile 的当前路径

    如何去运行相关模块:
        sudo  insmod   xxx.ko :自动执行入口函数
        查看内核打印信息:dmesg 
        
    如何删除当前模块:
        sudo rmmod xxx    :自动执行出口函数
    
    
    清除内核打印信息:
        sudo dmesg -c:清除打印信息的同时,将打印信息,显示在屏幕上
        sudo dmesg -C:清除打印信息的同时,不显示打印信息在屏幕上
        
    查看当前内核中存在的模块:
        lsmod    modinfo xxx.ko  可以查看到很多info

关于type的赋值:
        type:bool,invbool,charp,int,long,short,uint,ulong,ushort;

module_param_named(named_out,name_in,type,perm)
参数:name_out:加载模块时,参数的名字
	name_in:模块内部变量的名字
	type:参数的类型
	perm:访问权限

传递字符串到全局字符数组:
加载模块时传递字符串到一个全局字符数组module_param_string(name,string,len,perm)
参数:name:加载模块时,参数的名字
	string:模块内部字符数组的名字
	len:模块内部字符数组
	perm:访问权限

传递数组型参数	:
加载模块时传递参数到模块的数组中module_param_array(name,type,num,perm)
参数:name:加载模块时,参数的名字
	type:模块数组的数据类型
	num:传递数组成员的个数,NULL不关心用户传递的参数个数
	perm:访问权限

参数描述信息:MODULE_PARM_DESC( 参数名称,"参数描述信息");

sudo insmod module.ko num=1 module_out=8 str="hello" my_array=9,8,7,6,0

modinfo xxx.ko 查看依赖的模块

make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules

-C :获取编译该模块的内核的makefile 路径,  M:获取待编译模块所在的路径

 

EXPORT_SYMBOL(待导出的符号);

生成:Module.symvers   该文件中记录了  add的函数名 , 函数的指针

export.h-->/usr/src/linux-headers-3.5.0-23-generic/Module.symvers:   addr + symbol + module + EXPORT_SYMBOL 
     /proc/kallsyms

                    typedef void (*lpFunction)();

                    lpFunction lpReset = (lpFunction)0xF000FFF0;

                    lpReset();

1.获取到 module.symvers  insmode xxx.ko

2.完成对应的使用   extern int add(int a, int b);  extern int sub(int a, int b);

3.完成, 编译, 插入到内核


dev_t  :设备号的数据类型       
        dev_t from ;
        int major ,minor;
     获取主设备号major: major = MAJOR(from);
     获取次设备号minor: minor = MINOR(from);
     利用主次设备号获取设备号: from = MKDEV(major , minor);

 

    struct inode {------>某一个具体的文件--->设备节点--->/dev/xxx
        const struct inode_operations    *i_op;//对文件、设备节点操作时的相关操作方法  的集合
        dev_t            i_rdev;                  //设备号
        const struct file_operations    *i_fop;    //操作方法
        union {
            struct pipe_inode_info    *i_pipe;//网络相关的
            struct block_device    *i_bdev;    //块设备的抽象结构体
            struct cdev        *i_cdev;        //字符设备的抽象结构体

        };
    };

struct cdev {//字符设备对象
        //struct kobject kobj;//linux设备模型的底层组成
        struct module *owner;//表征其所归属的模块对象
        const struct file_operations *ops;//对于字符设备对象相关操作的方法集合,open,close,read,write,llseek
        dev_t dev;//该字符设备对象对应的设备号 -------------》 对应 inode 中的 i_rdev   container_of(inode->i_rdev, xxxxx);
    };

[1]设备号的申请
[1.1]自动申请设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
功能:自动申请设备号
参数:dev:对应的设备号
baseminor:起始次设备号
count:申请次设备的个数
name:名称
返回值:成功:0   失败:errno
			
			
[1.2]手动申请(可能存在设备号的冲突)
int register_chrdev_region(dev_t from, unsigned count, const char *name)
功能:手动申请设备号
参数:from:对应的设备号
count:申请次设备的个数
name:名称
返回值:成功:0   失败:errno
				
[2]申请得到对应的字符设备对象
struct cdev *cdev_alloc(void)
功能:申请cdev对象
参数:无
返回值:成功:struct cdev 的指针   失败:NULL
			
			
[3]填充字符设备对象的相关属性成员   
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
功能:初始化cdev[完成cdev与之相关联的操作方法集合的绑定]
参数:cdev:字符设备的指针对象
fops:操作方法集合
返回值:无
			
			
[4]字符设备对象的注册
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
功能:注册字符设备对象
参数:p:字符设备的指针对象
dev:该字符设备对象的设备号
count:注册的字符设备的个数
返回值:成功 : 0  失败:errno
---------------------------exit------------------------------
[1]设备号的注销
void unregister_chrdev_region(dev_t from, unsigned count)
功能:注销设备号
参数:from:对应的设备号
count:注销次设备号的个数
返回值:无
[2]void cdev_del(struct cdev *p)
功能:注销字符设备对象
参数:p:对应的字符设备对象
返回值:无
(0)字符设备驱动另一种方式
[1]字符设备驱动的简化版
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
功能:完成字符设备框架的初始化编写
参数:major:主设备号   (major= 0 完成设备号部分的自动申请   )
name:设备对应的名称  cat /proc/devices
fops:该设备的对应的操作方法集合
返回值:成功:0  /major  失败:errno
			
static inline void unregister_chrdev(unsigned int major, const char *name)
功能:注销字符设备框架(的退出部分编写)
参数:major:主设备号   (major= 0 完成设备号部分的自动申请   )
name:设备对应的名称  cat /proc/devices
返回值:无

(1)读写
用户空间------->内核空间的访问
(1)系统调用【read,write】
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);


头文件:include <linux/uaccess.h> //read write是针对用户来讲的  copy_to_user copy_from_user是针对内核来讲的
read:将底层数据传输给用户层   copy_to_user
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
功能:传输数据到用户空间
参数:to:用户空间的数据存储地址
from:内核空间的地址
n:传输数据的大小
返回值:成功:0   失败:传输数据的个数
			
write:将用户空间数据传输给底层   copy_from_user
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
功能:将用户空间的数据写入内核空间
参数:to:内核空间的地址
from:用户空间的地址
n:传输数据的大小
返回值:成功:0   失败:传输数据的个数
(2)控制(32bits被分为四部分)
#include <sys/ioctl.h>
int ioctl(int d, int request, ...);
功能:根据不同的命令码执行不同的操作
参数:d:文件描述符
request:命令码
...:视情况再填充  ,[地址]
返回值:成功:0   失败:-1
				
头文件:#include <asm-generic/ioctl.h>
ioctl:
底层实现的机制:
(1)完成unlocked_ioctl的底层实现:switch case
(2)完成不同命令码的封装
#define _IO(type,nr)		_IOC(_IOC_NONE,(type),(nr),0)
功能:封装命令码
参数:type:命令码的一个类型 【幻数】,一般使用相关字符来标志 eg:  #define type 'a'
nr: 命令代码参数  eg:0 , 1 , 2...
/*
#define _IOR(type,nr,size)	_IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size)	_IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size)	_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
linux-3.14/Documentation/ioctl/ioctl-number.txt记录了内核中可能被占用的一些命令码 
*/
	ioctl命令码组成:	32bit		
     _________________________________________
     | 方   向   | 数据尺寸 | 设备类型  | 序列号  |
     |----------|----------|----------|--------|
     |   2 bit  | 8~14 bit |   8 bit  | 8 bit  |

幻数 一般使用字符来表示 eg:# define type ‘a’
Nr:命令代码参数

#define TYPE 'A'
#define ONE _IO(TYPE, 0)
#define TWO _IO(TYPE, 1)
(3)自动创建设备节点[/proc/sys/kernel/hotplug]--->udev
头文件:#include <linux/device.h>
			
结构体:
struct class {
	const char		*name;//类名    /sys/class/xxx
	struct module	*owner;//所归属的模块
	//struct kobject	*dev_kobj;//linux设备模型相关-->生成文件夹
};
struct device {
	struct device	*parent;//该设备的parent
	struct device_private	*p;//存放该设备的私有数据
	//struct kobject kobj;//linux设备模型相关-->生成文件夹
	const char		*init_name; /* 设备名称 */
	//const struct device_type *type;//设备类型
	//struct bus_type	*bus;		/* 该设备挂接的总线*/
	//struct device_driver *driver;	/*支持当前设备的驱动对象*/
	dev_t			devt;	/* 设备号*/
	struct class		*class;//当前设备归属于哪一个类
	//void	(*release)(struct device *dev);//当设备移除时,会被调用
};
		
(1)struct class *class_create(struct module *owner,const char *name)
	功能:创建class
	参数:owner:THIS_MODULE
	name:对应的设备类/*/sys/class/xxx*/
	返回值:成功:struct class指针对象  失败:errno指针
			
IS_ERR(errno):判断其是否申请得到了对应的指针,如果说errno该指针是正确的话,那么IS_ERR会返回0,反之非0
PTR_ERR(errno):将错误指针,转换成具体的错误码
    /*
	#define class_create(owner, name)		\
	({						\
	    static struct lock_class_key __key;	\
		__class_create(owner, name, &__key);	\
	})
	struct class *__class_create(struct module *owner, const char *name, struct lock_class_key *key)
	*/

(2)void class_destroy(struct class *cls)
功能:销毁对应的class
参数:cls:struct class的指针对象
返回值:无
			
(3)struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
功能:创建对应的device
参数:class:struct class的指针对象
parent:设备父节点  如果没有 NULL
devt:设备号
drvdata:私有数据  没有的话 NULL
fmt:对于待生成的设备节点的命名格式    /dev/demo0  /dev/demo1   "%s%d"
...:"demo"   i
返回值:成功:struct device指针对象  失败:errno指针
			
(4)void device_destroy(struct class *class, dev_t devt)
功能:销毁对应的device
参数:class:struct class的指针对象
devt:设备号
返回值:无

内存管理模型:
           uma:一致性内存模型
           numa:非一致性内存管理模型

           速度的一致非一致
           /*数据结构struct pglist_data*/

给予页面管理之上的slab管理:gfp_t flags
linux系统在物理页分配的基础上实现了对更小内存空间进行管理的slab分配器。
slab思想:将一个内存页或者若干内存页分配成若干相等的小内存,供程序员使用。
slab分配器限制:只能在低端内存区进行分配;
典型:kmalloc 
			
static __always_inline void *kmalloc(size_t size, gfp_t flags)
功能:分配对应的虚拟内存
参数:size:分配内存区的大小
flags:内存分配标志
返回值:对应虚拟地址
特点:最大128k   , 分配虚拟地址,其虚拟地址空间连续,物理地址空间也是连续
类似函数:kzalloc:分配虚拟内存区并清零
			
			
void kfree(const void *x)
功能:释放对应的虚拟内存
参数:x:虚拟内存的起始地址
返回值:无

页面分配器函数的核心:
alloc_pages  &&   __get_free_pages  ---------->alloc_pages_node
alloc_pages  与   __get_free_pages的不同:前者较后者能分配高端内存以及低端内存; 后者只能获取低端内存区的内容;
[3.1]__alloc_pages/alloc_page<-------->__free_pages
[3.2]__get_free_pages/__get_free_page<-------->free_pages
[3.3]get_zeroed_page:分配内存页的同时,将其对应的物理内存区清零
						
unsigned long get_zeroed_page(gfp_t gfp_mask)
功能:分配并清零对应的内存页
参数:gfp_mask:表征当前分配内存的标志   GFP_KERNEL,GFP_ATOMIC
返回值:对应的页地址
					
#define GFP_KERNEL  (__GFP_WAIT | __GFP_IO | __GFP_FS) 	

__GFP_WAIT:当前正在向内核申请页分配的进程可以被阻塞,意味着调器可以在此请求期间调度另一个进程执行。
__GFP_IO:内核在查找空闲页的操作中可以进行IO操作,如此内核可以将换出的页写到硬盘中。
__GFP_FS:查找空闲页的过程中允许执行文件系统相关的操作__GFP_DMA

#define GFP_ATOMIC  (__GFP_HIGH)
__GFP_HIGH:内核允许使用紧急分配链表中的保留内存页,该请求必须以原子方式完成,意味着请求过程不允许被中断。
	
							   
[1]内存管理部件内部使用的标志 
__GFP_WAIT,__GFP_IO,__GFP_FS,__GFP_HIGH

[2]外部使用的内存分配标志
GFP_KERNEL,GFP_ATOMIC


#define GFP_ATOMIC	(__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)
#define GFP_KERNEL	(__GFP_RECLAIM | __GFP_IO | __GFP_FS)
#define GFP_KERNEL_ACCOUNT (GFP_KERNEL | __GFP_ACCOUNT)
#define GFP_NOWAIT	(__GFP_KSWAPD_RECLAIM)
#define GFP_NOIO	(__GFP_RECLAIM)
#define GFP_NOFS	(__GFP_RECLAIM | __GFP_IO)
#define GFP_USER	(__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
#define GFP_DMA		__GFP_DMA
#define GFP_DMA32	__GFP_DMA32
#define GFP_HIGHUSER	(GFP_USER | __GFP_HIGHMEM)
#define GFP_HIGHUSER_MOVABLE	(GFP_HIGHUSER | __GFP_MOVABLE)
#define GFP_TRANSHUGE_LIGHT	((GFP_HIGHUSER_MOVABLE | __GFP_COMP | \
			 __GFP_NOMEMALLOC | __GFP_NOWARN) & ~__GFP_RECLAIM)
#define GFP_TRANSHUGE	(GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM)

/* Convert GFP flags to their corresponding migrate type */
#define GFP_MOVABLE_MASK (__GFP_RECLAIMABLE|__GFP_MOVABLE)
#define GFP_MOVABLE_SHIFT 3


{2}给予页面管理之上的slab管理:gfp_t flags
linux系统在物理页分配的基础上实现了对更小内存空间进行管理的slab分配器。
slab思想:将一个内存页或者若干内存页分配成若干相等的小内存,供程序员使用。
slab分配器限制:只能在低端内存区进行分配;
典型:kmalloc 
			
static __always_inline void *kmalloc(size_t size, gfp_t flags)
功能:分配对应的虚拟内存
参数:size:分配内存区的大小
flags:内存分配标志
返回值:对应虚拟地址
特点:最大128k   , 分配虚拟地址,其虚拟地址空间连续,物理地址空间也是连续
类似函数:kzalloc:分配虚拟内存区并清零
			
			
void kfree(const void *x)
功能:释放对应的虚拟内存
参数:x:虚拟内存的起始地址
返回值:无

 

虚拟内存管理
转换过程:	
[1]vmalloc / vfree
void *vmalloc(unsigned long size)     // 没有标志 是在 固有的一个局域分配的
功能:分配对应的虚拟内存 
参数:size:分配内存区的大小
返回值:对应虚拟地址
特点:分配虚拟地址,其虚拟地址空间连续,但是物理地址空间不一定连续
		
void vfree(const void *addr)
功能:释放对应的虚拟内存
参数:addr:虚拟内存区的首地址
返回值:无
		
[2]linux内核虚拟地址空间构成
/*
4g------------------------------------
	            4k
-----------------------------------
        固定内存映射区 4m-4k
-----------------------------------
		高端内存映射区 4m
------------------------------------
		NULL 8K(保护)
------------------------------------vmalloc  end
		vmalloc内存区120m-8m-8k             //就是这个固有的区域
------------------------------------vmalloc  start
		vmalloc offset 8m
------------------------------------
		物理内存映射区896M
3g------------------------------------ 物理内存 3g 偏移   4k + 3g
		用户空间
0g------------------------------------
*/

各个地址的概念
 (1)物理地址:在datasheet中能够查到的地址称之为物理地址,实际设备的操作地址;
 (2)逻辑地址:将程序进行反汇编之后,其中能够看到的地址称之为逻辑地址;
 (3)虚拟地址:在操作系统程序员能够操作的地址称之为虚拟地址;
			
 如果我们想操作实际硬件的话:将物理地址-- mmu -->虚拟地址
			
 static inline void __iomem *ioremap(phys_addr_t offset, unsigned long size)
 功能:完成物理地址到虚拟地址的转换
 参数:offset:设备的物理地址
 size:映射的大小
 返回值:成功:映射成功的虚拟地址   失败:NULL
			
 static inline void iounmap(void __iomem *addr)
 功能:解除地址映射
 参数:addr:虚拟地址
 返回值:无
			
 static inline u32 readl(const volatile void __iomem *addr)
 功能:完成对虚拟地址的数据读取
 参数:addr:虚拟地址
 返回值:读取虚拟地址得到的数据
			
 static inline void writel(u32 b, volatile void __iomem *addr)
 功能:完成对虚拟地址的数据写入
 参数:b:待写入虚拟地址中的数据
 addr:虚拟地址
 返回值:无
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/io.h>
 
#define CLASS_NAME "demo_class"
#define DEVICE_NAME "demo"
#define MAX  64

#define GPF3_CON  0X114001E0
#define GPF3_DAT  0X114001E4

void __iomem *gpf3_con;
void __iomem *gpf3_dat;
char ker_buf[64] = {0};
int major = 0;
int count = 2;


int demo_open(struct inode *inode, struct file *file)
{
	writel((readl(gpf3_dat)|(1<<5)),gpf3_dat);//在打开操作时将gpf3_5这个灯点亮
	printk("%s,%d\n",__func__,__LINE__);
	return 0;
}
int demo_release(struct inode *inode, struct file *file)
{
	writel((readl(gpf3_dat)&~(1<<5)),gpf3_dat);//在关闭操作时,将led关闭
	printk("%s,%d\n",__func__,__LINE__);
	return 0;
}

struct file_operations f_ops = {
	.owner = THIS_MODULE,
	.open = demo_open,
	.release = demo_release,
};
struct class *cls = NULL;
struct device * dev = NULL;
static int __init demo_init(void)
{
	int ret = 0,i= 0;
	major = register_chrdev(major , DEVICE_NAME, &f_ops);
	if(major < 0){
		printk("register_chrdev ... %s,%d\n",__func__,__LINE__);
		return -EINVAL;
	}

	cls = class_create(THIS_MODULE,CLASS_NAME);
	if(IS_ERR(cls)){
		printk("class_create fail...\n");
		ret = PTR_ERR(cls);
		goto ERR_STEP1;
	}

	for(i = 0; i < count ; i++){
		dev = device_create(cls,NULL,MKDEV(major,i),NULL,"%s%d",DEVICE_NAME,i);
		if(IS_ERR(dev)){
				printk("device_create fail...\n");
				ret = PTR_ERR(dev);
				goto ERR_STEP2;
			}
	}

	/*********************************************/
	gpf3_con = ioremap(GPF3_CON,0x4);
	if(gpf3_con == NULL){
		printk("ioremap FAIL... \n");
		goto ERR_STEP3;
	}
	gpf3_dat = ioremap(GPF3_DAT,0x4);
	if(gpf3_dat == NULL){
		printk("ioremap FAIL... \n");
		goto ERR_STEP4;
	}

	writel((readl(gpf3_con)&~(0xf<<20))|(1<<20),gpf3_con);//gpf3_5输出功能的配置
	writel((readl(gpf3_dat)&~(1<<5)),gpf3_dat);//在初始化之后先令gpf3_5处于关闭的状态
	
	printk("%s,%d major :%d \n",__func__,__LINE__,major);
	return 0;
	
ERR_STEP4:	
	iounmap(gpf3_con);
ERR_STEP3:	
	device_destroy(cls,MKDEV(major,1));
ERR_STEP2:
	for(i-- ; i > 0 ; i--)
		device_destroy(cls,MKDEV(major,i));
	class_destroy(cls);
	
ERR_STEP1:
	unregister_chrdev(major,DEVICE_NAME);
	return ret;

}

static void __exit demo_exit(void)
{
	int i = 0;
	writel((readl(gpf3_dat)&~(1<<5)),gpf3_dat);//在初始化之后先令gpf3_5处于关闭的状态
	iounmap(gpf3_con);
	iounmap(gpf3_dat);
	for(i = 0; i < count ; i++)
		device_destroy(cls,MKDEV(major,i));
	class_destroy(cls);
	unregister_chrdev(major,DEVICE_NAME);
	printk("%s,%d\n",__func__,__LINE__);
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");



Linux设备驱动中的并发与竟态  https://blog.csdn.net/zmjames2000/article/details/87743122

概念:linux系统允许多进程,多cpu的一个操作系统,     并发执行的程序或cpu对共享资源的访问,会造成一种竟态。
    竟态:就是并发单元同时对同一块资源进行访问操作,造成该资源数据混乱的一种状态;
    
    为了确保共享资源的访问安全的措施:
        互斥:排他性
        同步:按照顺序访问

    
    补充概念:    
        临界区:就是访问共享资源的代码段
        临界资源:被共享的硬件或软件资源;
        并发源:多进程,多cpu等;

    为了防止产生竟态,将访问共享资源的代码段进行排他性访问或者顺序性访问;

单cpu

 /*中断屏蔽开始*/    --->local_irq_disable();
 /*中断屏蔽进行*/    --->执行对共享资源的访问
 /*中断解除屏蔽*/    --->local_irq_enable();

  应用注意事项:被中断屏蔽的时间应该尽量短,否则的话会造成当前系统响应进程调度以及中断反应缓慢;

 

 

自旋锁概念及使用场景:主要适用于smp,进程与进程间抢占 /*进程与中断间抢占(自旋锁的扩展功能:自旋锁的衍生锁)*/    

目的:保护共享资源      //ps.  中断是不能休眠的,

自旋锁: 如果获取不到资源的话,会一直在测试,会造成死锁 (忙等锁时间过长就会造成死锁)

所以占有锁的时间不能够过长。
所以不能在持有锁期间能够执行睡眠的函数,也不能执行被调度的函数。
因为时间过长会导致死锁。 其实自选锁就是忙等锁

信号量:跟自旋锁类似,其只能在进程上下文使用,不能在中断中用。
自旋锁有衍生锁,是能在中断上下文用的。

信号量只会测试一次,条件不满足就会去睡眠。(获取不到资源的时候)
这个是最大的区别之一。 两个区别 ,只能在进程中用,只测试一次,不满足就去睡眠 ,不是忙等锁

 typedef struct {
	struct lock_impl internal_lock;
} spinlock_t;

struct lock_impl {
	bool locked;
};
struct lock_impl {
	pthread_mutex_t mutex;
};


函数API(初始化,上锁,解锁):
(1)初始化
void spin_lock_init(spinlock_t *lock)
功能:初始化自旋锁
参数:lock:自旋锁的指针对象
返回值:无
						
#define spin_lock_init(_lock)				\
do {							\
	spinlock_check(_lock);				\
	raw_spin_lock_init(&(_lock)->rlock);		\
} while (0)

(2)上锁
void spin_lock(spinlock_t *lock)
功能:自旋锁上锁
参数:lock:自旋锁的指针对象
返回值:无

(3) void spin_unlock(spinlock_t *lock)
功能:自旋锁解锁
参数:lock:自旋锁的指针对象
返回值:无

——————————————————————————————————————————————————————————————————————————
static __always_inline void spin_lock(spinlock_t *lock)
{
	raw_spin_lock(&lock->rlock);
}

#define raw_spin_lock(lock)	_raw_spin_lock(lock)

#define _raw_spin_lock(lock)			__LOCK(lock)
#define _raw_spin_lock_nested(lock, subclass)	__LOCK(lock)
#define _raw_read_lock(lock)			__LOCK(lock)
#define _raw_write_lock(lock)			__LOCK(lock)
#define _raw_spin_lock_bh(lock)			__LOCK_BH(lock)
#define _raw_read_lock_bh(lock)			__LOCK_BH(lock)
#define _raw_write_lock_bh(lock)		__LOCK_BH(lock)
#define _raw_spin_lock_irq(lock)		__LOCK_IRQ(lock)
#define _raw_read_lock_irq(lock)		__LOCK_IRQ(lock)
#define _raw_write_lock_irq(lock)		__LOCK_IRQ(lock)
#define _raw_spin_lock_irqsave(lock, flags)	__LOCK_IRQSAVE(lock, flags)
#define _raw_read_lock_irqsave(lock, flags)	__LOCK_IRQSAVE(lock, flags)
#define _raw_write_lock_irqsave(lock, flags)	__LOCK_IRQSAVE(lock, flags)
#define _raw_spin_trylock(lock)			({ __LOCK(lock); 1; })
#define _raw_read_trylock(lock)			({ __LOCK(lock); 1; })
#define _raw_write_trylock(lock)			({ __LOCK(lock); 1; })
#define _raw_spin_trylock_bh(lock)		({ __LOCK_BH(lock); 1; })
#define _raw_spin_unlock(lock)			__UNLOCK(lock)
#define _raw_read_unlock(lock)			__UNLOCK(lock)
#define _raw_write_unlock(lock)			__UNLOCK(lock)
#define _raw_spin_unlock_bh(lock)		__UNLOCK_BH(lock)
#define _raw_write_unlock_bh(lock)		__UNLOCK_BH(lock)
#define _raw_read_unlock_bh(lock)		__UNLOCK_BH(lock)
#define _raw_spin_unlock_irq(lock)		__UNLOCK_IRQ(lock)
#define _raw_read_unlock_irq(lock)		__UNLOCK_IRQ(lock)
#define _raw_write_unlock_irq(lock)		__UNLOCK_IRQ(lock)
#define _raw_spin_unlock_irqrestore(lock, flags) \
					__UNLOCK_IRQRESTORE(lock, flags)
#define _raw_read_unlock_irqrestore(lock, flags) \
					__UNLOCK_IRQRESTORE(lock, flags)
#define _raw_write_unlock_irqrestore(lock, flags) \
					__UNLOCK_IRQRESTORE(lock, flags)

信号量:跟自旋锁类似,其只能使用在进程上下文,其当资源获取不到时,直接休眠等待;
                    应用注意事项:只能在进程上下文,不是忙等锁;  

信号量:跟自旋锁类似,其只能在进程上下文使用,不能在中断中用。
自旋锁有衍生锁,是能在中断上下文用的。

信号量只会测试一次,条件不满足就会去睡眠。(获取不到资源的时候)
这个是最大的区别之一。 两个区别 ,只能在进程中用,只测试一次,不满足就去睡眠 ,不是忙等锁

信号量 同时两个进程对同一个资源惊进行读写,去读是可以的,但是不能同时写

struct semaphore {
	raw_spinlock_t		lock;
	unsigned int		count;
	struct list_head	wait_list;
};


函数(初始化,获取,释放):
void sema_init(struct semaphore *sem, int val)
功能:初始化信号
参数:sem:信号量的结构体对象
val:给信号量的count的赋值  
返回值:无
					
int down_trylock(struct semaphore *sem)
功能:获取信号量
参数:sem:信号量的结构体对象 
返回值:成功:0  失败:1
					
void up(struct semaphore *sem)
功能:释放信号量
参数:sem:信号量的结构体对象 
返回值:无

-----------------------------------------------------------------
int down_trylock(struct semaphore *sem)
{
	unsigned long flags;
	int count;

	raw_spin_lock_irqsave(&sem->lock, flags);
	count = sem->count - 1;
	if (likely(count >= 0))   //内核编译的时候把 likely 往前放, unlikely 往后放
		sem->count = count;
	raw_spin_unlock_irqrestore(&sem->lock, flags);

	return (count < 0);
}
EXPORT_SYMBOL(down_trylock);

void up(struct semaphore *sem)
{
	unsigned long flags;

	raw_spin_lock_irqsave(&sem->lock, flags);
	if (likely(list_empty(&sem->wait_list)))
		sem->count++;
	else
		__up(sem);
	raw_spin_unlock_irqrestore(&sem->lock, flags);
}
EXPORT_SYMBOL(up);


static noinline void __sched __up(struct semaphore *sem)
{
	struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
						struct semaphore_waiter, list);
	list_del(&waiter->list);
	waiter->up = true;
	wake_up_process(waiter->task);
}

int wake_up_process(struct task_struct *p)
{
	return try_to_wake_up(p, TASK_NORMAL, 0);
}
EXPORT_SYMBOL(wake_up_process);

PS:互斥锁mutex,是信号量的count成员为1的特殊用法,以实现互斥

struct mutex {
	atomic_long_t		owner;
	spinlock_t		wait_lock;
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
	struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
	struct list_head	wait_list;
#ifdef CONFIG_DEBUG_MUTEXES
	void			*magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map	dep_map;
#endif
};

(1)void mutex_init(struct mutex *lock)
功能:完成互斥体的初始化
参数:lock:互斥体的结构体指针
返回值:无
# define mutex_init(mutex) \
do {							\
	static struct lock_class_key __key;		\
	\
	__mutex_init((mutex), #mutex, &__key);		\
} while (0)

void
__mutex_init(struct mutex *lock, const char *name, struct lock_class_key *key)
{
	atomic_long_set(&lock->owner, 0);  // 初始化是0
	spin_lock_init(&lock->wait_lock);
	INIT_LIST_HEAD(&lock->wait_list);
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
	osq_lock_init(&lock->osq);
#endif

	debug_mutex_init(lock, name, key);
}
EXPORT_SYMBOL(__mutex_init);
					
(2)	int mutex_trylock(struct mutex *lock)				
功能:获取互斥体
参数:lock:互斥体的结构体指针
返回值:Returns 1 if the mutex has been acquired successfully, and 0 on contention.
					
(3)void mutex_unlock(struct mutex *lock)
功能:释放互斥体
参数:lock:互斥体的结构体指针
返回值:无

________________________________________________________________
int __sched mutex_trylock(struct mutex *lock)
{
	bool locked = __mutex_trylock(lock);

	if (locked)
		mutex_acquire(&lock->dep_map, 0, 1, _RET_IP_);   

	return locked;
}
EXPORT_SYMBOL(mutex_trylock);

自旋锁是不带标志位的。 所以需要手动添加一个标志位。 信号量和互斥锁 里面是自带标志位的。 所以是自动去加锁 的。 

 

 

原子变量:将若干步操作在cpu上执行时变为一步  ,肯定不能被打断

函数API(初始化,获取,释放):
(1)初始化
#define ATOMIC_INIT(i)	{ (i) }
功能:给原子变量初始化
参数:i:给原子变量赋值  ,一般  i = 1
						
(2)int atomic_dec_and_test( atomic_t *v)	
功能:减去并测试当前原子变量
参数:v:原子变量的指针对象
返回值:如果v最开始被赋值为1 的话  ,那么此时 返回true反之则返回false

(3)void atomic_inc( atomic_t *v)
功能:增加当前原子变量
参数:v:原子变量的指针对象
返回值:无
/*
#define atomic_inc(v) atomic_add(1, v)
	static inline void atomic_add(int i, atomic_t *v)
*/

 

Linux设备驱动中的阻塞与非阻塞I/O

1、字符设备IO模型
    同步阻塞io模型:

    当系统调用read/write,底层并没有准备好相关的资源的情况下,该系统调用所代表的进程被挂起(睡眠),当底层准备好相关资源后 去唤醒被阻塞的进程;

     写操作: ///相关读操作也是一样的
     内核有一个 kernerbuf[],需要有空间才能写,只有当底层的 kerbuf,有空间,才能被写。如果满的话,写 buf 的进
程就被移走,去睡眠,等有空间了,才能被唤醒,来才做写。

等待队列:以队列为基础,结合进程组成的这样一种数据结构。
等待队列:负责管理链接系统种不满足条件的系统调用所代表的进程挂界项。

同步阻塞 IO 流程
操作等待队列的流程
1、等待队列头的定义
2、完成等待队列头的初始化
3、当系统调用 read/write 底层条件不满足,则将其设置为对应的睡眠态,并挂起对应的进程,实现阻塞
Wait_event_interruptible
4、实现对应的满足条件,并唤醒该进程

int flag = 0;
wait_queue_head_t  wq;

ssize_t demo_read(struct file *file, char __user *user, size_t size, loff_t *loff)
{
	if(size > MAX) size =MAX;

	if(!flag){  //所以这里必须是真   !sizeof(ker_buf)
		if(wait_event_interruptible( wq, flag)){  //flag=0表示假就return -ERESTARTSYS 
			return -ERESTARTSYS;
		}
	}
	if(copy_to_user(user,ker_buf,size)){
		printk("copy_to_user fail\n");
		return -EAGAIN;
	}
	flag = 0;
	printk("%s,%d\n",__func__,__LINE__);
	return size;
}
ssize_t demo_write(struct file *file, const char __user *user, size_t size, loff_t *loff)
{
	if(size > MAX)  size = MAX;

	if(copy_from_user(ker_buf,user,size)){  //如果是先写,必须等写完了flag=1  才能读
		printk("copy_from_user fail\n");	//如果是先读,也必须等flag=1 才能读,其实就是判断sizeof(ker_buf)
		return -EAGAIN;
	}
	
	flag = 1;

	wake_up(&wq);
	return size;
}


    同步非阻塞io模型

      当系统调用read/write,底层并没有准备好相关的资源的情况下, 直接报错返回;

     需要应用层 设置 flag   fd = open(PATH, O_RDWR | O_NONBLOCK) ;

    驱动底层 判断   if ( file->f_flags & O_NONBLOCK)  是不是设置, 设置了就直接返回报错。

     没有设置,就 向下走呗。

 


    
    异步阻塞io模型:  https://blog.csdn.net/zmjames2000/article/details/88104464

https://blog.csdn.net/zmjames2000/article/details/87736983

https://blog.csdn.net/zmjames2000/article/details/87994817

      其阻塞的并非是系统调用,而是将系统调用所对应的文件描述符,放到一个文件描述符数组中, 当检测到该文件描述符数组中的成员如果其阻塞的调节被解除,就会执行对应的操作

     struct file_operations -> poll

   应用层:

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

#define PATH "/dev/demo"
/*
struct pollfd  {			  
	int fd;	   //需要监听的文件描述符			
	short events; // 需要监听的事件   POLLIN  POLLOUT				  
	short revents;// 已发生的事件 		 
}		  
*/
char usr_rx[64] = {0};
char usr_tx[64] = {0};

int main()
{
	int fd,ret;
	fd = open(PATH,O_RDWR);
	if(fd < 0){
		perror("open");
	}
	struct pollfd fds[] = {
		[0]= {
			.fd = fd,
			.events = POLLIN,
		},
		[1] = {
			.fd = fd,
			.events = POLLOUT,
		}
	};

	while(1){
		ret = poll(fds,2,1000);
		if(ret > 0){
			if(fds[0].revents & POLLIN){
				if(read(fds[0].fd ,usr_rx, sizeof(usr_rx)) < 0){
					perror("read");
				}
				printf("usr_rx:%s\n",usr_rx);
			}else if(fds[1].revents & POLLOUT){
				printf("please input:\n");
				fgets(usr_tx,sizeof(usr_tx),stdin);
				/*char *fgets(char *s, int size, FILE *stream);*/
				usr_tx[strlen(usr_tx) - 1] = '\0';
				if(write(fds[1].fd,usr_tx,sizeof(usr_tx)) < 0){
					perror("write");
				}
			}
		}else if(ret == 0){
			printf("you time out\n");
		}else{
			printf("you error\n");
		}
	}
	close(fd);
	return 0;
}


____________________________________________________


wait_queue_head_t  wq;

unsigned int demo_poll(struct file *file, struct poll_table_struct *table)
{
	int mask = 0;
	poll_wait(file,&wq,table);

	if(flag == 1)//此时有数据写入
		return  mask |= POLLIN;
	else if(flag == 0)
		return  mask |=POLLOUT;
	printk("%s,%d \n ",__func__,__LINE__);
	return 0;
}


                     
    异步非阻塞io模型:https://blog.csdn.net/zmjames2000/article/details/88106833
    异步信号驱动io(异步通知):  

1、应用层 signal 绑定信号 sigio 和 read_handler,有人 发 sigio 信号过来的时候,就会触发 read_handler
2、设置异步通知模式,FASYNC 会触发底层的 fasync 驱动,该驱动的作用就是将其加入队列中
3、应用层就开始 while(1)
4、当有 echo 111 >dev/demo 的时候就会触发底层 的写操作,当底层的写操作完成的时候,就会发送一 个 kill_fasync 信号,
5、应用层收到信号就回去,执行相应的函数。
6、 向 IO 写的时候的,才能触发一个 IO 信号,写进入, 让 app 读,(echo 111 > /dev/demo App(收到信号,就会去处理绑定的函数)开始读。 相当与写入 IO ,让 app 去读

[1]同步阻塞io概念: 
等待队列:以队列为基础,结合进程组成的这样一种数据结构;
等待队列头:负责管理连接系统中不满足条件的系统调用所代表的进程的挂接项;
底层驱动实现对应的同步阻塞io的相关参数及API(初始化,睡眠,唤醒):
		/*
			struct __wait_queue_head {
				spinlock_t		lock;
				struct list_head	task_list;
			};
		*/
		struct __wait_queue_head wait_queue_head_t;
		
(1)定义等待队列头: wait_queue_head_t  wq;
(2)等待队列头得初始化
void init_waitqueue_head(wait_queue_head_t *q)
功能:实现等待队列头的初始化
参数:q:等待队列头的指针对象
返回值:无
		/*
			#define init_waitqueue_head(q)				\
			do {						\
				static struct lock_class_key __key;	\
									\
				__init_waitqueue_head((q), #q, &__key);	\
			} while (0)
		*/
(3)实现阻塞:
int wait_event_interruptible(wait_queue_head_t q ,bool  condition)
功能:实现对应系统调用的睡眠直到条件为真或被信号唤醒
参数:q:等待队列头
condition:唤醒该睡眠进程的条件
返回值:如果condition为真 则返回0   反之 直接报错返回-ERESTARTSYS
* The function will return -ERESTARTSYS if it was interrupted by a signal and 0 if @condition evaluated to true.
		/*
			#define wait_event_interruptible(wq, condition)				\
			({									\
				int __ret = 0;							\
				if (!(condition))						\
					__ret = __wait_event_interruptible(wq, condition);	\
				__ret;								\
			})
		*/	
(3)void wake_up(wait_queue_head_t *q)
功能:唤醒对应的等待队列
参数:q:等待队列头的指针对象
返回值:无
/*#define wake_up(x)			__wake_up(x, TASK_NORMAL, 1, NULL)*/

[2]同步非阻塞io概念:
相关参数或标志:O_NONBLOCK--->fd
fd = open(PATH, O_RDWR|O_NONBLOCK);
fd  ---->struct file->unsigned int 		f_flags;
		
【*】程序编写:同步非阻塞IO
(1)app:在open时,  ‘|O_NONBLOCK’
(2)driver: 前提是read的条件不满足  ,read   检测struct file->f_flags是否有被设置过非阻塞标志,如果有的话,直接报错返回


[3]异步阻塞io概念:
底层实现:  
struct file_operations ->poll
(1)poll_wait();
(2)返回满足条件的文件描述符的可读POLLIN  可写的掩码	POLLOUT

static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
功能:将阻塞项存放到对应的一个table
参数:struct file*指针
wait_address:等待队列头的指针对象
p:poll表
返回值:void
					
					
app实现:[select/poll]	

[4]异步非阻塞io概念:	
异步通知:
底层实现:	int (*fasync) (int, struct file *, int);
(1)触发对应的信号
如果是要在app中read ,那么就在底层的write中 触发信号
如果是要在app中write ,那么就在底层的read中 触发信号
					
void kill_fasync(struct fasync_struct **fp, int sig, int band)
功能:触发信号,驱使应用层的信号处理函数执行
参数:fp:异步通知结构体的二级指针
nfds:对应的信号 eg :SIGIO				
band:触发执行操作的方向  :POLL_IN/POLL_OUT
返回值:无
					
(2)将顶层的请求放入(移除)对应的队列中
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
功能:将顶层的请求放入(移除)对应的队列中
参数:fd:文件描述符
filp:file指针对象
on:表征此时是删除还是增加对应的请求  如果说on=0  移除   反之就是add
fapp:struct fasync_struct二级指针
返回值:It returns negative on error, 0 if it did no changes and positive if it added/deleted the entry.
				
(3)在release函数中完成对所有请求的移除
						
				
FASYNC:表征可以使用异步信号驱动IO


应用层实现:
(1)信号与其处理函数绑定
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
(2)设置标志
int fcntl(int fd, int cmd, ... /* arg */ );
功能:根据不同的命令码cmd去对fd执行不同的操作
参数:fd:文件描述符
cmd:命令码  //eg:F_SETFL设置对应的fd的flag
...:其余的参数
返回值:根据不同的命令码其返回值是不同的
					
/*F_GETFL  Value of file status flags.
F_GETOWN Value of descriptor owner.*/
						  
[2.1]	 首先获取到该fd的对应flags 
flags = fcntl(fd,F_GETFL)
fcntl(fd,F_SETFL,flags|FASYNC);

(3)设置当前进程id: F_SETOWN
fcntl(fd,F_SETOWN,getpid());

https://blog.csdn.net/zmjames2000/article/details/87879339

https://blog.csdn.net/zmjames2000/article/details/87868885

	PIC:program interrupt controller
	                -----------		 --------
	外设一-------->|	P    |	INT    |    C	|
	外设二-------->|	I    |-----    |    P	|
	外设三-------->|	C    | 软件    |    U	|
	...	 硬件中断号-----------		 --------

PIC的作用:完成中断硬件中断号到软件中断号的映射,中断屏蔽,中断触发电平的设置等;

中断实现里硬件设备按需获得处理器关注的机制,与查询方式相比大大节省里cpu时间。

 对于每一根中断线--->(硬件中断号)
struct irq_desc {
	struct irq_data		irq_data;	//保存软件中断号irq与chip信息
	irq_flow_handler_t	handle_irq;	//与中断触发电信号相关的函数
	struct irqaction	*action;	//与具体设备的中断处理的抽象  设备的中断处理函数
	struct module		*owner;		//中断所属的模块
	...
} ____cacheline_internodealigned_in_smp;

irq_data结构体中的成员irq_data保存软件中断号irq与chip信息
struct irq_data {
	unsigned int		irq;	//软件中断号
	struct irq_chip		*chip;	//可编程中断控制器pic
	...
};

struct irqaction {
	irq_handler_t handler; 	// 指向中断服务程序 
	unsigned long flags; 	// 中断标志 :设定对应的中断触发标志
	unsigned long mask; 	// 中断掩码 
	const char *name; 	   // I/O设备名 cat /proc/interrupts
	void *dev_id; 			// 用于共享中断时识别设备
	struct irqaction *next; // 用于共享中断时指向下一个描述符
	int irq; 		// IRQ线
	struct proc_dir_entry *dir; // 指向IRQn相关的/proc/irq/n目录的描述符	
};

linux中由若干中断号,将所有的中断的结构体,组织成一个中断数组
irq_desc              [irq_max];
系统中的中断数组名    系统中中断的数量
	
irq = 10 ;
irq_desc[10] --->描述该中断的结构体对象

cat   /proc/interrupts 

函数API:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
功能:申请对应的中断(绑定irq 与 中断处理函数 , 中断触发电平 , 中断名称)
参数:irq:中断号(软件)
handler:中断处理函数
flags:中断触发电平   IRQF_TRIGGER_FALLING  IRQF_TRIGGER_RISING
name:中断名称  /proc/interrupts下可以看到
dev:中断设备的专属数据 ,一般非共享中断可以写为NULL  ,反之共享中断必填
返回值:成功:等于0   失败:errno
		
void free_irq(unsigned int irq, void *dev_id)
功能:释放对应的中断
参数:irq:中断号
dev_id:中断设备的专属数据 
返回值:无

#define IRQF_TRIGGER_NONE	0x00000000
#define IRQF_TRIGGER_RISING	0x00000001
#define IRQF_TRIGGER_FALLING	0x00000002
#define IRQF_TRIGGER_HIGH	0x00000004
#define IRQF_TRIGGER_LOW	0x00000008
#define IRQF_TRIGGER_MASK	(IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
				 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE	0x00000010

 

gic:  interrupt-controller@10490000 {
    compatible = "arm, cortex-a9-gic";
    #interrupt-celss = <3>;  //描述中断参数个数 是三个
    interrupt-controller;  //中断控制器  可以被其他节点引用
    cpu-offset = <0x4000>;
    reg <0x10490000 0x10000>, <0x10480000 0x1000> ....
};

pinctrl@11000000 {
    gpx1: gpx1{
        gpio-controller;
        #gpio-cells = <2>;
        interrupt-controller;
        interrupt-parent = <&gic>;
        interrupts = <0 16 0 >,<0 17 0>,<0 18 0>,
                    <0 19 0>,<0 20 0>,....
        #interrupt-cells =<2>;
    };
};

gpx1-1-key{
    interrupt-parent = <&gpx1>
    interrupt = <1 2>   //1 表示< 0 17 0>  2表示 触发方式
};

也可以
xxxx-leds-gpio{
    compatible = " xxxx, leds";
    led2 = <&gpx2 7 0>
    led3 = <&gpx1 0 0>
    led4 = <&gpx3 4 0>
    led5 = <&gpx5 5 0>
};


           //中断域 中断  触发方式
interrupts = <0 16 0>
中断域: 0  spi    shared    
        1  ppi     private
        2  sgi

触发方式: 1.上升沿  2.下降沿  3.高电压  4.低电平


int irq = 0;
struct device_node *np;
np = of_find_node_by_path("/key@0x12345678");
irq = irq_of_parse_and_map(np,0);
ret = request_irq(irq,key_irq_handler,IRQF_TRIGGER_FALLING,NAME,NULL);

free_irq(irq,NULL);

1. 设备树的作用?   提供对应的设备信息

2.用设备树是为了获取具体的设备信息

3. 具体的信息是在设备树种被写成对应的节点

4. 具体的设备信息是被作为该节点的属性填充的

https://blog.csdn.net/zmjames2000/article/category/8689114

make  dtbs


中断底半部:
为了解决中断既能快速响应,又能执行大量的任务。

中断顶半部:快速响应 ------> 中断状态的获取   中断标志的清除  
中断底半部:执行大量的任

上半部能打断底部的执行。必须上半部执行完成才能执行下半部。

1.tasklet 中断上下文,不允许睡眠

struct tasklet_struct
{
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
	void (*func)(unsigned long);
	unsigned long data;
};


static inline void tasklet_schedule(struct tasklet_struct *t)
功能:实现tasklet的调度
参数:t:struct tasklet_struct指针对象
返回值:无



1. 
struct tasklet_struct task = {
	.func = task_func,
	.data = 999,
};

2.
int irq = 0;
struct device_node *np;
np = of_find_node_by_path("/key@0x12345678");
ret = request_irq(irq,key_irq_handler,IRQF_TRIGGER_FALLING,NAME,NULL);

irqreturn_t key_irq_handler(int irq, void *data)
{
	tasklet_schedule(&task);
	return IRQ_HANDLED;//表征中断处理成功
}

void task_func(unsigned long data)//底半部处理函数	
{
	unsigned long  i = 100000;
	while(i--);
	printk("%s,data:%ld\n",__func__,data);	
}




2.workqueue:进程上下文,,允许睡眠
struct work_struct {
	work_func_t func;//工作队列的底半部处理函数
};
typedef void (*work_func_t)(struct work_struct *work);
			
函数API:
INIT_WORK(struct work_struct *work,work_func_t func);
完成工作队列的初始化(绑定工作队列底半部处理函数)
			
/*
#define INIT_WORK(_work, _func)						\
	do {								\
	    __INIT_WORK((_work), (_func), 0);			\
	} while (0)
*/
		
static inline bool schedule_work(struct work_struct *work)
功能:实现work_struct的调度
参数:work:struct work_struct指针对象
返回值: * Returns %false if @work was already on the kernel-global workqueue and %true otherwise.


1.
struct work_struct wk;
INIT_WORK(&wk,work_func);

2.
int irq = 0;
struct device_node *np;
np = of_find_node_by_path("/key@0x12345678");
ret = request_irq(irq,key_irq_handler,IRQF_TRIGGER_FALLING,NAME,NULL);

irqreturn_t key_irq_handler(int irq, void *data)
{
	//tasklet_schedule(&task);
	schedule_work(&wk);
	return IRQ_HANDLED;//表征中断处理成功
}

void work_func(struct work_struct *work)
{
	unsigned long  i = 100000;
	while(i--);
	printk("%s\n",__func__);	
}


	

 

1、内核中gpio的函数API--->gpiolib.c

[1]申请、释放gpio管脚	
int gpio_request(unsigned gpio, const char *label)
功能:申请gpio   
参数:gpio:对应的gpio的管脚号
label:gpio标签//eg:“gpx1_1”
返回值:成功:0  失败:errno
		
void gpio_free(unsigned gpio)
功能:释放gpio   
参数:gpio:对应的gpio的管脚号
返回值:无
		
[2]标记gpio的使用方向包括输入还是输出/*成功返回零失败返回负的错误值*/	
static inline int gpio_direction_input(unsigned gpio)
功能:完成相应的gpio管脚的输入功能设置  
参数:gpio:对应的gpio的管脚号
返回值:成功:0  失败:errno
		
static inline int gpio_direction_output(unsigned gpio, int value)
功能:完成相应的gpio管脚的输出功能设置  
参数:gpio:对应的gpio的管脚号
value:设置输出的值
返回值:成功:0  失败:errno
		
[3]获得gpio引脚的值和设置gpio引脚的值(对于输出)
	
static inline void gpio_set_value(unsigned int gpio, int value)
功能:完成对应GPIO管脚的输出数据的设置  
参数:gpio:对应的gpio的管脚号
value:设置输出的电平
返回值:成功:0  失败:errno
		
static inline int gpio_get_value(unsigned int gpio)
功能:获取对应的GPIO管脚的数据 
参数:gpio:对应的gpio的管脚号
返回值:返回对应管脚的电平状态
		
[4]gpio当作中断口使用
static inline int gpio_to_irq(unsigned int gpio)
功能:根据GPIO管脚号来得到该管脚对应的中断号
参数:gpio:对应的gpio的管脚号
返回值:返回对应管脚的电平状态
		
		
static inline int of_get_named_gpio(struct device_node *np,const char *propname, int index)
功能:根据节点中GPIO管脚对应的“键名”来获取gpio管脚序号
参数:np:当前gpio管脚所在的节点
propname:“键名”
index:序号
返回值:成功:  GPIO管脚序号  失败:errno
1.
添加led的设备节点信息	
	led{				
		compatible = "xxxxx,led";			
		led=<&gpf3 4 0>,<&gpf3 5 0>;	
		       0            1
	};	

2.
struct device_node *np;
np = of_find_node_by_path("/led");
led4_gpio = of_get_named_gpio(np,"led",0);

ret = gpio_request(led4_gpio,"GPF3_4");

gpio_direction_output(led4_gpio,1);


gpio_set_value(led4_gpio,0);
gpio_free(led4_gpio);

 

3、用户空间gpio的调用
	内核配置 : /sys/class/gpio的交互界面的
	Device Drivers  --->
	 --- GPIO Support
		[*]   /sys/class/gpio/... (sysfs interface)  
		
		配置+编译+拷贝+挂载
		
	(1)/sys/class/gpio
		echo 220 > export  
			出现:/sys/class/gpio/gpio220
		echo 220 > unexport 
	
	(2)cd /sys/class/gpio/gpio220
		设置输入、输出方向
			echo  in/out > direction
			
	(3)给管脚电平赋值
		echo  1/0 > value

 

https://blog.csdn.net/zmjames2000/article/details/87812384

2.定时器
[1]定时器
[1.1]结构体	
struct timer_list {
	unsigned long expires;//定时时间   jiffies  +  2  
	void (*function)(unsigned long);//定时器时间达到后的处理函数
	unsigned long data;//给func传参
};
				
[2.2]函数
void init_timer(struct timer_list *timer)
功能:初始化定时器
参数:timer:定时器指针对象
返回值:无
/*
#define init_timer(timer)						\
	__init_timer((timer), 0)
*/
				
void add_timer(struct timer_list *timer)
功能:注册定时器
参数:timer:定时器指针对象
返回值:无
				
int del_timer(struct timer_list *timer)
功能:注销定时器
参数:timer:定时器指针对象
返回值: del_timer() of an inactive timer returns 0, del_timer() of an active timer returns 1
				
int mod_timer(struct timer_list *timer, unsigned long expires)
功能:修改定时器的定时时间
参数:timer:定时器指针对象
expires:定时到达时间
返回值:mod_timer() of an inactive timer returns 0, mod_timer() of an  active timer returns 1
				
				
[3]
HZ: 1s = 1hz
t = 1/f;
CONFIG_HZ=200 
1s   =  200 jiffies
		
[4]jiffies时间操作		
unsigned int jiffies_to_msecs(const unsigned long j);
unsigned int jiffies_to_usecs(const unsigned long j);
unsigned long msecs_to_jiffies(const unsigned int m);	
unsigned long usecs_to_jiffies(const unsigned int u);

unsigned long timespec_to_jiffies(const struct timespec *value);
void jiffies_to_timespec(const unsigned long jiffies, struct timespec *value);
unsigned long timeval_to_jiffies(const struct timeval *value);
void jiffies_to_timeval(const unsigned long jiffies,struct timeval *value);
1.
struct timer_list tm;
tm.expires = jiffies + msecs_to_jiffies(10);
tm.function = tm_function;
tm.data = 12233;

init_timer(&tm);
add_timer(&tm);

2.
struct device_node *np;
np = of_find_node_by_path("/key2_gpio");
ey2_gpio = of_get_named_gpio(np,"key2",0);
io_request(key2_gpio,"key2_gpio");
irq = gpio_to_irq(key2_gpio);

ret = request_irq(irq,key2_irq_handler,IRQF_TRIGGER_FALLING,"key2_gpio",NULL);


irqreturn_t key2_irq_handler(int irq, void *data)
{
	mod_timer(&tm,jiffies + msecs_to_jiffies(10));
	return IRQ_HANDLED;
}


void tm_function(unsigned long data)
{
	int value = 0;
	value = gpio_get_value(key2_gpio);
	if(!value){
		while(!gpio_get_value(key2_gpio));
		printk("key2 enter...\n");
	}
}

内核中:  设备=====总线=====驱动
引入总线设备驱动:可以将设备信息  与  驱动信息   分离开来

[1]总线 
[1.1]总线结构体
struct bus_type {
	const char		*name;//总线名称 platform  ,i2c
	int (*match)(struct device *dev, struct device_driver *drv);//指定总线上  device  device_driver 的匹配规则
// 只有match成功后,才能执行probe
};

[1.2]相关API
int bus_register(struct bus_type *bus);   //注册总线对象   /sys/bus/i2c  /sys/bus/platform
void bus_unregister(struct bus_type *bus);//注销总线对象
[1.3]注册成功现象
		
[2]设备
[1.1]设备结构体
struct device {
	//struct kobject kobj;//linux设备对象
	const char		*init_name; /*设备名称*/
	struct bus_type	*bus;		/* 该设备被挂接的总线 */
	struct device_driver *driver;	/* 支持该设备的驱动对象*/
	dev_t			devt;	/* 设备号*/
	void	(*release)(struct device *dev);/*设备移除时会被执行*/
};
[1.2]相关API
int device_register(struct device *dev)//注册device   /sys/bus/platform/devices
void device_del(struct device *dev)//注销
[1.3]注册成功现象	
	
	
[3]驱动	
[1.1]驱动结构体
struct device_driver {
	const char		*name; //名称
	struct bus_type		*bus;//依附的宗霞
	const struct of_device_id	*of_match_table;//设备驱动的一种匹配方式  --->设备树匹配
	int (*probe) (struct device *dev);//总线上的设备与驱动匹配成功后自动执行
	int (*remove) (struct device *dev);//总线上的设备与驱动任意一方移除的时会被调用
};
[1.2]相关API
int driver_register(struct device_driver *drv)   //驱动注册    /sys/bus/platform/drivers
void driver_unregister(struct device_driver *drv)//驱动注销
[1.3]注册成功现象
[1]platform总线
[1.1]总线结构体	
struct bus_type platform_bus_type = {
	.name		= "platform",
	.match		= platform_match,
};
	
	
[2]platform设备 + 资源
[1.1]设备结构体
struct platform_device {
	const char	*name;//名称
	int		id;// -1
	struct device	dev;//设备模型的参数
			
	struct resource	*resource;//填充对应的设备硬件信息	
	u32		num_resources;//设备信息的数量
	const struct platform_device_id	*id_entry;//兼容设备
};
			
struct resource {
	unsigned long flags;//用于区分不同的资源   IORESOURCE_MEM   IORESOURCE_IRQ
	resource_size_t start;
	resource_size_t end;
};
			
struct platform_device_id {
	char name[PLATFORM_NAME_SIZE];
};
		
[1.2]相关API
int platform_device_register(struct platform_device *pdev)
功能:注册platform_device
参数:pdev:struct platform_device的指针对象
返回值:成功: 0 错误 :errno
			
void platform_device_unregister(struct platform_device *pdev)
功能:注销platform_device
参数:pdev:struct platform_device的指针对象
返回值:无
		
[1.3]注册成功现象


		
[3]platform驱动	
[1.1]驱动结构体
struct platform_driver {
	int (*probe)(struct platform_device *);//platform_device与platform_driver匹配成功后自动执行
	int (*remove)(struct platform_device *);//platform_device与platform_driver任意一方移除时自动执行
	struct device_driver driver;
	const struct platform_device_id *id_table;
};
			
struct device_driver {
	const char		*name; //名称
	struct module		*owner;//指向该模块
	const struct of_device_id	*of_match_table;//设备驱动的一种匹配方式  --->设备树匹配
};
			
			
platform_device与platform——driver匹配成功的规则:
					device                                driver
[1.2]相关API
int platform_driver_register(struct platform_driver *drv)
功能:注册platform_driver
参数:pdev:struct platform_driver的指针对象
返回值:成功: 0 错误 :errno
/*#define platform_driver_register(drv) \
	__platform_driver_register(drv, THIS_MODULE)*/
			
			
void platform_driver_unregister(struct platform_driver *);
功能:注销platform_driver
参数:pdev:struct platform_driver的指针对象
返回值:无


[1.3]注册成功现象	
		为什么platform_device/platform_driver注册进内核,匹配成功的话会自动执行probe?
			思路:找注册函数?(了解即可)
			(1)__platform_driver_register
					driver_register
						ret = bus_add_driver(drv);
							klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
								if (drv->bus->p->drivers_autoprobe) {
									error = driver_attach(drv);
									if (error)
										goto out_unregister;
								}
								
			(2)int driver_attach(struct device_driver *drv)
					return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
						if (!driver_match_device(drv, dev))//执行总线中的match
						if (!dev->driver)
							driver_probe_device(drv, dev);
								ret = really_probe(dev, drv);//probe函数
		
		总线  设备 驱动模型中  对于设备及驱动的匹配  : bus_type->match   
                                                        xxx_driver->probe

所有的 总线 设备 驱动 模型中,对于设备及驱动的匹配:bus_type->match 先执行这个函数。
如果匹配成功就会执行 xxx_driver->probe 这个函数

 


adc

	adc{
		compatible = "xxxx,adc";
		reg = <0x126c0000 0x20>;//根据adc的寄存器的地址进行填充
		interrupt-parent = <&combiner>;//根据第九章 第十章 中断控制器  combiner控制器进行相关的填充
		interrupts = <10 3>;    //第10组的第三个序号
	};

 combiner:interrupt-controller@10440000 {
        compatible = "samsung,exynos4210-combiner";
        interrupt-controller;//中断控制器
        #interrupt-cells = <2>;//如果说继承该“combiner”,使用该“combiner”中的一个具体的中断,
								//那么对于该中断的描述应该是两个成员
        reg = <0x10440000 0x1000>;
                        0       1         2        3
        interrupts = <0 0 0>, <0 1 0>, <0 2 0>, <0 3 0>,
                    <0 4 0>, <0 5 0>, <0 6 0>, <0 7 0>,
                    <0 8 0>, <0 9 0>, <0 10 0>, <0 11 0>,
                    <0 12 0>, <0 13 0>, <0 14 0>, <0 15 0>;
    };    

                intG10_7    DMC1_PPC_PEREV_M       DMC1
10        42    intG10_6    DMC1_PPC_PEREV_A       DMC1
                intG10_5    DMC0_PPC_PEREV_M       DMC0
                intG10_4    DMC0_PPC_PEREV_A       DMC0
                intG10_3    ADC                    Geberal ADC

 

2.pwm
    pwm占空比就是一个脉冲周期内高电平的所整个周期占的比例。例如1秒高电平1秒低电平的PWM波占空比是50%
pwm就是脉冲宽度调制,应用在从测量、通信到功率控制与变换的许多领域中。

https://blog.csdn.net/zmjames2000/article/details/87969719

 


 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

常用字符设备驱动函数总结 ----- 记录总结tool 的相关文章

  • python-爬虫初识-自动登录(二)

    目录 一 BeautifulSoup模块详细介绍 二 自动登录github 一 BeautifulSoup模块详细介绍 BeautifulSoup是一个模块 该模块用于接收一个HTML或XML字符串 然后将其进行格式化 之后遍可以使用他提供
  • 自定义一个类加载器

    为什么要自定义类加载器 类加载机制 http www cnblogs com xrq730 p 4844915 html 类加载器 http www cnblogs com xrq730 p 4845144 html 这两篇文章已经详细讲解
  • HKPCA Show携手电巢直播开启“云”观展!掀起一场电子人的顶级狂欢!

    近日 国际电子电路 深圳 展览会 HKPCA Show 已于深圳国际会展中心圆满举办 本次展览划分七大主题专区 面积超50 000平方米 展位超2500个 汇聚众多行业知名 有影响力的参展商 引用现场观众的一句话讲 这种规模的电子展览会真是
  • 北欧--2022年Python爬虫心得

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 前言 网络爬虫也称为 蜘蛛 它可以在海量的互联网信息爬取需要的信息 简单地说它是模拟人类请求网站的行为 即自动请求网页 抓取数据 然后从中提取有价值的数据 具体步骤如下 首先
  • oracle主键约束删除,oracle删除主键查看主键约束及创建联合主键

    oracle删除主键查看主键约束及创建联合主键 1 主键的删除 ALTER TABLE TABLENAME DROP PRIMARY KEY 执行上面的SQL可以删除主键 如果不成功可以用 ALTER TABLE TABLENAME DRO
  • C/C++访问MySQL数据库

    C C 访问MySQL数据库 VS2019配置 打开mysql的安装目录 默认安装目录如下 C Program Files MySQL MySQL Server 8 0 确认 lib 目录和include 目录是否存在 打开VS2019 新
  • 如何将自己的项目jar包打包成docker 镜像

    首先将自己的项目打包成jar 并在自己本地先用java jar xxx jar启动下 看是否可以启动 随后将自己的jar包同级目录创建一个Dockerfile文件 并用notepad打开 文件无后缀 FROM kdvolder jdk8 V
  • linux下手动安装编译的通用步骤

    手动安装编译的通用步骤 在自己的下载目录中 1 在官网下载压缩包 wget xxx 2 解压文件 tar zxf xxx 3 开始编译安装 查看解压的目录下的文件 是config还是autogen之类的 来决定使用 autogen sh还是
  • 如何使用memset函数

    如何使用memset函数 memset用处 memset使用方法 memset用处 memset函数是主要用于初始化字符串的一个函数 也可以用于初始化自定义类型数组 结构体数组和其他类型数组 memset使用方法 memset函数原型如下
  • 机器学习19:反卷积算法

    机器学习19 反卷积算法 转载和整理 在整理全卷积网络的过程中 被反卷积的概念困扰很久 于是将反卷积算法单独整理为一篇博客 本文主要转载和整理自知乎问题如何通俗易懂地解释反卷积 中的高票答案 1 反卷积概述 应用在计算机视觉的深度学习领域
  • java将本地图片复制添加水印并导出到本地

    模板信息 package com example demo ChartGraphics import com sun image codec jpeg JPEGCodec import com sun image codec jpeg JP
  • Mybatis动态公用sql

  • 文件夹自动同步工具

    这是我之前开发的文件夹自动同步工具 主要实现开发机和服务器之间的文件夹同步 项目地址 https github com mike zhang autoSync 问题描述 在windows下修改代码 到服务器上去编译 但每次都要通过winsc
  • JAR 文件揭密

    JAR 文件揭密 探索 JAR 文件格式的强大功能 转载 原文地址 http www ibm com developerworks cn java j jar 简介 大多数 Java 程序员都熟悉对 JAR 文件的基本操作 但是只有少数程序
  • 【c++ 之 多态】

    目录 前言 多态 认识多态 多态的定义与实现 构成多态的条件 虚函数 1 协变 基类与派生类虚函数返回值不同 2 析构函数的重写 c 11 两个虚函数修饰关键字 final override 重载 重写 重定义再理解 抽象类 抽象类的概念
  • 飞书“蒙冤”,还是舆论有噪声?

    飞书遭微信大范围屏蔽 添加好友 共享文档等功能遭禁 初次看到这个消息的时候并没有过于惊讶 毕竟头条系和腾讯的 摩擦 早已是公关圈老生常谈的话题 双方的较量从2017年底至今 已是3年持久战 这次的较量又有些不同 有人大肆渲染飞书在帮助中小企
  • python绘制三维图

    作者 桂 时间 2017 04 27 23 24 55 链接 http www cnblogs com xingshansi p 6777945 html 本文仅仅梳理最基本的绘图方法 一 初始化 假设已经安装了matplotlib工具包
  • Hadoop集群的9870页面,DataNode启动不了的解决办法

    原因 多次格式化hdfs namenode format的操作 配置文件错误或者说修改过master配置没有进行格式化后续操作
  • Android平台GB28181设备接入端如何实时更新经纬度实现国标平台侧电子地图位置标注

    技术背景 我们在做GB28181设备接入端的时候 其中有个功能 不难但非常重要 那就是GB28181实时位置的订阅 mobileposition subscribe 和上报 notify 特别是执法记录仪 智能安全帽 车载终端等场景下 现场

随机推荐

  • mysql show variables sql_mode_MySQL的三种常见sql_mode

    MySQL数据库的中有一个环境变量sql mode 定义了mysql应该支持的sql语法 数据校验等 我们可以通过以下方式查看当前数据库使用的sql mode mysql gt select sql mode sql mode STRICT
  • 投稿指南【NO.12_8】【极易投中】核心期刊投稿(组合机床与自动化加工技术)

    近期有不少同学咨询投稿期刊的问题 大部分院校的研究生都有发学术论文的要求 少部分要求高的甚至需要SCI或者多篇核心期刊论文才可以毕业 但是核心期刊要求论文质量高且审稿周期长 所以本博客梳理一些计算机特别是人工智能相关的期刊 供大家参考投稿
  • 可视化笔记3--matplotlib 常见图形绘制3

    可视化笔记3 matplotlib 常见图形绘制3 接着上一篇博文 继续简单学习了下matplotlib绘图功能 基本包括 图片保存及工具栏使用 区域填充 形状绘制 图形美化 绘制极坐标 绘制积分函数 散点和条形图综合案例 相应学习笔记分享
  • 什么是HIS,以及HIS的作用,特点,组成部分

    什么叫HIS HIS系统定制开发服务 HIS系统作用 HIS系统开发价格 HIS系统的主要组成部分 HIS系统的基本概述 HIS 即Hospital Information System 直译为中文就是医院信息系统利用计算机软硬件技术 网络
  • VSCode搭建STM32开发环境

    废话不多说 直接步入正题 所需软件如下 GNU Arm Embedded Toolchain Mingw w64 make openocd STM32CubeMx VSCode 一 环境篇 1 GNU Arm Embedded Toolch
  • OpenCV与图像算法笔记

    本博客为 OpenCV算法精解 基于Python与C 一书 参阅源代码链接 的阅读笔记 根据理解对书中绝大多数算法做了总结和描述 对Numpy较为熟悉 Python方面仅对与C 不同的注意事项做了标注 书作者整体按照冈萨雷斯的经典教材 数字
  • CDN架构

    CDN公司在整个互联网中部署了数以百计的节点 Cache服务器集群 这些Cache服务器都分布在各个网络运营商的IDC机房中 位置尽量靠近用户网络 CDN系统将内容从源站复制到各个节点 在内容提供者更新内容时 CDN系统将更新后的内容重新分
  • 模板的全特化与偏特化

    模板为什么要特化 因为编译器认为 对于特定的类型 如果你能对某一功能更好的实现 那么就该听你的 模板分为类模板与函数模板 特化分为全特化与偏特化 全特化就是限定死模板实现的具体类型 偏特化就是如果这个模板有多个类型 那么只限定其中的一部分
  • 基于Verilog HDL 和FPGA的寻线小车设计代码

    刚进实验室的第一个项目 做一个寻线小车 我就用FPGA芯片尝试做了一个 用到的零件有 车模一个 一块L298N驱动模块 一块cyclone IV芯片 具体型号是EP4CE6E22C8N 四个电机 一个电源 一个四灰度寻迹模块 或者红外寻迹模
  • 使用ffmpeg将WebM文件转换为MP4文件的简单应用程序

    tiktok网上下载的short视频是webm格式的 有些程序无法处理该程序 比如roop程序 本文介绍了如何使用wxPython库创建一个简单的GUI应用程序 用于将WebM文件转换为MP4文件 这个应用程序使用Python编写 通过调用
  • 对eureka,ribbon,feign和负载均衡的理解

    以下是个人理解 仅供参考 eureka获取ip和端口的方法 GetMapping test public String Test String serverName List
  • 简单排序 插入排序详解 C语言入门

    欢迎关注笔者 你的支持是持续更博的最大动力 目录 问题描述 思路 代码 相关内容 其他 问题描述 给n个数按从小到大排序 插入排序 思路 插入排序 把无序部分元素插入有序部分 1 用无序部分的第1个元素 和前面有序部分每一个元素比较 2 如
  • C# WPF在xaml中的数据绑定

    在WPF的绑定中个人分为两种 非集合数据绑定和集合数据绑定 非集合数据绑定 指的是包含数据为单个de控件的数据绑定 而不是有很多子项 例如Button TextBox TextBlock等等的绑定 集合数据绑定 指的是包含很多子项的控件的数
  • 打印机怎么扫描到电脑_电脑连不上打印机怎么办,告诉你解决方法

    打印机的作用是非常强大的 在工作时经常会需要打印一些报表文件什么的 这时就离不开打印机的功能 而想要使用打印机 就必须要连接电脑 但有时电脑连不上打印机怎么办呢 是什么原因造成的呢 1 如果打印机是正常的情况下 就需要看下是否是连接的问题了
  • hexo部署:创建个人博客并部署到github

    前言 本文详细记录使用hexo搭建并部署个人博客的全过程 原文地址 https xuedongyun cn post 46487 创建hexo项目 在有node js环境的情况下 搭建项目 npm install hexo cli g he
  • 基于Android的中小学家校互动平台的设计与实现

    基于Android的中小学家校互动平台的设计与实现 摘 要 如今 互联网发展越来越快 手机应用开发技术日渐完善 随着社会信息化水平的提高 以及对新一代教育的日益关注 家校之间的沟通 家校互动的重要性日益凸显 这种需求也使得家校互动的市场受到
  • 秋招算法面经集合

    面试锦囊之面经分享系列 持续更新中 欢迎后台回复 面试 加入讨论组交流噢 写在前面 秋招告一段落 整理文件的时候发现之前记录的面经问题 主要是秋招前期的一些面试 后期由于实习比较忙没花时间整理 希望自己的面经可以帮助到有需要的人吧 由于个人
  • vite框架使用eslint+prettier配置,并且将错误信息显示在浏览器界面上

    在vscode编辑器当中使用vite框架 配置eslint 首先在vite项目中 安装eslint plugin vue依赖 npm install save dev eslint eslint config prettier eslint
  • 当你输入一个网址/点击一个链接,发生了什么?(以www.baidu.com为例)

    假设你用一个全新的浏览器 第一次启动的那种 访问百度 http www baidu com 在你敲入网址并按下回车之后 将会发生以下神奇的事情 浏览器先尝试从Host文件中获取http www baidu com 对应的IP地址 如果能取到
  • 常用字符设备驱动函数总结 ----- 记录总结tool

    打印等级 define KERN EMERG lt 0 gt system is unusable define KERN ALERT lt 1 gt action must be taken immediately define KERN