Linux第87步_阻塞IO实验
阻塞IO是“应用程序”对“驱动设备”进行操作,若不能获取到设备资源,则阻塞IO应用程序的线程会被“挂起”,直到获取到设备资源为止。
“挂起”就是让线程进入休眠,将CPU的资源让出来。线程进入休眠后,当设备文件可以操作时,就必须唤醒这个休眠的线程。通常是在中断函数里完成唤醒工作,Linux内核是采用“等待队列(wait queue)”来完成阻塞线程的唤醒工作。
阻塞IO应用举例:
1、添加“EXTI3.c”
#include "EXTI3.h"
#include <linux/gpio.h>
//使能gpio_request(),gpio_free(),gpio_direction_input(),
//使能gpio_direction_output(),gpio_get_value(),gpio_set_value()
#include <linux/of_gpio.h>
//使能of_gpio_named_count(),of_gpio_count(),of_get_named_gpio()
#include <linux/delay.h>
//Linux内核中用到的延时函数
//使能ndelay(),udelay(),mdelay()
#include <linux/of_irq.h>
//使能irq_of_parse_and_map()
#include <linux/interrupt.h>
//request_irq(),free_irq(),enable_irq(),disable_irq(),disable_irq_nosync()struct Button_dev strButton;int Get_gpio_Button_num(void);
int Button_gpio_request(void);
void Button_free(void);
int Read_Button(void);static irqreturn_t key_interrupt_fun(int irq, void *dev_id)
{atomic_set(&strButton.button_irq_status, 1);/*设置原子变量值strButton.button_irq_status.counter=1*/wake_up_interruptible(&strButton.read_wait); /* 唤醒 */return IRQ_HANDLED;
}int Get_gpio_Button_num(void)
{int ret = 0;const char *str;strButton.button_irq_status = (atomic_t)ATOMIC_INIT(0);/*初始化原子变量*/atomic_set(&strButton.button_irq_status, 0);/*设置原子变量初始值strButton.button_irq_status.counter=0*/init_waitqueue_head(&strButton.read_wait);/* 初始化等待队列头 */strButton.keyvalue=0;/* 设置Button所使用的GPIO *//* 1、获取设备节点:strButton */strButton.nd = of_find_node_by_path("/key0");//path="/key0,使用“全路径的节点名“在“stm32mp157d-atk.dts“中查找节点“key0”//返回值:返回找到的节点,如果为NULL,表示查找失败。if(strButton.nd == NULL) {printk("key0 device node not find!\r\n");return -EINVAL;}/* 2.读取status属性 */ret = of_property_read_string(strButton.nd, "status", &str);//在key0节点中,status = "okay";//指定的设备节点strButton.nd//proname="status",给定要读取的属性名字//out_string=str:返回读取到的属性值//返回值:0,读取成功,负值,读取失败。if(ret < 0) return -EINVAL;if(strcmp(str, "okay")) return -EINVAL;//strcmp(s1,s2),当s1<s2时,返回值为负数//strcmp(s1,s2),当s1>2时,返回值为正数//strcmp(s1,s2),当s1=s2时,返回值为0/* 3、获取compatible属性值并进行匹配 */ret = of_property_read_string(strButton.nd, "compatible", &str);//在key0节点中,compatible = "zgq,key";//指定的设备节点strButton.nd//proname="compatible",给定要读取的属性名字//out_string=str:返回读取到的属性值//返回值:0,读取成功,负值,读取失败。if(ret < 0) {printk("key0 node: Failed to get compatible property\n"); return -EINVAL;}if (strcmp(str, "zgq,key")) {printk("key0 node: Compatible match failed\n");return -EINVAL;}/* 4、 根据设备树中的"key-gpio"属性,得到key0所使用的key0编号 */strButton.button_gpio_number = of_get_named_gpio(strButton.nd, "key-gpio", 0);//在key0节点中,key-gpio = <&gpiog 3 GPIO_ACTIVE_LOW>//np=strButton.nd,指定的“设备节点”//propname="key-gpio",给定要读取的属性名字//Index=0,给定的GPIO索引为0//返回值:正值,获取到的GPIO编号;负值,失败。if(strButton.button_gpio_number < 0) {printk("can't get key-gpio");return -EINVAL;}/* 5 、获取GPIO对应的中断号 */strButton.irq_num = irq_of_parse_and_map(strButton.nd, 0);//dev=strButton.nd:为设备节点;//Index=0:索引号,intemrupts属性可能包含多条中断信息,通过index指定要获取的信息;//返回值:中断号;if(!strButton.irq_num){return -EINVAL;}printk("key-gpio num = %d\r\n", strButton.button_gpio_number);//打印结果为:“key-gpio num = 99“//因为GPIO编号是从0开始的,GPIOG端口的序号是6,每个端口有16个IO口,因此GPIOI0的编号为6*16 + 3 = 99return 0;
}int Button_gpio_request(void)
{int ret = 0;unsigned long irq_flags;/* 5.向gpio子系统申请使用“gpio编号” */ret = gpio_request(strButton.button_gpio_number, "Button0");//gpio=strButton.button_gpio_number,指定要申请的“gpio编号”//label="Button",给这个gpio引脚设置个名字为"Button0"//返回值:0,申请“gpio编号”成功;其他值,申请“gpio编号”失败;if (ret) {printk(KERN_ERR "Button: Failed to request key0\n");return ret;}/* 6、设置PG3为输入模式*/ret = gpio_direction_input(strButton.button_gpio_number);//gpio=strButton.button_gpio_number,指定的“gpio编号”if(ret < 0) {printk("can't set gpio!\r\n");}/* 获取设备树中指定的中断触发类型 */irq_flags = irq_get_trigger_type(strButton.irq_num);//strButton.irq_num为中断号if ( irq_flags==IRQF_TRIGGER_NONE )irq_flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;/*申请中断*/ret = request_irq(strButton.irq_num, key_interrupt_fun, irq_flags, "Key0_IRQ", NULL);//irq=strButton.irq_num:要申请中断的中断号;//handler=key_interrupt_fun:中断处理函数,当中断发生以后就会执行此中断处理函数;//fags=irq_flags:中断标志;可以在文件“include/linux/interrupt.h”里面查看所有的中断标志;//name="Key0_IRQ":中断名字,设置以后可以在“/proc/interrupts”文件中看到对应的中断名字;//dev=NULL:如果将flags设置为IRQF_SHARED的话,dev用来区分不同的中断。//一般情况下将dev设置为“设备结构体”,dev会传递给中断处理函数irg_handler_t的第二个参数。//返回值:0表示中断申请成功,如果返回“-EBUSY”的话表示中断已经被申请过了, 其他负值,表示中断申请失败。if (ret) {printk(KERN_ERR "Button: Failed to request irq\n");return ret;}return 0;
}//函数功能:释放Button的gpio
void Button_free(void)
{free_irq(strButton.irq_num, NULL); /* 释放中断 */gpio_free(strButton.button_gpio_number);/* 释放IO */
}//读取按键的值
int Read_Button(void)
{u8 ch;int status;int ret;/* 加入等待队列,当有按键按下或松开动作发生时,才会被唤醒 */ret=wait_event_interruptible(strButton.read_wait, atomic_read(&strButton.button_irq_status) !=0 );/* 读取strButton.button_irq_statusv.counter的值,读取按键中断标志 */ //等待以“strButton.read_wait为等待队列头的等待队列”被唤醒,属于等待队列唤醒;//当条件满足atomic_read(&strButton.button_irq_status) !=0时,会执行唤醒,否则会一直阻塞。//唤醒后,会将进程设置为"TASK_INTERRUPTIBLE"状态,即进程可以被信号打断。ch=10;while(ch)//消抖{mdelay(20);//延时20毫秒消抖if(gpio_get_value(strButton.button_gpio_number) == 0) ch=10;ch--;}//mdelay(50);//延时50毫秒消抖ch=0;if(strButton.keyvalue) ch=1;if(ch)//关灯{strButton.keyvalue=KEY_OFF;//准备中断返回值printk("Button: off\r\n");}else//开灯{strButton.keyvalue=KEY_ON;//准备中断返回值printk("Button: on\r\n");}status=strButton.keyvalue;//发送中断返回值atomic_set(&strButton.button_irq_status, 0);/*原子变量初始值strButton.button_irq_status.counter=0, 状态重置 */return status;
}
2、添加“EXTI3.h”
#ifndef __EXTI3_H
#define __EXTI3_H#include <linux/types.h>
/*
数据类型重命名
使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
*/
#include <linux/of.h> //使能device_node结构
#include <linux/wait.h> //使能wait_queue_head_t结构#define KEY_ON 1 /* 按键按下 */
#define KEY_OFF 0 /* 按键松开 */struct Button_dev{struct device_node *nd; /*设备节点*/int button_gpio_number; /*Button所使用的GPIO编号*/int irq_num; /* 中断号 */atomic_t button_irq_status; /*原子变量*/wait_queue_head_t read_wait; /* 读等待队列头 */int keyvalue;
};
extern struct Button_dev strButton;extern int Get_gpio_Button_num(void);
extern int Button_gpio_request(void);
extern void Button_free(void);
extern int Read_Button(void);#endif
3、添加“EXTI3_drv.c”
#include "EXTI3_drv.h"
#include "EXTI3.h"
#include <linux/types.h>
//数据类型重命名
//使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
//使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
#include <linux/ide.h>
//使能copy_from_user(),copy_to_user()
#include <linux/module.h>
//使能EXTI3Driver_init(),EXTI3Driver_exit()
#include <linux/gpio.h>
//使能gpio_request(),gpio_free(),gpio_direction_input(),
//gpio_direction_output(),gpio_get_value(),gpio_set_value()
#include <linux/spinlock.h>
//使能DEFINE_SPINLOCK(),spin_lock_init(),spin_lock(),spin_trylock(),spin_is_locked()
//spin_lock_irq(),spin_unlock_irq(),spin_lock_irqsave(),spin_unlock_irqrestore()#define EXTI3Driver_CNT 1 //定义设备数量为1
#define EXTI3Driver_NAME "EXTI3Driver" //定义设备的名字struct EXTI3Driver_dev strEXTI3Driver;/* 打开设备 */
static int EXTI3Driver_open(struct inode *inode, struct file *filp)
{/*通过判断原子变量的值来检查EXTI3有没有被别的应用使用*/if (!atomic_dec_and_test(&strEXTI3Driver.lock)){//当strEXTI3Driver.lock.counter=1时,atomic_dec_and_test()返回1//从strEXTI3Driver.lock.counter减1,如果结果为0就返回1,否则返回0;atomic_inc(&strEXTI3Driver.lock);/*小于0的话就加1,使其原子变量等于0*/return -EBUSY; /* EXTI3被使用,返回忙*/}filp->private_data = &strEXTI3Driver; /*设置私有数据*/printk("EXTI3Driver_open!\r\n");return 0;
}/* 从设备读取数据,保存到首地址为buf的数据块中,长度为cnt个字节 */
//file结构指针变量flip表示要打开的设备文件
//buf表示用户数据块的首地址
//cnt表示用户数据的长度,单位为字节
//loff_t结构指针变量offt表示“相对于文件首地址的偏移”
static ssize_t EXTI3Driver_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{int ret = 0;int value;value=Read_Button();//读取按键的值 ret = copy_to_user(buf, &value, sizeof(value));//将value拷贝到buf[]中return ret;
}/* 向设备写数据,将数据块首地址为buf的数据,长度为cnt个字节,发送给用户 */
//file结构指针变量flip表示要打开的设备文件
//buf表示用户数据块的首地址
//cnt表示用户数据的长度,单位为字节
//loff_t结构指针变量offt表示“相对于文件首地址的偏移”
static ssize_t EXTI3Driver_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/* 关闭/释放设备 */
static int EXTI3Driver_release(struct inode *inode, struct file *filp)
{struct EXTI3Driver_dev *dev = filp->private_data;atomic_inc(&dev->lock);/*关闭驱动文件的时候释放原子变量,便于其它线程使用*/printk("EXTI3Driver_release!\r\n");return 0;
}/*声明file_operations结构变量MyCharDevice_fops*/
/*它是指向设备的操作函数集合变量*/
const struct file_operations EXTI3Driver_fops = {.owner = THIS_MODULE,.open = EXTI3Driver_open,.read = EXTI3Driver_read,.write = EXTI3Driver_write,.release = EXTI3Driver_release,
};/*驱动入口函数 */
static int __init EXTI3Driver_init(void)
{int ret;strEXTI3Driver.lock = (atomic_t)ATOMIC_INIT(0);/*初始化原子变量*/atomic_set(&strEXTI3Driver.lock, 1);/*原子变量初始值strEXTI3Driver.lock.counter=1*/ret=Get_gpio_Button_num();//读引脚编号if(ret < 0) return ret; /* 1、申请“gpio编号”*/ret=Button_gpio_request();//申请“gpio编号” if(ret < 0) return ret;//向gpio子系统申请使用“gpio编号” 失败/*2、申请设备号*/strEXTI3Driver.major=0;if(strEXTI3Driver.major)/*如果指定了主设备号*/{strEXTI3Driver.devid = MKDEV(strEXTI3Driver.major, 0);//输入参数strEXTI3Driver.major为“主设备号”//输入参数0为“次设备号”,大部分驱动次设备号都选择0//将strEXTI3Driver.major左移20位,再与0相或,就得到“Linux设备号”ret=register_chrdev_region( strEXTI3Driver.devid,\EXTI3Driver_CNT, \EXTI3Driver_NAME );//strEXTI3Driver.devid表示起始设备号//EXTI3Driver_CNT表示次设备号的数量//EXTI3Driver_NAME表示设备名if(ret < 0)goto free_gpio;}else{ /* 没有定义设备号 */ret=alloc_chrdev_region( &strEXTI3Driver.devid,\0, \EXTI3Driver_CNT,\EXTI3Driver_NAME);/* 申请设备号 *///strEXTI3Driver.devid:保存申请到的设备号//0:次设备号的起始地址//EXTI3Driver_CNT:要申请的次设备号数量;//EXTI3Driver_NAME:表示“设备名字”if(ret < 0)goto free_gpio;strEXTI3Driver.major = MAJOR(strEXTI3Driver.devid);/* 获取分配号的主设备号 *///输入参数strEXTI3Driver.devid为“Linux设备号”//将strEXTI3Driver.devid右移20位得到“主设备号”strEXTI3Driver.minor = MINOR(strEXTI3Driver.devid);/* 获取分配号的次设备号 *///输入参数strEXTI3Driver.devid为“Linux设备号”//将strEXTI3Driver.devid与0xFFFFF相与后得到“次设备号”}/*3、注册字符设备*/strEXTI3Driver.cdev.owner = THIS_MODULE;//使用THIS_MODULE将owner指针指向当前这个模块cdev_init(&strEXTI3Driver.cdev,&EXTI3Driver_fops);//注册字符设备,初始化“字符设备结构变量strEXTI3Driver.cdev”//strEXTI3Driver.cdev是等待初始化的结构体变量//EXTI3Driver_fops就是字符设备文件操作函数集合/*4、添加字符设备*/ret=cdev_add(&strEXTI3Driver.cdev,strEXTI3Driver.devid,EXTI3Driver_CNT);//添加字符设备/*&strEXTI3Driver.cdev表示指向要添加的字符设备,即字符设备结构strEXTI3Driver.cdev变量*///strEXTI3Driver.devid表示设备号//EXTI3Driver_CNT表示需要添加的设备数量if(ret < 0 ) //添加字符设备失败goto del_register;printk("dev id major = %d,minor = %d\r\n", strEXTI3Driver.major, strEXTI3Driver.minor);printk("EXTI3Driver_init is ok!!!\r\n");/*5、自动创建设备节点 */strEXTI3Driver.class =class_create(THIS_MODULE, EXTI3Driver_NAME);if (IS_ERR(strEXTI3Driver.class)){goto del_cdev;}/*6、创建设备 */strEXTI3Driver.device = device_create(strEXTI3Driver.class, NULL, strEXTI3Driver.devid, NULL, EXTI3Driver_NAME);//创建设备//设备要创建在strEXTI3Driver.class类下面//NULL表示没有父设备//strEXTI3Driver.devid是设备号;//参数drvdata=NULL,设备没有使用数据//EXTI3Driver_NAME是设备名字//如果设置fmt=EXTI3Driver_NAME 的话,就会生成/dev/EXTI3Driver_NAME设备文件。//返回值就是创建好的设备。if (IS_ERR(strEXTI3Driver.device)){goto destroy_class;}return 0;destroy_class:class_destroy(strEXTI3Driver.class);//删除类//strEXTI3Driver.class就是要删除的类del_cdev:cdev_del(&strEXTI3Driver.cdev);//删除字符设备//&strEXTI3Driver.cdev表示指向需要删除的字符设备,即字符设备结构strEXTI3Driver.cdev变量del_register:unregister_chrdev_region(strEXTI3Driver.devid, EXTI3Driver_CNT);/* 释放设备号 *///strEXTI3Driver.devid:需要释放的起始设备号//EXTI3Driver_CNT:需要释放的次设备号数量;free_gpio://申请设备号失败/*释放gpio编号*/Button_free();return -EIO;
}/*驱动出口函数 */
static void __exit EXTI3Driver_exit(void)
{/*1、删除字符设备*/cdev_del(&strEXTI3Driver.cdev);/*删除字符设备*//*&strEXTI3Driver.cdev表示指向需要删除的字符设备,即字符设备结构&strEXTI3Driver.cdev变量*//*2、 释放设备号 */unregister_chrdev_region(strEXTI3Driver.devid, EXTI3Driver_CNT);/*释放设备号 *///strEXTI3Driver.devid:需要释放的起始设备号//EXTI3Driver_CNT:需要释放的次设备号数;/*3、 删除设备 */device_destroy(strEXTI3Driver.class, strEXTI3Driver.devid);//删除创建的设备//strEXTI3Driver.class是要删除的设备所处的类//strEXTI3Driver.devid是要删除的设备号/*4、删除类*/class_destroy(strEXTI3Driver.class);//删除类//strEXTI3Driver.class就是要删除的类/*5、释放gpio编号*/Button_free();
}module_init(EXTI3Driver_init);
//指定EXTI3Driver_init()为驱动入口函数
module_exit(EXTI3Driver_exit);
//指定EXTI3Driver_exit()为驱动出口函数MODULE_AUTHOR("Zhanggong");//添加作者名字
MODULE_LICENSE("GPL");//LICENSE采用“GPL协议”
MODULE_INFO(intree,"Y");
//去除显示“loading out-of-tree module taints kernel.”
4、添加“EXTI3_drv.h”
#ifndef __EXTI3_DRIVER_H
#define __EXTI3_DRIVER_H#include <linux/types.h>
/*
数据类型重命名
使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
*/
#include <linux/cdev.h> //使能cdev结构
#include <linux/cdev.h> //使能class结构和device结构struct EXTI3Driver_dev{dev_t devid; /*声明32位变量devid用来给保存设备号*/int major; /*主设备号*/int minor; /*次设备号*/struct cdev cdev; /*字符设备结构变量cdev */struct class *class; /*类*/struct device *device; /*设备*/atomic_t lock; /*原子变量*/
};
extern struct EXTI3Driver_dev strEXTI3Driver;#endif
5、添加“LED.c”
#include "LED.h"
#include <linux/gpio.h>
//使能gpio_request(),gpio_free(),gpio_direction_input(),
//使能gpio_direction_output(),gpio_get_value(),gpio_set_value()
#include <linux/of_gpio.h>
//使能of_gpio_named_count(),of_gpio_count(),of_get_named_gpio()struct MyLED_dev strMyLED;int Get_led0_num(void);
int led_GPIO_request(void);
void MyLED_free(void);
void led_switch(u8 sta);int Get_led0_num(void)
{int ret = 0;const char *str;/* 设置LED所使用的GPIO *//* 1、获取设备节点:strMyLED */strMyLED.nd = of_find_node_by_path("/led0");//path="/led0,使用“全路径的节点名“在“stm32mp157d-atk.dts“中查找节点“led0”//返回值:返回找到的节点,如果为NULL,表示查找失败。if(strMyLED.nd == NULL) {printk("led0 node not find!\r\n");return -EINVAL;}/* 2.读取status属性 */ret = of_property_read_string(strMyLED.nd, "status", &str);//在led0节点中,status = "okay";//指定的设备节点strMyLED.nd//proname="status",给定要读取的属性名字//out_string=str:返回读取到的属性值//返回值:0,读取成功,负值,读取失败。if(ret < 0) return -EINVAL;if (strcmp(str, "okay")) return -EINVAL;//strcmp(s1,s2),当s1<s2时,返回值为负数//strcmp(s1,s2),当s1>2时,返回值为正数//strcmp(s1,s2),当s1=s2时,返回值为0/* 3、获取compatible属性值并进行匹配 */ret = of_property_read_string(strMyLED.nd, "compatible", &str);//在led0节点中,compatible = "zgq,led";//指定的设备节点strMyLED.nd//proname="compatible",给定要读取的属性名字//out_string=str:返回读取到的属性值//返回值:0,读取成功,负值,读取失败。if(ret < 0) {printk("led0 node: Failed to get compatible property\n"); return -EINVAL;}if (strcmp(str, "zgq,led")) {printk("led0 node: Compatible match failed\n");return -EINVAL;}/* 4、 根据设备树中的"led-gpio"属性,得到LED所使用的LED编号 */strMyLED.led_gpio_number = of_get_named_gpio(strMyLED.nd, "led-gpio", 0);//在led0节点中,led-gpio = <&gpioi 0 GPIO_ACTIVE_LOW>//np=strMyLED.nd,指定的“设备节点”//propname="led-gpio",给定要读取的属性名字//Index=0,给定的GPIO索引为0//返回值:正值,获取到的GPIO编号;负值,失败。if(strMyLED.led_gpio_number < 0) {printk("can't get led-gpio");return -EINVAL;}printk("led-gpio num = %d\r\n", strMyLED.led_gpio_number);//打印结果为:“led-gpio num = 128“//因为GPIO编号是从0开始的,GPIOI端口的序号是8,每个端口有16个IO口,因此GPIOI0的编号为8*16=128return 0;
}int led_GPIO_request(void)
{int ret = 0;/* 5.向gpio子系统申请使用“gpio编号” */ret = gpio_request(strMyLED.led_gpio_number, "LED-GPIO");//gpio=strMyLED.led_gpio_number,指定要申请的“gpio编号”//Iabel="LED-GPIO",给这个gpio引脚设置个名字为"LED-GPIO"//返回值:0,申请“gpio编号”成功;其他值,申请“gpio编号”失败;if (ret) {printk(KERN_ERR "strMyLED: Failed to request led-gpio\n");return ret;}/* 6、设置PI0为输出,并且输出高电平,默认关闭LED灯 */ret = gpio_direction_output(strMyLED.led_gpio_number, 1);//gpio=strMyLED.led_gpio_number,指定的“gpio编号”,这里是128,对应的是GI0引脚//value=1,设置引脚输出高电平//返回值:0,设置“引脚输出为vakued的值”成功;负值,设置“引脚输出为vakued的值”失败。if(ret < 0) {printk("can't set gpio!\r\n");}return 0;
}//函数功能:释放MyLED的gpio
void MyLED_free(void)
{gpio_free(strMyLED.led_gpio_number);
}void led_switch(u8 sta)
{if(sta == LEDON) {gpio_set_value(strMyLED.led_gpio_number, 0); /* 打开LED灯 */}
else if(sta == LEDOFF) {gpio_set_value(strMyLED.led_gpio_number, 1); /* 关闭LED灯 */}
}
6、添加“LED.h”
#ifndef __LED_H
#define __LED_H#include <linux/types.h>
/*
数据类型重命名
使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
*/
#include <linux/of.h> //使能device_node结构#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */struct MyLED_dev{struct device_node *nd; /*设备节点*/int led_gpio_number; /*led所使用的GPIO编号*/
};
extern struct MyLED_dev strMyLED;extern int Get_led0_num(void);
extern int led_GPIO_request(void);
extern void MyLED_free(void);
extern void led_switch(u8 sta);#endif
7、添加“LED_drv.c”
#include "LED_drv.h"
#include "LED.h"
#include <linux/types.h>
//数据类型重命名
//使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
//使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
#include <linux/ide.h>
//使能copy_from_user(),copy_to_user()
#include <linux/module.h>
//使能LEDDriver_init(),LEDDriver_exit()
#include <linux/gpio.h>
//使能gpio_request(),gpio_free(),gpio_direction_input(),
//gpio_direction_output(),gpio_get_value(),gpio_set_value()#define LEDDriver_CNT 1 //定义设备数量为1
#define LEDDriver_NAME "LEDDriver" //定义设备的名字struct LEDDriver_dev strLEDDriver;/* 打开设备 */
static int LEDDriver_open(struct inode *inode, struct file *filp)
{/*通过判断原子变量的值来检查LED有没有被别的应用使用*/if (!atomic_dec_and_test(&strLEDDriver.lock)){//当strLEDDriver.lock.counter=1时,atomic_dec_and_test()返回1//从strLEDDriver.lock.counter减1,如果结果为0就返回1,否则返回0;atomic_inc(&strLEDDriver.lock);/*小于0的话就加1,使其原子变量等于0*/return -EBUSY; /* LED被使用,返回忙*/}filp->private_data = &strLEDDriver; /*设置私有数据*/printk("LEDDriver_open!\r\n");return 0;
}/* 从设备读取数据,保存到首地址为buf的数据块中,长度为cnt个字节 */
//file结构指针变量flip表示要打开的设备文件
//buf表示用户数据块的首地址
//cnt表示用户数据的长度,单位为字节
//loff_t结构指针变量offt表示“相对于文件首地址的偏移”
static ssize_t LEDDriver_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/* 向设备写数据,将数据块首地址为buf的数据,长度为cnt个字节,发送给用户 */
//file结构指针变量flip表示要打开的设备文件
//buf表示用户数据块的首地址
//cnt表示用户数据的长度,单位为字节
//loff_t结构指针变量offt表示“相对于文件首地址的偏移”
static ssize_t LEDDriver_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int ret = 0;unsigned char databuf[1];unsigned char ledstat;ret = copy_from_user(databuf, buf, cnt);
//将buf[]中的前cnt个字节拷贝到databuf[]中if(ret <0){printk("kernel write failed!\r\n");ret = -EFAULT;}ledstat = databuf[0];/*获取到应用传递进来的开关灯状态*/led_switch(ledstat);/*执行开灯或执行关灯*/return ret;
}/* 关闭/释放设备 */
static int LEDDriver_release(struct inode *inode, struct file *filp)
{struct LEDDriver_dev *dev = filp->private_data;atomic_inc(&dev->lock);/*关闭驱动文件的时候释放原子变量,便于其它线程使用*/printk("LEDDriver_release!\r\n");return 0;
}/*声明file_operations结构变量MyCharDevice_fops*/
/*它是指向设备的操作函数集合变量*/
const struct file_operations LEDDriver_fops = {.owner = THIS_MODULE,.open = LEDDriver_open,.read = LEDDriver_read,.write = LEDDriver_write,.release = LEDDriver_release,
};/*驱动入口函数 */
static int __init LEDDriver_init(void)
{int ret;strLEDDriver.lock = (atomic_t)ATOMIC_INIT(0);/*初始化原子变量*/atomic_set(&strLEDDriver.lock, 1);/*原子变量初始值strLEDDriver.lock.counter=1*/ret=Get_led0_num();//读引脚编号if(ret < 0) return ret; /* 1、申请“gpio编号”*/ret=led_GPIO_request();//申请“gpio编号” if(ret < 0) return ret;//向gpio子系统申请使用“gpio编号” 失败/*2、申请设备号*/strLEDDriver.major=0;if(strLEDDriver.major)/*如果指定了主设备号*/{strLEDDriver.devid = MKDEV(strLEDDriver.major, 0);//输入参数strLEDDriver.major为“主设备号”//输入参数0为“次设备号”,大部分驱动次设备号都选择0//将strLEDDriver.major左移20位,再与0相或,就得到“Linux设备号”
ret=register_chrdev_region( strLEDDriver.devid,\LEDDriver_CNT, \LEDDriver_NAME );//strLEDDriver.devid表示起始设备号//LEDDriver_CNT表示次设备号的数量//LEDDriver_NAME表示设备名if(ret < 0)goto free_gpio;}else{ /* 没有定义设备号 */
ret=alloc_chrdev_region( &strLEDDriver.devid,\0, \LEDDriver_CNT,\LEDDriver_NAME);/* 申请设备号 *///strLEDDriver.devid:保存申请到的设备号//0:次设备号的起始地址//LEDDriver_CNT:要申请的次设备号数量;//LEDDriver_NAME:表示“设备名字”if(ret < 0)goto free_gpio;strLEDDriver.major = MAJOR(strLEDDriver.devid);/* 获取分配号的主设备号 *///输入参数strLEDDriver.devid为“Linux设备号”//将strLEDDriver.devid右移20位得到“主设备号”strLEDDriver.minor = MINOR(strLEDDriver.devid);/* 获取分配号的次设备号 *///输入参数strLEDDriver.devid为“Linux设备号”//将strLEDDriver.devid与0xFFFFF相与后得到“次设备号”}/*3、注册字符设备*/strLEDDriver.cdev.owner = THIS_MODULE;//使用THIS_MODULE将owner指针指向当前这个模块cdev_init(&strLEDDriver.cdev,&LEDDriver_fops);//注册字符设备,初始化“字符设备结构变量strLEDDriver.cdev”//strLEDDriver.cdev是等待初始化的结构体变量//LEDDriver_fops就是字符设备文件操作函数集合/*4、添加字符设备*/ret=cdev_add(&strLEDDriver.cdev,strLEDDriver.devid,LEDDriver_CNT);//添加字符设备/*&strLEDDriver.cdev表示指向要添加的字符设备,即字符设备结构strLEDDriver.cdev变量*///strLEDDriver.devid表示设备号//LEDDriver_CNT表示需要添加的设备数量if(ret < 0 ) //添加字符设备失败goto del_register;printk("dev id major = %d,minor = %d\r\n", strLEDDriver.major, strLEDDriver.minor);printk("LEDDriver_init is ok!!!\r\n");/*5、自动创建设备节点 */strLEDDriver.class =class_create(THIS_MODULE, LEDDriver_NAME);if (IS_ERR(strLEDDriver.class)){goto del_cdev;}/*6、创建设备 */strLEDDriver.device = device_create(strLEDDriver.class, NULL, strLEDDriver.devid, NULL, LEDDriver_NAME);//创建设备//设备要创建在strLEDDriver.class类下面//NULL表示没有父设备//strLEDDriver.devid是设备号;//参数drvdata=NULL,设备没有使用数据//LEDDriver_NAME是设备名字//如果设置fmt=LEDDriver_NAME 的话,就会生成/dev/LEDDriver_NAME设备文件。//返回值就是创建好的设备。if (IS_ERR(strLEDDriver.device)){goto destroy_class;}return 0;destroy_class:class_destroy(strLEDDriver.class);//删除类//strLEDDriver.class就是要删除的类del_cdev:cdev_del(&strLEDDriver.cdev);//删除字符设备//&strLEDDriver.cdev表示指向需要删除的字符设备,即字符设备结构strLEDDriver.cdev变量del_register:unregister_chrdev_region(strLEDDriver.devid, LEDDriver_CNT);/* 释放设备号 *///strLEDDriver.devid:需要释放的起始设备号//LEDDriver_CNT:需要释放的次设备号数量;free_gpio://申请设备号失败/*释放gpio编号*/MyLED_free();return -EIO;
}/*驱动出口函数 */
static void __exit LEDDriver_exit(void)
{/*1、删除字符设备*/cdev_del(&strLEDDriver.cdev);/*删除字符设备*//*&strLEDDriver.cdev表示指向需要删除的字符设备,即字符设备结构&strLEDDriver.cdev变量*//*2、 释放设备号 */unregister_chrdev_region(strLEDDriver.devid, LEDDriver_CNT);/*释放设备号 *///strLEDDriver.devid:需要释放的起始设备号//LEDDriver_CNT:需要释放的次设备号数;/*3、 删除设备 */device_destroy(strLEDDriver.class, strLEDDriver.devid);//删除创建的设备//strLEDDriver.class是要删除的设备所处的类//strLEDDriver.devid是要删除的设备号/*4、删除类*/class_destroy(strLEDDriver.class);//删除类//strLEDDriver.class就是要删除的类/*5、释放gpio编号*/MyLED_free();
}module_init(LEDDriver_init);
//指定LEDDriver_init()为驱动入口函数
module_exit(LEDDriver_exit);
//指定LEDDriver_exit()为驱动出口函数MODULE_AUTHOR("Zhanggong");//添加作者名字
MODULE_LICENSE("GPL");//LICENSE采用“GPL协议”
MODULE_INFO(intree,"Y");
//去除显示“loading out-of-tree module taints kernel.”
8、添加“LED_drv.h”
#ifndef __LED_DRIVER_H
#define __LED_DRIVER_H#include <linux/types.h>
/*
数据类型重命名
使能bool,u8,u16,u32,u64, uint8_t, uint16_t, uint32_t, uint64_t
使能s8,s16,s32,s64,int8_t,int16_t,int32_t,int64_t
*/
#include <linux/cdev.h>
//使能cdev结构
//使能class结构和device结构struct LEDDriver_dev{dev_t devid; /*声明32位变量devid用来给保存设备号*/int major; /*主设备号*/int minor; /*次设备号*/struct cdev cdev; /*字符设备结构变量cdev */struct class *class; /*类*/struct device *device; /*设备*/atomic_t lock; /*原子变量*/
};
extern struct LEDDriver_dev strLEDDriver;#endif
9、添加“ButtonLED_APP.c”
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"#include <unistd.h>
//Linux系统编程下用到的延时函数
//使能usleep(),sleep()//#include <delay.h>
//Linux内核中用到的延时函数
//使能ndelay(),udelay(),mdelay()#define LED_OFF 0 /* 关灯 */
#define LED_ON 1 /* 开灯 *//*
参数argc: argv[]数组元素个数
参数argv[]:是一个指针数组
返回值: 0 成功;其他 失败
./ButtonLED_APP /dev/EXTI3Driver /dev/LEDDriver
*/
int main(int argc, char *argv[])
{int fd_button;int fd_led;int retvalue;int keyvalue;int status;/* 1. 判断参数 */if(argc != 3){printf("Error Usage!\r\n");return -1;}//argv[]是指向输入参数“./ButtonLED_APP /dev/EXTI3Driver /dev/LEDDriver”/* 2. 打开文件 */fd_button = open(argv[1], O_RDONLY);//O_RDONLY表示只读模式;//如果打开“/dev/EXTI3Driver”文件成功,则fd_button为“文件描述符”,argv[1]="/dev/EXTI3Driver"if(fd_button < 0){printf("Can't open file %s\r\n", argv[1]);return -1;}fd_led = open(argv[2], O_RDWR);//如果打开“/dev/LEDDriver”文件成功,则fd_led为“文件描述符”,argv[2]=“/dev/LEDDriver”if(fd_led < 0){printf("Can't open file %s\r\n", argv[2]);return -1;}/* 3. 读文件 */while(1){read(fd_button, &keyvalue, sizeof(keyvalue));if (keyvalue == LED_ON){//如果按键按下printf("KEY0 Press, value = %#X\r\n", keyvalue);/* 按下 */status = LED_ON;write(fd_led, &status, 1);sleep(1);//保证LED亮1妙}else if (keyvalue == LED_OFF){//如果按键松开printf("KEY0 Press, value = %#X\r\n", keyvalue);/* 按下 */status = LED_OFF;write(fd_led, &status, 1);//file结构指针变量fd_led表示要打开的设备文件//buf=&status表示用户数据块的首地址//cnt=1表示用户数据的长度,单位为字节}}/* 关闭设备 */retvalue = close(fd_button);//fd_button表示要关闭的“文件描述符”//返回值等于0表示关闭成功//返回值小于0表示关闭失败if(retvalue < 0){printf("Can't close file %s\r\n", argv[1]);return -1;}retvalue = close(fd_led);//fd_led表示要关闭的“文件描述符”//返回值等于0表示关闭成功//返回值小于0表示关闭失败if(retvalue < 0){printf("Can't close file %s\r\n", argv[2]);return -1;}return 0;
}
10、添加“Makefile”
#Linux一个Makefile编译多个内核驱动
KERNELDIR := /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31
#使用“:=”将其后面的字符串赋值给KERNELDIR
CURRENT_PATH := $(shell pwd)
#采用“shell pwd”获取当前打开的路径
#使用“$(变量名)”引用“变量的值”
MyAPP := ButtonLED_APPobj-m := MyButton.o MyLED.oMyButton-y := EXTI3_drv.o EXTI3.o
MyLED-y := LED_drv.o LED.oCC := arm-none-linux-gnueabihf-gccdrv:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modulesapp:$(CC) $(MyAPP).c -o $(MyAPP)clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) cleanrm $(MyAPP)install:sudo cp *.ko $(MyAPP) /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -
11、添加“c_cpp_properties.json”
{"configurations": [{"name": "Linux","includePath": ["${workspaceFolder}/**","/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31","/home/zgq/linux/Linux_Drivers/IO_Block", "/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include","/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/include","/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31/arch/arm/include/generated"],"defines": [],"compilerPath": "/usr/bin/gcc","cStandard": "gnu11","cppStandard": "gnu++14","intelliSenseMode": "gcc-x64"}],"version": 4
}
12、编译
输入“make clean回车”
输入“make drv回车”
输入“make app回车”
输入“make install回车”
输入“ls /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/ -l回车”查看是存在“ButtonLED_APP,MyButton.ko和MyLED.ko”
13、测试
启动开发板,从网络下载程序
输入“root”
输入“cd /lib/modules/5.4.31/回车”
切换到“/lib/modules/5.4.31/”目录
注意:“lib/modules/5.4.31/”在虚拟机中是位于“/home/zgq/linux/nfs/rootfs/”目录下,但在开发板中,却是位于根目录中。
输入“ls -l”查看“ButtonLED_APP,MyButton.ko和MyLED.ko”是否存在
输入“depmod”,驱动在第一次执行时,需要运行“depmod”
输入“modprobe MyButton.ko”,加载“MyButton.ko”模块
输入“modprobe MyLED.ko”,加载“MyLED.ko”模块
输入“lsmod”查看有哪些驱动在工作
输入“cat /proc/interrupts”,查看中断是否注册;
输入“ls /dev/EXTI3Driver -l回车”,发现节点文件“/dev/EXTI3Driver”
输入“ls /dev/LEDDriver -l回车”,发现节点文件“/dev/LEDDriver”
输入“./ButtonLED_APP /dev/EXTI3Driver /dev/LEDDriver回车”
按下按钮,等待串口输出“KEY0 Press, value = 0X1”,同时LED会亮。
相关文章:

Linux第87步_阻塞IO实验
阻塞IO是“应用程序”对“驱动设备”进行操作,若不能获取到设备资源,则阻塞IO应用程序的线程会被“挂起”,直到获取到设备资源为止。 “挂起”就是让线程进入休眠,将CPU的资源让出来。线程进入休眠后,当设备文件可以操…...

C/C++基础----常量和基本数据类型
HelloWorld #include <iostream>using namespace std;int main() {// 打印cout << "Hello,World!" << endl;return 0; }c/c文件和关系 c和c是包含关系,c相当于是c的plus版本c的编译器也可以编译c语言c文件.cpp结尾.h为头文件.c为c语言…...

什么是生成式AI?有哪些特征类型
生成式AI是人类一种人工智能技术,可以生成各种类型的内容,包括文本、图像、音频和合成数据。那么什么是人工智能?人工智能和机器学习之间的区别是什么?有哪些技术特征? 人工智能是一门学科,是计算机科学的一…...

《Linux C/C++服务器开发实践》之第7章 服务器模型设计
《Linux C/C服务器开发实践》之第7章 服务器模型设计 7.1 I/O模型7.1.1 基本概念7.1.2 同步和异步7.1.3 阻塞和非阻塞7.1.4 同步与异步和阻塞与非阻塞的关系7.1.5 采用socket I/O模型的原因7.1.6(同步)阻塞I/O模型7.1.7(同步)非阻…...

SSH穿透ECS访问内网RDS数据库
处于安全考虑,RDS一般只会允许指定的IP进行访问,而我们开发环境的IP往往是动态的,每次IP变动都需要去修改RDS的白名单,为我们的工作带来很大的不便。 那么如何去解决这个问题? 假如我们有一台ESC服务器,E…...

python 有哪些函数
Python内置的函数及其用法。为了方便记忆,已经有很多开发者将这些内置函数进行了如下分类: 数学运算(7个) 类型转换(24个) 序列操作(8个) 对象操作(7个) 反射操作(8个) 变量操作(2个) 交互操作(2个) 文件操作(1个) 编译执行(4个) 装饰器(3个) …...

ubuntu web端远程桌面控制
本方案采用x11vncnovnc来实现x11vnc的安装和配置可以参考UOS搭建VNC及连接教程_uos安装vnc-CSDN博客;并把/lib/systemd/system/x11vnc.service内容修改为如下: [Unit]DescriptionStart x11vnc at startup.Aftermulti-user.target[Service]TypesimpleExecStart/usr/bin/x11vnc …...

PCL 点到三角形的距离(3D)
文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 给定三角形ABC和点P,设Q为描述ABC上离P最近的点。求Q的一个方法:如果P在ABC内,那么P的正交投影点就是离P最近的点Q。如果P投影在ABC之外,最近的点则必须位于它的一条边上。在这种情况下,Q可以通过计算线段AB、…...

C# wpf 嵌入外部程序
WPF Hwnd窗口互操作系列 第一章 嵌入Hwnd窗口 第二章 嵌入WinForm控件 第三章 嵌入WPF控件 第四章 嵌入外部程序(本章) 第五章 底部嵌入HwndHost 文章目录 WPF Hwnd窗口互操作系列前言一、如何实现?1、定义属性2、进程嵌入(1&…...

【ELK】ELK企业级日志分析系统
搜集日志;日志处理器;索引平台;提供视图化界面;客户端登录 日志收集者:负责监控微服务的日志,并记录 日志存储者:接收日志,写入 日志harbor:负责去连接多个日志收集者&am…...

详细的讲一下java的接口回调
Java的接口回调是一种允许程序在特定事件发生时通知其他对象的机制。这是观察者设计模式的一种实现方式,常用于实现事件监听和异步处理。接口回调允许对象之间进行松耦合的交互:一个对象只知道它可以调用另一个对象的方法,但它不需要知道这个…...

如何将powerpoint(PPT)幻灯片嵌入网页中在线预览、编辑并保存到服务器?
猿大师办公助手不仅可以把微软Office、金山WPS和永中Office的Word文档、Excel表格内嵌到浏览器网页中实现在线预览、编辑保存等操作,还可以把微软Office、金山WPS和永中Office的PPT幻灯片实现网页中在线预览、编辑并保存到服务器。 猿大师办公助手把本机原生Office…...

[Java基础揉碎]日期类
目录 日期类 第一代日期类 第二代日期类 第三代日期类 >前面两代日期类的不足分析 针对以上问题Java在jdk8加入了以下方法 jdk8的时间格式化 时间戳 第三代日期类更多方法 日期类 [知道怎么查,怎么用即可,不用每个方法都背] 第一代日期类 1) Date: …...

4.10作业
//.h文件#ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTimerEvent> //定时器事件类 #include <QTime> //时间类 #include <QString> #include <QPushButton> //按钮类 #include <QLabel> //标签类 #include <QT…...

Hive概述与基本操作
一、Hive基本概念 1.什么是hive? (1)hive是数据仓库建模的工具之一 (2)可以向hive传入一条交互式的sql,在海量数据中查询分析得到结果的平台 2.Hive简介 Hive本质是将SQL转换为MapReduce的任务进行运算,底层由HDFS…...

安装 FFmpeg
安装 FFmpeg 1. Install FFmpeg On Ubuntu2. Install FFmpeg On Ubuntu 16.042.1. First add the repository2.2. Update the newly added repository2.3. Now install the ffmpeg2.4. For opening the ffmpeg for that type ffpmeg on the terminal 3. Uninstall ffmpegRefere…...

18、差分
差分 题目描述 输入一个长度为n的整数序列。 接下来输入m个操作,每个操作包含三个整数l, r, c,表示将序列中[l, r]之间的每个数加上c。 请你输出进行完所有操作后的序列。 输入格式 第一行包含两个整数n和m。 第二行包含n个整数,表示整…...

13 指针(上)
指针是 C 语言最重要的概念之一,也是最难理解的概念之一。 指针是C语言的精髓,要想掌握C语言就需要深入地了解指针。 指针类型在考研中用得最多的地方,就是和结构体结合起来构造结点(如链表的结点、二叉树的结点等)。 本章专题脉络 1、指针…...

AI 对话完善【人工智能】
AI 对话【人工智能】 前言版权开源推荐AI 对话v0版本:基础v1版本:对话数据表tag.jsTagController v2版本:回复中textarea.jsChatController v3版本:流式输出chatLast.jsChatController v4版本:多轮对话QianfanUtilChat…...

利用数组储存表格数据
原理以及普通数组储存表格信息 在介绍数组的时候说过,数组能够用来储存任何同类型的数据,这里的意思就表明只要是同一个类型的数组据就可以储存到一个数组中。那么在表格中同一行的数据是否可以储存到同一个数组中呢?答案自然是可以ÿ…...

[数据概念|数据技术]智能合约如何助力数据资产变现
“ 区块链上数据具有高可信度,智能合约将区块链变得更加智能化,以支持企业场景。” 之前鼹鼠哥已经发表了一篇文章,简单介绍了区块链,那么,智能合约又是什么呢?它又是如何助力数据资产变现的呢?…...

JS中的常见二进制数据格式
格式描述用途示例ArrayBuffer固定长度的二进制数据缓冲区,不直接操作具体的数据,而是通过类型数组或DataView对象来读写用于存储和处理大量的二进制数据,如文件、图像等let buffer new ArrayBuffer(16);TypedArray基于ArrayBuffer对象的视图…...

uniapp开发h5端使用video播放mp4格式视频黑屏,但有音频播放解决方案
mp4格式视频有一些谷歌播放视频黑屏,搜狗浏览器可以正常播放 可能和视频的编码格式有关,谷歌只支持h.264编码格式的视频播放 将mp4编码格式修改为h.264即可 转换方法: 如果是自己手动上传文件可以手动转换 如果是后端接口调取的地址就需…...

Hive的分区与排序
一、Hive分区 1.引入: 在大数据中,最常见的一种思想就是分治,我们可以把大的文件切割划分成一个个的小的文件,这样每次操作一个个小的文件就会很容易了,同样的道理,在hive当中也是支持这种思想的ÿ…...

Objective-C学习笔记(内存管理、property参数)4.9
1.引用计数器retainCount:每个对象都有这个属性,默认值为1,记录当前对象有多少人用。 为对象发送一条retain/release消息,对象的引用计数器加/减1,为对象发一条retainCount,得到对象的引用计数器值,当计数器…...

C语言进阶课程学习记录-第29课 - 指针和数组分析(下)
C语言进阶课程学习记录-第29课 - 指针和数组分析(下) 数组名与指针实验-数组形式转换实验-数组名与指针的差异实验-转化后数组名加一的比较实验-数组名作为函数形参小结 本文学习自狄泰软件学院 唐佐林老师的 C语言进阶课程,图片全部来源于课…...

一起学习python——基础篇(13)
前言,python编程语言对于我个人来说学习的目的是为了测试。我主要做的是移动端的开发工作,常见的测试主要分为两块,一块为移动端独立的页面功能,另外一块就是和其他人对接工作。 对接内容主要有硬件通信协议、软件接口文档。而涉…...

SOCKS代理概述
在网络技术的广阔领域中🌐,SOCKS代理是一个核心组件,它在提升在线隐私保护🛡️、实现匿名通信🎭以及突破网络访问限制🚫方面发挥着至关重要的作用。本文旨在深入探讨SOCKS代理的基础,包括其定义…...

AI助力M-OFDFT实现兼具精度与效率的电子结构方法
编者按:为了使电子结构方法突破当前广泛应用的密度泛函理论(KSDFT)所能求解的分子体系规模,微软研究院科学智能中心的研究员们基于人工智能技术和无轨道密度泛函理论(OFDFT)开发了一种新的电子结构计算框架…...

【数据结构】单链表(一)
上一篇【数据结构】顺序表-CSDN博客 我们了解了顺序表,但是呢顺序表涉及到了一些问题,比如,中间/头部的插入/删除,时间复杂度为O(N);增容申请空间、拷贝、释放旧空间会有不小的消耗;增容所浪费的空间... 我们如何去解…...