首页 文章 精选 留言 我的

精选列表

搜索[快速入门],共10000篇文章
优秀的个人博客,低调大师

图像处理之滤镜、图文智能排版开发详解,从入门到起飞

在上一篇👉如何给应用增加图片编辑功能:剪裁、滤镜、贴纸、排版、标签,我们详细讲解了“图片”风口下,各类应用可能面临的“窘境”:由于应用内缺乏图片编辑设计能力,或者提供的图片编辑设计能力不足,导致原本功能强大的优质应用,没法提高用户留存率,影响应用自身竞争实力。华为图像服务旨在为各类交互应用解决这一问题,可以说是应时而生,应势而生。在详细介绍华为图像服务五大图像编辑能力分别是什么、各自的应用场景以及功能价值的基础上,接下来将为大家带来五大图片编辑功能的具体开发步骤。 滤镜服务 开发步骤 滤镜服务开发步骤如下,需要您提供图片和相关参数,得到相应的返回值。 1、导入滤镜服务包。 import com.huawei.hms.image.vision.*; import com.huawei.hms.image.vision.bean.ImageVisionResult; 2、获取滤镜服务实例。 // 获取ImageVisionImpl对象 ImageVisionImpl imageVisionAPI = ImageVision.getInstance(this); 3、服务初始化,调用setVisionCallBack时需要实现ImageVision.VisionCallBack接口,重写其中的onSuccess(int successCode)和onFailure(int errorCode)方法。 框架初始化成功后会回调onSuccess方法,在onSuccess方法中,需要再初始化滤镜服务。调用滤镜初始化接口时,只有通过校验,第三方应用才能使用滤镜服务,initCode必须为0,滤镜服务初始化成功。 框架初始化失败时会回调onFailure方法,传回失败错误码。 imageVisionAPI.setVisionCallBack(new ImageVision.VisionCallBack() { @Override public void onSuccess(int successCode) { int initCode = imageVisionAPI.init(context, authJson); ... } @Override public void onFailure(int errorCode) { ... } }); 入参authJson: 参数列表 类型 M/O(必选/可选) 说明 projectId String M 您在配置AppGallery Connect时得到的项目ID(agconnect-services.json文件中的product_id)。 appId String M 当前App的APPID(agconnect-services.json文件中的app_id)。 authApiKey String M 鉴权使用的Apikey(agconnect-services.json文件中的api_key)。 clientSecret String M 客户端秘钥(agconnect-services.json文件中的client_secret)。 clientId String M 客户端ID(agconnect-services.json文件中的client_id)。 token String O 会话token,用于验证第三方App身份,建议由您的服务器通过client_id+client_secret从AGC获取。 说明:图文智能排版服务,图像主题标签服务token为必选值,获取方式参见:token获取方式。 4、构建参数对象。 参数列表 类型 M/O(必选/可选) 说明 requestJson JSONObject M 图片处理请求参数。 imageBitmap Bitmap M 需要处理的图片(宽高比在1:3和3:1之间,宽高像素均不超过8000)。 requestJson字段信息: 参数列表 类型 M/O(必选/可选) 说明 requestId String O 业务提供的请求ID。 taskJson JSONObject M 具体的业务请求信息。 authJson JSONObject M 鉴权参数。 滤镜的taskJson字段信息: 参数列表 类型 M/O(必选/可选) 说明 filterType int O 颜色映射的图片索引,索引范围[0,24](0为原图)。 intensity float O 滤镜强度,取值范围[0,1.0],默认为1.0。 compressRate float O 压缩率,取值范围(0,1.0],默认为1.0。 filterType映射表: 1 2 3 4 5 6 7 8 9 10 11 12 黑白 棕调 慵懒 小苍兰 富士 桃粉 海盐 薄荷 蒹葭 复古 棉花糖 青苔 13 14 15 16 17 18 19 20 21 22 23 24 日光 时光 雾霾蓝 向日葵 硬朗 古铜黄 黑白调 黄绿调 黄调 绿调 青调 紫调 authJson映射表:请参见authJson表 滤镜服务requestJson示例: { "requestId":"requestId", "taskJson":{"intensity":"1","filterType":"1","compressRate":"1"}, "authJson":{"projectId":"projectIdTest","appId":"appIdTest","authApiKey":"authApiKeyTest","clientSecret":"CSecretTest","clientId":"CIdTest","token":"tokenTest"} } 5、滤镜服务获取结果。 您在调用getColorFilter接口时,需要输入待处理图片的Bitmap,并选择需要的滤镜效果。滤镜服务根据传入的参数对原始图片进行处理后,返回处理后图片的Bitmap。 // 获取visionResult返回值 new Thread(new Runnable() { @Override public void run() { ImageVisionResult visionResult = imageVisionAPI.getColorFilter(requestJson,imageBitmap); } }).start(); visionResult返回值: 参数列表 类型 M/O(必选/可选) 说明 resultCode int M 服务状态码。 response JSONObject O 服务返回的结果。 image Bitmap O 处理后的图片数据,对于直接返回图片的API,通过该字段传递处理后的图片数据。 response字段信息: 参数列表 类型 M/O(必选/可选) 说明 requestId String O 业务提供的请求id(如果请求携带此参数,则返回此参数值)。 serviceId String M 调用的服务名。 说明:调用getColorFilter接口时,需要开启一个子线程去执行,不能在主线程执行。 6、停止服务。 当不再需要滤镜效果时,调用该接口停止服务,stopCode为0时,停止服务成功。 if (null != imageVisionAPI) { int stopCode = imageVisionAPI.stop(); } 图文智能排版服务 开发步骤 图文智能排版服务开发步骤如下,需要您提供图片和相关参数,得到相应的返回值。 1、导入图文智能排版服务包。 import com.huawei.hms.image.vision.bean.ImageLayoutInfo ; import com.huawei.hms.image.vision.*; 2、获取图文智能排版服务实例 // 获取ImageVisionImpl 对象 ImageVisionImpl imageVisionAPI = ImageVision.getInstance(this); 3、服务初始化,与滤镜服务一致,可参见开发步骤中的相关描述。 构建参数对象。 参数列表 类型 M/O(必选/可选) 说明 requestJson JSONObject M 图片处理请求参数。 imageBitmap Bitmap M 需要制作图文智能排版的图片(宽高比9:16)。 requestJson字段信息: 参数列表 类型 M/O(必选/可选) 说明 requestId String O 业务提供的请求ID。 taskJson JSONObject M 具体的业务请求信息。 authJson JSONObject M 鉴权参数。 图文智能排版的taskJson字段信息: 参数列表 类型 M/O(必选/可选) 说明 title String M 文案标题,必填字段,不超过7个中文汉字(总字符数量不超过10个),如果超过字数限制会被强制截断。 description String M 文案内容,不超过44个中文汉字(总字符数量不超过66个),超过字数限制则进行截断,用‘…’代替。 copyRight String O 图片版权归属的个人/公司名称,建议不超过7个中文汉字(总字符数量不超过10个),超过字数限制则进行截断,用‘…’代替。 anchor String O “详情”或“查看更多”,建议4个中文汉字(总字符数不超过6个)超过字数限制则进行截断,用‘…’代替。 isNeedMask boolean O 是否需要蒙层。 styleList构建参数 JSONArray O 样式列表,默认[],若为默认值,依据描述文本是否有换行符选择文本;若用户传入list,从用户给出的版式中选择。 取值范围['info1', 'info2', …, 'info9']。 说明 styleList中,info8为竖板排版,当前仅支持中文版式,不支持其他语言版式;info3为默认兜底版式;若用户输入info8且输入标签、文本描述有非中文语种,返回用户info3版式。 authJson映射表:请参见authJson字段定义。 图文智能排版服务requestJson示例: { "requestId":"requestId", "taskJson":{"title":"轻奢新生代","description":"远离城市的喧嚣","copyRight":"华为杂志锁屏","isNeedMask":false,"anchor":"查看详情","styleList":["info1"]}, "authJson":{"projectId":"projectIdTest","appId":"appIdTest","authApiKey":"authApiKeyTest","clientSecret":"CSecretTest","clientId":"CIdTest","token":"tokenTest"} } ImageLayoutInfo返回值: 参数列表 类型 M/O(必选/可选) 说明 resultCode int M 返回结果码。 viewGroup ViewGroup O 返回的目标view。 maskView View O 返回蒙层view(无蒙层时为null)。 response JSONObject O 返回结果。 response字段: 参数列表 类型 M/O(必选/可选) 说明 locationX int O 返回view位于手机的起始位置X。 locationY int O 返回view位于手机的起始位置Y。 maskColor int O 返回蒙层的颜色值。 colorHeigh int O 返回蒙层的高度(无蒙层时为0)。 requestId String O 业务提供的请求ID(如果请求时携带了就返回,没有携带就不返回)。 serviceId String M 调用的服务名。 说明:①因为接口涉及网络请求,需要开启子线程去调用接口。②使用图文智能排版服务时,您需要保证提供的token是有效的,否则无法使用该服务。③token的获取方式可参见token获取方式。 6、停止服务。 当不再需要图文智能排版效果时,调用该接口停止服务,stopCode为0时,执行成功。 if (null != imageVisionAPI) { int stopCode = imageVisionAPI.stop(); } 在下一篇中,将继续带来在应用中添加图像裁剪能力、图像主题标签能力、贴纸花字能力的开发详解,敬请期待~ >>访问华为图像服务官网,了解更多相关内容 >>获取华为图像服务开发指导文档 >>华为HMS Core官方论坛 >>华为图像服务开源仓库地址:GitHub、Gitee 点击右上角头像右方的关注,第一时间了解华为移动服务最新技术~

优秀的个人博客,低调大师

原创 | Git入门教程,详解Git文件的四大状态

点击上方蓝字,关注并星标,和我一起学技术。 大家好,欢迎来到周一git专题。 git clone 在上一篇文章当中我们聊了怎么在github当中创建一个属于自己的项目(repository),简称repo。除了建立自己的repo之外,我们更多的情况是拷贝别人的repo,这样才可以获得别人整理好的代码资料什么的,也更符合开源(白嫖)精神嘛。 这也不是什么难题,相信很多人都知道,当我们想要获取其他人的repo的时候,可以通过git clone命令进行拉取。比如你想要获取我们这个教程的repo,可以通过下面这个命令。 gitclonegit@github.com:moutsea/git-tutorial.git 这个命令我们都知道,通过git clone再加上repo的地址就可以了。但是这里的地址是哪里来的呢?简单介绍一下,这是在github当中找到的。我们点击Code那个绿色按钮,在下方的弹框里点击一下,就可以复制下来。一般情况下我们默认使用SSH协议,如果你看过我们上篇文章的话,你一定知道我在说什么。当然你也可以用HTTPS。 还有一个问题是我们clone下来的这个repo它存在哪里呢?答案也很简单,就是我们在哪里运行的命令它就存在哪里。 另外再说一个小技巧,我们这样clone下来之后会在我们本地新建一个文件夹,然后把这个repo当中的内容存在里面。这个文件夹的名字默认是这个repo的名字,如果你不喜欢这个名字,也可以在命令当中进行设置,设置的方法也很简单,就是在命令最后加上一个你想要起的名字。 比如这样,你得到的文件夹就是TechFlow。 gitclonegit@github.com:moutsea/git-tutorial.gittechflow git 四大状态 即使是git新手应该也都知道git三板斧,也就是常说的git add,git commit和git push。但是当我们使用这些命令的时候,有没有想过我们为什么要用这些命令呢?它们究竟代表了什么含义,这么做的意义是什么,如果我们不这么干又会发生什么? 如果我只是简单地告诉你git add就是添加,git commit就是提交,那么其实一点用也没有,和没说一样。因为关于git底层的运行机制一点也没提,我们也不知道为什么要添加,要提交,提交了添加了意味着什么。所以要解释清楚git这三板斧的原理,需要我们做一些更细致地解释,至少需要把git内部的四个状态讲清楚。 在我们进行这一段之前,首先和大家明确一个概念,就是git系统和我们计算机当中的文件系统其实是两码事。虽然git有很多神奇的操作,可以自由地回滚或者是创建文件,但它们依然是两套系统。git并不会自发地感知文件系统当中文件的变更,除非我们执行相关的命令。可以理解为它是被动响应的,毕竟git只是我们安装的一个软件,并不是操作系统的一部分。 这一点看似是废话,但是是很重要的基础,如果没搞明白,后面会产生很多疑惑。 我们继续来说git内部的状态,这四个状态分别是untrack,modified,committed和staged。之所以用英文,是为了大家以后阅读其他文档不会产生歧义。因为大家翻译的译名可能有多个版本,这会导致歧义。下面来简单介绍一下这几个状态分别意味着什么。 untrack 首先是untrack,untrack我们直译就可以了。track有轨道以及记录的意思,所以untrack就是还没记录。那么什么样的东西是还没记录的呢?比如可以想到新生儿,刚出生的新生儿名字都没有,当然也没有记录在案,所以需要登记一下人口。那么在登记之前,就可以认为这些新生儿是untrack的。 迁移到开发当中来,我们新创建的文件其实就是系统里的“新生儿”。在我们将它们记录在案之前,它们的状态就是untrack。所以当你在一个git项目当中新建了文件的时候,如果你用git status命令去查看git当中的状态,就会看到系统会提示你有些文件状态是untrack。 这里的展示是乱码,是因为我用的中文。这一串乱码就是“第三篇”的意思。我们可以注意到,在输出的结果最后一行,系统提示我们可以用git add命令来track它。这个也是git很人性化的一点,很多时候它会提醒我们可以使用什么命令做成什么样的事情。所以大家千万不要忽视这些日志,里面的信息是很重要的。 modified 下一个说的状态是modified,modified顾名思义就是修改过的意思。针对的就是已经登记在案的文件最近又发生了改动的情况,也就是说我们最近改过了某一个之前已经登记在案的文件,那么当我们查看状态的时候得到的就是modified,表示改动了,之前的记录已经不是最新的了,我们需要更新。 同样,我们可以通过git status命名来查看modified的情况。 我们看最下方的红字,它说的是“第三篇”这个文件我们已经有了新的改动,可以使用git add命令来将它更新,或者是使用git restore命令来取消这个文件的登记信息,也就是让他回到“新生儿”的状态。 staged 接下来介绍的状态是staged,它没有很好的翻译,可以大概理解成暂存。也就是说我们把所有的改动都记录下来了,现在git系统当中记录的已经是这个文件最新的状态了。 当我们创建了新的文件,或者是有了新的改动,执行git add之后,得到的状态就是staged。这个时候当我们执行git status,就会看到我们当下创建和更新了哪些文件。注意在所有的改动都暂存的情况下,git status是不会出现红色的提示的,只会有绿色的提示信息。 当然这里的文字之所以有颜色是因为我使用了zsh这个终端,如果不配置是没有的,就只能看到白色的的文字。zsh这个终端只在Linux和MacOS当中有,windows没有。而win的终端和系统一直被程序员们吐槽难用,建议有条件的同学可以研究一下Linux,装个虚拟机也好。 当我们终端没有颜色高亮的时候,就只能通过上面的文本来判断了,如果出现了Untracked files或者是Changes to be committed这些提示语的话,说明你还有改动没有同步到git当中来,可以通过git add命令完成。 committed 最后讲的一个状态就是committed,这个committed表示的已提交。前面说了staged只是暂存,还没有真正提交进git系统当中。只有通过命令git commit之后,才算是真正把暂存区的代码提交了。经过git commit命令之后,所有被提交的文件的状态就是committed。 这个时候如果我们执行git status再来查看,会看到提示nothing to commit, working tree clean. 这就表示我们所有的改动都已经提交进本地的git仓库当中了,以后及时我们不小心删错了代码,或者是做了一些修改。只要本地的git仓库还在,这些代码就都还可以找得回来。一直到这里为止,我们所有的操作都是离线的,都不需要网络参与。这也是我们前文说的git的一个优点之一。 git commit之后,我们就可以通过git push来把本地的改动同步到远程了。当然这一步是肯定需要网络的,并且也可能会遇到很多问题,其中也有很多细节,我们之后再详细展开。 我们用一张图来总结一下上面提到四种状态,以及git的整个工作流来加深一下印象。 总结 看完了上面关于git状态的介绍之后,想必大家就可以明白,我们在使用git的时候,最常用的三板斧也就是git add,git commit以及git push的命令究竟是干嘛的了。 git add可以把所有的改动,无论是修改的还是新建的都存入暂存区。git commit可以将暂存区的改动提交到本地git仓库,最后git push可以把本地仓库的改动同步到远端。看起来好像平平无奇对吧,但我们仔细琢磨会发现一个很奇怪的点,那就是既然我们git add和git commit都是提交,只不过是提交的目的地不同,一个是暂存区一个是本地仓库。那么为什么我们不能直接将它们合并呢?我们git add就是直接提交到本地仓库不行吗? 实际上SVN这个版本控制工具就是这么做的,但是这有一个问题就是当我们提交的时候,它会让我们选择我们要提交的文件。如果改动量小还好,如果改动量很大,我们要手动去一个一个输入需要提交的文件显然是一个非常麻烦的事情。而有了暂存区之后,我们就可以在开发的时候,一边开发一个边把文件提交到暂存区,最后直接一起commit到仓库就可以了。就可以避免最后提交之前的麻烦了,因为反正提交这个操作一定是原子的,要么全部成功,要么全部失败,是不允许部分成功这种情况发生的。 而且很多极客更加喜欢在终端环境当中操作代码,而不是在一个弹出来的界面里点点点,这会让他们觉得非常不极客(逼格太低)。有了暂存区之后就可以很方便地做到这一点。 到这里,我们的文章就结束了,感谢您的阅读。相信看完之后,对于git当中的状态以及它们的作用应该有了一个基础的了解,并且应该还学到了一个装逼技能,就是问你的小伙伴,你知道为什么git里有一个暂存区而SVN里没有吗?因为不极客。 衷心祝愿大家每天都有所收获。如果还喜欢今天的内容的话,请来一个三连支持吧~(点赞、在看、转发) - END - 本文分享自微信公众号 - TechFlow(techflow2019)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

从新手村开始,手把手带你入门梳理内核代码

作者:姜亚华(@二如公子),《精通 Linux 内核——智能设备开发核心技术》的作者,一直从事与 Linux 内核和 Linux 编程相关的工作,研究内核代码十多年,对多数模块的细节如数家珍。曾负责华为手机 Touch、Sensor 的驱动和软件优化(包括 Mate、荣耀等系列),以及 Intel 安卓平台 Camera 和 Sensor 的驱动开发(包括 Baytrail、Cherrytrail、Cherrytrail CR、Sofia 等)。现负责 DMA、Interrupt、Semaphore 等模块的优化与验证(包括 Vega、Navi 系列和多款 APU 产品)。 往期回顾: Java 离内核有多远? 在上一期内容中,我们介绍了从 JVM 到内核的编译原理,告诉大家应用和系统工程师如何接触到内核。本文将从一个简单的底层硬件模块入手,一步步教大家如何梳理内核代码。适合精力集中在内核,不太需要关心用户空间的工程师,比如驱动工程师、嵌入式工程师等,以及想往这方面学习发展的朋友。 初探内核 版本信息与往期一致: Ubuntu (lsb_release -a) Distributor ID: Ubuntu Description: Ubuntu 19.10 Release: 19.10 Linux (uname -a) Linux yahua 5.5.5 #1 SMP … x86_64 x86_64 x86_64 GNU/Linux 在往期的访谈中,我们讨论过如何阅读内核代码,在这里按照之前讨论的思路详细扩展下。 在 drivers/input/keyboard 下面的文件是键盘驱动,我们选择 lm8333.c 吧(没什么特殊理由,其他的也可以)。 找到 module_init,xxx_init,module_xxx,这些就是模块(驱动也是一种模块)的入口(进阶点 1,系统的启动过程),lm8333.c 内对应的是 module_i2c_driver(lm8333_driver),注册 driver。 lm8333_driver 定义如下: static struct i2c_driver lm8333_driver = { .driver = { .name = "lm8333", }, .probe = lm8333_probe, .remove = lm8333_remove, .id_table = lm8333_id, }; 驱动和设备匹配后,会回调 probe(进阶点 2,LinuxDevice Driver,LDD),也就是 lm8333_probe,它的关键代码如下: static int lm8333_probe(struct i2c_client *client, const struct i2c_device_id *id) //1 { const struct lm8333_platform_data *pdata = dev_get_platdata(&client->dev); struct lm8333 *lm8333; struct input_dev *input; lm8333 = kzalloc(sizeof(*lm8333), GFP_KERNEL); //7 input = input_allocate_device(); //8 lm8333->client = client; //10 lm8333->input = input; //11 input->name = client->name; //13 input->dev.parent = &client->dev; //14 input->id.bustype = BUS_I2C; //15 input_set_capability(input, EV_MSC, MSC_SCAN); //16 err = matrix_keypad_build_keymap(pdata->matrix_data, …, input); //18 if (pdata->debounce_time) { err = lm8333_write8(lm8333, LM8333_DEBOUNCE, pdata->debounce_time / 3); //22 } if (pdata->active_time) { err = lm8333_write8(lm8333, LM8333_ACTIVE, pdata->active_time / 3); //27 } err = request_threaded_irq(client->irq, NULL, lm8333_irq_thread, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "lm8333", lm8333); //32 err = input_register_device(input); //34 i2c_set_clientdata(client, lm8333); //36 return 0; } probe 的任务是驱动的初始化和设置,初学阶段,并不需要每一行代码都深入学习,可以先尝试将代码分类,以 lm8333_probe 为例。 第 1 行,函数的参数类型是固定的,背后是 LDD。 第 7 行,申请内存,背后是内存管理。暂且把它当成c语言的 malloc 也无妨。 第 8/13~16/18/34,input 相关,背后是 input 子系统。 第 22/27 行,写寄存器,背后是 i2c 总线。 第 32 行,request_threaded_irq,背后是中断处理。 这些背后的机制每一个都是一个进阶点。 初始化完毕,中断产生后,会调用 request_threaded_irq 时传递的 lm8333_irq_thread,继续梳理它的逻辑。 static irqreturn_t lm8333_irq_thread(int irq, void *data) { struct lm8333 *lm8333 = data; u8 status = lm8333_read8(lm8333, LM8333_READ_INT); if (!status) return IRQ_NONE; if (status & LM8333_ERROR_IRQ) { //省略 } if (status & LM8333_KEYPAD_IRQ) lm8333_key_handler(lm8333); return IRQ_HANDLED; } 可以看到 lm8333_irq_thread 先读寄存器来判断产生中断的原因,是 ERROR 还是 KEYPAD,如果是后者,调用 lm8333_key_handler。 static void lm8333_key_handler(struct lm8333 *lm8333) { struct input_dev *input = lm8333->input; u8 keys[LM8333_FIFO_TRANSFER_SIZE]; u8 code, pressed; int i, ret; ret = lm8333_read_block(lm8333, LM8333_FIFO_READ, LM8333_FIFO_TRANSFER_SIZE, keys); for (i = 0; i < LM8333_FIFO_TRANSFER_SIZE && keys[i]; i++) { pressed = keys[i] & 0x80; code = keys[i] & 0x7f; input_event(input, EV_MSC, MSC_SCAN, code); input_report_key(input, lm8333->keycodes[code], pressed); } input_sync(input); } lm8333_key_handler 读寄存器,然后根据寄存器的值判断实际的按键,调用 input_report_key 报告数据。 好了,lm8333.c 的逻辑我们清楚了:初始化、设置中断、读取数据并 report。 我们从 lm8333 的硬件角度看看,它是一个比较简单的芯片,datasheet(数据手册,下载地址)也并不复杂,摘取其中一段。 nACCESS.bus (I2C-compatible) communication interface to the host nFour general purpose host programmable I/O pins with two optional (slow) external Interrupts n16 byte FIFO buffer to store key pressed and key released events nHost programmable active time and debounce time 兼容 i2c 总线,支持中断,16 字节的 buffer,主机可编程有效时间和去抖时间。 再看看寄存器(这个文档称之为 command)表。 CMD DataBits Description 0x20 FIFO_READ 128 Read an event from the FIFO. Maximum 14 event codes stored in the FIFO. MSB = 1: key pressed. MSB = 0: key released. 0x22 DEBOUNCE 8 Default is 10 ms. Valid range 1255. Time ~ n x 4 ms … … … 再看看代码里出现的 i2c 读写的地址 LM8333_DEBOUNCE(0x22)和 LM8333_FIFO_READ(0x20)这些,这个表就是依据。 驱动做的事情可以分为两个方面,一方面是处理芯片本身的逻辑,比如中断、i2c、寄存器和时序等;另一方面是系统方面的,驱动和设备匹配、中断处理、数据传递(报告)等。 lm8333 比较简单,但复杂的芯片多如牛毛,所以驱动工程师也可以分为两类,一类比较专注于芯片本身的逻辑,另一类游到内核的大海中去了。 复杂的芯片本身就是一个完整的系统,成千上万的寄存器,错综复杂的模块,能将这些弄清楚也是有很大挑战的。除此之外,复杂的芯片很多都有配套的软件架构,比如 ISP(Camera)相关的 V4L2(VideoFor Linux 2),GPU 相关的 DRM(Direct Rendering Manager)。 很明显,芯片本身的逻辑并不是本文的重点,我们更关心如何游到内核。 再看 lm8333.c,大概清楚它的主要流程后,我们基本就算脱离新手村了,就像网游一样可以进入到下一阶副本了。回忆一下,在新手村,我们只需要识别出哪些函数属于其他模块,了解它们的基本原理,熟悉本身模块的逻辑即可。 进入第二个阶段,最好先从与日常工作关系最密切的模块入手。比如 lm8333,连接在 i2c 总线上,获取数据后通过 input 子系统 report,就可以从 i2c 和 input 入手。 学习 i2c 的过程中,还要解决 i2c 总线和 lm8333 的关系,这就涉及到 LDD。 深入 input 子系统的过程中,如果你对用户空间得到数据的过程感兴趣,就涉及到文件系统、poll/epoll 等。 当然了,在这个阶段,最好还是把文件系统这些复杂的模块当作黑盒。小碎步前进,不断有收获。 稍微复杂些的驱动可能还会有电源管理、工作队列和等待队列等机制,也可以在这个阶段内梳理它们的原理,至于它们背后的进程管理这些也可以先放放。 有了这一身装备,应付副本里的小 BOSS 也绰绰有余了,相比新手村那会也更有成就感,可以仗剑天涯了。 第三个阶段就是解决之前遗留的疑问了,将内存管理、文件系统和进程管理等一一拿下,比如 lm8333_probe 调用的 kzalloc、input 子系统涉及的 sysfs 文件系统、工作队列和中断处理相关的进程调度,一步步深入挖掘。 在之前的问答活动里我曾说过,“我已经把自己看过的代码的截图放在随书资料中了,算是一小段捷径吧。这些截图里面,某函数、它调用的函数等函数调用关系使用红线标示(如下图),内容包括内存管理、文件系统和进程管理三大模块。” 这些截图是随书资料,但并不是光盘那种。想要获取资料的朋友欢迎在下面评论留言,或者邮件(linux_kernel_os@163.com)联系我,有任何疑问也可以找我共同探讨。 往期回顾: Java 离内核有多远? 云计算时代,容器底层 cgroup 如何使用 云计算时代,容器底层 cgroup 的代码实现分析 云计算时代,容器底层 cgroup 如何实现资源分组?

优秀的个人博客,低调大师

知新 | koa框架入门到熟练第一章

介绍 koa,是基于Node.js 平台的下一代的web开发框架。是由Express原班人马打造,致力于成为一个更小的,更加富有表现力的,web框架。使用koa编写web应用,可以免除重复的回调函数嵌套,并极大的提高错误处理的效率,koa框架不仅仅在内核方法中可以绑定任何中间件,它仅仅提供了一个轻量级,优雅的函数库,思路和express相差不少。 koa框架的安装 安装koa 安装koa框架和安装之前的模块一样。使用如下命令安装 npm install --save koa 使用save参数,表明将会自动修改package.json 文件。自动添加依赖项 hello world 输入如下的代码,运行hello world const koa = require("koa"); const app = new koa(); // 配置中间件 app.use(async (ctx) => { ctx.body = "hello world"; }) // 监听端口 app.listen(3000); 运行文件 PS C:\Users\Administrator\IdeaProjects\untitled3> node ./ming.js 输出结果如下 异步的处理 由于js是单线程的,所以,使用回调函数处理异步等问题。 回调函数处理 const koa = require("koa"); const app = new koa(); function getData(callback){ setTimeout(function () { var name = "ming"; callback(name); }, 1000) } // 从外部获取异步方法里的数据 getData(function (data) { console.log(data) }) // 监听端口 app.listen(3000); 输出结果 PS C:\Users\Administrator\IdeaProjects\untitled3> node ./ming.js ming 使用promise处理异步 const koa = require("koa"); const app = new koa(); // promise 处理异步 // resolve 成功的回调函数 // reject 失败的回调函数 var p = new Promise(function (resolve, reject) { setTimeout(function () { var name = "张三"; }, 1000) }) // 获取异步的结果 p.then((data) => { console.log(data); }) // 监听端口 app.listen(3000); 运行结果如下 PS C:\Users\Administrator\IdeaProjects\untitled3> node ./ming.js ming 关于async await promise 其中async是异步的缩写,await被认为是async wait的缩写,所以,async用于申明一个函数为异步的,await用于等待一个异步方法执行完成。 简单理解 async 让方法变成异步await 等待异步方法执行完成。 async 实际例子 这里使用实际的例子,更好理解。 同步函数 function getData(){ return "ming"; } console,log(getData()) 输出结果 PS C:\Users\Administrator\IdeaProjects\untitled3> node ./ming.js ming async 可以让该方法变成异步 const koa = require("koa"); const app = new koa(); // promise 处理异步 // resolve 成功的回调函数 // reject 失败的回调函数 async function getData(){ return "这是一个数据"; } console.log(getData()); // 监听端口 app.listen(3000); 输出结果如下所示 其中promise为一个异步的数据 PS C:\Users\Administrator\IdeaProjects\untitled3> node ./ming.js Promise { '这是一个数据' } 获取该数据 const koa = require("koa"); const app = new koa(); // promise 处理异步 // resolve 成功的回调函数 // reject 失败的回调函数 async function getData(){ return "这是一个数据"; } var p = getData(); p.then((data) => { console.log(data); }) // 监听端口 app.listen(3000); 输出结果如图所示 PS C:\Users\Administrator\IdeaProjects\untitled3> node ./ming.js 这是一个数据 await 使用await方法获取到异步的信息 const koa = require("koa"); const app = new koa(); // promise 处理异步 // resolve 成功的回调函数 // reject 失败的回调函数 async function getData(){ return "这是一个数据"; } async function test(){ // 此时运行的为,发现该函数是一个异步函数,遇到了await进入等待状态,等待getData执行完毕,再往下执行 var d = await getData(); console.log(d) } test() // 监听端口 app.listen(3000); 运行结果 PS C:\Users\Administrator\IdeaProjects\untitled3> node ./ming.js 这是一个数据 koa 路由 路由是根据不同的url地址,加载不同页面实现不同的功能。 安装路由 npm install --save koa-router 使用路由 const koa = require("koa"); const app = new koa(); const router = require("koa-router")(); router.get("/", (ctx, next) => { ctx.body = "ming"; }) // 启动路由 app.use(router.routes()); app.use(router.allowedMethods()); // 监听端口 app.listen(3000); 运行结果如下所示 其中可以添加async作为异步方法来调用 const koa = require("koa"); const app = new koa(); const router = require("koa-router")(); router.get("/", async (ctx, next) => { ctx.body = "ming"; }) // 启动路由 app.use(router.routes()); // 设置响应头 app.use(router.allowedMethods()); // 监听端口 app.listen(3000); 获取链接的参数值 通过router获取路由的参数值链接为 获取格式化好的 http://localhost:3000/?ming=3 代码为 const koa = require("koa"); const app = new koa(); const router = require("koa-router")(); router.get("/", async (ctx, next) => { // query 返回的是格式化好的参数对象 // querystring 返回的是请求的字符串 console.log(ctx.query) ctx.body = "ming"; }) // 启动路由 app.use(router.routes()); // 设置响应头 app.use(router.allowedMethods()); // 监听端口 app.listen(3000); 访问的结果是 PS C:\Users\Administrator\IdeaProjects\untitled3> node ./ming.js [Object: null prototype] { ming: '3' } 获取未格式化好的 PS C:\Users\Administrator\IdeaProjects\untitled3> node ./ming.js ming=3 const koa = require("koa"); const app = new koa(); const router = require("koa-router")(); router.get("/", async (ctx, next) => { // query 返回的是格式化好的参数对象 // querystring 返回的是请求的字符串 console.log(ctx.querystring) ctx.body = "ming"; }) // 启动路由 app.use(router.routes()); // 设置响应头 app.use(router.allowedMethods()); // 监听端口 app.listen(3000); 设置动态路由 const koa = require("koa"); const app = new koa(); const router = require("koa-router")(); // 配置动态路由 router.get("/:id", async (ctx, next) => { // query 返回的是格式化好的参数对象 // querystring 返回的是请求的字符串 console.log(ctx.params) ctx.body = "ming"; }) // 启动路由 app.use(router.routes()); // 设置响应头 app.use(router.allowedMethods()); // 监听端口 app.listen(3000); 访问的链接为 http://localhost:3000/ming 输出的内容 PS C:\Users\Administrator\IdeaProjects\untitled3> node ./ming.js { id: 'ming' } koa 中间件 这里配置koa的中间件中间件就是匹配路由完成做的一系列的操作,把它称之为中间件。 中间件的功能主要有: 执行任何代码 修改请求和响应的对象 终结请求,响应循环 调用堆栈中的下一个中间件。 需求: 打印出中间件相关内容 const koa = require("koa"); const app = new koa(); const router = require("koa-router")(); // 中间件 app.use(async (ctx) => { ctx.body = "这是一个中间件"; }) // 配置动态路由 router.get("/:id", async (ctx, next) => { // query 返回的是格式化好的参数对象 // querystring 返回的是请求的字符串 console.log(ctx.params) ctx.body = "ming"; }) // 启动路由 app.use(router.routes()); // 设置响应头 app.use(router.allowedMethods()); // 监听端口 app.listen(3000); 运行结果 此时访问任何页面出现的都是这个内容, 持续匹配 因为访问的时候,没有加上next,此时造成的无法进入到匹配路由的阶段。 const koa = require("koa"); const app = new koa(); const router = require("koa-router")(); // 中间件 app.use(async (ctx, next) => { ctx.body = "这是一个中间件"; // 进入路由匹配 next(); }) // 配置动态路由 router.get("/:id", async (ctx, next) => { // query 返回的是格式化好的参数对象 // querystring 返回的是请求的字符串 console.log(ctx.params) ctx.body = "ming"; }) // 启动路由 app.use(router.routes()); // 设置响应头 app.use(router.allowedMethods()); // 监听端口 app.listen(3000); 由于js是单线程的,此时需要添加await,进行访问。 const koa = require("koa"); const app = new koa(); const router = require("koa-router")(); // 中间件 app.use(async (ctx, next) => { ctx.body = "这是一个中间件"; // 进入路由匹配 await next(); }) // 配置动态路由 router.get("/:id", async (ctx, next) => { // query 返回的是格式化好的参数对象 // querystring 返回的是请求的字符串 console.log(ctx.params) ctx.body = "ming"; }) // 启动路由 app.use(router.routes()); // 设置响应头 app.use(router.allowedMethods()); // 监听端口 app.listen(3000); 路由持续匹配 路由由于没有await next,造成路由匹配到以后就不再匹配,所以添加next,能把两个相同的路由按照顺序,匹配完成。 const koa = require("koa"); const app = new koa(); const router = require("koa-router")(); // 中间件 app.use(async (ctx, next) => { ctx.body = "这是一个中间件"; // 进入路由匹配 await next(); }) // 配置动态路由 router.get("/:id", async (ctx, next) => { // query 返回的是格式化好的参数对象 // querystring 返回的是请求的字符串 console.log(ctx.params) ctx.body = "ming"; await next(); }) router.get("/:id", async (ctx, next) => { // 此时匹配到这点 await next(); }) // 启动路由 app.use(router.routes()); // 设置响应头 app.use(router.allowedMethods()); // 监听端口 app.listen(3000); 中间件的执行顺序 一般是洋葱模型,作为中间件的执行顺序。 错误处理中间件 // 中间件 app.use(async (ctx, next) => { console.log("这是一个中间件"); // 进入洋葱 next() // 出洋葱 if(ctx.status = 404){ ctx.status = 404; ctx.body = "这是一个 404 页面"; } }) 第三方中间件 例如进行静态文件托管的时候,使用的是第三方中间件 const static = require('koa-static'); const staticPath = './static'; app.use(static( path.join(__dirname, staticPath); )) const bodyParser = require('koa-bodyparser'); app.use(bodyParser()); 这样就完成了静态文件托管

优秀的个人博客,低调大师

xmake从入门到精通10:多个子工程目标的依赖配置

xmake是一个基于Lua的轻量级现代化c/c++的项目构建工具,主要特点是:语法简单易上手,提供更加可读的项目维护,实现跨平台行为一致的构建体验。 本文主要详细讲解下,如果在一个项目中维护和生成多个目标文件的生成,以及它们之间的依赖关系设置。 项目源码 官方文档 target到底是什么? xmake的概念定义里,一个独立的项目工程可能会有多个子工程组织在一起,每个子工程对应只能生成一个唯一的目标文件,例如:可执行程序,静态库或者动态库等。 而这里所说的每个子工程就是xmake里面所说的target,字面意思就是目标子工程。 因此每个子工程,我们都可以通过新增一个target在xmake.lua里面维护,例如: target("test1") set_kind("binary") add_files("src/test1/*.c") target("test2") set_kind("binary") add_files("src/test2/*.c") 上面我们就定义了两个独立的子工程目标,编译时候会生成两个互不依赖的可执行文件。 从根域继承全局设置 暂时先不谈target间的依赖问题,如果我们有许多通用设置,每个target下都得设置一遍,那会非常冗余,也不好维护。 因此,我们可以把这些配置移到target域的外面,也就是根作用域中去设置,这样对当前xmake.lua以及所有子xmake.lua中的target都会生效,例如: add_links("tbox") add_linkdirs("lib") add_includedirs("include") target("test1") set_kind("binary") add_files("src/test1/*.c") target("test2") set_kind("binary") add_files("src/test2/*.c") 比如这两target都需要链接tbox库,放置在外层根域设置,test1和test2都能加上对应links。 目标间的依赖设置 那如果某个target需要用到另外一个tatget生成的静态库,应该怎么配置呢? 一种方式就是通过add_linkdirs和add_links手动指定对应target最后生成的目录库所在目录,然后把链接加上。 target("foo") set_kind("static") add_files("foo/*.c") add_defines("FOO") target("test1") set_kind("binary") add_includedirs("foo/inc") add_links("foo") add_linkdirs("$(buildir)") add_files("test1/*.c") add_defines("FOO") target("test2") set_kind("binary") add_includedirs("foo/inc") add_links("foo") add_linkdirs("$(buildir)") add_files("test2/*.c") add_defines("FOO") 上述配置中,test1和test2都会用到libfoo库,并且需要获取到libfoo库的头文件路径,库路径和链接,并且在使用过程中还需要额外设置-DFOO宏定义开关才行。 看上去没啥,其实这么写有两个问题: test目标和另外两个库目标之间是有编译顺序依赖的,如果test先编译就会提示链接库找不到 配置太过繁琐不好维护,test1和test2有很多冗余配置 那有没有更加简单可靠的配置方式呢,其实我们只需要add_deps来对target间配置上依赖关系即可。 target("foo") set_kind("static") add_files("*.c") add_defines("FOO", {public = true}) add_includedirs("foo/inc", {public = true}) target("test1") set_kind("binary") add_deps("foo") add_files("test1/*.c") target("test2") set_kind("binary") add_deps("foo") add_files("test2/*.c") 对比下,test1和test2的配置,是不是精简了好多?仅仅通过add_deps("foo")就继承了libfoo的所有导出设置:linkdirs, links, includedirs以及defines 其中target自身生成的库默认就会自动导出链接设置,而includedirs和defines通过设置public属性,我们也将它们标记为导出,这样可以被test目标继承到。 并且,现在有了依赖关系,xmake在编译的时候,会自动处理这些target之间的编译顺序,保证不会出现链接的时候,libfoo库还没有生成的问题。 依赖继承的进一步解析 级联依赖继承 根据上文所说,target会自动继承依赖目标中的配置和属性,不需要额外调用add_links, add_linkdirs和add_rpathdirs等接口去关联依赖目标了。 并且继承关系是支持级联的,例如: target("library1") set_kind("static") add_files("*.c") add_includedirs("inc") -- 默认私有头文件目录不会被继承 add_includedirs("inc1", {public = true}) -- 此处的头文件相关目录也会被继承 target("library2") set_kind("static") add_deps("library1") add_files("*.c") target("test") set_kind("binary") add_deps("library2") 上面的配置中,test依赖library2,然后library2又依赖library1,那么通过add_deps仅仅添加library2的依赖,test就可以完整继承整个依赖链上的所有导出设置。 禁用默认的继承行为 那如果我们不想继承依赖target的任何配置,如何操作呢? add_deps("dep1", "dep2", {inherit = false}) 通过显式设置inherit配置,来告诉xmake,这两个依赖的配置是否需要被继承,如果不设置,默认就是启用继承的。 可继承的导出属性详解 上文,我们还通过 add_includedirs("inc1", {public = true}), 设置public为true, 将includedirs的设置公开给其他依赖的子target继承。 目前对于target的编译链接flags相关接口设置,都是支持继承属性的,可以人为控制是否需要导出给其他target来依赖继承,目前支持的属性有: 属性 描述 private 默认设置,作为当前target的私有配置,不会被依赖的其他target所继承 public 公有配置,当前target,依赖的子target都会被设置 interface 接口设置,仅被依赖的子target所继承设置,当前target不参与 这个其实参考借鉴了cmake的设计,目前xmake中只要跟target相关的所有编译链接设置接口,都是支持可见性导出的,例如:add_includedirs, add_defines, add_cflags等等。 关于这块的详细信息,可以看下:https://github.com/xmake-io/xmake/issues/368 原文:https://tboox.org/cn/2019/12/13/quickstart-10-target-deps/ 个人主页 个人项目

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册