Linux设备驱动之gpio-keys
Linux设备驱动之gpio-keys
前两个章节介绍了Linux字符设备和platform设备的注册,他们都是比较基础的,让大家理解Linux内核的设备驱动是如何注册、使用的。但在工作中,个人认为完全手写一个字符设备驱动的机会比较少,更多的都是基于前人的代码修修补补过三年。在内核驱动中,更多的会基于platform设备进行具体设备驱动的注册与使用,下面以内核原生的gpio-keys为例,向大家介绍一个简单的按键驱动是如何配置、工作的。
dts配置
前面有简单的介绍,设备树用于描述板端硬件信息,配合驱动进行使用的,Linux内核原生的gpio-keys驱动支持的dts如下:
gpio-keys {compatible = "gpio-keys"; /* 用于匹配内核gpio-keys驱动 */autorepeat; /* 标记是否自动重复该按键,想想常按的情况 */up {label = "GPIO Key UP"; /* 按键的标签 */linux,code = <103>; /* 按键的键值,理解为键盘上的字母类 */gpios = <&gpio1 0 1>; /* 使用的是哪个gpio */};down {label = "GPIO Key DOWN";linux,code = <108>;interrupts = <1 IRQ_TYPE_LEVEL_HIGH 7>; /* 中断配置 */};};
上面介绍到的dts配置,最后在驱动端都会进行解析,使用,那么驱动又具体是如何使用的呢,继续。
gpio-keys的platform驱动
Linux内核基本都可以看到类似以下的代码:
[drivers/input/keyboard/gpio_keys.c]/* 设备驱动匹配信息 */
static const struct of_device_id gpio_keys_of_match[] = {{ .compatible = "gpio-keys", },{ },
};
MODULE_DEVICE_TABLE(of, gpio_keys_of_match);static struct platform_driver gpio_keys_device_driver = {.probe = gpio_keys_probe, /* platform设备驱动匹配了,则调用probe函数 */.shutdown = gpio_keys_shutdown,.driver = {.name = "gpio-keys",.pm = &gpio_keys_pm_ops,.of_match_table = gpio_keys_of_match,.dev_groups = gpio_keys_groups,}
};static int __init gpio_keys_init(void)
{/* 注册platform driver */return platform_driver_register(&gpio_keys_device_driver);
}static void __exit gpio_keys_exit(void)
{platform_driver_unregister(&gpio_keys_device_driver);
}/* late_initcall与modeule_init的宏作用一样,* 都是将函数添加到驱动段,方便开机启动加载驱动,* late_initcall与modeule_init的差异是加载* 的优先级不一致*/
late_initcall(gpio_keys_init);
module_exit(gpio_keys_exit);
在platform和设备树章节已经介绍,当设备驱动匹配时,将会调用到probe函数。下面看看gpio-keys的probe是怎样获取dts信息的。PS:dts是怎么解析的,platform device是什么时候注册的,后面其他章节我们再进行介绍。
/** Handlers for alternative sources of platform_data*//** Translate properties into platform_data*/
static struct gpio_keys_platform_data *
gpio_keys_get_devtree_pdata(struct device *dev)
{struct gpio_keys_platform_data *pdata;struct gpio_keys_button *button;struct fwnode_handle *child;int nbuttons;/* 查看当前的gpio-keys节点有多少个子节点,每个节点代表一个按键 */nbuttons = device_get_child_node_count(dev);if (nbuttons == 0)return ERR_PTR(-ENODEV);pdata = devm_kzalloc(dev,sizeof(*pdata) + nbuttons * sizeof(*button),GFP_KERNEL);if (!pdata)return ERR_PTR(-ENOMEM);button = (struct gpio_keys_button *)(pdata + 1);pdata->buttons = button;pdata->nbuttons = nbuttons;/* 是否自动重复 */pdata->rep = device_property_read_bool(dev, "autorepeat");/* 按键的标签 */device_property_read_string(dev, "label", &pdata->name);device_for_each_child_node(dev, child) {if (is_of_node(child))button->irq =irq_of_parse_and_map(to_of_node(child), 0);/* 按键码值 */if (fwnode_property_read_u32(child, "linux,code",&button->code)) {dev_err(dev, "Button without keycode\n");fwnode_handle_put(child);return ERR_PTR(-EINVAL);}fwnode_property_read_string(child, "label", &button->desc);/* 输入的类型,一般设置为KEY */if (fwnode_property_read_u32(child, "linux,input-type",&button->type))button->type = EV_KEY;/* 该按键是否设置为唤醒源 */button->wakeup =fwnode_property_read_bool(child, "wakeup-source") ||/* legacy name */fwnode_property_read_bool(child, "gpio-key,wakeup");/* 唤醒的状态 */fwnode_property_read_u32(child, "wakeup-event-action",&button->wakeup_event_action);/* 是否可休眠 */button->can_disable =fwnode_property_read_bool(child, "linux,can-disable");/* 按键去抖配置 */if (fwnode_property_read_u32(child, "debounce-interval",&button->debounce_interval))button->debounce_interval = 5;button++;}return pdata;
}
在上面的代码可以看到,就是一个解析dts的操作,gpio_keys_get_devtree_pdata()将会进行以下操作:
- 确认gpio-keys节点存在多少个子节点,每个子节点代表一种按键功能;
- 获取”autorepeat“字段的信息赋值到pdata->rep,这个变量决定着是否允许重复发送事件;
- 获取各button的中断号、事件代码code、label、输入的类型、唤醒等信息;
之后驱动会依据这些信息进行按键驱动的适配以及操作。下面通过gpio-keys驱动学习Linux input子系统。
input子系统
Linux input子系统是linux内核用于管理各种输入设备的部分,内核将给用户导出一套固定的硬件无关的input API,供用户空间程序使用。
input系统分为三块:input core、input drivers和event handles。数据传输从底层硬件到input driver,再经过input core到event handles,最后到达用户空间。
input core
input子系统的core代码主要是input.c,该文件集成模块,模块的注册函数实现如下:
static int __init input_init(void)
{int err;/* 注册input类 */err = class_register(&input_class);if (err) {pr_err("unable to register input_dev class\n");return err;}/* 在/proc创建 bus/input/devices handlers */err = input_proc_init();if (err)goto fail1;/* 注册input字符设备 */err = register_chrdev_region(MKDEV(INPUT_MAJOR, 0),INPUT_MAX_CHAR_DEVICES, "input");if (err) {pr_err("unable to register char major %d", INPUT_MAJOR);goto fail2;}return 0;fail2: input_proc_exit();fail1: class_unregister(&input_class);return err;
}
在input.c是input core的核心文件,input core主要是承上启下,为input drivers提供输入设备注册和操作接口,如input_register_device()函数;通知event handles对事件进行处理;在/proc下产生相应的设备信息。input core将会负责将input drivers和event handles联通,具体是如何完成这个操作的呢,继续看input drivers和event handles。
相关结构体
[linux/input.h]
/** The event structure itself*/struct input_event {struct timeval time; /* 输入事件时间 */__u16 type; /* 类型 */__u16 code; /* 事件代码 [linux/input-event-codes.h] */__s32 value; /* 事件值(当type为EV_KEY时,value:0表示按键抬起,1表示按键按下) */
};[linux/input-event-codes.h]
/** Event types*/#define EV_SYN 0x00 /* 所有input设备都具备的同步事件,主要是与client同步事件队列 */
#define EV_KEY 0x01 /* 按键 */
#define EV_REL 0x02 /* 鼠标事件 相对坐标 */
#define EV_ABS 0x03 /* 手写板事件 绝对坐标 */
#define EV_MSC 0x04 /* 其他类型 */
#define EV_SW 0x05 /* 开关状态事件 */
#define EV_LED 0x11 /* LED事件 */
#define EV_SND 0x12 /* 音频事件 */
#define EV_REP 0x14 /* 用于指定自动重复事件 */
#define EV_FF 0x15 /* 用于初始化具有力反馈功能的设备并使该设备反馈 */
#define EV_PWR 0x16 /* 电源管理 */
#define EV_FF_STATUS 0x17 /* 用于接收力反馈设备状态 */
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
gpio_keys_probe()
继续回到gpio_keys_probe()函数,从dts中获取到button信息之后,将会通过调用devm_input_allocate_device()([input.c])函数创建input_dev设备。接着是通过gpio_keys_setup_key()函数,为各个button申请相应的GPIO、中断资源等。最后,通过input_register_device()函数,注册input设备。至此,可以看到input drivers注册完成。
input handle
input handle的相关操作在evdev.c实现,该文件恰好自己构成一个模块,先分析模块的注册函数,注册函数只是调用了input_register_handler()([input.c]),该函数主要是为系统中的输入设备注册一个新的输入处理程序,并将其附加到与该处理程序兼容的所有输入设备上。该模块将在input dev注册时,将会通过input core完成与input dev完成相应的connect,当event发生时,又将会处理相应的event事件并上报。
input core、input drivers和event handles三块的简单介绍如上,下面,我们将带着问题去阅读代码,进一步了解Linux input子系统。
FAQ
input core是如何知道,当前有多少输入设备,而这些输入设备又分别是支持什么类型的事件?
在介绍上面的input drivers的时候就有提到,在driver的probe函数中,将会通过input_register_device()函数注册input device,下面来分析一下该函数的实现。
int input_register_device(struct input_dev *dev)
{/* 检查input dev支持的事件类型,注册device等 */...error = mutex_lock_interruptible(&input_mutex);if (error)goto err_device_del;/* 将该input device添加到input_dev_list链表 */list_add_tail(&dev->node, &input_dev_list);/* 在linux input子系统中,一个input device的输入事件* 将会发送到系统中所有的event handles,所以这里从保存* event handles的全局链表input_handler_list中,逐个获* 取event handles添加input device */list_for_each_entry(handler, &input_handler_list, node)input_attach_handler(dev, handler);/* 当注册input device成功,将会通过该函数唤醒profs的poll线程 */input_wakeup_procfs_readers();mutex_unlock(&input_mutex);...
}
然后我们再回到这个问题,input core是如何知道有多少的input devices,显然,是在通过input_register_device()注册input device的时候,同时会把input device添加到全局链表input_dev_list[input.c]中,这样,input core通过枚举input_dev_list链表,就可以得到相应的input device。
还剩一个问题,这些input device支持的事件类型和事件代码,又是从哪里填充的呢?
上面在介绍input device的时候,我们是以gpio-keys为例,同样的,在这里我们继续以它为例,也即gpio_keys.c。
在input device的probe()函数中,如上面介绍,将会通过gpio_keys_get_devtree_pdata()函数从dts中获取button的相应信息,接着通过gpio_keys_setup_key()将上面获取得到的信息填充到input dev,比如通过input_set_capability()函数设置input dev支持的事件类型等。
这样,linux内核就知道,当前的input dev支持什么事件type以及code。
当发生输入事件时,信息又是如何传递到应用层(用户空间)?
继续以gpio-keys作为input dev例子进行这个问题的解答。
回到gpio_keys_setup_key()函数,在该函数中,获取gpio之后,将会申请相应的中断,同时设置中断函数,中断函数有两个,分别是gpio_keys_gpio_isr()和gpio_keys_irq_isr(),将会根据gpio的信息相应的选择其中一个,以gpio_keys_gpio_isr()中断函数,当相应的gpio中断信号到来时,系统将会调用该函数,而在该函数中,又将存在以下代码:
static irqreturn_t gpio_keys_gpio_isr(int irq, void *dev_id)
{...mod_delayed_work(system_wq,&bdata->work,msecs_to_jiffies(bdata->software_debounce));...
同时的,在gpio_keys_setup_key()函数中,是这样初始化bdata->work,所以,在中断函数中,设置延迟一段时间执行gpio_keys_gpio_work_func()函数。
INIT_DELAYED_WORK(&bdata->work, gpio_keys_gpio_work_func);
而gpio_keys_gpio_work_func()函数也很简单,主要是通过gpio_keys_gpio_report_event()函数报告相应的事件信息。
static void gpio_keys_gpio_work_func(struct work_struct *work)
{struct gpio_button_data *bdata =container_of(work, struct gpio_button_data, work.work);gpio_keys_gpio_report_event(bdata);if (bdata->button->wakeup)pm_relax(bdata->input->dev.parent);
}static void gpio_keys_gpio_report_event(struct gpio_button_data *bdata)
{const struct gpio_keys_button *button = bdata->button;struct input_dev *input = bdata->input;unsigned int type = button->type ?: EV_KEY;int state;/* 获取gpio的状态,是高电平还是低电平 */state = gpiod_get_value_cansleep(bdata->gpiod);if (state < 0) {dev_err(input->dev.parent,"failed to get gpio state: %d\n", state);return;}/* 通过input_event()函数报告input事件 */if (type == EV_ABS) {if (state)input_event(input, type, button->code, button->value);} else {input_event(input, type, *bdata->code, state);}/* 同步事件 */input_sync(input);
}void input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)
{unsigned long flags;/* 先检查input dev是否支持该type,这个dev->evbit是在调用input_set_capability()时设置的 */if (is_event_supported(type, dev->evbit, EV_MAX)) {spin_lock_irqsave(&dev->event_lock, flags);input_handle_event(dev, type, code, value);spin_unlock_irqrestore(&dev->event_lock, flags);}
}
而在input_handle_event()函数中,将会先通过input_get_disposition()函数回去事件的相应信息。
#define INPUT_IGNORE_EVENT 0 /* 忽略该事件 */
#define INPUT_PASS_TO_HANDLERS 1 /* input handles处理该事件 */
#define INPUT_PASS_TO_DEVICE 2 /* input device处理该事件 */
#define INPUT_SLOT 4
#define INPUT_FLUSH 8
#define INPUT_PASS_TO_ALL (INPUT_PASS_TO_HANDLERS | INPUT_PASS_TO_DEVICE)
gpio-keys中,事件是由handles处理,所以在input_handle_event()函数中,将会将事件的信息填充到input dev的vals数组。至此,回到gpio_keys_gpio_report_event()函数,将事件信息填充到input dev的相应结构体之后,最后将会调用input_sync(input)
函数同步事件。
static inline void input_sync(struct input_dev *dev)
{input_event(dev, EV_SYN, SYN_REPORT, 0);
}
通过input_sync()函数的代码我们可以知道,最终是发送EV_SYN事件,code为SYN_REPORT,这样的一个事件代码,将会在input_handle_event()函数中调用input_pass_values()函数,激发input handles处理事件。
而在input_pass_values()函数中,重要的也是以下部分:
static void input_pass_values(struct input_dev *dev,struct input_value *vals, unsigned int count)
{.../* 一般的,handle会为空,所以执行else部分代码 */handle = rcu_dereference(dev->grab);if (handle) {count = input_to_handler(handle, vals, count);} else {/* 从input dev的dev->h_list链表获取event handles,* 上面就有提到,一个input dev的事件,将会发送到所有的handles */list_for_each_entry_rcu(handle, &dev->h_list, d_node)if (handle->open) {count = input_to_handler(handle, vals, count);if (!count)break;}}...
}
代码跟踪到这里,又多了一个疑问:dev->h_list这个链表是什么时候填充的?
在input_register_device()函数中,通过调用input_attach_handler()函数,将event handles添加到dev->h_list,函数调用流程如下:
input_register_device()input_attach_handler()handler->connect(handler, dev, id)evdev_connect()input_register_handle()list_add_rcu(&handle->d_node, &dev->h_list)/list_add_tail_rcu(&handle->d_node, &dev->h_list)
了解到dev->h_list链表的填充过程之后,继续回到input_pass_values()函数,在该函数中,将会针对enable的event handle调用input_to_handler()函数,而在input_to_handler()函数重要的是调用handler的events函数—evdev_events()。
/** Pass incoming events to all connected clients.*/
static void evdev_events(struct input_handle *handle,const struct input_value *vals, unsigned int count)
{...client = rcu_dereference(evdev->grab);if (client)evdev_pass_values(client, vals, count, ev_time);else/* 主要是通过client_list链表获取handle client进行事件处理,* 可以将client理解为一个用户层的接收者,在open event时创建 */list_for_each_entry_rcu(client, &evdev->client_list, node)evdev_pass_values(client, vals, count, ev_time);...
}
在evdev_pass_values()函数中,将会通过__pass_event()将event信息填充到client的buffer缓冲区,如果code是SYN_REPORT,将会调用kill_fasync(&client->fasync, SIGIO, POLL_IN)
异步通知应用层,这样,input event传递到应用层,接着,用户程序就可以通过read函数读取event的详细信息。
相关文章:
Linux设备驱动之gpio-keys
Linux设备驱动之gpio-keys 前两个章节介绍了Linux字符设备和platform设备的注册,他们都是比较基础的,让大家理解Linux内核的设备驱动是如何注册、使用的。但在工作中,个人认为完全手写一个字符设备驱动的机会比较少,更多的都是基…...
【vue3页面展示代码】展示代码codemirror插件
技术版本: vue 3.2.40、codemirror 6.0.1、less 4.1.3、vue-codemirror 6.1.1、 codemirror/lang-vue 0.1.2、codemirror/theme-one-dark 6.1.2 效果图: 1.安装插件 yarn add codemirror vue-codemirror codemirror/lang-vue codemirror/theme-one-dar…...
【面试必刷TOP101】链表相加 单链表的排序
目录 题目:链表相加(二)_牛客题霸_牛客网 (nowcoder.com) 题目的接口: 解题思路: 代码: 过啦!!! 题目:单链表的排序_牛客题霸_牛客网 (nowcoder.com) 题目的接口:…...
Visual Studio复制、拷贝C++项目与第三方库配置信息到新的项目中
本文介绍在Visual Studio软件中,复制一个已有的、配置过多种第三方库的C项目,将其拷贝为一个新的项目,同时使得新项目可以直接使用原有项目中配置好的各类**C**配置、第三方库等的方法。 在撰写C 代码时,如果需要用到他人撰写的第…...
rust迭代器
迭代器用来遍历容器。 迭代器就是把容器中的所有元素按照顺序一个接一个的传递给处理逻辑。 Rust中的迭代器 标准库中定义了Iterator特性 trait Iterator {type Item;fn next(&mut self) -> Option<Self::Item>; }实现了Iterator特性的结构体就是迭代器。 很多类…...
软件定制开发的优势与步骤|APP搭建|小程序
软件定制开发的优势与步骤|APP搭建|小程序 定制开发的优势: 1. 满足特定需求:定制开发可以根据客户的实际需求进行设计和开发,使得软件系统能够更好地满足客户的业务目标。 2. 优化用户体验:通过深入了解客户的需求,定…...
ERR_CONNECTION_REFUSED等非标准的HTTP错误状态码原因分析和解决办法
文章目录 一、DNS Resolution Failed1,DNS服务器故障2,DNS配置错误3,DNS劫持4,域名过期-5,其他网络问题 二、ERR_CONNECTION_REFUSED-"ERR_CONNECTION_REFUSED" 错误可能有多种原因 三、ERR_SSL_PROTOCOL_ER…...
瀑布流 - Vue3基于Grid布局简单实现一个瀑布流组件
瀑布流 - Vue3基于Grid布局简单实现一个瀑布流组件 前言 在学习Grid布局之时,我发现其是CSS中的一种强大的布局方案,它将网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局,在刷某书和某宝首页时&…...
ES6面试题总结
1. 谈谈你对 ES6 的理解,为什么要学习es6? ES6是新一代的JS语言标准,对分JS语言核心内容做了升级优化,规范了JS使用标准,新增了JS原生方法,使得JS使用更加规范,更加优雅,更适合大型应用的开发。学习ES6是成…...
mybatisplus,jdbc 批量插入
1.测试用例 项目中遇到在做导入号码的时候我们会用到批量导入,提高入库的速度。接下来我们以10000条为测试用例。 1.1 批量执行sql语句 当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库…...
如何使用IP归属地查询API来追踪网络活动
引言 在当今数字化世界中,了解网络活动的源头和位置对于网络安全、市场研究和用户体验至关重要。IP归属地查询API是一种强大的工具,可以帮助您追踪网络活动并获取有关IP地址的重要信息。本文将探讨如何使用IP归属地查询API来追踪网络活动,以…...
【SQL】S0 系列博文大纲
系列博文大纲 SQL 学习环境建议系列博文相关书籍系列博文大纲阶段进展 SQL 学习环境建议 对于 SQL 语言的学习,博主本地使用:MySQL DataGrip; MySQL 提供本地数据库服务; DataGrip IDE,承担编程运行测试任务…...
2023年8月体育用品行业数据分析(京东数据产品)
当前,亚运会临近,这也带动了国民对体育消费的热情,体育产品内销逐渐旺盛,“亚运经济”红利开始显现。鲸参谋数据显示,今年8月份,京东平台上体育用品行业的销量为185万,同比增长2%;销…...
国内高校镜像网站
国内各大高校开源镜像站 排名不分前后 清华大学:https://mirrors.tuna.tsinghua.edu.cn/ 北京大学:https://mirrors.pku.edu.cn/ 北京外国语大学:http:// https://mirrors.bfsu.edu.cn/ 北京理工大学:https://mirrors.bit.e…...
Linux安装kafka-manager
相关链接https://github.com/yahoo/kafka-manager/releases kafka-manager-2.0.0.2下载地址 百度云链接:https://pan.baidu.com/s/1XinGcwpXU9YBF46qkrKS_A 提取码:tzvg 一、安装部署 1.把kafka-manager-2.0.0.2.zip拷贝到目录 /opt/app/elk 2.解压…...
MYSQL索引——B+树讲解
B-/B树看 MySQL索引结构 B-树 B-树,这里的 B 表示 balance( 平衡的意思),B-树是一种多路自平衡的搜索树.它类似普通的平衡二叉树,不同的一点是B-树允许每个节点有更多的子节点。下图是 B-树的简化图. B-树有如下特点: 所有键值分布在整颗树中; 任何一…...
VB将十进制整数转换成16进制以内的任意进制数
VB将十进制整数转换成16进制以内的任意进制数 数值转换,能够将十进制整数转换成16进制以内的任意进制数 Private Function DecToN(ByVal x%, ByVal n%) As StringDim p() As String, y$, r%p Split("0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F", ",")I…...
基于SpringBoot+Vue的宠物领养饲养交流管理平台设计与实现
前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌💗 👇🏻…...
【图像去噪】【TGV 正则器的快速计算方法】通过FFT的总(广义)变化进行图像去噪(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
windbg调试句柄问题
这里写自定义目录标题 winform,句柄资源不够强,程序crash句柄主程序c程序,加载的插件是c# dll,这时候如何用windbg调试dll库如果查看句柄和对象的关系!handle 怎么能知道哪个句柄是Form对话框的句柄如何查看句柄对应的类对象 winf…...
9月13-14日上课内容 第三章 ELK日志分析系统及部署实例
本章结构 ELK日志分析系统简介 ELK日志分析系统分为 Elasticsearch Logstash Kibana 日志处理步骤 1.将日志进行集中化管理 2.将日志格式化(Logstash) 并输出到Elasticsearch 3.对格式化后的数据进行索引和存储 (Elasticsearch) 4.前端数据的展示(Kibana) Elasticsearch介…...
服务器端应用的安装
前言:相信看到这篇文章的小伙伴都或多或少有一些编程基础,懂得一些linux的基本命令了吧,本篇文章将带领大家服务器如何部署一个使用django框架开发的一个网站进行云服务器端的部署。 文章使用到的的工具 Python:一种编程语言&…...
关于硬盘质量大数据分析的思考
近日,看到Backblaze分享了一遍关于硬盘运行监控数据架构的文章,觉得挺有意义的,本文就针对这方面跟大家聊聊。 作为一家在2021年在美国纳斯达克上市的云端备份公司,Backblaze一直保持着对外定期发布HDD和SSD的故障率稳定性质量报告…...
RK3568核心板分区空间不足,如何修改分区大小?
在对评估板进行开发验证时,时常会遇到根目录空间不足的情况,而在其他分区又有冗余空间,这时则需要对分区大小重新进行分配,合理化利用分区空间。 本文将基于HD-RK3568-IOT评估板主要讲解如何修改eMMC分区大小。 1. 分区表介绍…...
Linux系统怎么修改主机名
【微|信|公|众|号:厦门微思网络】 1.备份主机名文件 首先redhat修改主机名,在进行任何修改之前,请务必备份主机名文件。这样,即使出现意外情况,你也能够轻松恢复到原始状态。使用以下命令备份主机名文件࿱…...
BroadcastChannel方法跨浏览器窗口通信
1. 描述 同源 的不同浏览器窗口,Tab 页,frame 或者 iframe 下的不同文档之间可以通过 BroadcastChannel 相互通信。 2. 构造函数 通过 BroadcastChannel 类传入的参数创建实例,传入的参数将指定通道名称,在同源环境下该通道可以…...
山石网科国产化防火墙,打造全方位边界安全解决方案
互联网的快速发展促进了各行各业的信息化建设,但也随之带来了诸多网络安全风险。大部分组织机构采用统一互联网接入方案,互联网出口承担着内部用户访问互联网的统一出口和对外信息服务的入口,因此在该区域部署相匹配的安全防护手段必不可少。…...
AVL 树
文章目录 一、AVL 树的概念二、AVL 树的实现1. AVL 树的存储结构2. AVL 树的插入 一、AVL 树的概念 在 二叉搜索树 中,当我们连续插入有序的数据时,二叉搜索树可能会呈现单枝树的情况,此时二叉搜索树的查找效率为 O(N) 俄罗斯的两位数学家 …...
ggplot2做图(填坑中)
数据 df <- data.frame(x 1:10, y 1:10, f c(rep("A", 5), rep("B", 5))) 做图 1. 散点图 (scatter plot) # scatter plot scatter_plot <- function(df, metadata) {identical(rownames(df), rownames(metadata))data <- cbind(df, metada…...
Python日志处理器,同时打印到控制台和保存到文件中,并保证格式一致
使用logging模块的时候,默认是输出到控制台的,当然也可以配置输出到文件中,但是当你配置了文件后,控制台的输出就消失了,所以,需要一个策略即能保存到文件中,又能输出到控制台中。 下面是我做的…...
电子商务网站建设合同书/网站如何进行网络推广
算法常用面试题汇总 1.说一下什么是二分法?使用二分法时需要注意什么?如何用代码实现? 二分法查找(Binary Search)也称折半查找,是指当每次查询时,将数据分为前后两部分,再用中值和…...
长沙哪个公司做网站好/fba欧美专线
No Cortex-M SW Device Found 解决方法参考文章: (1)No Cortex-M SW Device Found 解决方法 (2)https://www.cnblogs.com/vhuichen/p/4104490.html 备忘一下。...
上海定制网站建设/合肥网站优化推广方案
以前很少写程序的,基础也很差,想锻炼一下自己,因为觉得作为计算机专业的人,没有一点编程能力,总是要觉得心虚。 这里都是高手,我这菜鸟可能会麻烦各位~请多多关照! 安装Android还算是比较顺利&a…...
男人和女人做受吃母乳视频网站免费/南宁百度seo公司
python 中时间格式转换 import time, datetime时间戳 时间戳转时间 timestamp time.time() # 当时时间下的时间戳 zerotimestamp datetime.datetime.utcfromtimestamp(time.time()) # 当时时间戳下巴黎时间计时的时间戳在时间戳上利用秒计时来实现时间的加减, …...
北京推广网站/广州市新闻发布
2019独角兽企业重金招聘Python工程师标准>>> 1.停止mysql服务 2.转至mysql安装目录的 bin 目录下 3.运行 mysqld --skip-grant-tables 4.新开cmd窗口,直接输入 mysql 然后不输入密码回车. 5.use mysql; 6.更新密码 update user set passwordpasswor…...
备案用网站建设方案/英文网站seo发展前景
由于公司业务比较多,部署的站点也比较多。为了网站安全运行,以防故障发生时能第一时间知晓,特意编写下面监控脚本,对网站访问状态和超时时间进行监控:当code状态为5xx或者访问超时时间大于10s时进行报警。脚本脚本如下…...