长期以来,Linux一直把具有较好的平均系统响应时间和较高的吞吐量作为调度算法的主要目标。但近年来,鉴于嵌入式系统的要求,Linux2.6在支持系统的实时性方面也做出了重大的改进。
Linux进程的时间片与权重参数 在处理器资源有限的系统中,所有进程都以轮流占用处理器的方式交叉运行。为使每个进程都有运行的机会,调度器为每个进程分配了一个占用处理器的时间额度,这个额度叫做进程的“时间片”,其初值就存放在进程控制块的counter域中。进程每占用处理器一次,系统就将这次所占用时间从counter中扣除,因为counter反映了进程时间片的剩余情况,所以叫做剩余时间片。
Linux调度的主要思想为:调度器大致以所有进程时间片的总和为一个调度周期;在每个调度周期内可以发生若干次调度,每次调度时,所有进程都以counter为资本竞争处理器控制权,counter值大者胜出,优先运行;凡是已耗尽时间片(即counter=0)的,则立即退出本周期的竞争;当所有未被阻塞进程的时间片都耗尽,那就不等了。然后,由调度器重新为进程分配时间片,开始下一个调度周期。
Linux基于时间片调度的基本思想如下图所示:
上面的调度方式主要体现的是一种公平性:如果一个进程的剩余时间片多,那么它在前期获得运行的时间片就少,为了公平性,就应该适当的赋予它更高的优先级。但是这仅仅是一个总体的原则,为了应付实际问题中的特殊状况,在上述平等原则的基础上,为了给特殊进程一些特殊照顾,在为进程分配时间片时,Linux允许用户通过一个系统调用nice()来设置参数nice影响进程的优先权。所以,系统真正用来确定进程的优先权时,使用的依据为权重参数weight,weight大的进程优先运行。
weight与nice、counter之间的关系大体如下:
weight 正比于 [counter+(20-nice)]
关于三者之间的这个关系将会在后面的内容中详细的讲到。
因为nice是用户在创建进程时确定的,在进程的运行过程中一般不会改变,所以叫做静态优先级;counter则随着进程时间片的小号在不断减小,是变化的,所以叫做动态优先级。
调度策略 目前,标准Linux系统支持非实时(普通)和实时两种进程。与此相对应的,Linux有两种进程调度策略:普通进程调度和实时进程调度。因此,在每个进程的进程控制块中都有一个域policy,用来指明该进程为何种进程,应该使用何种调度策略。
Linux调度的总体思想是:实时进程优先于普通进程,实时进程以进程的紧急程度为优先顺序,并为实时进程赋予固定的优先级;普通进程则以保证所有进程能平均占用处理器时间为原则。所以其具体做法就是:
对于实时进程来说,总的思想是为实时进程赋予远大于普通进程的固定权重参数weight,以确保实时进程的优先级。在此基础上,还分为两种做法:一种与时间片无关,另一种与时间片有关;对于普通进程来说,原则上以相等的weight作为所有进程的初始权重值,即nice=0,然后在每次进行进程调度时,根据剩余时间片对weight动态调整。 普通进程调度策略 如果进程控制块的policy的值为SCHED_OTHER,则该进程为普通进程,适用于普通进程调度策略。
时间片的分配 当一个普通进程被创建时,系统会先为它分配一个默认的时间片(nice=0)。在Linux2.4内核中,进程的默认时间片时按照下面的算法来计算的:
#if HZ < 200 #define TICK_SCALE(x) ((x)>>2) #elif HZ < 400 #define TICK_SCALE(x) ((x)>>1) #elif HZ < 800 #define TICK_SCALE(x) (x) #elif HZ < 1600 #define TICK_SCALE(x) ((x)<<1) #dele #define TICK_SCALE(x) ((x)<<2) #endif #define NICE_TO_TICKS(nice) (TICK_SCALE(20-(nice))+1) ...... 在每个调度周期之前,调度器为每个普通进程进程分配时间片的算法为:
p->counter = (p->counter>>1) + NICE_TO_TICKS(p->nice); 其中,p为进程控制块指针。
这里为什么需要加上p->counter>>1呢?这会在本文后面的内容中讲到。
例如,用户定义HZ为100,而counter初值和nice的默认值为0,于是可以计算出counter的值为6 ticks(((20-0)>>2)+1),也就是说,进程时间片的默认值大概为60ms(100Hz,10ms)。
weight的微调函数goodness() 尽管在默认情况下,系统为所有的进程都分配了相等的时间片,但在实际运行时,常常因各种原因使得进程的运行总是有先有后,于是经过一段时间运行后,在各进程之间就会产生事实上的不公平的现象,也就是各个进程在实际使用其时间片的方面形成了差异。剩余时间计数器counter的值就反映了这个差异的程度:该值大的,意味着这个进程占用处理器的时间少(吃亏了);该值小的,意味着这个进程占用处理器的时间多(占便宜了)。
题目:
设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) -- 将元素 x 推入栈中。pop() -- 删除栈顶的元素。top() -- 获取栈顶元素。getMin() -- 检索栈中的最小元素。 示例:
MinStack minStack = new MinStack(); minStack.push(-2); minStack.push(0); minStack.push(-3); minStack.getMin(); --> 返回 -3. minStack.pop(); minStack.top(); --> 返回 0. minStack.getMin(); --> 返回 -2. 解答:
1. 可以用两个栈实现,一个栈存数据,另一个栈存当前最小值:
class MinStack { public: /** initialize your data structure here. */ MinStack() { } void push(int x) { s1.push(x); if (s2.empty() || x <= getMin()) { s2.push(x); } } void pop() { if (s1.
随着ENVI/IDL版本的更新,IDL对矢量和栅格数据的处理也变得越来越简单化。其提供了很多方便的接口,使得用户调用和学习练习便捷成为了可能。
最近接触IDL,发现好多网上的代码都是延后的,新的接口代码理解和编写起来都比较方便,尤其是在做大量数据研究和应用时,使用批处理的方式显得尤其重要。新的接口还在摸索中,后续会推出,既是学习记录,也是分享。
使用IDL实现裁剪。其思路是:对栅格区域确定裁剪矢量范围,进行栅格掩膜(裁剪),然后重新定义多边形范围显示输出。
1.读取栅格和矢量文件:
raster=e.OpenRaster(input)
file_shp=e.OpenVector(input_shp)
2.进行掩膜(裁剪)处理:
Task_MASK=ENVITASK('VectorMaskRaster')
Task_MASK.data_ignore_value=0
Task_MASK.input_Mask_vector=file_shp
Task_MASK.input_raster=raster
Task_MASK.Execute
3.重新格框,输出文件:
Task = ENVITask('RegridRaster')
Task.INPUT_RASTER = raster
Task.GRID_DEFINITION = Grid
4.效果图:
IDL(裁剪)代码下载地址:https://download.csdn.net/download/qq_33356563/10568978
AndroidManifest.xml 文件详解 AndroidManifest.xml 文件详解 概述文件特性 包名和应用ID(application ID)应用程序组件(App components)意图过滤器(Intent filters)Icons和labels权限(Permissions)设备兼容性(Device compatibility) 文件规范 元素(Elements)属性(Attributes)多重值资源值(Resource values)字符串值(String value) 清单文件元素参考目录manifest文件代码样例 概述 每一个android应用程序项目都必须包含有AndroidManifest.xml文件(后称清单文件)并且放在项目资源集的根目录中(即main文件夹下)。清单文件描述了应用必要的信息提供给Android编译工具、Android操作系统以及应用商店(如Google Play)。
清单文件要求以下的声明:
声明与代码的命名空间相匹配的应用包名。 Android编译工具在编译项目的时候通过应用包名定位代码实体的位置。在应用进行打包的同时,编译工具使用Gradle编译文件中的application ID与该应用包名进行替换。以此作为应用在Android操作系统以及应用商店的唯一的标识符。
应用的组件 包括应用中所有的Activity、Service、Broadcast Receiver、Content Provider。每一个组件必须定义基本的属性,它可以声明设备配置的特性、“Intent filter”(用以描述组件在何种状况下启用)
应用所需要访问的权限 包括访问系统及其他应用所受保护的权限。应用也可声明供其他应用访问该应用特定内容的权限(如果其他应用需要访问已经声明权限的内容,也必须在自身应用清单中声明该权限)。
文件特性 以下内容讲述的是清单文件中一些重要的字符
包名和应用ID(application ID) 清单文件的根元素要求声明应用的包名称(一般情况下与项目中的Java目录结构一致)
以下代码片段展示的是根元素和应用包名com.example.myapp:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp" android:versionCode="1" android:versionName="1.0" > ... </manifest> 把应用打包成最终的APK,Android编译工具将 package属性应用于以下2处:
将该名称作为应用的命名空间以生成R.java类文件(用来访问应用的资源) 比如上面的清单文件中,R类文件被创建为com.example.myapp.R
用以解决清单文件中的相似类名,以缩短命名。 比如上面的清单文件中,activity声明为<activity android:name=".MainActivity">来代替com.example.myapp.MainActivity
要注意的是,一旦APK被编译,package属性也可以表示app全局唯一的应用Id。待编译器完成以上任务后,编译器将build.gradle文件中的applicationId熟悉与package进行替换。这个最终的值必须是全局唯一的,以此来保证应用的唯一性。
manifest中的package和build.gradle文件中的applicationId会让人感到些许困惑。通常把两者的值设置成一致便可避免不必要的麻烦。
应用程序组件(App components) 应用中每个创建的组件必须在清单文件中声明一个相对应的XML元素。
<activity>对应于Activity类<service>对应于Service类<receiver>对应于BroadcastReceiver类<provider>对应于ContentProvider类 如果创建的组件类未在清单文件中声明,操作系统将无法启用该组件。
Activity中的name属性必须指定完整包名,如下所示:
<manifest ... > <application ... > <activity android:name="com.example.myapp.MainActivity" .
环境说明:
1.为了保护电脑中数据的安全,除了给操作系统设置登录密码之外,最好对敏感的数据进行隐藏。
2.本教程介绍了2种隐藏文件或文件夹的方法,一种是通过图形化设置,另一种是通过命令行设置。
3.图形化设置简单,但隐密性较低。
4.命令行设置稍微复杂一点,但隐密性很高,除非用特殊的工具扫描,否则很难找到。
5.本教程适用于Windows系列的操作系统
操作过程:
1.通过图形化界面设置隐藏
鼠标右键需要隐藏的文件或文件夹,点击属性
勾选“隐藏”复选框,然后点击确定
若要访问隐藏的文件夹,使用“Win+R”组合键打开运行窗口,输入文件夹路径,这里为C:\test,点击确定
若要显示电脑中隐藏的文件夹,按“Alt”键调出菜单栏,选择“工具-文件夹选项”
此时,隐藏的test文件夹就会显示出来,设置了隐藏属性的文件夹图标是浅色
这种隐藏方法比较简单,隐蔽性并不高,只要在“文件夹选项”设置一下,别人就能看到隐藏的文件。
2.通过命令行设置隐藏
通过命令行设置隐藏,这个可就厉害了,如果你记不住路径,可能连你自己都找不到隐藏的文件夹,所以一定要记住文件夹所在的路径。
下面通过命令行演示将C盘下的aaa文件夹隐藏
使用“Win+R”组合键打开运行窗口,输入“cmd”,回车,打开命令提示符界面
在命令提示符界面中输入“attrib +a +s +r +h C:\aaa”,回车
此时aaa文件夹已经隐藏,就算在“文件夹选项”设置显示,aaa文件夹也无法显示
若要访问aaa文件夹,使用“Win+R”组合键打开运行窗口,输入文件夹路径即可
如果要取消隐藏,在命令提示符界面中输入“attrib -a -s -r -h C:\aaa”即可
隐藏文件跟隐藏文件夹一样,这里就不再赘述了。
1、连接数据库, 默认的用户和数据库是postgres
psql -U user -d dbname 2、切换数据库,相当于MySQL的use dbname
\c dbname 3、列举数据库,相当于mysql的show databases
\l 4、列举表,相当于mysql的show tables
\dt 5、查看表结构,相当于desc tblname,show columns from tbname
\d tblname 6、查看索引
\di 7、查看函数
\df 8、创建数据库
create database [数据库名]; 9、删除数据库:
drop database [数据库名]; 10、重命名一个表:
alter table [表名A] rename to [表名B]; 11、删除一个表:
drop table [表名]; 12、在已有的表里添加字段:
alter table [表名] add column [字段名] [类型]; 13、删除表中的字段:
alter table [表名] drop column [字段名]; 14、重命名一个字段:
alter table [表名] rename column [字段名A] to [字段名B]; 15、给一个字段设置缺省值:
一、怎么显示图片?
这是我项目的总体排版
1.解压UTF-8版本的Ueditor到webapp下 新建一个叫index.jsp的文件 内容如下
2.修改imageUrlPrefix的值 ,对应自己的项目名
3.因为SSM包含了mvc 过滤静态资源 所以去MVC配置
<mvc:resources location="/ueditor/" mapping="/ueditor/**"/>
4.导包,maven下不能直接右键直接导 所以要在pom中添加依赖
<!-- 百度富文本 -->
<dependency>
<groupId>com.baidu</groupId>
<artifactId>ueditor</artifactId>
<version>1.1.2</version>
</dependency> <dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20170516</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.9</version>
</dependency>
其中 ,com.baidu.ueditor可能会出错,自己可以通过ide工具安装 或者直接cmd来mvn安装
命令如下
5.运行项目 跑起来
二、可能遇到的问题
1.页面说图片配置有问题,不能上传
答 仔细检查第三步的项目名字有没有填错 前面有个/ 2.
不能回显,图片只显示名字
答 这种情况一般只在maven下才发生,普通的web项目就正常
解决:①仔细检查第三步的项目名字有没有填错
②还是pom.xml里面的事情,ueditor包没导对,重新开始导入
3.图片一直转啊转啊
解决办法 :同2的解决 还有一种可能是项目没启动。。
最终效果
故障 控制台运行webpack/npm时出现
Module not found: Error: Can't resolve 'XXX' in 'XXXX' 解决方案 npm i XXX --save 重新运行即可
如果提示ERROR
尝试执行
npm uninstall XXX npm i XXX --save
在Qt的Widget中显示图片,使其适应Label 的大小,直接上程序。
void Widget::show_frame(QImage &image)
{
QSize laSize=ui->label_carema->size();//label_carema是要显示图片的label的名称
QImage image1=image.scaled(laSize,Qt::IgnoreAspectRatio);//重新调整图像大小以适应窗口
ui->label_carema->setPixmap(QPixmap::fromImage(image1));//显示
}
效果如下:
用html5实现图片和文字拖拽到下面的文本框里的Demo jquery-3.3.1.js
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <style> div{ width: 400px; padding: 20px; border:4px solid #ccc; margin:20px auto; } #target{ border:4px dashed #666; min-height: 100px; line-height: 30px; transition: .3; } img{ width: 100%; } </style> </head> <body> <div id="box"> 如今全球都面临着严峻的能源和环境污染问题,而这也推动了生物能源的发展。最近国家发展改革委、国家能源局、财政部等十五部门联合印发了《关于扩大生物燃料乙醇生产和推广使用车用乙醇汽油的实施方案》,欲在2020年全国范围内基本实现车用E10乙醇汽油全覆盖,2025年力争纤维素乙醇实现规模化生产,形成更加完善的市场化运行机制。 </div> <div id="img"> <img id="innerImg" src="img/1.jpg" alt=""> </div> <div id="target"> </div> <script> document.ondragover = function(e){ e.preventDefault(); } document.ondrop = function(e){ e.
1. 前言 Wiki 是一个协同著作平台或称开放编辑系统。我们可以用Wiki来建设帮助系统,知识库系统。国内公共wiki最著名就是百度百科.那公司内部为什么要使用wiki呢?
2.内部wiki的作用 1.鼓励分享
分享是互联网的精神,wiki能将互帮互助融入到企业文化之中。除了工作上的成就,让员工在工作之余,能够体会到帮助他人的成就和快乐。
2.提升员工个人能力
很多难懂深奥的问题,专研半天终于搞懂了,但是你不一定能思路清晰给别人讲出来,并且,过了1个月之后再回头看,可能又不懂了。如果能有一篇总结性的文章出来,这样对自己、对大家,都是很好的提升。最开始写的时候,可能会有点困难;但只要坚持,只要肚子里有货,日子久了,下笔就流畅了。“总结”,是需要练习的;
3.传承技术
员工离职、特别是核心员工的离职,对公司是一笔不小的损失;如何将员工离职对公司造成的损失降到最低?如果员工将知识分享出来,将以往的工作经验都体现在wiki中,那么,接班的同事会更加顺畅,也会感激你;说到这,有人会想,我把知道的东西都写出来了,不是没有核心竞争力了么?如果这么想,就大错了,人总是在进步的,你写出来的越多,自我提升的也越快,说不定,还没等到你把自己知道的全都写出来,就已经得到岗位上的提升,而不是离职;你对公司的贡献,大家都看在眼里;再就是,创造了一个分享互助的文化,你也同样可以在同事那里学到很多。
3.搭建一个wiki玩玩-confluence confluence很多公司都在用,我这实习的公司也在用。虽然我是个java开发的,但我最近没什么事,我们来搭个玩玩。
Confluence是一个专业的wiki程序。它是一个知识管理的工具,通过它可以实现团队成员之间的协作和知识共享。 Confluence使用简单,但它强大的编辑和站点管理特征能够帮助团队成员之间共享信息,文档协作,集体讨论。
3.1 环境准备
CentOS 7.2 java jdk 1.8 mysql-server 5.6 confluence 5.6.6
3.2 创建数据库
mysql> CREATE DATABASE confluence CHARACTER SET utf8 COLLATE utf8_bin; mysql> GRANT ALL PRIVILEGES ON confluence.* TO 'confluence'@'localhost' IDENTIFIED BY '123456'; mysql> GRANT ALL PRIVILEGES ON confluence.* TO 'confluence'@'%' IDENTIFIED BY '123456'; mysql> FLUSH PRIVILEGES; 3.3 下载confluence,安装
curl -o atlassian-confluence-5.6.6-x64.bin https://downloads.atlassian.com/software/confluence/downloads/atlassian-confluence-5.6.6-x64.bin chmod +x atlassian-confluence-5.
AOP概述 AOP面向切面编程(Aspect Oriented Programming)
通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
常用于日志记录,性能统计,安全控制,事务处理,异常处理等
AOP术语 JoinPoint 连接点(切入点)
PointCut 连接点集合
Aspect(切面)
Advice(通知)
Target(织入到的方法)
Weave(织入)
Aspectj切入点表达式 execution(public * *(..)) 任何类的任何返回值的任何方法
execution(* set*(..)) 任何类的set开头的方法
execution(* com.yinghuo.service.AccountService.*(..)) 任何返回值的规定类里面的方法
execution(* com.yinghuo.service..*.*(..)) 任何返回值的,规定包或者规定包子包的任何类任何方法
Aspectj的Advice(面向方面编程的通知) @Before 方法执行之前
@After 方法执行之后
@ AfterReturning 方法正常执行完后执行
@ AfterThrowing 抛出异常时执行
@Around 前后都可以加逻辑
Spring AOP使用 首先要导入spring-aspects的jar包才可以
布局如图
注解实现 UserService接口
package cn.yinghuo.service; public interface UserService { public void save(); } UserServiceImpl实现接口
package cn.yinghuo.service.impl; import org.springframework.stereotype.Component; import cn.yinghuo.service.UserService; @Component public class UserServiceImpl implements UserService{ @Override public void save() { System.
requests库是一个常用的用于http请求的模块,它使用python语言编写,可以方便的对网页进行爬取,是学习python爬虫的较好的http请求模块。
一、 requests模块的安装 首先我们要继续requests模块的安装。
1、 pip命令安装 windows系统下只需要在命令行输入命令 pip install requests 即可安装在 linux 系统下,只需要输入命令 sudo pip install requests ,即可安装。 2、下载安装包安装 由于pip命令可能安装失败所以有时我们要通过下载第三方库文件来进行安装。
在github上的地址为:https://github.com/requests/requests 下载文件到本地之后,解压到python安装目录。 之后打开解压文件,在此处运行命令行并输入:python setup.py install 即可。
之后我们测试requests模块是否安装正确,在交互式环境中输入 import requests 如果没有任何报错,说明requests模块我们已经安装成功了
二、requests模块的使用方法 1、requests库的七个主要方法 方法解释requests.request()构造一个请求,支持以下各种方法requests.get()获取html的主要方法requests.head()获取html头部信息的主要方法requests.post()向html网页提交post请求的方法requests.put()向html网页提交put请求的方法requests.patch()向html提交局部修改的请求requests.delete()向html提交删除请求 (1)requests.get()
这个方法是我们平时最常用的方法之一,通过这个方法我们可以了解到其他的方法,所以我们详细介绍这个方法。 具体参数是:
r=requests.get(url,params,**kwargs) url: 需要爬取的网站地址。params: 翻译过来就是参数, url中的额外参数,字典或者字节流格式,可选。**kwargs : 12个控制访问的参数 我们先来讲讲**kwargs:
**kwargs有以下的参数,对于requests.get,其第一个参数被提出来了。
params:字典或字节序列, 作为参数增加到url中,使用这个参数可以把一些键值对以?key1=value1&key2=value2的模式增加到url中 例如:kv = {'key1':' values', 'key2': 'values'} r = requests.get('http:www.python123.io/ws', params=kw)
data:字典,字节序或文件对象,重点作为向服务器提供或提交资源是提交,,作为request的内容,与params不同的是,data提交的数据并不放在url链接里, 而是放在url链接对应位置的地方作为数据来存储。,它也可以接受一个字符串对象。json:json格式的数据, json合适在相关的html,http相关的web开发中非常常见, 也是http最经常使用的数据格式, 他是作为内容部分可以向服务器提交。 例如:kv = {'key1': 'value1'} r = requests.post('http://python123.io/ws', json=kv)
1、GigE Vision GigE Vision是由自动化影像协会AIA(Automated Imaging Association)发起指定的一种基于千兆以太网的图像传输的标准。
具有传输距离长(无中继时100米)、传输效率高并可向上升级到万兆网、通信控制方便、软硬件互换性强、可靠性高等优点,是未来数字图像领域的主要接口标准,必将被越来越多的商家多采用。
GigE Vision标准委员会的主要成员都是国际知名的图像系统软硬件提供商。
GigE Vision与标准千兆以太网相机,在硬件架构上基本完全一样(对网卡的要求有微小区别),只是在底层的驱动软件上有所区别。他主要解决标准千兆网的两个问题:
1. 数据包小而导致的传输效率低。标准千兆网的数据包为1440字节,而GigE Vision 采用所谓的“Jumbo packet”,其最大数据包可达16224字节。
2. CPU占用率过高。标准千兆网采用TCP/IP协议,在部分使用DMA控制以提高传输效率的情况下,可做到82MB/s时CPU占用率15%。GigE Vision 驱动采用UDP/IP协议,采用完全的DMA控制,大大降低了CPU的占用率,在同等配置情况下可做到108MB/s时CPU占用率为2%。 [1] 2、相机帧率和曝光时间的关系 工业相机参数之帧率相关知识详解:
工业相机是机器视觉系统的重要组成部分之一,在机器视觉系统中有着非常重要的作用。工业相机已经被广泛应用于工业生产线在线检测、智能交通,机器视觉,科研,军事科学,航天航空等众多领域。
工业相机的主要参数包括:分辨率、帧率、像素、像元尺寸、光谱响应特性等。下面我们来对工业相机帧率的相关知识进行讲解:
帧率(Frame rate)是用于测量显示帧数的量度。所谓的测量单位为每秒显示帧数(Frames per Second),简称:FPS或“赫兹”(Hz)。 由于人类眼睛的特殊生理结构,如果所看画面之帧率高于16fps的时候,就会认为是连贯的,此现象称之为视觉暂留。这也就是为什么电影胶片是一格一格拍摄出来,然后快速播放的。
每秒的帧数(fps)或者说帧率表示图形处理器处理场时每秒钟能够更新的次数。高的帧率可以得到更流畅、更逼真的动画。一般来说30fps就是可以接受的,但是将性能提升至60fps则可以明显提升交互感和逼真感,但是一般来说超过75fps一般就不容易察觉到有明显的流畅度提升了。如果帧率超过屏幕刷新率只会浪费图形处理的能力,因为监视器不能以这么快的速度更新,这样超过刷新率的帧率就浪费掉了。
最大帧率(Frame Rate)/行频(Line Rate):即相机采集传输图像的速率,对于面阵相机一般为每秒采集的帧数(Frames/Sec.),对于线阵相机为每秒采集的行数(Hz)。
相机帧率和曝光时间的关系: 有人问,为什么我们在使用工业相机的时候,将相机的曝光时间增加以后,相机的帧率就下降,而且下降得很厉害,相机的帧率和曝光的关系是怎样,如果想要获得固定的帧率,相机的曝光时间应该怎么设置?因此写下本文,解答了朋友的问题,也使用Sentech相机来做过相关的测试,帧率和曝光时间跟本文中所述一致。详细原理见下文所示:
Exposure and Sensor Readout
相机上的图像采集过程包括两个截然不同的部分。第一部分是曝光。曝光完成后,进行第二部分Readout过程即从传感器的寄存器中读出数据并传送出去(Readout过程)。
关于图像采集过程中,相机操作有两种常见的方法:“non-overlapped”的曝光和“overlapped”的曝光。在非重叠(“non-overlapped”)模式中,每个图像采集的周期中,相机在下一个图像采集开始前,均要完成曝光/读出整个过程。如图1所示。
Fig.1 Non-overlapped Exposure
虽然非重叠(“non-overlapped”)的模式,可适合于许多情况下,但它并不是最有效的方式。为了提高相机的帧率,允许在下一帧图像开始曝光时候,将前一帧获得的图像数据读出并传送出去。相机“重叠”(“overlapped”)曝光的方式见图2所示。
从图2中我们可以看到,相机读出数据和下一帧曝光开始出现重叠的情况,在同一个时刻内,相机执行两个操作,导致在同样的单位时间内,在“overlapped”曝光模式下,可以采集到更多的图片,即相机的帧率更高。
Fig.2 overlapped Exposure
从上边两个图中,我们可以知道在“non-overlapped”的曝光和“overlapped”的曝光模式底下,一帧图像的周期存在着这样的关系: "overlapped”的曝光模式下: FramePeriod ≤Exposure Time + ReadoutTime
“non-overlapped”的曝光模式下:FramePeriod > Exposure Time + Readout Time
以STC-A202A为例:
图1 Spec
从Spec中可知,其Pixel Frequency为:36.8181MHz,所以1Clock的时间为1/36.8181Mhz =27.
项目跑成功了,打开后进入一个activity时出问题,崩溃,报错 可以将问题定位在XML文件出了问题。 出错误的地方大概率是下面几种情况,可以根据错误日志进行排查。
首先重构一下项目,rebuild project,看是否消失。是否使用自定义的view,检查路径是否完整且正确。检查资源文件是否引用错误。检查自定义view的构造函数是否定义了。 View(Context context) //Simple constructor to use when creating a view from code View(Context context, AttributeSet attrs) //Constructor that is called when inflating a view from XML View(Context context, AttributeSet attrs, int defStyle) //Perform inflation from XML and apply a class-specific base style 我的问题出在资源引用错误,第四条,接下来是我按照日志排查的过程。
//只是摘录了 Cause by Caused by: android.view.InflateException: Binary XML file line #0: Binary XML file line #0: Error inflating class <unknown> Caused by: android.
内容来自吴恩达老师视频,网易云课堂有哦 ResNets 非常非常深的神经网络是很难训练的,因为存在梯度消失和梯度爆炸问题。ResNets是由残差块(Residual block)构建的,首先解释一下什么是残差块。
这是一个两层神经网络,在 层进行激活,得到 ,再次进行激活,两层之后得到 。计算过程是从 开始,首先进行线性激活,根据这个公式: ,通过 算出 ,即 乘以权重矩阵,再加上偏差因子。然后通过ReLU非线性激活函数得到 , 计算得出。接着再次进行线性激活,依据等式 ,最后根据这个等式再次进行ReLu非线性激活,即 ,这里的 是指ReLU非线性函数,得到的结果就是 。换句话说,信息流从 到 需要经过以上所有步骤,即这组网络层的主路径。 在残差网络中有一点变化,我们将 直接向后,拷贝到神经网络的深层,在ReLU非线性激活函数前加上 ,这是一条捷径。] 的信息直接到达神经网络的深层,不再沿着主路径传递,这就意味着最后这个等式 )去掉了,取而代之的是另一个ReLU非线性函数,仍然对 进行 函数处理,但这次要加上 ,即: ,也就是加上的这个 产生了一个残差块。 在上面这个图中,我们也可以画一条捷径,直达第二层。实际上这条捷径是在进行ReLU非线性激活函数之前加上的,而这里的每一个节点都执行了线性函数和ReLU激活函数。所以 插入的时机是在线性激活之后,ReLU激活之前。除了捷径,你还会听到另一个术语“跳跃连接”,就是指 跳过一层或者好几层,从而将信息传递到神经网络的更深层。
ResNet的发明者是何恺明(Kaiming He)、张翔宇(Xiangyu Zhang)、任少卿(Shaoqing Ren)和孙剑(Jiangxi Sun),他们发现使用残差块能够训练更深的神经网络。所以构建一个ResNet网络就是通过将很多这样的残差块堆积在一起,形成一个很深神经网络,我们来看看这个网络。
这并不是一个残差网络,而是一个普通网络(Plain network),这个术语来自ResNet论文。
把它变成ResNet的方法是加上所有跳跃连接,每两层增加一个捷径,构成一个残差块。如图所示,5个残差块连接在一起构成一个残差网络。
如果我们使用标准优化算法训练一个普通网络,比如说梯度下降法,或者其它热门的优化算法。如果没有残差,没有这些捷径或者跳跃连接,凭经验你会发现随着网络深度的加深,训练错误会先减少,然后增多。而理论上,随着网络深度的加深,应该训练得越来越好才对。也就是说,理论上网络深度越深越好。但实际上,如果没有残差网络,对于一个普通网络来说,深度越深意味着用优化算法越难训练。实际上,随着网络深度的加深,训练错误会越来越多。
但有了ResNets就不一样了,即使网络再深,训练的表现却不错,比如说训练误差减少,就算是训练深达100层的网络也不例外。有人甚至在1000多层的神经网络中做过实验,尽管目前我还没有看到太多实际应用。但是对 的激活,或者这些中间的激活能够到达网络的更深层。这种方式确实有助于解决梯度消失和梯度爆炸问题,让我们在训练更深网络的同时,又能保证良好的性能。也许从另外一个角度来看,随着网络越来深,网络连接会变得臃肿,但是ResNet确实在训练深度网络方面非常有效。 微信公众号:任冬学编程
tx工作的后端开发仔,分享后端技术、机器学习、数据结构与算法、计算机基础、程序员面试等话题。欢迎关注。
作者:知乎用户
链接:https://www.zhihu.com/question/37290469
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
友情提醒:比特币采用区块链技术,但是区块链并不等同于比特币;全篇基于比特币底层区块链技术讲述,所以,部分模型可能不适用于以太坊等。另外,由于文章采用了一定的抽象、类举的叙事方式,中间或多或少有些地方会跟区块链底层严谨的技术实现有出入,如果让你觉得困惑,可以在评论下方留言或者私信我一起探讨。最后,也是受限于自己知识结构的不完整,这篇文章会随着我对区块链更深入认识后,随时进行修订,最后更新时间可参考该回答下方的时间戳。
另外,作为一篇科普性文章,大家可以随意转载,注明这篇文章的出处和作者即可,无需再单独私信询问。
---
首先不要把区块链想的过于高深,他是一个分布在全球各地、能够协同运转的数据库存储系统,区别于传统数据库运作——读写权限掌握在一个公司或者一个集权手上(中心化的特征),区块链认为,任何有能力架设服务器节点的人都可以参与其中。来自全球各地的掘金者在当地部署了自己的节点,并连接到区块链网络中,成为这个分布式数据库存储系统中的一个节点;一旦加入,该节点享有同其他所有节点完全一样的权利与义务(去中心化、分布式的特征)。与此同时,对于在区块链上开展服务的人,可以往这个系统中的任意的节点进行读写操作,最后全世界所有节点会根据某种机制的完成一次又依次的同步,从而实现在区块链网络中所有节点的数据完全一致。
上图中,高亮的点就是区块链系统中分布在全球各地的一个个节点;而这些节点可以简单理解为一台服务器服务器集群
为了更简单的阐述那篇文章所构建的世界观,文中所讨论的节点全部粗暴的理解为官方参考实现节点,即最标准的一种节点类型,这些节点不仅可以参与挖矿共识、还可以数据存储和数据点对点传递;不涉及其他复杂的节点类型。关于节点的分类,可以阅读我的专栏文章《区块链节点与钱包的分类、边际和使命,看这篇文章就足够了》
# 问题的由来 我们反复提到区块链是一个去中心化的系统,确实,「去中心化」在区块链世界里面是一个很重要的概念,很多模型(比如账本的维护、货币的发行、时间戳的设计、网络的维护、节点间的竞争等等等等)的设计都依赖于这个中心思想,那到底什么是去中心化呢?在解释真正去中心化之前,我们还是先简单了解下什么是中心化吧。
中心化?
回忆一下你在网上购买一本书的流程:
第一步,你下单并把钱打给支付宝;第二步,支付宝收款后通知卖家可以发货了;第三步,卖家收到支付宝通知之后给你发货;第四步,你收到书之后,觉得满意,在支付宝上选择确认收货;第五步,支付宝收到通知,把款项打给卖家。流程结束。 你会发现,虽然你是在跟卖家做交易,但是,所有的关键流程都是在跟支付宝打交道。这样的好处在于:万一哪个环节出问题,卖家和买家都可以通过支付宝寻求帮助,让支付宝做出仲裁。这就是一个最简单的基于中心化思维构建的交易模型,它的价值显著,就是建立权威,通过权威背书来获得多方的信任,同时依赖权威方背后的资本和技术实力确保数据的可靠安全。
你一定会摆出一个巨大的问号脸 ⊙.⊙?——“通过权威背书来获得多方的信任,同时依赖权威方背后的资本和技术实力确保数据的可靠安全”,真的可以嘛?!
假如说,支付宝程序发生重大BUG,导致一段时间内的转账记录全部丢失,或者更彻底一点,支付宝的服务器被ISIS恐怖组织的一个导弹全部炸毁了。而我刚刚转出去的100元找谁说理去,这个时候,你就成了刀殂上的鱼肉;支付宝有良心,会勉为其难承认你刚刚转账的事实,但他不承认你也没辙,因为确实连他自己也不知道这笔转账是否真实存在。
上述就是中心化最大的弊端——过分依赖中心和权威,也就意味着逐渐丧失自己的话语权。
去中心化?
那么去中心化的形态是什么样子呢?还是拿刚才那个例子继续,我们构建一个极简的去中心化的交易系统,看看我们是如何在网络上从不认识的卖家手里买到一本书的。
第一步,你下单并把钱打给卖家;第二步,你将这条转账信息记录在自己账本上;第三步,你将这条转账信息广播出去;第四步,卖家和支付宝在收到你的转账信息之后,在他们自己的账本上分别记录;第五步,卖家发货,同时将发货的事实记录在自己的账本上;第六步,卖家把这条事实记录广播出去;第七步,你和支付宝收到这条事实记录,在自己的账本上分别记录;第八步,你收到书籍。至此,交易流程走完。 刚才“人为刀俎我为鱼肉”的情况在这个体系下就比较难发生,因为所有人的账本上都有着完全一样的交易记录,支付宝的账本服务器坏了,对不起卖家的账本还存在,我的账本还存在;这些都是这笔交易真实发生的铁证。
当然,在这套极简的交易系统中,你已经发现了诸多漏洞和不理解,比如说三方当中有一个是坏人,他故意记录了对他更有利的转账信息怎么办;又比如说消息在传递过程中被黑客篡改了怎么办等等等等。这在以往的计算机概论或者计算机网络书本上中可能都有提及到——“类两军”和“拜占庭将军”问题。这里就不打算赘述,因为暂时跟主线不相关,感兴趣的同学可以去Google或者百度一下,你只需要知道,在我们下面即将展开讲到的区块链系统中,通过巧妙的设计,足以解决上述存在的BUG。
既然话已说到这份上,相信了解一点技术、特别是有运维背景的同学大概能够从极简交易系统中窥视到了更多区块链的一些影子——
分布式存储,通过多地备份,制造数据冗余让所有人都有能力都去维护共同一份数据库让所有人都有能力彼此监督维护数据库的行为 在我看来,你猜测的基本上没错。其实这些就是区块链技术最核心的东西,外人看起来高大上、深不可测,但探究其根本发现就是这么简单和淳朴。当然,这里面肯定会有很多很多很多细枝末节的技术需要重构。
如果你差不多认同上面的观点,那我们应该基本上可以达成共识,分布式部署肯定是构建去中心化网络理所当然的解决方向——通过P2P协议将全世界所有节点计算机彼此相互连接,形成一张密密麻麻的网络;以巧妙的机制,通过节点之间的交易数据同步来保证全球计算机节点的数据共享和一致。
哈哈,说的轻巧,“交易数据这么重要的东西,在一个完全不信任的P2P网络节点中以一种错综复杂的方式传递,数据的一致性和安全性谁来保证,如果说互相监督,他们到底怎么做到?”
好了,不卖关子了,下面让我们围绕这个最最最最直接的问题开始进入到真正区块链的世界,抽丝剥茧看看它到底是如何一步一步形成的,又是如何一步一步稳定运转。
# 从全球节点到交易数据 这张图的制作的意义为的是帮助你在宏观上先快速理解区块链中所涉及到的相关名词以及他们的层级关系。同时,文章的知识结构和设计思路也大抵上也会按照:
首先,将区块作为最小单位体,讲述极简区块链系统是如何运转的;接着,进入到比区块更小单位体——交易记录,理解区块链是如何处理数据的;最后,将所有知识点柔和在一起,重回到区块和区块链,完整讲述整个工作流程。 希望你在这个引导和结构下有一个比较好的阅读体验。Let's go~
# 区块,混沌世界的起源 既然已经达成共识,所以,我们事先构建好了一个去中心化的P2P网络;同时,为了让读者朋友们听起来更轻松,我先粗暴的规定在这个极简的区块链系统里,每十分钟有且仅产生一笔交易。
故事继续,在节点的视野里,大概每十分钟会凭空产生一个建立在自己平行宇宙世界的神奇区块(你可以将区块想象为一个盒子),这个区块里放着一些数字货币以及一张小纸条,小纸条上记录了这十分钟内产生的那唯一一笔交易信息,比如说——“小A转账给了小B100元”;当然,这段信息肯定是被加密处理过的,为的就是保证只有小A和小B(通过他们手上的钥匙)才有能力解读里面真正的内容。
这个神奇的区块被创造出来之后,很快被埋在了地底下,至于埋在哪里?没有一个人知道,所以需要所有计算机节点一起参与进来掘地三尺后才有可能找到(找到一个有效的工作量证明)。显然,这是一件工作量巨大、成果随机的事件。但是呢,对于计算机节点来说,一旦从地底下挖出这个区块,他将获得区块内价值不菲的数字货币,以及“小A转账给了小B100元”过程中小A所支付的小费。同时,对于这个节点来说,也只有他才有权利真正记录小纸条里的内容,这是一份荣耀,而其他节点相当于只能使用它的复制品,一个已经没有数字货币加持的副本。当然这个神奇的区块还有一些其他很特别的地方,后面我们会再细细聊。
为了更好的描述,我们将计算机节点从地底下挖出区块的过程叫做「挖矿」,刚才说了,这是一件工作量巨大、运气成分较多、但收益丰厚的事儿。
过了一会儿,来自中国上海浦东新区张衡路上的一个节点突然跳出来很兴奋的说:“ 我挖到区块了!里面的小纸条都是有效的!奖励归我!” 。虽然此刻张衡路节点已经拿到了数字货币,但对于其他计算机节点来说,因为这里面还涉及到其他一些利益瓜葛,他们不会选择默认相信张衡路节点所说的话;基于陌生节点彼此不信任的原则,他们拿过张衡路节点所谓挖到的区块(副本),开始校验区块内的小纸条信息是否真实有效等等。在区块链世界里,节点们正是通过校验小纸条信息的准确性,或间接或直接判断成功挖出区块的节点是否撒谎。(如何定义小纸条信息真实有效,后面会讲解,这里暂不做赘述)。
在校验过程中,各个节点们会直接通过下面两个行为表达自己对张衡路节点的认同(准确无误)和态度:
停止已经进行了一半甚至99.99%的挖矿进程;将张衡路节点成功挖出的区块(副本)追加到自己区块链的末尾。 你可以稍微有点困惑:停止可能已经执行了99.99%的挖矿行为,那之前99.99%的工作不是就白做了嘛?!然后,区块链的末尾又是个什么鬼东西?
对于第一个困惑。我想说,你说的一点没错,但是没办法,现实就是这么残酷,即便工作做了99.99%,那也得放弃,这99.99%的工作劳苦几乎可以视为无用功,绝对的伤财劳众。第二个困惑,区块链和区块链的末尾是什么鬼?这里因为事先并没有讲清楚,但是你可以简单想象一下:区块是周期性不断的产生和不断的被挖出来,一个计算机节点可能事先已经执行了N次“从别人手上拿过区块 -> 校验小纸条有效性”的流程,肯定在自己的节点上早已经存放了N个区块,这些区块会按照时间顺序整齐的一字排列成为一个链状。没错,这个链条,就是你一直以来认为的那个区块链。如果你还是不能够理解,没关系,文章后面还会有很多次机会深入研究。
# 走进区块内,探索消息的本质 上面我们构建了一个最简单的区块链世界的模型,相信大多数同学都已经轻松掌握了。但是别骄傲也别着急,这还只是一些皮毛中的皮毛,坐好,下面我们准备开车了。
前面我们说到“大概每十分钟会凭空产生一个神奇的区块,这个区块里放了一张小纸条,上面记录了这十分钟内产生的这唯一一笔交易信息”。显然,十分钟内产生的交易肯定远不止一条,可能是上万条,这上万条数据在区块链世界是如何组织和处理的呢?另外,为什么在纸条上记录的只是某一次的交易信息,而不是某一个人的余额?余额好像更符合我们现实世界的理解才对。
既然存在这样那样的疑问。现在我们就把视线暂时从“区块”、“区块链”这些看起来似乎较大实体的物质中移开,进入到区块内更微观的世界里一探究竟,看看小纸条到底是怎么一回事,它的产生以及它终其一生的使命:
发起交易的时候,发起人会收到一张小纸条,他需要将交易记录比如说“盗盗转账给张三40元”写在纸上。说来也神奇,当写完的那一刹那,在小纸条的背面会自动将这段交易记录格式化成至少包含了“输入值”和“输出值”这两个重要字段;“输入值”用于记录数字货币的有效来源,“输出值”记录着数字货币发往的对象。刚刚创建的小纸条立马被标记成为“未确认”的小纸条。从地下成功挖出区块并最终连接到区块链里的小纸条一开始会被标记为“有效”。若这条有效的小纸条作为其他交易的输入值被使用,那么,这个有效的小纸条很快会被标记为“无效”。因为各种原因,区块从链上断开、丢弃,曾经这个区块内被标记为“有效”的小纸条会被重新标记为“未确认”。区块链里面没有账户余额的概念,你真正拥有的数字资产实际上是一段交易信息;通过简单的加减法运算获知你数字钱包里的余额。 上面的1、2、3仅仅作为结论一开始强行灌输给你的知识点,其中有几个描述可能会有点绕,让你觉得云里雾里,没有关系,因为我们立刻、马上就开始会细说里面的细枝末节。
上图,是区块内,盗盗在一张小纸条上记录下的交易信息,后被格式化的呈现
上图就是从无数打包进区块内的小纸条中,抽取出来的一张,以及它最终被格式化后的缩影。单看右侧的图可能很容易产生误会,虽然看起来有多行,但实际上就是“盗盗转账给张三40个比特币”这一条交易数据另外的一种呈现形态。因为区块链世界里面这么规定,每一条交易记录,必须有能力追溯到交易发起者 发起这笔交易、其中所涉及金额的上一笔全部交易信息;即这笔钱从何而来的问题。这其实很容易理解,在去中心化的网络中,通过建立交易链、和通过交易链上的可溯源性间接保证数据安全和有效。
我们继续看,在区块链世界里,我们是如何仅通过“盗盗转账给张三40个比特币” 这条交易信息完成转账流程的。其实跟现实中你在路边买一个包子的流程大抵上相同。
第一步:判断是否有足够的余额完成交易
这里我们再一次重申,在比特币的区块链世界里是没有余额的概念(以太坊的底层区块链有余额概念),余额是通过简单数字的加减最终获得,你拥有所谓的数字货币实际上是因为你拥一条交易记录,即 “盗盗转账给张三40个比特币”!这里,我们还是拿这条记录说事:
追溯“输出值”是“盗盗”相关的全部有效交易记录作为,对有效交易中的数字进行简单求和,判断是否大于等于40,如果确实大于等于,则将这些有效的交易记录合并形成一条新的交易记录(如下图)。如果小于40,其实可以不需要再继续往下探讨。
DDR3 地址线
DDR3为减少地址线,把地址线分为行地址线和列地址线,在硬件上是同一组地址线; 地址线和列地址线是分时复用的,即地址要分两次送出,先送出行地址,再送出列地址。
一般来说列地址线是10位,及A0...A9;行地址线数量根据内存大小,BANK数目,数据线位宽等决定(感觉也应该是行地址决定其他) ; BANK
bank是存储库的意思,也就是说,一块内存内部划分出了多个存储库,访问的时候指定存储库编号,就可以访问指定的存储库,内存中划分了多少个bank,要看地址线中有几位BA地址,如果有两位,说明有4个bank,如果有3位,说明有8个bank DDR3 容量计算
下面这张图是芯片k4t1g164qf资料中截取的;以1Gb容量的DDR2颗粒为例(其他的类似);假设数据线位宽为16位,则看64Mb x 16这一列: bank地址线位宽为3,及bank数目为 2^3=8;
行地址线位宽位13,及A0…A12;
列地址线位宽为10,及A0…A9;
有 2^3 * 2^13 * 2^10 = 2^26 =2^6Mb = 64Mb
再加上数据线,则容量为 64Mb x 16 = 128M Byte = =1G bit
对于4Gb的16bit DDR3,
bank address有三个bit,所以单个16bit DDR3内部有8个bank. 表示行的有A0~A14,共15个bit,说明一个bank中有2^15个行。 表示列的有A0~A9,共10个bit,说明一个bank中有2^10个行。 来看看单块16bit DDR3容量: 2^3*2^15*2^10=2^28=256M 我们的内存是512M,到这儿怎么变成256M了?被骗了? 呵呵,当然没有。 忘了我们前面一直提到的16bit。 16bit是2个byte对吧。 访问一个地址,内存认为是访问16bit的数据,也就是两个字节的数据。 256M个地址,也就是对应512M的数据了。
再来看看两个16bit是如何组成一个32bit的。 有一个概念一定要清楚,这儿所说的两个16bit组成一个32bit,指的是数据,与地址没有关系。 我开始这一块没搞清楚,一直认为是两个16bit的地址组成了一个32bit的地址。然后高位地址,地位地址,七七八八。。。 之后没一点头绪。 将16bit/32bit指的是数据宽度之后,就非常明了了。 每一块16bit DDR3中有8个bank,2^15个row,2^10个column。也就是有256M个地址。 看前面的连线可知,两块16bit DDR3的BA0~BA2和D0~D14其实是并行连接到CPU。 也就是说,CPU其实认为只有一块内存,访问的时候按照BA0~BA2和D0~D14给出地址。 两块16bit DDR3都收到了该地址。 它们是怎么响应的呢? 两块内存都是16bit,它们收到地址之后,给出的反应是要么将给定地址上2个字节送到数据线上,要么是将数据线上的两个字节写入到指定的地址。 再看数据线的连接,第一片的D0~D15连接到了CPU的D0~D15,第二片的D0~D15连接到了CPU的D16~D31。 CPU认为自己访问的是一块32bit的内存,所以CPU每给出一个地址,将访问4个字节的数据,读取/写入。 这4字节数据对应到CPU的D0~D31,又分别被连接到两片内存的D0~D15,这样一个32bit就被拆成了两个16bit.
由操作系统定义,并由操作系统所操控的一个特殊的数据结构实例叫做进程。它连接了用户代码,拥有代码运行所需的独立内存空间,在调度器的调度下使用分配给它的处理器时间片来运行。
进程及其私有内存空间 进程类似于UCOSIII中的任务,它也是用户应用程序可执行代码在系统中的一个运行过程。系统中用来表示进程身份和存在的也是控制块,只不过叫做进程控制块。进程与UCOSIII任务之间最重要的一个区别就是:进程具有自己的内存空间,进程的程序代码就运行在这个归自己所有的内存空间之中。当然,进程的控制块记录了进程的这个私有内存空间。
在UCOSIII中提到,一个任务的组成部分:任务堆栈、任务控制块、任务代码。同样Linux的进程除了这三块,同时还需要进程自己的内存空间。一个典型的进程控制块如下图所示:
进程控制块的成员mm就是指向进程内存控制块的指针,而这个进程内存控制块则关联了进程的虚拟空间结构和表示了它所占用的物理内存空间结构。
所谓进程私有内存空间,就是系统使程序运行为程序分配的程序空间。保证程序具有私有空间的基础就是虚拟内存技术。系统把程序运行所需的物理内存页框地址映射成虚拟地址,并以页表的形式提供给了程序,从而使得程序只能通过页表运行于自己的物理空间而不会干扰到系统中的其它进程。
结合前面关于Linux内存管理的讲解可知,Linux进程控制块中的mm成员如下图所示:
文章参考:【Linux】Linux虚拟内存空间描述。
另外,由于Linux进程分为用户空间和内核空间两个部分,它有时运行于用户空间,有时运行于内核空间,因此为了保护各自的现场数据,一个进程还需要两个堆栈:用户堆栈和系统堆栈,如下图所示:
最后区分一下进程和线程:在多任务系统中具有私有内存空间的正在运行的程序叫做进程,而没有私有内存空间的叫做线程。如此说来,其实UCOSIII的任务是划分到线程的。
Linux进程的状态 在Linux系统中,一个进程被创建之后,在系统中可以有下面5种状态。进程的当前状态记录在进程控制块的state成员中。
就绪状态和运行状态 就绪状态的状态标志state的值为TASK_RUNNING。此时,程序已被挂入运行队列,处于准备运行状态。一旦获得处理器使用权,即可进入运行状态。
当进程获得处理器而运行时 ,state的值仍然为TASK_RUNNING,并不发生改变;但Linux会把一个专门用来指向当前运行任务的指针current指向它,以表示它是一个正在运行的进程。
可中断等待状态 状态标志state的值为TASK_INTERRUPTIBL。此时,由于进程未获得它所申请的资源而处在等待状态。一旦资源有效或者有唤醒信号,进程会立即结束等待而进入就绪状态。
不可中断等待状态 状态标志state的值为TASK_UNINTERRUPTIBL。此时,进程也处于等待资源状态。一旦资源有效,进程会立即进入就绪状态。这个等待状态与可中断等待状态的区别在于:处于TASK_UNINTERRUPTIBL状态的进程不能被信号量或者中断所唤醒,只有当它申请的资源有效时才能被唤醒。
这个状态被应用在内核中某些场景中,比如当进程需要对磁盘进行读写,而此刻正在DMA中进行着数据到内存的拷贝,如果这时进程休眠被打断(比如强制退出信号)那么很可能会出现问题,所以这时进程就会处于不可被打断的状态下。
停止状态 状态标志state的值为TASK_STOPPED。当进程收到一个SIGSTOP信号后,就由运行状态进入停止状态,当受到一个SIGCONT信号时,又会恢复运行状态。这种状态主要用于程序的调试,又被叫做“暂停状态”、“挂起状态”。
中止状态 状态标志state的值为TASK_DEAD。进程因某种原因而中止运行,进程占有的所有资源将被回收,除了task_struct结构(以及少数资源)以外,并且系统对它不再予以理睬,所以这种状态也叫做“僵死状态”,进程成为僵尸进程。
在进程的整个生命周期中,它可在5种状态之间转换。Linux进程5种状态之间的转换关系如下图所示:
文章参考:Linux进程状态解析 之 R、S、D、T、Z、X (主要有三个状态)。
Linux有两类进程:一类是普通用户进程,它既可以在用户空间运行,又可通过系统调用进入内核空间,并在内核空间运行;另一类叫做内核进程,这种进程只能在内核空间运行。
Linux的进程控制块 在Linux中,线程、进程使用的是相同的核心数据结构。可以说,在Linux2.4的内核里只有进程,其中包含轻量进程(线程)。一个进程在核心中使用一个task_struct结构来表示,包含了大量描述该进程的信息。
Linux系统中作为进程控制块(PCB)的数据结构叫做task_struct。这个进程控制块要负责记录和跟踪进程在系统中的全部信息。
尽管task_struct数据结构庞大而复杂,但其成员可按功能分成一些组成部分。task_struct的数据结构应包含如下信息:
进程的当前状态;调度信息:调度器需要知道这些信息,以便判断系统中进程的迫切度;进程标识:系统中每个进程都有一个进程标识pid;进程的通信信息:Linux支持经典的Unix IPC机制,如信号、管道和信号灯;支持系统V中的IPC机制,包括共享内存、信号灯和消息队列;进程与其他进程之间关系的信息:Linux中所有进程都是相互关联的。除了根进程外,所有进程都有一个父进程,也可能有子进程或者兄弟进程,所以每个进程的task_struct结构中要包含有指向其父进程、兄弟进程或子进程的指针;使用文件的信息:进程可自由地打开或关闭文件。进程的task_struct结构中包含一个指向文件系统及它所打开文件的指针;虚拟内存与物理内存关系的信息:所有进程都有自己的内存空间;计时器:核心需要记录进程的创建时间及其在其生命周期中消耗的CPU的时间;处理器中与进程有关的信息。 Linux可以管理512个进程,每个进程的进程控制块指针都存放在一个数组中。为了使系统可快速访问正在运行的进程,Linux系统把当前运行进程的指针存放在指针变量current中。
进程控制块task_struct的部分定义如下:
struct task_struct { volatile long state; /* 进程的状态 */ unsigned long flags; /* 与管理有关的状态信息 */ int prio, static_prio, normal_prio; //优先级,静态优先级 struct list_head tasks; //进程链表 struct list_head ptrace_children; struct list_head ptrace_list; struct mm_struct *mm, *active_mm; //指向进程存储空间的指针 pid_t pid; //进程的pid pid_t tgid; struct task_struct *real_parent; /* 真父进程指针 */ struct task_struct *parent; /* 父进程指针 */ struct list_head children; /* 子进程链表 */ struct list_head sibling; /* 兄弟进程链表 */ struct task_struct *group_leader; /* threadgroup leader */ struct timespec start_time; /* monotonic time */ struct timespec real_start_time; /* boot based time */ struct thread_struct thread; unsigned long rt_priority; //实时优先级 struct fs_struct *fs; //进程所在文件目录 struct files_struct *files; //进程打开文件信息 struct dentry *proc_dentry; //proc文件的dentry struct backing_dev_info *backing_dev_info; struct signal_struct *signal; //信号 struct sighand_struct *sighand; .
1 登陆某个节点
SSH compute-3-1
2 环境变量设置
export CUDA_VISIBLE_DEVICES=0
1.in查询相当于多个or条件的叠加,例如:
select * from user where user_id in (1,2,3);
等效于
select * from user where user_id = 1 or user_id = 2 or user_id = 3;
not in与in相反,如下
select * from user where user_id not in (1,2,3);
等效于
select * from user where user_id != 1 and user_id != 2 and user_id != 3;
1.find_in_set基本语法
FIND_IN_SET(str,strlist)
str 要查询的字符串,strlist 字段名 参数以”,”分隔 如 (1,2,6,8)
如果str不在strlist 或strlist 为空字符串,则返回值为 0 。如任意一个参数为NULL,则返回值为 NULL。这个函数在第一个参数包含一个逗号(‘,’)时将无法正常运行。
+----+---------+-----------+-------------+
Go语言的字符串类型string在本质上就与其他语言的字符串类型不同:
Java的String、C++的std::string以及Python3的str类型都只是定宽字符序列
Go语言的字符串是一个用UTF-8编码的变宽字符序列,它的每一个字符都用一个或多个字节表示
即:一个Go语言字符串是一个任意字节的常量序列。
Golang的双引号和反引号都可用于表示一个常量字符串,不同在于:
双引号用来创建可解析的字符串字面量(支持转义,但不能用来引用多行)
反引号用来创建原生的字符串字面量,这些字符串可能由多行组成(不支持任何转义序列),原生的字符串字面量多用于书写多行消息、HTML以及正则表达式
而单引号则用于表示Golang的一个特殊类型:rune,类似其他语言的byte但又不完全一样,是指:码点字面量(Unicode code point),不做任何转义的原始内容。
There are two forms: raw string literals and interpreted string literals.
Raw string literals are character sequences between back quotes, as in foo .Interpreted string literals are character sequences between double quotes, as in “bar”. A rune literal represents a rune constant, an integer value identifying a Unicode code point. A rune literal is expressed as one or more characters enclosed in single quotes, as in ‘x’ or ‘\n’.
由于笔记本是几年前买的了,当时是4000+,现在用起来感到卡顿,启动、运行速度特别慢,就决定换个固态硬盘,加个内存条,再给笔记本续命几年。
先说一下加固态硬盘SSD的好处:
1.启动快 2.读取延迟小 3.写入速度快 4.无噪音 5.重量轻 6.不会发生机械故障 7.抗震动
缺点是:1.比同容量的机械硬盘贵很多 2.使用寿命比机械硬盘短,但是够用了
加装内存条的好处:
加一条内存,可以提高系统运行的效率,可以提高内存的存储量,可以使多个程序可以同时和CPU进行数据交换。内存越大你能同时运行的软件越多。平常在使用电脑过程中,如果使用软件卡顿,这是由内存不足造成的。
我现在还是学生党一枚,专业是软件工程,还在入门阶段,还没接触项目啊什么的,感觉对笔记本性能要求并不高,用笔记本就是写些c/c++/java代码,偶尔打打游戏(要求不高的话打打dnf,cf还行,想玩吃鸡就不是加个固态和内存条的事了,可能要换个笔记本)。我的笔记本是4G内存+500G机械硬盘,改装目标是8G内存+120固态+500G机械。因为我的笔记本带了光驱,大致过程就是把原来的机械硬盘位置上装上固态硬盘,再在光驱的位置上装上机械硬盘,最后再插上内存条废话不多说,说一下准备工作:
准备材料:固态硬盘(120G),内存条(4G),光驱支架(12.77mm×9.5mm)(用来在光驱的位置上装机械硬盘),螺丝刀(一般买固态硬盘都会送),U盘(用来重装系统,拆机之前就做好系统)
1.固态硬盘的选择
1>先确定笔记本可不可以加固态。近几年买的笔记本一般都是可以的,可以在BIOS的SATA Mode中,查看是否支持AHCI;也可以在购买时问一下商家,或者可以选择直接拆机看一下。主流SATA3,向前兼容是SATA2。
2>容量大小选择,一般都是120G(128G),250G(256G),500G,1T的。我买的是120G的,因为偶尔打打游戏感觉有点小,但是经济限制啊,如果只是学习娱乐的话120完全足够了,只是用来装系统和软件,电影,音乐放在机械硬盘就好了。
3>选品牌。这个也很重要,可以多了解一些。我选的是闪迪,用了一星期,感觉还不错。
2.内存条的选择
1>先确定笔记本可不可以加内存条。一般情况下笔记本电脑都可以增加内存,可以通过CPU-Z这个软件查看,可以看到有几个插槽。如果有一个插槽显示是空,就说明还可以加。也可以直接拆机查看。如果只插了一个内存条的话插槽2会显示灰色。 win10系统可以通过任务管理器查看。任务栏右键---任务管理器---性能---内存
查看电脑最大支持多少G内存的方法:
运行---cmd---输入“wmic memphysical get maxcapacity”---Enter
得到一串以千字节KB为单位的数字,除以1024再除以1024得到的就是以GB为单位的电脑最大支持内存。
2>确定内存条型号。
频率:通过任务管理器查看。任务栏右键---任务管理器---性能---内存
虽然越高频率,性能越好(当然价格也越高),但两个不同频率的内存条同时使用,会使高频率的那个调频至低频率。
电压:通过CPU-Z这个软件查看SPD
或者也可以根据 此电脑右键---属性---系统查看
1.5V是DDR3,表示第三代标准版。1.35V是DDR3L,表示第三代低电压版。
注:一般2015年后的电脑才有DDR4接口。
3>内存条的容量和品牌。我用的是金士顿4G,还不错,提升很多。
3.开始装机(准备工作做好了,装机很简单的)
1>切断电源,去掉笔记本电池,在笔记本底部确认硬盘和内存条位置。其实很容易看出来,如果不确定也可以区网上搜一下和你笔记本型号匹配的拆机图。
2>接下来就开拆了。用螺丝刀把螺丝取出,不是所有螺丝都要取,很容易看出来取哪些。记得保存好螺丝,而且要记清楚,拆下来后有好几种不同的螺丝。取下螺丝后,揭下挡板。电脑型号不同,他的挡板设计不同,有的采用的卡槽的设计,可能会不太好取,小心一些就可以了。
3>最重要的一步:把机械硬盘换成固态硬盘。
把1号位上的机械硬盘取下来。先取下来固定在电脑4个角的螺丝,拿出来带着固定支架的机械硬盘,把侧面的螺丝取出来,就可以把机械硬盘取下来了,保存好螺丝,不需要取那张塑料纸。
把螺丝拧回去,固定好,固态硬盘安装完成。
接下来就是安装内存条,这个很简单,不需要拆卸,直接按就好了。忘了拍照片,就简单说一下,或者可以看买内存条送的说明书,里面也有详细的安装教程。把准备好的内存条对准插槽,倾斜45°插入,此时的内存条是翘起来的,然后将内存条下压,直到下面两边的固定开关自然扣紧。如果想取下内存条,将固定开关向外拨开即可取下内存条。
安装好底部挡板,拧上螺丝。开始拆光驱,换机械。
拆下螺丝,保存好,就可以把光驱抽出来了。如果卡的比较紧,可以在电池那里看到一个银白色的物品,那个就是光驱,可用螺丝刀把它向外推。
接下里就是
1.把机械硬盘装进准备好的光驱支架里,对好插入,然后固定,光驱支架里有固定用的软塞。
2.换上光驱的面板(光驱旁边的那个黑色挡板),也可以用光驱支架自带的。
3.把光驱上的固定装置装到购买的光驱支架上,拆下来装到光驱支架对应的位置。
4.把完成的光驱支架装回笔记本,并拧上螺丝,大功告成!
注:如果还需要用到光驱,可以再购买一条SATA转USB数据线,连接上电脑就可以继续用。
4.最后一步:装系统
这里就用到准备的好的U盘了,装系统这个我不擅长,还是在同学的帮助下完成的,换上固态,大家还需要了解一下4K对齐,没有4K对齐最显著的影响是硬盘的读写速度极度低下,而且固态硬盘的使用寿命会减短,大家可以通过下面文章了解一下。
1.4K对齐 2.u盘装系统 3.u盘装系统
5.大功告成!!!成功续命!!!
以前用的是win7系统,现在换成了win10。开机速度从1分钟提高到了20秒,刺激! 来几张对比图。这是启动同样的程序,内存使用率对比。
最终下来笔记本性能各方面提升好多,无论是学习还是娱乐都可以轻松带动!
windows--preference--
把这两个东西取消勾选 然后应用 即可取消报错
引言 本文是我学习清华大学出版社《数据结构》课程有关单链表知识点的感悟,主要在于课本上算法伪代码的实现,仅作学习交流之用
正文
线性表的顺序存储结构要求在逻辑顺序上相邻的两个结构物理位置也必须相邻,这就造成了一个后果,即插入删除一个元素时可能会移动大量的指针,进而导致程序的时间复杂度迅速上升。因此为了尽可能减少程序存储的时间复杂度,就有了链式存储结构这有一种存储的方式,这种结构的特点是逻辑上相邻的元素(有一定先后顺序的元素)在物理位置上不一定相邻。由于链式存储的缘故,使得元素在物理位置上有一种跳跃的特性。换言之,两个有先后顺序的元素不一定处在相邻的内存区域。
与顺序表相比不同的是,由于其物理位置不一定相邻的特性,每个数据元素与之间的关系不能仅仅通过改变其位序来进行访问,相对地,每个数据元素除了存储本身的信息之外,还需要一个信息块用于存储其后继元素的信息。这就是平时所说的数据域同指针域。数据域用于存储数据元素本身的信息,元素的类型可以有int 型,char型,float型等,指针域则用于存储一个指针(链),同时具有数据域与指针域的数据元素称作一个结点,n个结点通过指针相互连接称作一个链表,也可以理解为顺序表的链式存储结构。链式存储结构示意图如下:
由存储结构的示意图,可以将单链表的某个节点用“结构指针”的方法来表示,每个结点定义为Node类型的结构体,一个结构体内包括int型的变量data作为数据域,Node类型的指针变量记作next,同时,定义Node型的对象记作Lnode,返回类型为Node型(指向结点)的指针变量记作linklist。
#include <iostream> #include <stdlib.h> #include <stdio.h> using namespace std; typedef struct Node { int data; struct Node *next; }Lnode,*linklist; 有时候需要在单链表中确定一个指向第一个结点的指针,记作单链表的头指针,同时也可以在第一个结点之前附加设置一个结点,称作单链表的头结点,头结点的数据域可以存储其他的信息,例如可以存储一个布尔型变量用于表示该条链表是否被访问过,示意图如下,H表示头指针,h1表示头结点,不存储任何数据,而a3表示尾结点(其指针域为空):
单链表的结点结构完成后,需要了解单链表的基本操作函数,与顺序表类似,基本操作函数主要为三个:1.单链表元素的插入 2.单链表元素的删除 3.单链表元素的查找。
1.单链表元素的插入,主要思路如下:1)定义函数的自变量为头指针head,int型变量 i、e,i 用于指示插入结点的位置(操作函数要求在第i个结点之前插入),e代表插入元素的数值。2)定义指针变量p ,用linklist p语句进行表示,先将p初始化为头指针head,再将p指针指向其后继结点,循环直到第i -1个结点为止。3)新建结点s(由s指针指向的结点),为结点s分配内存,将s结点插入作为p结点的后继结点。示意图以及代码如下:
linklist Insert(linklist head,int i,int e) { linklist p,s; p=head; for(int j=0;j<=i-1;++j) { p=p->next; //从第0个结点位置开始向后寻找至第i-1个位置 } s=(Lnode*)malloc(sizeof(Lnode)); s->data=e; s->next=p->next; p->next=s; return head; }//在结点的第i个位置前插入元素 2.单链表元素的删除 基本思路如下:1)定义链表的头指针head,定义int型变量i用于控制位序(定位删除结点的位置),e用于返回被删除元素的值 2)定义指针p,q,用linklist p,q语句表示,p指针初始化为head(指向头结点),将p指针不停往头结点后继推移,直至指向位序为i-1的结点为止 3)用指针q指向p指针所指结点的后继结点,再将p指针的后继结点指为q指针所指结点的后继结点,用e返回q指针所指结点的数据域值,释放q指针所指结点的空间,这样就在不删除其余元素的情形下删除了位序为i的元素。示意图以及代码如下:
linklist Delete(linklist head,int i,int e) { linklist p,q; p=head; for(int j=0;j<i-1;j++) { p=p->next; } q=p->next; e=q->data; cout<<e<<endl; p->next=q->next; free(q); } 3.
var arr = [7,2,0,-3,5]; 1 1.apply()应用某一对象的一个方法,用另一个对象替换当前对象 var max = Math.max.apply(null,arr); console.log(max) 1 2 由于max()里面参数不能为数组,所以借助apply(funtion,args)方法调用Math.max(),function为要调用的方法,args是数组对象,当function为null时,默认为上文,即相当于apply(Math.max,arr) 2.call()调用一个对象的一个方法,以另一个对象替换当前对象 var max1 = Math.max.call(null,7,2,0,-3,5) console.log(max1) 1 2 call()与apply()类似,区别是传入参数的方式不同,apply()参数是一个对象和一个数组类型的对象,call()参数是一个对象和参数列表 3.sort()+reverse() //sort()排序默认为升序,reverse()将数组掉个 var max3 = arr.sort().reverse()[0]; console.log(max3) 1 2 3 4.sort() //b-a从大到小,a-b从小到大 var max2 = arr.sort(function(a,b){ return b-a; })[0]; console.log(max2) 来源:https://blog.csdn.net/web_hwg/article/details/68065587
**提出目的**
多人姿态估计主要的挑战来自被遮挡的关键点、不可见的关键点及复杂的背景。论文设计了级联金字塔网络(CPN)用于解决这种问题。算法包含GlobalNet和RefineNet两步。基于FPN,GlobalNet用来检测简单的关键点,RefineNet使用在线关键点挖掘损失检测困难度关键点。
应用:行为识别,人机交互。
主要方法:自下而上的方法:DeepCut, DeeperCut, OpenPose;自上而下方法:G-RMI, Mask RCNN。单人姿态估计:hourglass模型。
可能影响关键点检测的因素分析:行人检测,数据预处理。
**方法描述**
1.行人检测
FPN,ROIAlign代替ROIPooling
2.网络结构
结构图
GlobalNet
浅层分辨率高,但缺少语义信息,深层语义信息丰富但分辨率低。U-shape常用来同时获得较好度分辨率和语义信息。论文使用与FPN相同度特征金字塔结构。GlobalNet定位简单度特在点如眼睛比较容易,但定位下图中的臀部点比较困难。
示意图
RefineNet
与HyperNet类似,在层间传输信息,使用上采样和串联综合不同层的信息。
**实验结果**
MS COCO test-challenge
NMS对结果的影响
行人检测对结果的影响
COCO Keypoints-challenge
前言 关于LSTM原理: http://colah.github.io/posts/2015-08-Understanding-LSTMs/关于LSTM原理(译文):https://blog.csdn.net/Jerr__y/article/details/58598296关于Tensorflow+LSTM的使用:https://www.knowledgemapper.com/knowmap/knowbook/jasdeepchhabra94@gmail.comUnderstandingLSTMinTensorflow(MNISTdataset)关于Tensorflow+LSTM的使用(译文):https://yq.aliyun.com/articles/202939 正文 本文只是介绍tensorflow中的BasicLSTMCell中num_units,关于LSTM和如何使用请看前言的教程。
在使用Tensorflow跑LSTM的试验中, 有个num_units的参数,这个参数是什么意思呢?
先总结一下,num_units这个参数的大小就是LSTM输出结果的维度。例如num_units=128, 那么LSTM网络最后输出就是一个128维的向量。
我们先换个角度举个例子,最后再用公式来说明。
假设在我们的训练数据中,每一个样本 x 是 28*28 维的一个矩阵,那么将这个样本的每一行当成一个输入,通过28个时间步骤展开LSTM,在每一个LSTM单元,我们输入一行维度为28的向量,如下图所示。
那么,对每一个LSTM单元,参数 num_units=128 的话,就是每一个单元的输出为 128*1 的向量,在展开的网络维度来看,如下图所示,对于每一个输入28维的向量,LSTM单元都把它映射到128维的维度, 在下一个LSTM单元时,LSTM会接收上一个128维的输出,和新的28维的输入,处理之后再映射成一个新的128维的向量输出,就这么一直处理下去,知道网络中最后一个LSTM单元,输出一个128维的向量。
从LSTM的公式的角度看是什么原理呢?我们先看一下LSTM的结构和公式:
参数 num_units=128 的话,
对于公式 (1) , h = 128 ∗ 1 h=128*1 h=128∗1维, x = 28 ∗ 1 x=28*1 x=28∗1 维,[h,x]便等于 156 ∗ 1 156*1 156∗1维, W = 128 ∗ 156 W=128*156 W=128∗156 维,所以 W ∗ [ h , x ] = 128 ∗ 156 ∗ 156 ∗ 1 = 128 ∗ 1 , b = 128 ∗ 1 W*[h,x]=128*156 * 156*1=128*1, b=128*1 W∗[h,x]=128∗156∗156∗1=128∗1,b=128∗1 维, 所以 f = 128 ∗ 1 + 128 ∗ 1 = 128 ∗ 1 f=128*1+128*1=128*1 f=128∗1+128∗1=128∗1 维;对于公式 (2) 和 (3),同上可分析得 i = 128 ∗ 1 i=128*1 i=128∗1 维, C ~ = 128 ∗ 1 \widetilde{C}=128*1 C =128∗1 维;对于公式 (4) , f ( t ) = 128 ∗ 1 , C ( t − 1 ) = 128 ∗ 1 , f ( t ) .
前言:UICollectionViewCell 和 UITableViewCell 滑动状态一样,都会掉同一方法 方法如下。
用途:当滑动cell不想让cell刷新等。
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
_cellRloaderType = 0;
NSLog(@"滑动开始");
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
NSLog(@"滑动松开自动滑动开始");
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
_cellRloaderType = 1;
NSLog(@"滑动结束");
}
希望能帮到大家。
SpringCloud-Hystrix(服务熔断,服务降级) Hystrix(豪猪)
注明:此项目为本人学习尚硅谷老师的教学视频然后整理核心的配置文件,所有的项目均在以下地址下载。
https://github.com/xwbGithub/microservicecloud下载
本项目请参考microservicecloud-provider-dept-hystrix-8001项目
服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C有调用其他的微服务,这就是所谓的”扇出”,如扇出的链路上某个微服务的调用响应式过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统雪崩,所谓的”雪崩效应”
Hystrix:
Hystrix是一个用于分布式系统的延迟和容错的开源库。在分布式系统里,许多依赖不可避免的调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整个服务失败,避免级联故障,以提高分布式系统的弹性。
断路器:
“断路器”本身是一种开关装置,当某个服务单元发生故障监控(类似熔断保险丝),向调用方法返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方法无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延。乃至雪崩。
服务熔断:
熔断机制是应对雪崩效应的一种微服务链路保护机制,
当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回”错误”的响应信息。当检测到该节点微服务响应正常后恢复调用链路,在SpringCloud框架机制通过Hystrix实现,Hystrix会监控微服务见调用的状况,当失败的调用到一个阈值,缺省是5秒内20次调用失败就会启动熔断机制,熔断机制的注解是@HystrixCommand
服务熔断 1、microservicecloud-provider-dept-hystrix-8001
pom坐标 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency> yml文件 修改服务名称instance-id:为自己的微服务名称即可
server: port: 8001 mybatis: config-location: classpath:mybatis/mybatis.cfg.xml # mybatis配置文件所在路径 type-aliases-package: com.atguigu.springcloud.entities # 所有Entity别名类所在包 mapper-locations: - classpath:mybatis/mapper/**/*.xml # mapper映射文件 spring: application: name: microservicecloud-dept datasource: type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型 driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包 url: jdbc:mysql://localhost:3306/cloudDB01 # 数据库名称 username: root password: 123456 dbcp2: min-idle: 5 # 数据库连接池的最小维持连接数 initial-size: 5 # 初始化连接数 max-total: 5 # 最大连接数 max-wait-millis: 200 # 等待连接获取的最大超时时间 # eureka 客户端注册eureka服务器列表 eureka: client: service-url: # 指定单台机器 # defaultZone: http://localhost:7001/eureka # 指定集群服务器列表 defaultZone: http://eureka7001.
图网络(GN)在深度学习短板即因果推理上拥有巨大潜力,很有可能成为机器学习领域的下一个增长点,而图神经网络(GNN)正属于图网络的子集。GNN近期在图分类任务上得到了当前最佳的结果,但其存在平面化的局限,因而不能将图分层表征。现实应用中,很多图信息都是层级表征的,例如地图、概念图、流程图等,捕获层级信息将能更加完整高效地表征图,应用价值很高。在本文中,来自斯坦福等大学的研究者通过在GNN中结合一种类似CNN中空间池化的操作——可微池化,实现了图的分层表征DIFFPOOL在深度GNN的每一层针对节点学习可微分的软簇分配,将节点映射到一组簇中去,然后这些簇作为粗化输入,输入到GNN下一层。
介绍 近年来人们开发图神经网络的兴趣持续激增。图神经网络,即可以在比如社交网络数据或分子结构数据等图结构数据上运行的通用深度学习架构。GNN一般是将底层图作为计算图,通过在图上传递、转换和聚合节点特征信息学习神经网络基元以生成单个节点嵌入。生成的节点嵌入可以作为输入,用于如节点分类或连接预测的任何可微预测层,完整的模型可以通过端到端的方式训练。 然而,现有的GNN结构的主要限制在于太过平坦,因为它们仅通过图的边传播信息,无法以分层的方式推断和聚合信息。例如,为了成功编码有机分子的图结构,就要编码局部分子结构(如单个的原子和与这些原子直接相连的键)和分子图的粗粒结构(如在分子中表示功能单元的原子基团和键)。对图分类任务而言缺少分层结构尤其成问题,因为这类任务是要预测与整个图相关的标签。在图分类任务中应用 GNN,标准的方法是针对图中所有的节点生成嵌入,然后对这些节点嵌入进行全局池化,如简单地求和或在数据集上运行神经网络。这种全局池化方法忽略了可能出现在图中的层级结构,也阻碍了研究人员针对完整图的预测任务建立有效的GNN模型。 研究者在此提出了DIFFPOOL,这是一个可以分层和端到端的方式应用于不同图神经网络的可微图池化模块。DIFFPOOL允许开发可以学习在图的层级表征上运行的更深度的GNN模型。他们开发了一个和CNN中的空间池化操作相似的变体,空间池化可以让深度CNN在一张表征越来越粗糙的图上迭代运行。与标准CNN相比,GNN的挑战在于图不包含空间局部性的自然概念,也就是说,不能将所有节点简单地以 [m×m patch] [ m × m p a t c h ] 的方式池化在一张图上,因为图复杂的拓扑结构排除了任何直接、决定性的 [patch] [ p a t c h ] 的定义。此外,与图像数据不同,图数据集中包含的图形节点数和边数都不同,这使得定义通用的图池化操作更具挑战性。 为了解决上述问题,我们需要一个可以学习如何聚合节点以在底层图上建立多层级架构的模型。DIFFPOOL在深度GNN的每一层学习了可微分的软分配,这种软分配是基于学习到的嵌入,将节点映射为一组聚类。以该方法为框架,作者通过分层的方式「堆叠」了 GNN 层建立了深度 GNN:GNN 模块中 l l 层的输入节点对应GNN模块中l−1l−1层学到的聚类簇。因此,DIFFPOOL的每一层都能使图越来越粗糙,然后训练后的DIFFPOOL就可以产生任何输入图的层级表征。本研究展示了DIFFPOOL可以结合到不同的GNN方法中,这使准确率平均提高了7%,并且在五个基准图分类任务中,有四个达到了当前最佳水平。最后,DIFFPOOL可以学习到与输入图中明确定义的集合相对应的可解释的层级簇。
最近工作 作者的工作是基于最近GNN和图分类的研究。 GNN的最近工作。最近几年提出了许多GNN模型,有受CNN启发的方法,还有RNN、递归网络和循环置信传播。大部分方法都属于Gilmer提出的neural message passing框架,在这种观点下GNN是一种message passing算法,节点特征和邻居关系通过GNN而迭代计算出节点表示。Hamilton总结了这个领域的最近进展,Bronstein概述了图卷积的联系。 GNN实现的图分类。GNN应用于许多任务如节点分类、链接预测、图分类和信息化学。在图分类背景下所面临的一个问题是如何更好的通过GNN产生节点嵌入以表示出整个网络的特征。一般的方法有,在最后一层对节点嵌入进行简单求和或平均、引入与所有节点连接的虚拟节点和用深度学习聚合节点嵌入。然而这些方法都有一个限制,不能学习层级表示(所有节点在一个单层进行全局池化),所以不能捕获现实世界的自然结构。最近有一些方法,用CNN将所有节点嵌入串联起来,但是这需要节点的拓扑排序。 最后,最近也有一些工作,将GNN和确定性聚类方法结合起来以学习层级图表示。与这个不同的是,作者的方法是在端对端训练的框架下自动学习层级结构表示,而不是依靠确定性聚类方法。
提出方法 DIFFPOOL的关键想法是在多层GNN结构中引入节点的可微层级池化。这一节概述DIFFPOOL模块以及如何在GNN中应用。 图 G G 表示为(A,F)(A,F),其中 A∈{0,1}n×n A ∈ { 0 , 1 } n × n 是邻接矩阵, F∈Rn×d F ∈ R n × d 是节点特征矩阵, d d 是每个节点的特征维数。给定一个带标签的图数据D={(G1,y1),(G2,y2),…}D={(G1,y1),(G2,y2),…},其中 yi y i 是图 Gi∈G G i ∈ G 的类标签,任务目标是寻找映射 f:G→Y f : G → Y 。 相对于标准监督学习过程,这里的困难主要在于如何更好的从输入的图中提取特征,为了应用深度学习等机器学习方法进行分类,我们需要将每个图转换成一个有限维向量。 GNN。在这个工作中,作者以端到端训练的方式使用GNN学习提取用于图分类的特征。GNN使用message passing结构 :H(k)=M(A,H(k−1);θ(k)) : H ( k ) = M ( A , H ( k − 1 ) ; θ ( k ) ) ,其中 H(k)∈Rn×d H ( k ) ∈ R n × d 是GNN迭代 k k 次后的节点嵌入,MM是由邻接矩阵和参数 θ(k) θ ( k ) 决定的message传播函数, H(k−1) H ( k − 1 ) 是上一步message passing产生的节点嵌入。输入节点嵌入 H(0) H ( 0 ) 初始化为节点输入特征, H(0)=F H ( 0 ) = F 。 传播函数 M M 有多种实现方式。有一种流行的GNN变种GCN,M的实现方式是将线性变换和ReLU非线性激活结合起来 H(k)=M(A,H(k−1);W(k))=ReLU(D~−12A~D~−12H(k−1)W(k−1))H(k)=M(A,H(k−1);W(k))=ReLU(D~−12A~D~−12H(k−1)W(k−1)) A~=A+I,D~=∑jA~ij A ~ = A + I , D ~ = ∑ j A ~ i j 其中 W(k)∈Rd×d W ( k ) ∈ R d × d 是需要训练的权重矩阵。作者提出的可微分池化层能应用到任意GNN模型中,不论 M M 以何种方式实现。GNN迭代KK次产生最终节点嵌入 Z=H(K)∈Rn×d Z = H ( K ) ∈ R n × d ,其中 K K 一般是2−62−6之间,以下论述中忽略GNN的内部结构,并简单记为 Z=GNN(A,X) Z = G N N ( A , X ) 。 GNN和池化层的堆叠。GNN的实现内部是平面化的,信息只能通过边传播。作者的目标是提出一个通用的、端对端可微分的方法,将GNN模块堆叠为层级结构。给定原始图的邻接矩阵 A∈Rn×n A ∈ R n × n 后可以产生GNN的输出 Z=GNN(A,X) Z = G N N ( A , X ) ,之后给出一个粗化的图,粗化图的节点数为 m<n m < n ,邻接矩阵为 A′∈Rm×m A ′ ∈ R m × m ,节点嵌入为 Z′∈Rm×d Z ′ ∈ R m × d 。这个粗化图作为下一层GNN的输入,经过 L L 次重复产生越来越粗化的图,并分别由串联的GNN进行处理。于是我们的目标是学习如何使用上一层GNN的输出结果对节点进行聚类或池化,再把聚类或池化所输出的粗化图作为下一层GNN的输入。设计GNN的池化层是比较困难的,相比于一般的粗化图任务,不是在一个图上对节点进行聚类,而是在图集合上进行层级池化,在推理时要对许多不同的图结构进行自适应池化。 基于分配学习的可微分池化。上述提到的DIFFPOOL,难点在于使用GNN的输出学习节点分配的聚类,将L个GNN堆叠起来,可微池化层利用上一个GNN产生的节点嵌入进行节点聚类,从而产生粗化图,并以端对端方式进行训练学习。于是GNN产生的节点嵌入,既用于图分类,又用于层级池化,而这通过大量的图样本进行训练学习。以下先描述,DIFFPOOL在有了节点分配矩阵后具体如何聚类池化,再描述在GNN架构下如何产生分配矩阵。 用分配矩阵进行池化。将ll层的聚类分配矩阵记为 S(l)∈Rnl×nl+1 S ( l ) ∈ R n l × n l + 1 。 S(l) S ( l ) 的每一行代表在 l l 层中的nlnl个节点中的一个节点(或一个节点聚类簇),每一列代表 l+1 l + 1 层中的 nl+1 n l + 1 节点聚类簇, S(l) S ( l ) 提供了从 l l 层的图节点到l+1l+1层的图节点(聚类簇)的软分配。 现在我们已经有了 l l 层的节点分配矩阵,将这ll层图的邻接矩阵记为 A(l) A ( l ) , l l 层图的节点嵌入记为Z(l)Z(l)。DIFFPOOL可微池化层在此基础上产生输入图的粗化图, (A(l+1),Xl+1)=DIFFPOOL(A(l),Z(l)) ( A ( l + 1 ) , X l + 1 ) = D I F F P O O L ( A ( l ) , Z ( l ) ) , A(l+1) A ( l + 1 ) 是下一层粗化图的邻接矩阵, X(l+1) X ( l + 1 ) 是下一层粗化图的节点(聚类簇)输入特征。 X(l+1)=S(l)TZ(l)∈Rnl+1×d(3) (3) X ( l + 1 ) = S ( l ) T Z ( l ) ∈ R n l + 1 × d A(l+1)=S(l)TA(l)S(l)∈Rnl+1×nl+1(4) (4) A ( l + 1 ) = S ( l ) T A ( l ) S ( l ) ∈ R n l + 1 × n l + 1 公式 (3) ( 3 ) 是根据分配矩阵,将上一层的节点嵌入 Xl X l 转换成下一层的节点(聚类簇)嵌入 X(l+1) X ( l + 1 ) ,类似的,公式 (4) ( 4 ) 是将上一层的邻接矩阵 A(l) A ( l ) 转换成下一层粗化图的邻接矩阵 Al+1 A l + 1 。 A(l+1) A ( l + 1 ) 是一个 l l 层的全连接实值矩阵,而Al+1ijAijl+1代表 l+1 l + 1 层聚类簇 i i 和聚类簇jj之间的连接强度。类似的, X(l+1) X ( l + 1 ) 的第 i i 行代表第ii个聚类簇的输入特征。最后, A(l+1) A ( l + 1 ) 和 X(l+1) X ( l + 1 ) 作为下一层GNN的输入特征。 学习并产生分配矩阵。现在描述DIFFPOOL如何产生分配矩阵 S(l) S ( l ) 。我们使用两个独立的GNN(嵌入GNN和池化GNN)产生两个矩阵, l l 层中的嵌入GNN为 Z(l)=GNNl,emded(A(l),X(l))(5)(5)Z(l)=GNNl,emded(A(l),X(l)) 将 l l 层邻接矩阵A(l)A(l)和输入特征 X(l) X ( l ) 作为一个标准GNN的输入,进而产生一个新的嵌入 Z(l) Z ( l ) 。相比之下,池化GNN则使用 A(l) A ( l ) 和 X(l) X ( l ) 产生分配矩阵 S(l)=softmax(GNNl,pool(A(l),X(l)))(6) (6) S ( l ) = s o f t m a x ( G N N l , p o o l ( A ( l ) , X ( l ) ) ) 其中 softmax s o f t m a x 应用于输出矩阵的每一行。注意到这两个GNN使用相同的输入数据,但是具有不同的参数和作用:嵌入GNN对输入特征产生节点嵌入,池化GNN对节点产生概率分配,从而对应粗化图的聚类簇。 在 l=0 l = 0 层时,公式 (5),(6) ( 5 ) , ( 6 ) 的输入就是原始图的邻接矩阵 A A 和节点特征FF,而倒数第二层 (l=L−1) ( l = L − 1 ) 的分配矩阵 S(L−1) S ( L − 1 ) 是全为1的向量,这样就能在最后一层 (l=L) ( l = L ) 将所有节点归到一个聚类簇,并最后产生一个代表整个图的嵌入向量。最后的嵌入表示作为一个可微分类器的输入特征,整个系统以端对端的形式进行随机梯度下降训练。 置换不变性。为了更好分类,池化层应具有置换不变性。对于DIFFPOOL,作者表明只要GNN组件满足置换不变性,那么整体就会满足了。 令 P∈{0,1}n×n P ∈ { 0 , 1 } n × n 为置换矩阵,若 GNN(A,X)=GNN(PAPT,X) G N N ( A , X ) = G N N ( P A P T , X ) ,则 DIFFPOOL(A,Z)=DIFFPOOL(PAPT,PX) D I F F P O O L ( A , Z ) = D I F F P O O L ( P A P T , P X ) 链接预测及正则化。在实际中,仅从图分类的梯度信号中训练池化GNN是困难的,这是一个非凸优化问题。为了缓解这个问题,在训练时在加上链接预测目标,这促使邻近节点一起池化。在每层 l l 中,最小化LLP=∥A(l)−S(l)S(l)T∥FLLP=∥A(l)−S(l)S(l)T∥F,其中 ∥⋅∥F ∥ ⋅ ∥ F 是 Frobenius F r o b e n i u s 范数。 另一个池化GNN的重要特征是,节点的聚类簇分配向量应当接近 one−hot o n e − h o t 向量,这样能使聚类簇更加清晰明显。此外还要正则化聚类簇分配的熵,为此加入最小化 LE=1n∑ni=1H(Si) L E = 1 n ∑ i = 1 n H ( S i ) ,其中 H H 为熵,SiSi为 S S 的第ii行。在训练时,每层的 LLP L L P 和 LE L E 都添加到分类损失一起。在实际中,作者发现这样训练会收敛的更加缓慢,然而效果更好,并且聚类簇的分配也更有解释性。 实验 作者为了评估DIFFPOOL的优势,将DIFFPOOL与最优秀的图分类方法相比,并回答下列问题: Q1:与其他已提出的GNN池化方法相比,DIFFPOOL如何? Q2:与现有最好的图分类任务模型相比,结合了DIFFPOOL的GNN如何? Q3:DIFFPOOL对输入图给出了有意义且可解释的簇吗? 数据集。使用多种图分类基准数据,如蛋白质数据集(ENZYMES,PROTEINS,D&D),社交网络数据集(REDDIT-MULTI-12K),科研合作数据集(COLLAB)。 10% 10 % 的数据作为验证集,剩下作为训练数据并以 10 10 折验证方式评估模型结果。 论文地址
public class TimeOver { public static void mDate(String date){ int dateSum = 0; int year = Integer.valueOf(date.substring(0,4)); int month = Integer.valueOf(date.substring(5,7)); int day = Integer.valueOf(date.substring(8,10)); for(int i=1;i<month;i++){ switch(i){ case 1: case 3: case 5: case 7: case 8: case 10: case 12: dateSum+=31; break; case 4: case 6: case 9: case 11: dateSum+=30; break; case 2: //判断是否是闰年 if((year % 4 == 0 && year%100 != 0)|| (year % 400 == 0)){ dateSum += 29; }else{ dateSum += 28; } } } System.
转发自 作者kingeasternsun https://studygolang.com/articles/10155?fr=sidebar
本文主要基于官方文档Go Concurrency Patterns: Context以及视频Advanced Go Concurrency Patterns的学习而得。
背景 在go服务器中,对于每个请求的request都是在单独的goroutine中进行的,处理一个request也可能设计多个goroutine之间的交互, 使用context可以使开发者方便的在这些goroutine里传递request相关的数据、取消goroutine的signal或截止日期。
Context结构 // A Context carries a deadline, cancelation signal, and request-scoped values // across API boundaries. Its methods are safe for simultaneous use by multiple // goroutines. type Context interface { // Done returns a channel that is closed when this Context is canceled // or times out. Done() <-chan struct{} // Err indicates why this context was canceled, after the Done channel // is closed.
内核也是程序,也应该具有自己的虚存空间,但是作为一种为用户程序服务的程序,内核空间有它自己的特点。
内核空间与用户空间的关系 在一个32位系统中,一个程序的虚拟空间最大可以是4GB,那么最直接的做法就是,把内核也看作是一个程序,使它和其他程序一样也具有4GB空间。但是这种做法会使系统不断的切换用户程序的页表和内核页表,以致影响计算机的效率。解决这个问题的最好做法就是把4GB空间分成两个部分:一部分为用户空间,另一部分为内核空间,这样就可以保证内核空间固定不变,而当程序切换时,改变的仅是程序的页表。这种做法的唯一缺点便是内核空间和用户空间均变小了。
例如:在i386这种32位的硬件平台上,Linux在文件page.h中定义了一个常量PAGE_OFFSET:
#ifdef CONFIG_MMU #define __PAGE_OFFSET (0xC0000000) //0xC0000000为3GB #else #define __PAGE_OFFSET (0x00000000) #endif #define PAGE_OFFSET ((unsigned long)__PAGE_OFFSET) Linux以PAGE_OFFSET为界将4GB的虚拟内存空间分成了两部分:地址0~3G-1这段低地址空间为用户空间,大小为3GB;地址3GB~4GB-1这段高地址空间为内核空间,大小为1GB。
当系统中运行多个程序时,多个用户空间与内核空间的关系可以表示如下图:
如图中所示,程序1、2……n共享内核空间。当然,这里的共享指得是分时共享,因为在任何时刻,对于单核处理器系统来说,只能有一个程序在运行。
内核空间的总体布局 Linux在发展过程中,随着硬件设备的更新和技术水平的提高,其内核空间布局的发展也是一种不断打补丁的方式。这样的后果就是使得内核空间被分成不同的几个区域,而且在不同的区域具有不同的映射方式。通常,人们认为Linux内核空间有三个区域,即DMA区(ZONE_DMA)、普通区(ZONE_NORMAL)和高端内存区(ZONE_HIGHMEM)。
实际物理内存较小时内核空间的直接映射 早期计算机实际配置的物理内存通常只有几MB,所以为了提高内核通过虚拟地址访问物理地址内存的速度,内核空间的虚拟地址与物理内存地址采用了一种从低地址向高地址依次一一对应的固定映射方式,如下图所示:
可以看到,这种固定映射方式使得虚拟地址与物理地址的关系变得很简单,即内核虚拟地址与实际物理地址只在数值上相差一个固定的偏移量PAGE_OFFSET,所以当内核使用虚拟地址访问物理页框时,只需在虚拟地址上减去PAGE_OFFSET即可得到实际物理地址,比使用页表的方式要快得多!
由于这种做法几乎就是直接使用物理地址,所以这种按固定映射方式的内核空间也就叫做“物理内存空间”,简称物理内存。另外,由于固定映射方式是一种线性映射,所以这个区域也叫做线性映射区。
当然,这种情况下(计算机实际物理内存较小时),内核固定映射空间仅占整个1GB内核空间的一部分。例如:在配置32MB实际物理内存的x86计算机系统时,内核的固定映射区便是PAGE_OFFSET~(PAGE_OFFSET+0x02000000)这个32MB空间。那么内核空间剩余的内核虚拟空间怎么办呢?
当然还是按照普通虚拟空间的管理方式,以页表的非线性映射方式使用物理内存。具体来说,在整个1GB内核空间中去除固定映射区,然后在剩余部分中再去除其开头部分的一个8MB隔离区,余下的就是映射方式与用户空间相同的普通虚拟内存映射区。在这个区,虚拟地址和物理地址不仅不存在固定映射关系,而且通过调用内核函数vmalloc()获得动态内存,故这个区就被称为vmalloc分配区,如下图所示:
对于配置32MB实际物理内存的x86计算机系统来说,vmalloc分配区的起始位置为PAGE_OFFSET+0x02000000+0x00800000。
这里说明一下:这里说的内核空间与物理页框的固定映射,实质上是内核页对物理页框的一种“预定”,并不是说这些页就“霸占”了这些物理页框。即只有当虚拟页真正需要访问物理页框时,虚拟页才与物理页框绑定。而平时,当某个物理页框不被与它对应的虚拟页所使用时,该页框完全可以被用户空间以及后面所介绍的内核kmalloc分配区使用。
总之,在实际物理内存较小的系统中,实际内存的大小就是内核空间的物理内存区与vmalloc分配区的边界。
ZONE_DMA区与ZONE_NORMAL区 对于整个1GB的内核空间,人们还把该空间头部的16MB叫做DMA区,即ZONE_DMA区,因为以往硬件将DMA空间固定在了物理内存的低16MB空间;其余区则叫做普通区,即ZONE_NORMAL。
内核空间的高端内存 随着计算机技术的发展,计算机的实际物理内存越来越大,从而使得内核固定映射区(线性区)也越来越大。显然,如果不加以限制,当实际物理内存达到1GB时,vmalloc分配区(非线性区)将不复存在。于是以前开发的、调用了vmalloc()的内核代码也就不再可用,显然为了兼容早期的内核代码,这是不能允许的。
下图就表示了这种内核空间所面临的局面:
显然,出现上述问题的原因就是没有预料到实际物理内存可以超过1GB,因而没有为内核固定映射区的边界设定限制,而任由其随着实际物理内存的增大而增大。
解决上述问题的方法就是:对内核空间固定映射区的上限加以限制,使之不能随着物理内存的增加而任意增加。Linux规定,内核映射区的上边界的值最大不能大于一个小于1G的常数high_menory,当实际物理内存较大时,以3G+high_memory为边界来确定物理内存区。
例如:对于x86系统,high_memory的值为896M,于是1GB内核空间余下的128MB为非线性映射区。这样就确保在任何情况下,内核都有足够的非线性映射区以兼容早期代码并可以按普通虚存方式访问实际物理内存的1GB以上的空间。
也就是说,高端内存的最基本思想:借一段地址空间,建立临时地址映射,用完后释放,达到这段地址空间可以循环使用,访问所有物理内存。当计算机是物理内存较大时,内核空间的示意图如下:
习惯上,Linux把内核空间3G+high_memory~4G-1的这个部分叫做高端内存区(ZONE_HIGHMEM)。
总结一下:在x86结构的内核空间,三种类型的区域(从3G开始计算)如下:
ZONE_DMA:内核空间开始的16MBZONE_NORMAL:内核空间16MB~896MB(固定映射)ZONE_HIGHMEM :内核空间896MB ~ 结束(1G) 根据应用目标不同,高端内存区分vmalloc区、可持久映射区和临时映射区。内核空间中高端内存的布局如下图所示:
vmalloc映射区 vmalloc映射区时高端内存的主要部分,该区间的头部与内核线性映射空间之间有一个8MB的隔离区,尾部与后续的可持久映射区有一个4KB的隔离区。
vmalloc映射区的映射方式与用户空间完全相同,内核可以通过调用函数vmalloc()在这个区域获得内存。这个函数的功能相当于用户空间的malloc(),所提供的内存空间在虚拟地址上连续(注意,不保证物理地址连续)。
可持久内核映射区 如果是通过 alloc_page() 获得了高端内存对应的 page,如何给它找个线性空间?
内核专门为此留出一块线性空间,从PKMAP_BASE开始,用于映射高端内存,就是可持久内核映射区。
在可持久内核映射区,可通过调用函数kmap()在物理页框与内核虚拟页之间建立长期映射。这个空间通常为4MB,最多能映射1024个页框,数量较为稀少,所以为了加强页框的周转,应及时调用函数kunmap()将不再使用的物理页框释放。
临时映射区 临时映射区也叫固定映射区和保留区。该区主要应用在多处理器系统中,因为在这个区域所获得的内存空间没有所保护,故所获得的内存必须及时使用;否则一旦有新的请求,该页框上的内容就会被覆盖,所以这个区域叫做临时映射区。
关于高端内存区一篇很不错的文章:linux 用户空间与内核空间——高端内存详解。
内核内存分配修饰符gfp 为了在内核内存请求函数对请求进行必要的说明,Linux定义了多种内存分配修饰符gfp。它们是行为修饰符、区修饰符、类型修饰符。
行为修饰符 在内存分配函数中的行为修饰符说明内核应当如何分配内存。主要行为修饰符如下:
Linux的主要内核内存分配行为修饰符 修饰符说明__GFP_WAIT分配器可以休眠__GFP_HIGH分配器可以访问紧急事件缓冲池__GFP_IO分配器可以启动磁盘IO__GFP_FS分配器可以启动文件系统IO__GFP_COLD分配器应该使用高速缓冲中快要淘汰的页框__GFP_NOWARN分配器不发出警告__GFP_REPEAT分配失败时重新分配__GFP_NOFAILT分配失败时重新分配,直至成功__GFP_NORETRY分配失败时不再重新分配 区修饰符 区修饰符说明需要从内核空间的哪个区域中分配内存。内存分配器默认从内核空间的ZONE_NORMAL开始逐渐向高端获取为内存请求者分配内存区,如果用户特意需要从ZONE_DMA或ZONE_HOGNMEM获得内存,那么就需要内存请求者在内存请求函数中使用以下两个区修饰符说明:
硬件平台:高通msm8953_64
系统平台:Android 7.1.2
编译平台:Ubuntu 14.04.2 LTS(64bit)
从接触Android系统驱动开始,就有一个需求,怎样提高android系统的启动速度。所以也在网上搜索了很多资料,看了很多优质的博客。很多文章都有提到:Zygote进程预加载类的时候是比较耗时间的
Android系统中preloaded-classes的文件路径:frameworks/base/preloaded-classes
Zygote进程预加载的那一部分比较耗时,这是一种用空间换取时间的优化方法。在预加载的过程中,有一部分是预加载Class。它的主要逻辑就是从一个preloaded-classes的文件中读取Class加载到内存中。preloaded-classes里面都是各个进程常用的类,所以很多人的建议是,不要去动这一部分的代码,也有建议是删掉一些加载时间比较长的Class,来缩短开机时间。
那么这里就有三个问题,我们先将问题提列出来,然后在来一一解决。
问题一:能否在系统启动过程中不加载preloaded-classes类
问题二:加载preloaded-classes类是按照什么逻辑标准来加载的
问题三:如何重新生成preloaded-classes类文件
1、 解答一:能否在系统启动过程中不加载preloaded-classes类? 说实话,这个疑问还真是我最开始的时候的想法。既然目的是为了加快系统启动,那么我们让系统在启动过程中不加载preloaded-classes类,那岂不是最快的方法了。然后就开始着手验证:我将源码中的preloaded-classes文件全部删除
方法如下:
rm -f frameworks/base/preloaded-classes
rm -f out/target/product/msm8953_64/system/etc/preloaded-classes
然后重新编译整个工程文件:make -j12
或者
只编译system.image文件:make systemimage -j12
然后重新烧入android的所有系统固件,发现android系统在启动过程中运行到开机动画之后就卡死了,无法正常的进入到系统launcher桌面。 经过查看系统启动信息发现,Zygote进程在ZygoteInit类的main()函数中,创建完Socket服务端后还不能立即孵化新的进程,因为这个"卵"中还缺少必要的"核酸",这个"核酸"就是预装的Framework大部分类及资源。
2、 解答二:加载preloaded-classes类是按照什么逻辑标准来加载的? 这里有一位前辈总结的很不错,可以参考一下。
链接:http://news.tuxi.com.cn/viewtt/q/20171227G00RSK00.html
启动过程中,首先通过registerZygoteSocket()登记端口,接着调用preloadClasses()装载相关类。这里大概要装载1000多个类,具体装载类见platform\frameworks\base\preloaded-classes。preloaded-classes这个文件是由WritePreloadedClassFile.java文件自动生成。
具体文件路径:frameworks/base/tools/preload/WritePreloadedClassFile.java
该类中有两个比较重要的定义:
static final int MIN_LOAD_TIME_MICROS = 1250;
static final int MIN_PROCESSES = 10;
分析该类的main函数,有如下一段代码:
public static void main(String[] args) throws IOException, ClassNotFoundException { String rootFile = args[0]; Root root = Root.fromFile(rootFile); //2.1部分 Writer out = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(Policy.
首先 用普通用户登录
然后切换至root权限 命令如下
sudo su
切换后 就是root权限,然后使用命令切换至根目录 cd /
然后再输入 passwd root 此时就会现在输入新密码,输入两次
最后会显示修改成功了,这时候你就注销用户然后再链接试试
网络通信协议: 通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。 网络通信协议有很多种,目前应用最广泛的是TCP/IP协议(Transmission Control Protocal/Internet Protoal传输控制协议/英特网互联协议),它是一个包括TCP协议和IP协议,UDP(User Datagram Protocol)协议和其它一些协议的协议组,首先了解一下TCP/IP协议组的层次结构。 在进行数据传输时,要求发送的数据与收到的数据完全一样,这时,就需要在原有的数据上添加很多信息,以保证数据在传输过程中数据格式完全一致。TCP/IP协议的层次结构比较简单,共分为四层,如图所示: TCP/IP网络模型 上图中,TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层,每层分别负责不同的通信功能,接下来针对这四层进行详细地讲解。 链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动。 网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络。 传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议。 应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等。 传输层的两个重要的高级协议,分别是UDP和TCP,其中UDP是User Datagram Protocol的简称,称为用户数据报协议,TCP是Transmission Control Protocol的简称,称为传输控制协议。 UDP协议 UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。 由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议。 TCP协议 TCP协议是面向连接的通信协议,即在传输数据前先在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”。第一次握手,客户端向服务器端发出连接请求,等待服务器确认,第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求,第三次握手,客户端再次向服务器端发送确认信息,确认连接。整个交互过程如下图所示。 由于TCP协议的面向连接特性,它可以保证传输数据的安全性,所以是一个被广泛采用的协议,例如在下载文件时,如果数据接收不完整,将会导致文件数据丢失而不能被打开,因此,下载文件时必须采用TCP协议。 UDP通信 前面介绍了UDP是一种面向无连接的协议,因此,在通信时发送端和接收端不用建立连接。UDP通信的过程就像是货运公司在两个码头间发送货物一样。在码头发送和接收货物时都需要使用集装箱来装载货物,UDP通信也是一样,发送和接收的数据也需要使用“集装箱”进行打包。 TCP通信 TCP通信同UDP通信一样,都能实现两台计算机之间的通信,通信的两端都需要创建socket对象。 区别在于,UDP中只有发送端和接收端,不区分客户端与服务器端,计算机之间可以任意地发送数据。 而TCP通信是严格区分客户端与服务器端的,在通信时,必须先由客户端去连接服务器端才能实现通信,服务器端不可以主动连接客户端,并且服务器端程序需要事先启动,等待客户端的连接。
人脸识别,人脸识别门禁系统的原理 我们都知道,我们的脸在我们的日常生活和社会活动中扮演着非常重要和特殊的角色。这张脸有时传达着人类的身份,因为它在世界上是独一无二的。用我们的脸作为开门的钥匙比使用卡片或其他东西更安全。这是人脸识别技术,一种访问类型适用于住宅、工业、政府、交通和军事领域在人们生活中,帮助防止资金损失和声誉损失,甚至防止盗窃、破坏公物、闲逛,和其他意想不到的犯罪行为。
人脸识别
人脸识别访问控制系统是一种从人脸识别设备中对数字图像或视频帧进行验证的高技术。人脸识别将通过一个具有人脸识别功能的视频门对讲机来分析人脸图像输入的特征。人脸识别测量整个面部结构,包括眼睛、鼻子、嘴和耳朵之间的距离。
人脸识别将定位用户的面部,并与面部数据库进行匹配。
主要有四级流程解释人脸识别
人脸识别技术操作:首先,大量的身体或行为面部样本收集的系统在系统设置。然后,当相对的站在人脸识别机前面或靠近它时,人脸识别会从人脸识别装置捕捉到数字图像或视频帧。然后,从示例中提取一个惟一的数据,并创建一个模板,然后将模板与一个新示例进行比较。最后,人脸识别系统将决定从新样本中提取的特征是否匹配。一般来说,人脸识别访问系统的工作原理是将选定的人脸特征与数据库中的人脸进行比较。
同时,与使用指纹或虹膜的其他生物识别系统相比,人脸识别技术系统具有明显的优势,因为我们不需要触摸设备。所有的人脸图像都可以从远处捕捉到,而不需要接触到被识别的人,这使得门的访问变得更加容易和快捷。
TAG:天波融合通信解决方案, 天波监狱智能可视会见系统,远程探视系统企业,语音系统,天波智能电话机天波软交换,天波智慧对讲,天波程控交换机,IPPBX ,电话交换机,智能电话机,语音网关,智慧对讲,人脸识别系统,人脸识别,人脸识别门禁,人脸识别解决方案,面部识别终端,人脸识别终端,人脸识别系统考勤,智能人脸识别,人脸识别考勤机,人脸识别门禁机,人脸识别支付
windows server 2012R2 部署安装 hmail 环境说明: 系统: windows server2012 R2
软件版本: hMailServer-5.6.7-B2425.exe
邮件客户端: foxmail7.2版本
加密工具: hMailServer_SSL_0_9_8j.rar
hMailServer下载地址:https://www.hmailserver.org/download/file.php?id=360
hMailServer_SSL下载地址:https://www.hmailserver.org/download/file.php?id=72
一.安装过程: 这里安装过程进行省略,使用默认的安装过程即可 数据库sql server 二.配置信息如下: 需要进行配置的地方:
1.配置域名 2.配置smtp兼容性 3.配置IP段地址 4.创建测试账号 5.发送邮件进行测试 用创建好的多用户进行发送邮件测试,均可以正常收发,注意,使用foxmail作为客户端的时候,服务器地址要写对应服务器所在的IP 地址。
三.配置SSL加密进行测试 1.安装插件 先安装vcredist_x86.exe,然后进行安装 再安装Win32OpenSSL_Light-0_9_8j.exe,默认安装在C盘
2.配置秘钥 打开openssl bin 目录所在的位置 输入cmd
openssl genrsa -out key.pem 2048 openssl req -new -key key.pem -out request.csr Country Name (2 letter code) [XX]:cn State or Province Name (full name) []:yunnan Locality Name (eg, city) [Default City]:kunming Organization Name (eg, company) [Default Company Ltd]:hmailserver.
public static List<String> getFilesAllName(String path) { File file=new File(path); File[] files=file.listFiles(); if (files == null){Log.e("error","空目录");return null;} List<String> s = new ArrayList<>(); for(int i =0;i<files.length;i++){ s.add(files[i].getAbsolutePath()); } return s; }
早先学vue的时候在知乎写的学习笔记,今天想起来了给搬到博客上。知乎链接 这几天刚接触vue.js,由于没有接触过es6和webpack,打算直接用html+js构建vue项目,发现虽然官方文档是html+js写的,但是不够详细,网上的例子又都是vue-cli构建的,于是简单总结分享一下这两天的成果,希望能给同样初学的小伙伴们一点帮助。
demo案例 对于官方文档上的内容我就不过多重复了,希望大家能先看一下组件以前的部分。在这里我就简单的进行一下总结,先上一个小例子(这个例子都看不懂的话还是先把官方文档仔细看一看吧):
<body> <div id="app"> <input v-model="message"/> <p>{{ message }}</p> <button v-on:click="show">点我</button> </div> <script> new Vue({ el: '#app', data: { message: 'Hello world' }, methods:{ show:function(){alert("run...")} } }) </script> 运行结果:
从上面例子可以看到vue的基本使用:数据绑定,有了一个初步认识之后,让我们来看看vue的基本语法
一、vue实例 创建根实例,所有的内容都应该包含在根实例中
new Vue({ el: '#app', data: {//数据放这里 message: 'Hello world' }, methods:{//方法放这里 show:function(){alert("run...")} } }) 二、模板语法 这部分请直接去官方文档学习,我只简单提一下如何区别快速记忆一些常用的
文本:文本绑定数据的方式
<p>{{ message }}</p> 属性:html属性绑定数据的方式
<div v-bind:class="{'red': true}"> <div :href="url">//简写格式 指令:html没有的属性即是vue的指令,以v-xxx的格式:如v-for、v-if···
数据绑定:前面的可以算是单向的数据绑定,而双向数据绑定需要用到v-model指令
<input v-model="message"/> 事件:
<!-- 完整语法 --> <a v-on:click="
/**
* @author Administrator
* 随机数并与输入值进行比较
*/
public class Random {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
random(sc.nextInt());
}
public static void random(int x){
int sis = 0;
int a=1;
for(int i=0;i<100;i++){
sis = (int) (Math.random()*10+1);//产生1到10的随机数
System.out.println("打印出循环的数据:"+sis);
if(sis == x){
break;
}
a++;
}
System.out.println("总共"+a+"次");
}
}
public static void main(String[] args) {
/*String [] ac = {"sche"," ","1123","we"};
String a = "we";
for(int i=0;i< ac.length;i++){
if(ac[i]==a){
System.out.println("索引位置是:"+i);
break;
}
}*/
/*
* 查找数组中指定元素第一次出现的索引值。
int[] arr = {98,23,16,35,72};
查找23在数组中的索引值。
*/
System.out.println(getIndex(23));
}
private static int getIndex(int x) {
int[] arr = {98,23,16,35,72};
for (int i = 0; i < arr.length; i++) {
if (arr[i] == x) {
return i;
}
}
return -1; //若数组中没有则返回-1
}
public class Juxing {
public static void main(String[] args) {
for(int i=0;i<5;i++){
for(int j=0;j<=4;j++){
System.out.print("*");
}
System.out.println();
}
}
}
目录
1.什么是字典树?
2.字典树有什么用?
1.以最节约空间的方式存储大量字符串.且存好后是有序的
2.快速查询某字符串s在字典树中是否已存在,甚至出现过几次
3.字典树实现思路
4.模版代码
(1).以数组模拟动态分配的只带增查的字典树模版
(2).以动态分配为实现的带增删改查的字典树模版.
1.什么是字典树? 首先字典树是一种数据结构,用于处理大量字符串.,优点在于利用字符串的公共前缀,在存储时节约存储空间,并在查询时最大限度的减少无谓的字符串比较.
2.字典树有什么用? 1.以最节约空间的方式存储大量字符串.且存好后是有序的 因为是有序的,故而字典树不仅可用于大量字符串的存储,还可用于大量字符串的排序.
2.快速查询某字符串s在字典树中是否已存在,甚至出现过几次 因为当字典树预处理好之后,查询字符串s在当前的出现情况的效率为strlen(s),异常高效,故而常用于搜索引擎等.
3.字典树实现思路 首先我们已经知道了字典树是一种数据结构,而一个数据结构的重点就在于:
1.怎么有规则的把数据存储下来
2.怎么有规则的去高效的得到自己需要的数据
我们最终的目标就是用程序实现一个如下图的树:
颜色为黑色的表示只是字符串中间的字符,为蓝色的表示是字符串末尾的那个字符,空白那个是树根节点.每个节点的意义实际上就是根节点到这个结点所经过的每个字符组成的字符串.故而图中这个树实际上意味着已存在字符串"","to","tea","ted","ten","a","i","in","inn"
4.模版代码 (1).以数组模拟动态分配的只带增查的字典树模版 //一个只带添加字符串与查找字符串的字典树(为了效率以数组实现) #include <stdio.h> #include <string.h> #include <stdlib.h> int charmapping[256]; //字符映射数组,charmapping[i]=x表示ascii码为i的字符对应于treenode中的next[x] void init_charmapping(){ for(int i='a';i<='z';i++){ //我的这个字典树现在只允许输入小写字符组成的字符串,然而由于有charmapping的存在,增加新字符添加映射并且增大maxn就好,很方便. charmapping[i]=i-'a'; } } const int maxn=26; //这里假设字符串中只出现26个小写字母 const int maxm=100000; struct treenode{ bool end; //标志此节点是否是某字符串的结尾 treenode* next[maxn]; }head; treenode memory[maxm]; //字典树所用到的数组空间 int mallocp=0; //模拟内存分配 void init(){ head.end=1; for(int i=0;i<maxn;i++) head.next[i]=NULL; } treenode* createnew(){ treenode* newnode; newnode=&memory[mallocp++]; newnode->end=0; for(int i=0;i<maxn;i++) newnode->next[i]=NULL; return newnode; } void update(char* s){ int k=0,temp; treenode* t=&head; while(s[k]){ temp=charmapping[s[k]]; if(!
yum update更新一半挂了,会有很多软件包留在仓库,引起各种各样的问题 首先
yum clean all 安装 package-cleanup工具,有下面命令就不需要安装了,有的系统会自带
yum install yum-utils 然后更新一下仓库 package-cleanup --cleandupes 现在yum 应该就恢复正常了
继续yum update 可能会有问题,没有请忽略 根据提示是systemtap这个软件引起的,
rpm -qa |grep systemtap 所以我暂时卸载掉了他的几个包
rpm -e systemtap-2.8-10.el7.x86_64 systemtap-devel-2.8-10.el7.x86_64 发现问题解决 这个软件貌似是监控程序,卸载不会引起问题,待观察中
再次更新仓库: package-cleanup --cleandupes 重新安装systemtap
yum install -y systemtap 成功
下载安装nginx 进入nginx官网下载稳定版本zip,解压之后即可使用nginx了 nginx启动方式(三种) 1.进入nginx所在目录的命令行,输入nginx即可启动nginx,只是当前命令行会锁定 2.进入nginx所在目录的命令,输入start nginx即可启动nginx,相对于第一种来说不会锁定当前命令行 3.直接双击nginx所在目录的exe应用程序即可启动nginx 启动之后将会在任务管理器中看到两个nginx.exe进程,分别代表着master process和worker process,其中work_process由自己在配置文件中指定数量 nginx的常用命令 1.nginx -v查看nginx的版本 2.nginx -t查看配置文件是否出错,常用于在修改配置文件之后重启nginx之前对其进行检查 3.nginx.exe -s quit 快速停止nginx服务,可能并不会保存相关的信息 4.nginx.exe -s stop 在保存了相关的数据信息之后完整有序的停止nginx服务 5.nginx.exe -s reload重启nginx服务 6.nginx.exe -s reopen 重新打开日志文件 nginx配置文件详解 参考博客Nginx配置文件nginx.conf中文详解 为防止博主删除,现将原文拷贝成代码: #定义Nginx运行的用户和用户组 user www www; #nginx进程数,建议设置为等于CPU总核心数。 worker_processes 8; #全局错误日志定义类型,[ debug | info | notice | warn | error | crit ] error_log /var/log/nginx/error.log info; #进程文件 pid /var/run/nginx.pid; #一个nginx进程打开的最多文件描述符数目,理论值应该是最多打开文件数(系统的值ulimit -n)与nginx进程数相除,但是nginx分配请求并不均匀,所以建议与ulimit -n的值保持一致。 worker_rlimit_nofile 65535; #工作模式与连接数上限 events { # 参考事件模型,use [ kqueue | rtsig | epoll | /dev/poll | select | poll ]; epoll模型是Linux 2.
内存是程序得以运行的重要物质基础。如何在有限的内存空间运行较大的应用程序,曾是困扰人们的一个难题。为解决这个问题,人们设计了许多的方案,其中最成功的当属虚拟内存技术。Linux作为一个以通用为目的的现代大型操作系统,当然也毫不例外的采用了优点甚多的虚拟内存技术。
虚拟内存 为了运行比实际物理内存容量还要大的程序,包括Linux在内的所有现代操作系统几乎毫无例外的都采用了虚拟内存技术。虚拟内存技术,可让系统看上去具有比实际物理意义内存大的多的内存空间,并为实现多道程序的执行创造了条件。
虚拟内存的概念 总所周知,为了对内存中的存储单元进行识别,内存中的每一个存储单元都必须有一个确切的地址。而一台计算机的处理器能访问多大的内存空间就取决于处理器的程序计数器,该计数器的字长越长,能访问的空间就越大。
例如:对于程序计数器位数为32位的处理器来说,他的地址发生器所能发出的地址数目为2^32=4G个,于是这个处理器所能访问的最大内存空间就是4G。在计算机技术中,这个值就叫做处理器的寻址空间或寻址能力。
照理说,为了充分利用处理器的寻址空间,就应按照处理器的最大寻址来为其分配系统的内存。如果处理器具有32位程序计数器,那么就应该按照下图的方式,为其配备4G的内存:
这样,处理器所发出的每一个地址都会有一个真实的物理存储单元与之对应;同时,每一个物理存储单元都有唯一的地址与之对应。这显然是一种最理想的情况。
但遗憾的是,实际上计算机所配置内存的实际空间常常小于处理器的寻址范围,这是就会因处理器的一部分寻址空间没有对应的物理存储单元,从而导致处理器寻址能力的浪费。例如:如下图的系统中,具有32位寻址能力的处理器只配置了256M的内存储器,这就会造成大量的浪费:
另外,还有一些处理器因外部地址线的根数小于处理器程序计数器的位数,而使地址总线的根数不满足处理器的寻址范围,从而处理器的其余寻址能力也就被浪费了。例如:Intel8086处理器的程序计数器位32位,而处理器芯片的外部地址总线只有20根,所以它所能配置的最大内存为1MB:
在实际的应用中,如果需要运行的应用程序比较小,所需内存容量小于计算机实际所配置的内存空间,自然不会出什么问题。但是,目前很多的应用程序都比较大,计算机实际所配置的内存空间无法满足。
实践和研究都证明:一个应用程序总是逐段被运行的,而且在一段时间内会稳定运行在某一段程序里。
这也就出现了一个方法:如下图所示,把要运行的那一段程序自辅存复制到内存中来运行,而其他暂时不运行的程序段就让它仍然留在辅存。
当需要执行另一端尚未在内存的程序段(如程序段2),如下图所示,就可以把内存中程序段1的副本复制回辅存,在内存腾出必要的空间后,再把辅存中的程序段2复制到内存空间来执行即可:
在计算机技术中,把内存中的程序段复制回辅存的做法叫做“换出”,而把辅存中程序段映射到内存的做法叫做“换入”。经过不断有目的的换入和换出,处理器就可以运行一个大于实际物理内存的应用程序了。或者说,处理器似乎是拥有了一个大于实际物理内存的内存空间。于是,这个存储空间叫做虚拟内存空间,而把真正的内存叫做实际物理内存,或简称为物理内存。
那么对于一台真实的计算机来说,它的虚拟内存空间又有多大呢?计算机虚拟内存空间的大小是由程序计数器的寻址能力来决定的。例如:在程序计数器的位数为32的处理器中,它的虚拟内存空间就为4GB。
可见,如果一个系统采用了虚拟内存技术,那么它就存在着两个内存空间:虚拟内存空间和物理内存空间。虚拟内存空间中的地址叫做“虚拟地址”;而实际物理内存空间中的地址叫做“实际物理地址”或“物理地址”。处理器运算器和应用程序设计人员看到的只是虚拟内存空间和虚拟地址,而处理器片外的地址总线看到的只是物理地址空间和物理地址。
由于存在两个内存地址,因此一个应用程序从编写到被执行,需要进行两次映射。第一次是映射到虚拟内存空间,第二次时映射到物理内存空间。在计算机系统中,第两次映射的工作是由硬件和软件共同来完成的。承担这个任务的硬件部分叫做存储管理单元MMU,软件部分就是操作系统的内存管理模块了。
在映射工作中,为了记录程序段占用物理内存的情况,操作系统的内存管理模块需要建立一个表格,该表格以虚拟地址为索引,记录了程序段所占用的物理内存的物理地址。这个虚拟地址/物理地址记录表便是存储管理单元MMU把虚拟地址转化为实际物理地址的依据,记录表与存储管理单元MMU的作用如下图所示:
综上所述,虚拟内存技术的实现,是建立在应用程序可以分成段,并且具有“在任何时候正在使用的信息总是所有存储信息的一小部分”的局部特性基础上的。它是通过用辅存空间模拟RAM来实现的一种使机器的作业地址空间大于实际内存的技术。
从处理器运算装置和程序设计人员的角度来看,它面对的是一个用MMU、映射记录表和物理内存封装起来的一个虚拟内存空间,这个存储空间的大小取决于处理器程序计数器的寻址空间。
可见,程序映射表是实现虚拟内存的技术关键,它可给系统带来如下特点:
系统中每一个程序各自都有一个大小与处理器寻址空间相等的虚拟内存空间;在一个具体时刻,处理器只能使用其中一个程序的映射记录表,因此它只看到多个程序虚存空间中的一个,这样就保证了各个程序的虚存空间时互不相扰、各自独立的;使用程序映射表可方便地实现物理内存的共享。 Linux的虚拟内存技术 以存储单元为单位来管理显然不现实,因此Linux把虚存空间分成若干个大小相等的存储分区,Linux把这样的分区叫做页。为了换入、换出的方便,物理内存也就按也得大小分成若干个块。由于物理内存中的块空间是用来容纳虚存页的容器,所以物理内存中的块叫做页框。页与页框是Linux实现虚拟内存技术的基础。
虚拟内存的页、物理内存的页框及页表 在Linux中,页与页框的大小一般为4KB。当然,根据系统和应用的不同,页与页框的大小也可有所变化。
物理内存和虚拟内存被分成了页框与页之后,其存储单元原来的地址都被自然地分成了两段,并且这两段各自代表着不同的意义:高位段分别叫做页框码和页码,它们是识别页框和页的编码;低位段分别叫做页框偏移量和页内偏移量,它们是存储单元在页框和页内的地址编码。下图就是两段虚拟内存和物理内存分页之后的情况:
为了使系统可以正确的访问虚存页在对应页框中的映像,在把一个页映射到某个页框上的同时,就必须把页码和存放该页映像的页框码填入一个叫做页表的表项中。这个页表就是之前提到的映射记录表。一个页表的示意图如下所示:
页模式下,虚拟地址、物理地址转换关系的示意图如下所示:
也就是说:处理器遇到的地址都是虚拟地址。虚拟地址和物理地址都分成页码(页框码)和偏移值两部分。在由虚拟地址转化成物理地址的过程中,偏移值不变。而页码和页框码之间的映射就在一个映射记录表——页表中。
请页与交换 虚存页面到物理页框的映射叫做页面的加载。
当处理器试图访问一个虚存页面时,首先到页表中去查询该页是否已映射到物理页框中,并记录在页表中。如果在,则MMU会把页码转换成页框码,并加上虚拟地址提供的页内偏移量形成物理地址后去访问物理内存;如果不在,则意味着该虚存页面还没有被载入内存,这时MMU就会通知操作系统:发生了一个页面访问错误(页面错误),接下来系统会启动所谓的“请页”机制,即调用相应的系统操作函数,判断该虚拟地址是否为有效地址。
如果是有效的地址,就从虚拟内存中将该地址指向的页面读入到内存中的一个空闲页框中,并在页表中添加上相对应的表项,最后处理器将从发生页面错误的地方重新开始运行;如果是无效的地址,则表明进程在试图访问一个不存在的虚拟地址,此时操作系统将终止此次访问。
当然,也存在这样的情况:在请页成功之后,内存中已没有空闲物理页框了。这是,系统必须启动所谓地“交换”机制,即调用相应的内核操作函数,在物理页框中寻找一个当前不再使用或者近期可能不会用到的页面所占据的页框。找到后,就把其中的页移出,以装载新的页面。对移出页面根据两种情况来处理:如果该页未被修改过,则删除它;如果该页曾经被修改过,则系统必须将该页写回辅存。
系统请页的处理过程如下所示:
为了公平地选择将要从系统中抛弃的页面,Linux系统使用最近最少使用(LRU)页面的衰老算法。这种策略根据系统中每个页面被访问的频率,为物理页框中的页面设置了一个叫做年龄的属性。页面被访问的次数越多,则页面的年龄最小;相反,则越大。而年龄较大的页面就是待换出页面的最佳候选者。
快表 在系统每次访问虚存页时,都要在内存的所有页表中寻找该页的页框,这是一个很费时间的工作。但是,人们发现,系统一旦访问了某一个页,那么系统就会在一段时间内稳定地工作在这个页上。所以,为了提高访问页表的速度,系统还配备了一组正好能容纳一个页表的硬件寄存器,这样当系统再访问虚存时,就首先到这组硬件寄存器中去访问,系统速度就快多了。这组存放当前页表的寄存器叫做快表。
总之,使用虚拟存储技术时,处理器必须配备一些硬件来承担内存管理的一部分任务。承担内存管理任务的硬件部分叫做存储管理单元MMU。存储管理单元MMU的工作过程如下图所示:
页的共享 在多程序系统中,常常有多个程序需要共享同一段代码或数据的情况。在分页管理的存储器中,这个事情很好办:让多个程序共享同一个页面即可。
具体的方法是:使这些相关程序的虚拟空间的页面在页表中指向内存中的同一个页框。这样,当程序运行并访问这些相关页面时,就都是对同一个页框中的页面进行访问,而该页框中的页就被这些程序所共享。下图是3个程序共享一个页面的例子:
页的保护 由上可知,页表实际上是由虚拟空间转到物理空间的入口。因此,为了保护页面内容不被没有该页面访问权限的程序所破坏,就应在页表的表项中设置一些访问控制字段,用于指明对应页面中的内容允许何种操作,从而禁止非法访问。
下图是页表项中存放控制信息的一种可能的形式:
注意:其中的PCD位表示着是否允许高速缓存(cache)。
如果程序对一个页试图进行一个该页控制字段所不允许的操作,则会引起操作系统的一次中断——非法访问中断,并拒绝这种操作,从而保护该页的内容不被破坏。
多级页表 需要注意的是,页表是操作系统创建的用于内存管理的表格。因此,一个程序在运行时,其页表也要存放到内存空间。如果一个程序只需要一个页表,则不会有什么问题。但如果,程序的虚拟空间很大的话,就会出现一个比较大的问题。
比如:一个程序的虚拟空间为4GB,页表以4KB为一页,那么这个程序空间就是1M页。为了存储这1M页的页指针,那么这个页表的长度就相当大了,对内存的负担也很大了。所以,最好对页表也进行分页存储,在程序运行时只把需要的页复制到内存,而暂时不需要的页就让它留在辅存中。为了管理这些页表页,还要建立一个记录页表页首地址的页目录表,于是单级页表就变成了二级页表。二级页表的地址转换如下图所示:
当然,如果程序的虚拟空间更大,那么也可以用三级页表来管理。为了具有通用性,Linux系统使用了三级页表结构:页目录(Page Directory,PGD)、中间页目录(Page Middle Directory,PMD)、页表(Page Table,PTE)。
Linux的页表结构 为了通用,Linux系统使用了三级页表结构:页目录、中间页目录和页表。PGD为顶级页表,是一个pgd_t数据类型(定义在文件linux/include/page.h中)的数组,每个数组元素指向一个中间页目录;PMD为二级页表,是一个pmd_t数据结构的数组,每个数组元素指向一个页表;PTE则是页表,是一个pte_t数据类型的数组,每个元素中含有物理地址。
为了应用上的灵活,Linux使用一系列的宏来掩盖各种平台的细节。用户可以在配置文件config中根据自己的需要对页表进行配置,以决定是使用三级页表还是使用二级页表。
在系统编译时,会根据配置文件config中的配置,把目录include/asm符号连接到具体CPU专用的文件目录中。例如,对于i386CPU,该目录符号会连接到include/asm-i386,并在文件pgable-2level-defs.h中定义了二级页表的基本结构,如下图:
其中还定义了:
#define PGDIR_SHIFT 22 //PGD在线性地址中的起始地址为bit22 #define PTRS_PER_PGD 1024 //PGD共有1024个表项 #define PTRS_PER_PTE 1024 //PTE共有1024个表项 #endif 在文件include/asm-i386/pgtable.
原文路径:https://www.cnblogs.com/sungong1987/p/6873409.html
MySQL如果频繁的修改一个表的数据,那么这么表会被锁死。造成假死现象。
比如用Navicat等连接工具操作,Navicat会直接未响应,只能强制关闭软件,但是重启后依然无效。解决办法:
首先执行:
show full processlist; //列出当前的操作process,一般会看到很多waiting的process,说明已经有卡住的proces了,我们要杀死这些process!!
再执行:
kill processid; //processid表示process的id,比如kill 3301,就会将id为3301的process杀死。
使用 kill 将所有的 id 杀死。然后重启MySQL,一般就会解决了。
重启MySQL:
net stop mysql //停止MySQL
net start mysql //启动MySQL
情景引入 小白:起床起床起床起床。。。。快起床~
我:怎么了又,大惊小怪,吓到我了。
小白:我有事有事想找你,十万火急呢~~
我:你能有什么事?反正我不信。。那你说说看~~
小白:就是我有两个小表弟,叫大白和二白,他们现在每天睡觉之前都要分别和我聊天,让我给他们讲故事,如果不讲他们就不睡觉。但是,如果一个个的跟他们轮流来说的话,我就需要每天说两遍,而且我还要找准他们的时间点,这个有时候我有事情都无法实现这个问题,他们就会很生气。。。
我:这不是挺好的嘛,小孩子就是爱听故事的呀。。。
小白:我也愿意讲,但是时间这个不是很好控制,有没有类似,比如我可以之前就描述好了,然后定点给他们两个一起发消息,而可以抛开时间和其他因素的影响呢?
我:这个嘛,很简单呀,你可以让他们关注你的一个公众号,这样你再定时的推送给他们故事不就可以了嘛。。或者,你可以拉他们进你的一个群这样,就方便了呀~
小白:这样是可以,但是如果以后还有小表妹要听我讲,我就要如此反复的做。。感谢好麻烦好麻烦。。。
我:emmm,我理解你的意思,你就想实现一种很多人都能够进行类似一种消息推送的方式嘛。。。
小白:对的对的。。就是这样一种,,,我记得我们在技术方面好像也有一种类似的技术,这个叫做什么去了呢?
我:这就是消息中间件,一种生产者和消费者的关系。
小白:我也想学我也想学,,你快给我讲讲,给我讲讲呗。。
我:真拿你没办法,好吧。。。下面我就给你讲一下这方面的知识。
#情景分析
其实,小白的这个问题,是一种比较普遍的问题。既然我们作为技术人员,当然我们就要从技术成分去分析如何解决了。这里面其实就是包含着一种消息中间件的技术。它也是最近技术层面用得非常非常多的,这也是非常值得我们进行学习。。这在如今的秒杀系统,推荐系统等等,都有广泛的应用。。所以,这章我就主要来跟大家说说这方面的知识。
#基本概念的引导
本模块主要讲解关于消息中间件的相关基础知识,也是方便我们后面的学习。 ###什么是中间件?
非操作系统软件,非业务应用软件,不是直接给最终用户使用,不能直接给用户带来价值的软件,我们就可以称为中间件(比如Dubbo,Tomcat,Jetty,Jboss都是属于的)。
什么是消息中间件? 百度百科解释:消息中间件利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型,它可以在分布式环境下扩展进程间的通信。
关键点:关注于数据的发送和接受,利用高效可靠的异步消息机制传递机制集成分布式系统。
先简单的用下面这个图说明:
为什么要使用消息中间件 举几个例子,我想你就会明白了。(其实使用消息中间件主要就是为了解耦合和异步两个作用)
1:微博,都用过吧。那么,当我们新关注一个用户,那么系统会相应的推送消息给我们,并且还做了很多关于我们关注的处理。这就是消息中间件的异步。
2:秒杀系统。100件商品,几十万个人在抢,那这个怎么弄呢?总不能就把服务器给宕机了吧。那么就可以把用户的请求进行缓存,然后再异步处理。
3:系统A给系统B进行通信,而系统B需要对A的消息进行相应处理之后才能给A反馈,这时候,总不能让A就傻傻等着吧。那么,这就是异步的功能。
###什么是JMS?
Java消息服务(Java Message Service)应用程序接口是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。
总结起来说就是:Java对于应用程序之间进行信息交互的API(而且是异步)。
里面有下面的概念需要理解,对后续有帮助:
提供者:实现JMS的消息服务中间件服务器。客户端:发送或接受消息的应用。生产者/发布者:创建并发送消息的客户端。消费者/订阅者:接受并处理消息的客户端。消息:应用程序之间传递的数据。消息模式:在客户端之间传递消息的模式,JMS主要是队列模式和主体模式。队列模式特点:
(1)客户端包括生产者和消费者。
(2)队列中的一个消息只能被一个消费者使用。
(3)消费者可以随时取消息。主体模式特点:
(1)客户端包括发布者和订阅者。
(2)主题中的消息可以被所有订阅者消费。
(3)消费者不能消费订阅之前发送的消息。 什么是AMQP? AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。
简单点说:就是对于消息中间件所接受的消息传输层的协议(不懂传输层,那么就需要多看看计算机网络相关知识了,OSI的层次划分),只有这样才能保证客户端和消息中间件能够进行交互(换位思考:HTTP和HTTPS甚至说是TCP/IP与UDP协议都要的道理)。
emmm,比较一下JMS和AMQP的不同吧。。
JMS是定义与Java,而AMQP是一种传输层协议。JMS是属于Java的API,而AMQP是跨语言的。JMS消息类型只有两种(主题和队列,后续会说),而AMQP是有五种。JMS主要就是针对Java的开发的Client,而AMQP是面向消息,队列,路由。 什么是ActiveMQ呢? ActiveMQ 是Apache出品,最流行的,能力强劲的开源消息总线。ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现,尽管JMS规范出台已经是很久的事情了,但是JMS在当今的J2EE应用中间仍然扮演着特殊的地位。
简单点说:不就是为了实现我上述所想要的需求嘛。然后它就是一种实现的方式。就比如,Tomcat是什么?不就是为了实现一种client与服务器之间的交互的一种产品嘛。。所以,不需要死记概念,自己理解就好。
ActiveMQ的安装 环境:Windows 步骤:
(1)登录到ActiveMQ的官网,下载安装包。http://activemq.apache.org/activemq-5154-release.html
(2)下载Zip文件
(3)解压Zip文件,目录如下
(4)启动ActiveMQ服务(注意:要右键以管理员身份进行运行)
注意:有两种方式,第一种就是类似tomcat启动,那么启动图会一直显示。
而第二种的话,就是把这个ActiveMQ注册到服务列表中,这样更方便我们进行操作。(推荐使用这种)
(5)登录,验证是否启动成功