win10自带输入法开启学习功能
win10的输入法十分简洁,用起来也十分酸爽,对于程序员来说再适合不过了。
只是唯一不好的是输入法没有学习功能(默认情况下没有,需要手动开启),最基本的根据词频调整顺序的功能都没有。
想想这么牛逼的输入法连这个功能都没有岂不是个渣渣?百度一番之后,果然。。。
进入设置页面,点击隐私:

然后选择语音、墨迹书写和键入,把开关打开:

enjoy it!
win10的输入法十分简洁,用起来也十分酸爽,对于程序员来说再适合不过了。
只是唯一不好的是输入法没有学习功能(默认情况下没有,需要手动开启),最基本的根据词频调整顺序的功能都没有。
想想这么牛逼的输入法连这个功能都没有岂不是个渣渣?百度一番之后,果然。。。
进入设置页面,点击隐私:

然后选择语音、墨迹书写和键入,把开关打开:

enjoy it!
使用percpu变量时编译报错:
### make in drv
make -C /usr/src/linux-wm M=/Packet/ac/module/saas_ctl/drv modules
CC [M] /Packet/ac/module/saas_ctl/drv/saas_action.o
CC [M] /Packet/ac/module/saas_ctl/drv/saas_main.o
/Packet/ac/module/saas_ctl/drv/saas_main.c: In function 'match_saas_rules':
/Packet/ac/module/saas_ctl/drv/saas_main.c:395: error: 'per_cpu__g_ssl_cpu_data' undeclared (first use in this function)
/Packet/ac/module/saas_ctl/drv/saas_main.c:395: error: (Each undeclared identifier is reported only once
/Packet/ac/module/saas_ctl/drv/saas_main.c:395: error: for each function it appears in.)
cc1: warnings being treated as errors
/Packet/ac/module/saas_ctl/drv/saas_main.c:395: error: type defaults to 'int' in declaration of 'type name'
/Packet/ac/module/saas_ctl/drv/saas_main.c:395: error: invalid type argument of 'unary *' (have 'int')
make[3]: *** [/Packet/ac/module/saas_ctl/drv/saas_main.o] Error 1
make[2]: *** [_module_/Packet/ac/module/saas_ctl/drv] Error 2
make[1]: *** [all] Error 2
make: *** [drv] Error 2原因:
percpu变量在其他模块定义的,当前模块使用前要声明:
DECLARE_PER_CPU(type, name);
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/add-two-numbers
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
给出两个非空的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储一位数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字0之外,这两个数都不会以0开头。
示例:
思路:
我们使用变量来跟踪进位,并从包含最低有效位的表头开始模拟逐位相加的过程。:

算法:
就像你在纸上计算两个数字的和那样,我们首先从最低有效位也就是列表l1和l2的表头开始相加。由于每位数字都应当处[0, 9]的范围内,我们计算两个数字的和时可能会出现 “溢出”。例如,5 + 7 = 12。在这种情况下,我们会将当前位的数值设置为2,并将进位carry = 1带入下一次迭代。进位carry必定是0或1,这是因为两个数字相加(考虑到进位)可能出现的最大和为 9 + 9 + 1 = 19。
伪代码如下:
遍历列表l1和l2直至到达它们的尾端。
请注意,我们使用哑结点来简化代码。如果没有哑结点,则必须编写额外的条件语句来初始化表头的值。
特别注意以下情况:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode *rs, *p, *q, *cur;
int carry = 0, sum, x, y;
rs = new ListNode(0);
cur = rs;
p = l1;
q = l2;
// [1] 注意循环终止的条件是两个链表都不为空了才停止
while (p || q) {
// [2] 注意p或者q等于NULL的清情况
x = p ? p->val : 0;
y = q ? q->val : 0;
// [3] 注意加上进位
sum = x + y + carry;
cur->next = new ListNode(sum % 10);
carry = sum / 10;
// [4] 注意处理p或q节点等于NULL的情况
p = p ? p->next : NULL;
q = q ? q->next : NULL;
cur = cur->next;
}
// [5] 注意最后的进位
if (carry) {
cur->next = new ListNode(carry);
}
// [6] 返回rs的下一个节点
return rs->next;
}
};
复杂度分析:
来源:力扣(LeetCode)
链接:581. 最短无序连续子数组,
著作权归领扣网络所有。
给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。
你找到的子数组应是最短的,请输出它的长度。
示例 1:
输入: [2, 6, 4, 8, 10, 9, 15]
输出: 5
解释: 你只需要对 [6, 4, 8, 10, 9] > 进行升序排序,那么整个表都会变为升序排序。
说明 :
算法分析
对数组进行排序,将未排序数组和已排序的数组对比。数组两端朝中间遍历,找到最左边和最右边的不匹配元素,中间的元素就是无序的数组范围。
代码:
class Solution {
public:
int findUnsortedSubarray(vector<int>& nums) {
int i, j;
// 建立辅助数组
vector<int> sortNums(nums);
// 排序
sort(sortNums.begin(), sortNums.end());
// 找到
for (i = 0; i < nums.size(); i++) {
if (nums[i] != sortNums[i]) {
break;
}
}
// 考虑数组已全部有序的情况
if (i == nums.size()) {
return 0;
}
for (j = nums.size() - 1; j >= 0; j--) {
if (nums[j] != sortNums[j]) {
break;
}
}
return j - i + 1;
}
};
复杂度分析
时间复杂度:O(nlog(n)) 。排序消耗nlogn的时间。
空间复杂度:O(n) 。拷贝了一份原数组来进行排序。
以{ 2, 6, 4, 8, 10, 9, 15 }为例,该数组中,最短无序数组是{6, 4, 8, 10, 9},可以大概看出的规律是:区间的起点在原数组的第一个降序序列附近(6和4这里),区间的终点在从右到左第一个升序序列附近(10和9这里),因此这里可以认为无序子数组的起点和终点和整个数组的升降点相关。
那是不是说直接从第一个降序点和升序点取子数组就可以了呢?不行,以{ 3, 4, 6, 9, 2, 1 }为例,从左往右的第一个降序点是9和2,从右往左的升序点是1和2,但是失序的数组并不只是这三个元素{9, 2, 1},而是{3, 4, 6, 9, 2, 1}。此时从左往右看,元素9左边大于1的数组序列也都属于失序数组元素;从右往左看,元素2右边所有比9小的也属于失序数组。
因此能通过以下算法即便能找出失序数组:
代码:
class Solution {
public:
int findUnsortedSubarray(vector<int>& nums) {
int max = INT_MIN;
int min = INT_MAX;
int i, j;
// 从左往右,出现降序之后找最大值
for (i = 1; i < nums.size(); i++) {
if (nums[i] < nums[i - 1]) {
min = min < nums[i] ? min : nums[i];
}
}
// 从右往左,出现升序之后找最小值
for (j = nums.size() - 1; j > 0; j--) {
if (nums[j - 1] > nums[j] ) {
max = max > nums[j - 1] ? max : nums[j - 1];
}
}
// 从左往右找到第一个大于min的元素
for (i = 0; i < nums.size(); i++) {
if (nums[i] > min)
break;
}
// i遍历到最后一个元素,说明数组已经有序了
if (i == nums.size())
return 0;
// 从右往左找到第一个小于max的元素
for (j = nums.size() - 1; j >= 0; j--) {
if (nums[j] < max)
break;
}
// 计算区间大小
return j - i + 1;
}
};
算法复杂度
时间复杂度:O(n)。使用了4个O(n)的循环。
空间复杂度:O(1)。使用了常数空间。
C++中class和struct的区别:
网上还流传着其他一些的区别,但总体来说最大的区别就是这两点,其他的区别或许并不常用到。
第一、C++中的struct可以定义成员函数,但是C语言不行,C语言中的结构体可以定义函数指针。
例如以下代码:
struct stu_st {
void print();
};
int main() {
return 0;
}使用GCC编译会报错:

第二、C语言声明结构体必须要加struct,C++不用。
C语言中如果不加struct声明变量,编译器会报错:

如若不想加struct修饰,则需要使用typedef来重新定义类型。
第三、C语言中空结构体大小为0,C++中结构体大小为1。
相同的代码:
#include "stdio.h"
struct stu_st {
};
int main() {
printf("%u\n", (unsigned int)sizeof(struct stu_st));
return 0;
}使用gcc和g++编译结果也不一样:

Linux模块是一些可以作为独立程序来编译的函数和数据类型的集合。之所以提供模块机制,是因为Linux本身是一个单内核。单内核由于所有内容都集成在一起,效率很高,但可扩展性和可维护性相对较差,模块机制可弥补这一缺陷。
Linux模块可以通过静态或动态的方法加载到内核空间,静态加载是指在内核启动过程中加载;动态加载是指在内核运行的过程中随时加载。
一个模块被加载到内核中时,就成为内核代码的一部分。模块加载入系统时,系统修改内核中的符号表,将新加载的模块提供的资源和符号添加到内核符号表中,以便模块间的通信。
内核提供了接口函数来注册我们自己的模块:
// linux/init.h
/**
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls() (if
* builtin) or at module insertion time (if a module). There can only
* be one per module.
*/
#define module_init(x) __initcall(x);
/**
* module_exit() - driver exit entry point
* @x: function to be run when driver is removed
*
* module_exit() will wrap the driver clean-up code
* with cleanup_module() when used with rmmod when
* the driver is a module. If the driver is statically
* compiled into the kernel, module_exit() has no effect.
* There can only be one per module.
*/
#define module_exit(x) __exitcall(x);module_init和module_exit分别用于加载和卸载模块,其中的x的函数声明为:
int f(void);首先下载内核源码:yum groupinstall "Development Tools",、安装好后源码在/usr/src/kernels/2.6.32-754.9.1.el6.x86_64/,注意最后面的一串内核版本,可能和uname -r不一致。
然后准备测试代码test.c:
#include <linux/module.h>
#include <linux/init.h>
static int test_init(void) {
printk("===Test Module Start!===\n");
return 0;
}
static int test_exit(void) {
printk("===Test Module Has Been Closed!===");
return 0;
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("MaQian");其中linux/module.h和linux/init.h是必须包含的两个问头文件,MODULE_LICENSE是模块许可证声明,一般设置为GPL,可选。MODULE_AUTHOR是模块作者,可选。
# test是模块的名字
obj-m := test.o
# 内核代码的位置
KERNEL := /usr/src/kernels/2.6.32-754.9.1.el6.x86_64/
# 当前模块的目录
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNEL) M=$(PWD) modules
.PHONY: clean
clean:
rm -rf *.o *.ko如果不出意外,执行make即可生成我们要的模块文件test.ko,但是一般情况下,第一次编译是会失败的,会报错,错误的解决方案在最下面。
挂载驱动使用insmod,卸载使用rmmod,先开启另外一个窗口开始打印调试信息:
> while :; do dmesg -c; sleep 1; done插入模块到系统:
> insmod test.ko输出:
===Test Module Start!===卸载:
rmmod test.ko输出:
===Test Module Has Been Closed!===
make -C /usr/src/kernels/2.6.32-754.6.3.el6.x86_64/ M=/home/maqian/code/hello_netfilter modules
make[1]: Entering directory `/usr/src/linux-2.6.32'
ERROR: Kernel configuration is invalid.
include/linux/autoconf.h or include/config/auto.conf are missing.
Run 'make oldconfig && make prepare' on kernel src to fix it.
WARNING: Symbol version dump /usr/src/linux-2.6.32/Module.symvers
is missing; modules will have no dependencies and modversions.
Building modules, stage 2.
/usr/src/linux-2.6.32/scripts/Makefile.modpost:42: include/config/auto.conf: No such file or directory
make[2]: *** No rule to make target `include/config/auto.conf'. Stop.
make[1]: *** [modules] Error 2
make[1]: Leaving directory `/usr/src/linux-2.6.32'
make: *** [modules] Error 2进入到内核代码目录,执行sudo make oldconfig && sudo make prepare,一路回车确认。
make -C /usr/src/kernels/2.6.32-754.6.3.el6.x86_64/ M=/home/maqian/code/hello_netfilter modules
make[1]: Entering directory `/usr/src/linux-2.6.32'
WARNING: Symbol version dump /usr/src/linux-2.6.32/Module.symvers
is missing; modules will have no dependencies and modversions.
Building modules, stage 2.
MODPOST 0 modules
/bin/sh: scripts/mod/modpost: No such file or directory
make[2]: *** [__modpost] Error 127
make[1]: *** [modules] Error 2
make[1]: Leaving directory `/usr/src/linux-2.6.32'
make: *** [modules] Error 2还是在刚刚的目录执行:sudo make scripts。
当一个模块编译完成之后,使用insmod加载,使用rmmod加载,lsmod可以查看已经挂载的模块,modinfo查看一个模块的详细信息。
插入驱动报错:
insmod: error inserting 'test.ko': -1 Invalid module formatdmesg错误:
test: no symbol version for module_layout检查驱动内核版本是否一致,最开始出现这个问题是因为内核代码是自己手动下载的,后面改成yum下载就好了。
Makefile中执行shell命令需要在命令前面加上@,例如打印一个变量的值
KERNEL = $(shell uname -r)
INC = /usr/src/kernels/$(KERNEL)/
print:
@echo $(KERNEL)
@echo $(INC)执行make print会有得到理想的输出:
2.6.32-754.6.3.el6.x86_64
/usr/src/kernels/2.6.32-754.6.3.el6.x86_64/如果不加@符号最后的结果是:
echo 2.6.32-754.6.3.el6.x86_64
2.6.32-754.6.3.el6.x86_64
echo /usr/src/kernels/2.6.32-754.6.3.el6.x86_64/
/usr/src/kernels/2.6.32-754.6.3.el6.x86_64/可以看到多了几行,分别把每个echo命令也打印出来了。
当shell脚本中存在多行命令时,换行时要加上\,例如:
@./configure --prefix=/usr/lib \
aaa=1 bbb=2把shell命令的值赋值需要加上shell关键字,如:
PWD = @(shell pwd) 学习linux的网络框架netfilter,想用centos作为路由器,在下面接PC产生流量测试。
默认情况下linux是没有开启数据包转发功能的,需要手动配置,linux使用centos6.9,网络拓扑图如下:

路由器的eth0口接外网,IP地址192.168.123.102,内网口eth0地址10.0.0.x/24,希望PC通过路由nat上网。
开启路由转发首先检查好防火墙配置,清空原有的nat规则:
iptable -F然后添加nat规则,一共有两种方式:
第一种:iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -j SNAT --to 192.168.123.102
第二种:iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
其中的部分参数含义为:
-t: 类型为nat-A: 添加新规则到规则链的末尾POSTROUTING: 在包就要离开防火墙之前改变其源地址-s: 源地址段,这里设置我的内网地址网段10.0.0.0/24-j SNAT: 满足snat条件的时候跳转--to: 跳转时设置的Ip地址-o: 跳转时的出口设备为eth0具体可参考防火墙规则,这里设置好后,开启设备的数据包转发:
echo 1 > /proc/sys/net/ipv4/ip_forward然后快乐的
转发永久生效
上面的echo开启数据包转发只是临时生效,下次重启后就失效了,如果需要永久生效,得修改/etc/sysctl.conf文件,把net.ipv4.ip_forwar设置为1。
> sed -i 's#net.ipv4.ip_forward = 0#net.ipv4.ip_forward = 1#g' /etc/sysctl.conf
> grep net.ipv4.ip_forwar /etc/sysctl.conf # 确认是否修改成功了
net.ipv4.ip_forward = 1 # 已经修改好了
> sysctl -p # 生效配置
net.ipv4.ip_forward = 1 # 这里也可以看到值被修改成了1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.default.accept_source_route = 0
kernel.sysrq = 0
kernel.core_uses_pid = 1
net.ipv4.tcp_syncookies = 1
kernel.msgmnb = 65536
kernel.msgmax = 65536
kernel.shmmax = 68719476736
kernel.shmall = 4294967296检测是否生效,给PC配置好IP,然后ping百度,同时设备上也开启抓包:
maqian@d2.maqian.co:~$ tcpdump -i eth1 -nnn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth1, link-type EN10MB (Ethernet), capture size 65535 bytes
16:35:42.880920 IP 10.0.0.2.63573 > 223.5.5.5.53: 48582+ A? baidu.com. (27)
16:35:42.924100 IP 223.5.5.5.53 > 10.0.0.2.63573: 48582 2/5/5 A 220.181.57.216, A 123.125.115.110 (229)
16:35:42.938192 IP 10.0.0.2 > 220.181.57.216: ICMP echo request, id 1, seq 11, length 40
16:35:43.021923 IP 220.181.57.216 > 10.0.0.2: ICMP echo reply, id 1, seq 11, length 40
16:35:43.947976 IP 10.0.0.2 > 220.181.57.216: ICMP echo request, id 1, seq 12, length 40
16:35:44.028291 IP 220.181.57.216 > 10.0.0.2: ICMP echo reply, id 1, seq 12, length 40
16:35:44.963608 IP 10.0.0.2 > 220.181.57.216: ICMP echo request, id 1, seq 13, length 40
16:35:45.043968 IP 220.181.57.216 > 10.0.0.2: ICMP echo reply, id 1, seq 13, length 40
16:35:45.979290 IP 10.0.0.2 > 220.181.57.216: ICMP echo request, id 1, seq 14, length 40
16:35:46.058447 IP 220.181.57.216 > 10.0.0.2: ICMP echo reply, id 1, seq 14, length 40
16:35:47.923349 ARP, Request who-has 10.0.0.2 tell 10.0.0.1, length 28
16:35:47.923695 ARP, Reply 10.0.0.2 is-at 00:0c:29:60:22:cc, length 46
区别:
其他:
条件变量是多线程的一种同步方式,它允许多个线程以无竞争的方式等待特定事件发生。无竞争的意思是,当条件满足时,条件满足这个讯号会发送给所有的监听者线程,但多个线程中只有一个能获取到特定事件。条件变量需要配合互斥量一起使用。
相关数据结构和函数:
#include <pthread.h>
// 销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
// 初始化条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
// 初始化条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;条件变量的数据结构为pthread_cond_t,和其他同步的数据结构一样,也提供了两种方式来初始化。一种是通过赋值的方式,一种是通过函数的方式。在使用pthread_cond_init对初始化条件变量的时候,attr参数一般为NULL。
对条件变量加锁的方式:
#include <pthread.h>
// 等待条件满足
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
// 带有超时条件的等待
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);在对条件变量加锁的时候,需要传入一把已经处于加锁状态的互斥量,此时函数会把当前线程放到条件等待的列表上并对互斥量解锁。此时线程进入条件等待状态,当有信号到达时便会触发。
通知条件满足的函数:
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);pthread_cond_signal函数会唤醒条件等待队列上的至少一个线程,pthread_cond_broadcast会唤醒条件队列上的所有线程。
条件队列一般用于消息队列,用于统一、协调多个生产者和消费者之间的竞争关系。
以下使用了三个线程作为消费者,分别从全局队列上获取消息消费:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include "log.h"
#define THREAD_COUNT 3
// 消息结构
struct msg_st {
unsigned int msg_id;
struct msg_st *next;
};
static struct msg_st *msg_queue = NULL;
static pthread_cond_t g_cond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
// 消费线程函数
void *handle_msg(void *arg) {
struct msg_st *msg;
while (1) {
pthread_mutex_lock(&g_mutex);
while (msg_queue == NULL) {
pthread_cond_wait(&g_cond, &g_mutex);
}
// 提取消息
msg = msg_queue;
msg_queue = msg_queue->next;
pthread_mutex_unlock(&g_mutex);
// 退出
if (msg->msg_id == (unsigned int) -1) {
info("Thread 0x%x exit!", (unsigned int) pthread_self());
break;
}
info("Thread 0x%x: msg_id = %u", (unsigned int) pthread_self(), msg->msg_id);
}
return NULL;
}
// 生产消息函数
void create_msg(struct msg_st *msg) {
pthread_mutex_lock(&g_mutex);
msg->next = msg_queue;
msg_queue = msg;
pthread_mutex_unlock(&g_mutex);
pthread_cond_signal(&g_cond);
}
int main() {
int i;
struct msg_st msg[10];
pthread_t pid[THREAD_COUNT];
debug("start create threads");
// 创建3个线程作为消费者
for (i = 0; i < 3; i++) {
pthread_create(&pid[i], NULL, handle_msg, NULL);
}
debug("start create msgs");
// 生产10个消息
for (i = 0; i < 10; i++) {
msg[i].msg_id = i + 1;
msg[i].next = NULL;
create_msg(&msg[i]);
}
// 休眠1秒,确保所有消息都被消费者消费完成
sleep(1);
// 退出所有线程
debug("start create exit msgs");
for (i = 0; i < THREAD_COUNT; i++) {
msg[i].msg_id = (unsigned int) -1;
msg[i].next = NULL;
create_msg(&msg[i]);
}
// 回收所有线程
debug("start join threads");
for (i = 0; i < THREAD_COUNT; i++) {
pthread_join(pid[i], NULL);
}
debug("program end");
return 0;
}运行结果,10个消息分别被3个线程打印出来了:

条件变量是否存在惊群效应呢?
不会,线程执行wait操作的时候会被放到一个条件等待队列里面去。当条件满足的时候,系统会自动选择队列前面的线程来消费队列。
不会,执行wait操作时需要的是一把已经加锁的互斥量,这个锁在wait函数中会解开。
这样做的目的:如果在wait前没有加锁,生产者线程产生了一个消息并发送信号,再执行wait后信号就丢失了。
wait收到信号之后要对互斥量加锁,此时锁还被生产者持有,消费者依旧还要等待锁的释放,才能持有锁。对消费者而言,最理想的情况就是生产者产生消息后,我立马就能读取消息,此时的互斥量是用来和其他消费者线程同步的,而不是和生产者线程同步。