Quantcast
Channel: Sam的技术Blog
Viewing all 158 articles
Browse latest View live

Android6.0权限学习

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

很久没有做Android App了。最近在搞Camera程序时,突然发现Android6.0后, 权限思路使用有了较大不同。现记录,学习,摘抄如下: 

1. Android 6(API23)的动态权限: 
Android 6.0+ 版本, 不再像之前版本那样在安装阶段询问用户是否同意APP所要求的一大堆权限。 而是在APP执行危险行为前,向用户即时发送请求。若用户同意,才执行此危险操作。

在APP开发层面, Android 6.0之前,只需要在清单文件中添加权限即可。这些权限会在App安装时显示出来。
而在Android6.0和以上版本, 对危险权限,除了在清单文件添加权限,还需要在程序中动态申请。 比如在需要打开Camera时,需要动态申请权限。直到用户同意,才能正常使用。

普通权限: 

ACCESS_LOCATION_EXTRA_COMMANDS 定位权限

ACCESS_NETWORK_STATE 网络状态权限

ACCESS_NOTIFICATION_POLICY 通知 APP通知显示在状态栏

ACCESS_WIFI_STATE WiFi状态权限

BLUETOOTH 使用蓝牙权限

BLUETOOTH_ADMIN 控制蓝牙开关

BROADCAST_STICKY 粘性广播

CHANGE_NETWORK_STATE 改变网络状态

CHANGE_WIFI_MULTICAST_STATE 改变WiFi多播状态,应该是控制手机热点(猜测)

CHANGE_WIFI_STATE 控制WiFi开关,改变WiFi状态

DISABLE_KEYGUARD 改变键盘为不可用

EXPAND_STATUS_BAR 扩展bar的状态

GET_PACKAGE_SIZE 获取应用安装包大小

INTERNET 网络权限

KILL_BACKGROUND_PROCESSES 杀死后台进程

MODIFY_AUDIO_SETTINGS 改变音频输出设置

NFC 支付

READ_SYNC_SETTINGS 获取手机设置信息

READ_SYNC_STATS 数据统计

RECEIVE_BOOT_COMPLETED 监听启动广播

REORDER_TASKS 创建新栈

REQUEST_INSTALL_PACKAGES 安装应用程序

SET_TIME_ZONE 允许应用程序设置系统时间区域

SET_WALLPAPER 设置壁纸

SET_WALLPAPER_HINTS 设置壁纸上的提示信息,个性化语言

TRANSMIT_IR 红外发射

USE_FINGERPRINT 指纹识别

VIBRATE 震动

WAKE_LOCK 锁屏

WRITE_SYNC_SETTINGS 改变设置

SET_ALARM 设置警告提示

INSTALL_SHORTCUT 创建快捷方式

UNINSTALL_SHORTCUT 删除快捷方式

以上这些只是普通权限,我们开发的时候,正常使用就行了,需要的权限在清单文件配置即可。

 

危险权限

这类权限需要在需要的时候,需要我们动态申请,比如:当我们需要打开相机拍摄照片的时候需要我们通过代码的方式在需要的地方去申请权限。Android6.0中权限问题中我们需要注意的是:

1:由于权限API的问题,我们的Actiivty最好是AppCompatActivity类型的,也就是说在你的BaseActivity需要继承AppCompatActivity

2:权限是分组的,同一组的权限申请其中一个,同组的权限就全部都申请了

 

特殊权限 组:

CALENDAR 日历

CAMERA 相机

CONTACTS 联系人

LOCATION 定位

MICROPHONE 麦克相关,比如录音

PHONE 手机状态

SENSORS 传感器

SMS 短信

STORAGE 存储权限


  • int checkSelfPermission(String permission) 用来检测应用是否已经具有权限,这个方法是在API23中才有的,为了兼容低版本,建议使用v4包中的ContextCompat.checkSelfPermission,在下面的注意事项中有解释,这里就不在赘述了

  • void requestPermissions(String[] permissions, int requestCode) 进行请求单个或多个权限,第一个参数是请求的权限集合,第二个参数是请求码,在回调监听中可以用来判断是哪个权限请求的结果

  • void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 用户对请求作出响应后的回调,请求成功或者失败的监听

  • shouldShowRequestPermissionRationale这个API可以帮我们判断接下来的对话框是否包含”不再询问“选择框。在6.0之前的版本永远返回的是fasle



关于Camera, 有个项目很有意思: 
https://github.com/saki4510t/UVCCamera

 

Windows环境下OpenCV开发环境搭建(VS2012+OpenCV2.4.9)

$
0
0
作者: Sam (甄峰) sam_code@hotmail.com

1. 下载OpenCV For Windows Pack:
https://opencv.org/releases.html
因为在嵌入式平台上使用了OpenCV2.4.9. 所以在Windows平台也选择对应版本。 

2. 安装: 
OpenCV2.4.9 安装包其实是个压缩包:
Sam把它放到: D:\Local_Dev\opencv
在这个目录下,会包含两个子文件夹,分别是build, sources.
顾名思义: build包含了使用OpenCV时所需头文件,库文件。
sources则包含OpenCV的源码以及例子代码和文档。

3. 设置环境变量: 
有两个环境变量需要添加:
1. 把 opencv\build\x86\vc11\bin和opencv\build\x64\vc11\bin 添加到系统 PATH中。
2. 把opencv\build加入到用户变量opencv中。
分析如下: 
问题1: PATH环境变量要添加什么?
可执行文件查找路径,OpenCV有些可执行文件,在不同的时刻被调用。所以需要把他们的路径加入系统环境变量中。这样才能在需要时找到他们。
opencv_createsamples.exe, opencv_haartraining.exe,opencv_performance.exe,opencv_traincascade.exe

问题2:是添加X86目录下的,还是添加X64目录下的
对32Bit系统,添加X86目录下对应目录。 对64bit,则可以两个目录 (x86,x64)均添加。这样,就可以在编译x64和Win32之间随意切换了。 
总之,这里选择x86或x64,与本身系统关系不大,其实与编译的目标Target有关。

问题3:VS版本和OpenCV下vs10,vs11,vs12目录的对应关系: 
在OpenCV配置中,vc10对应的是vs2010, vc11对应的是VS2012, vc12对应的是vs2013.



为了能够一次配置OpenCV Include路径,Library路径,指定Library库这些动作。在VS2012上做了如下动作: 

4. 配置VS2012工程中的INCLUDE路径: 
VS2012中, 视图-->属性管理器  -->Debug|Win32 -->Microsoft.Cpp.Win32.user



通用属性 --> VC++目录 --> 包含目录:
添加 opencv\build\include,  opencv\build\include\opencv2, opencv\build\include\opencv



5. 配置VS2012工程中的Library路径: 
VS2012中, 视图-->属性管理器  -->Debug|Win32 -->Microsoft.Cpp.Win32.user
通用属性 --> VC++目录 --> 库目录 
在其中添加了opencv\build\x86\vc11\lib
这里又会有个抉择,要包含x86还是X64。 关键是看: 使用win32编译器还是x64编译器。
我们使用 win32编译器。 所以选择x86版本。





6. 链接库的配置: 
视图-->属性管理器  -->Debug|Win32 -->Microsoft.Cpp.Win32.user
通用属性 --> 链接器-->输入-->附加的依赖项
添加: 

opencv_ml249d.lib
opencv_calib3d249d.lib
opencv_contrib249d.lib
opencv_core249d.lib
opencv_features2d249d.lib
opencv_flann249d.lib
opencv_gpu249d.lib
opencv_highgui249d.lib
opencv_imgproc249d.lib
opencv_legacy249d.lib
opencv_objdetect249d.lib
opencv_ts249d.lib
opencv_video249d.lib
opencv_nonfree249d.lib
opencv_ocl249d.lib
opencv_photo249d.lib
opencv_stitching249d.lib
opencv_superres249d.lib
opencv_videostab249d.lib
opencv_objdetect249.lib
opencv_ts249.lib
opencv_video249.lib
opencv_nonfree249.lib
opencv_ocl249.lib
opencv_photo249.lib
opencv_stitching249.lib
opencv_superres249.lib
opencv_videostab249.lib
opencv_calib3d249.lib
opencv_contrib249.lib
opencv_core249.lib
opencv_features2d249.lib
opencv_flann249.lib
opencv_gpu249.lib
opencv_highgui249.lib
opencv_imgproc249.lib
opencv_legacy249.lib
opencv_ml249.lib



此时,全部设置已经完成,重启电脑。 打开OpenCV例子。就可以正常编译运行了。 





OpenCV 文档: 
http://www.opencv.org.cn/opencvdoc/2.3.2/html/genindex.html


 

AndroidCamera程序研究

$
0
0
作者: Sam (甄峰) sam_code@hotmail.com

Sam在Android平台下获取Camera数据,长期使用在NativeC层,利用V4L2接口获取Camera数据。或利用交叉编译OpenCV,并强行修改其底层, 使之使用V4L2接口而获得Camera数据。

在Android APP层面,在Android4.0之前,使用API获取Camera数据,只需要添加权限即可正常工作。在Android6.0时代,只知道需要动态权限,但没有真正使用过。 有一次和朋友交流时, 他提到Saki4510t/UVCCamera项目(https://github.com/saki4510t/UVCCamera)。Sam其实很疑惑为何需要用此种方式获取Camera数据。Android系统难道不允许APP去拿 Camera数据么?所以研究之。

1. 在Android 5.1.1平台利用系统自带Camera获取数据: 

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

intent.putExtra(MediaStore.EXTRA_OUTPUT, your-store-uri);
startActivity(intent);


利用putExtra指定存储路径,还可以指定其它Camera设置。拍照完成后,可以存指定路径获取照片或视频。

2.在Android7.1.2平台利用系统自带Camera获取数据

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);


利用putExtra指定存储路径,还可以指定其它Camera设置。拍照完成后,可以存指定路径获取照片或视频。
intent.putExtra(MediaStore.EXTRA_OUTPUT, your-store-uri);
startActivity(intent);


3.使用 android.hardware.Camera 在早期Android版本使用:


4. 使用android.hardware.Camera2 在Android6.0上使用(动态权限):



 

OpenCV4Android编译详解

$
0
0
作者: Sam(甄峰)  sam_code@hotmail.com

0. 背景简介: 
之前为Android平台分别编译过OpenCV2.0,  OpenCV2.4.20 OpenCV3.0等版本。并记录下来:
http://blog.sina.com.cn/s/blog_602f87700102wwvb.html
http://blog.sina.com.cn/s/blog_602f87700102vewh.html
http://blog.sina.com.cn/s/blog_602f87700102vdnw.html
http://blog.sina.com.cn/s/blog_602f87700101de4o.html
也为Linux编译过OpenCV各个版本: 
http://blog.sina.com.cn/s/blog_602f87700102wuv7.html

但当前遇到一些新要求: 
1. 使用clang而非g++编译。
2. 使用stlport而非gnustl.
考虑到NDK R18将不再支持GCC. 也强力推荐libc++. 所以这个研究也是必要的。

此次编译,
1. 将使用NDK R15C, NDK R10.  NDK R17b 等版本NDK。 但必须使用clang.
2. 分别编译OpenCV3.1.  OpenCV3.4.


1. OpenCV3.1编译脚本研究:
1.0. 目录结构和脚本入口
在OpenCV3.1 目录下,包含platforms/scripts. 这个目录存放着一系列编译脚本。我们为ARM平台的Android编译OpenCV。则使用cmake_android_arm.sh 这个脚本。
#cd platforms
# sh scripts/cmake_android_arm.sh

#!/bin/sh
cd `dirname $0`/..

mkdir -p build_android_arm
cd build_android_arm

cmake -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DCMAKE_TOOLCHAIN_FILE=../android/android.toolchain.cmake $@ ../..

讲解如下: 
cd 'dirname $0'/..
进入scripts的上一级目录。即platforms.

mkdir -p build_android_arm
cd build_android_arm
创建build_android_arm目录。并进入。

cmake -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DCMAKE_TOOLCHAIN_FILE=../android/android.toolchain.cmake $@ ../..
使用cmake编译。 使用的Makefile为../..下的CMakeLists.txt。
cmake $@ ../.. 
即把参数以列表形式传递进来。


1.1. CMAKE_TOOLCHAIN_FILE分析
1.1.1:
android/android.toolchain.cmake
它是ANdroid  CMake toolchain file. 并明确指出使用NDK 如-r10d.(这也许解释了为何使用R15C时,无法指定clang为编译器)

文件中讲解了如何对编译器相关的选项进行设置。
使用如下方法:
A:设置到环境变量中。
export
=
例如: 
export ANDOIRD_NDK=/opt/android-ndk-r10d
这个方法不一定起效。但Sam没看出原因。

B: 写入scripts/cmake_android_arm.sh
利用它传递进去。 
-D
=
例如,把如下内容加入cmake_android_arm.sh: 
-DANDROID_ABI=armeabi-v7a


1.1.2:设置选项
指定NDK目录
ANDROID_NDK=/opt/android-ndk-r10d

指定指令集:
ANDROID_ABI=armeabi-v7a
可选项有很多,armeabi, armeabi-v7a,  armeabi-v7a-hard with NEON,  armeabi-v7a with NEON, x86, mips, arm64-v8a. 等等。

指定Native  API Level:
ANDROID_NATIVE_API_LEVEL=android-24

指定ToolChain:
ANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-4.9

支持以下设置(脚本对R10C 以后的NDK处理的不够好,会导致Clang找不到):
#        * aarch64-linux-android-4.9
#        * aarch64-linux-android-clang3.4
#        * aarch64-linux-android-clang3.5
#        * arm-linux-androideabi-4.6
#        * arm-linux-androideabi-4.8
#        * arm-linux-androideabi-4.9 (default)
#        * arm-linux-androideabi-clang3.4
#        * arm-linux-androideabi-clang3.5
#        * mips64el-linux-android-4.9
#        * mips64el-linux-android-clang3.4
#        * mips64el-linux-android-clang3.5
#        * mipsel-linux-android-4.6
#        * mipsel-linux-android-4.8
#        * mipsel-linux-android-4.9
#        * mipsel-linux-android-clang3.4
#        * mipsel-linux-android-clang3.5
#        * x86-4.6
#        * x86-4.8
#        * x86-4.9
#        * x86-clang3.4
#        * x86-clang3.5
#        * x86_64-4.9
#        * x86_64-clang3.4
#        * x86_64-clang3.5

指定C++库:
ANDROID_STL=gnustl_shared

#      Possible values are:
#        none           -> Do not configure the runtime.
#        system         -> Use the default minimal system C++ runtime library.
#                          Implies -fno-rtti -fno-exceptions.
#                          Is not available for standalone toolchain.
#        system_re      -> Use the default minimal system C++ runtime library.
#                          Implies -frtti -fexceptions.
#                          Is not available for standalone toolchain.
#        gabi++_static  -> Use the GAbi++ runtime as a static library.
#                          Implies -frtti -fno-exceptions.
#                          Available for NDK r7 and newer.
#                          Is not available for standalone toolchain.
#        gabi++_shared  -> Use the GAbi++ runtime as a shared library.
#                          Implies -frtti -fno-exceptions.
#                          Available for NDK r7 and newer.
#                          Is not available for standalone toolchain.
#        stlport_static -> Use the STLport runtime as a static library.
#                          Implies -fno-rtti -fno-exceptions for NDK before r7.
#                          Implies -frtti -fno-exceptions for NDK r7 and newer.
#                          Is not available for standalone toolchain.
#        stlport_shared -> Use the STLport runtime as a shared library.
#                          Implies -fno-rtti -fno-exceptions for NDK before r7.
#                          Implies -frtti -fno-exceptions for NDK r7 and newer.
#                          Is not available for standalone toolchain.
#        gnustl_static  -> Use the GNU STL as a static library.
#                          Implies -frtti -fexceptions.
#        gnustl_shared  -> Use the GNU STL as a shared library.
#                          Implies -frtti -fno-exceptions.
#                          Available for NDK r7b and newer.
#                          Silently degrades to gnustl_static if not available.


1.2: cmake/OpenCVFindLibsPerf.cmake:
这个文件用来查找需要的库和头文件目录。
例如: 
platforms/scripts/cmake_android_arm.sh 中添加:
-DHAVE_EIGEN=1
此时 调用: sh scripts/cmake_android_arm.sh 
关于Eigen的结果有点异常:
--   Other third-party libraries:
--     Use Eigen:                   YES (ver ..)
没有得到版本号,则一定没有找到对应头文件等。查看CMakeLists.txt。以下几项内容缺失。
${EIGEN_WORLD_VERSION}.${EIGEN_MAJOR_VERSION}.${EIGEN_MINOR_VERSION}

则修改如下:
if(WITH_EIGEN)
  find_path(EIGEN_INCLUDE_PATH "Eigen/Core"
            PATHS /usr/local /opt /usr  ENV ProgramFiles ENV ProgramW6432
            PATH_SUFFIXES include/eigen3  /home/sam/work/current/Reaearch/OpenCV3.1/opencv-3.1.0/3rdparty/eigen
            DOC "The path to Eigen3/Eigen2 headers"
            CMAKE_FIND_ROOT_PATH_BOTH)

1.3:cmake/OpenCVDetectCXXCompiler.cmake
这里设置编译器FLAGS。
例如:
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
  set(CMAKE_COMPILER_IS_GNUCXX 1)
  set(CMAKE_COMPILER_IS_CLANGCXX 1)
endif()
if(CMAKE_C_COMPILER_ID STREQUAL "Clang")
  set(CMAKE_COMPILER_IS_GNUCC 1)
  set(CMAKE_COMPILER_IS_CLANGCC 1)
endif()
设置了Clang的Flags。

1.4:cmake/OpenCVCompilerOptions.cmake
设置编译选项。
例如: 如果想增加clang编译器编译时的编译选项。可以做如下动作:
if(CMAKE_COMPILER_IS_CLANGCXX)
add_extra_compiler_option(-std=c++11)
endif()
则把 -std=c++11加入了编译想选。










#!/bin/sh
cd `dirname $0`/..

mkdir -p build_android_arm
cd build_android_arm

cmake -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON -DHAVE_EIGEN=1 -DWITH_FFMPEG=ON -DHAVE_CAMV4L2=ON -DBUILD_TBB=ON -DWITH_TBB=ON -DBUILD_EXAMPLES=1  -DANDROID_STL=stlport_shared -DCMAKE_TOOLCHAIN_FILE=../android/android.toolchain.cmake $@ ../..

doctutorials/introduction/android_binary_package/android_ocl_intro.markdown.



 

NDK下各编译器对C++特性的支持

$
0
0
作者: Sam (甄峰)   sam_code@hotmail.com

Sam对C++并不擅长,但在使用NDK编译众多第三方库时,会遇到一些C++ 特性支持问题。通常的做法都是在编译选项中增加-std=c++11或者-std=c++0x.  但还是有一些特性无法通过编译。现分析如下。

0. 背景介绍:
NDK在R10之后,就推荐使用clang, 而非GCC.  后期版本更是把clang作为缺省编译器。同时,NDK下也提供多种C++库,包括: NDK自带的(system, system_re),  GAbi(gabi++_static, gabi++_shared), STLPort(stlport_static, stlport_shared), GNU-STL(gnustl_static, gnustl_shared). 

libstdc++(默认) 默认最小系统 C++ 运行时库。 不适用
gabi++_static GAbi++ 运行时(静态)。 C++ 异常和 RTTI
gabi++_shared GAbi++ 运行时(共享)。 C++ 异常和 RTTI
stlport_static STLport 运行时(静态)。 C++ 异常和 RTTI;标准库
stlport_shared STLport 运行时(共享)。 C++ 异常和 RTTI;标准库
gnustl_static GNU STL(静态)。 C++ 异常和 RTTI;标准库
gnustl_shared GNU STL(共享)。 C++ 异常和 RTTI;标准库
c++_static LLVM libc++ 运行时(静态)。 C++ 异常和 RTTI;标准库
c++_shared LLVM libc++ 运行时(共享)。 C++ 异常和 RTTI;标准库


1. C++11一些特性:
std::move 和 vector::data()支持测试:
1.1: 当使用clang+gnustl_shared 这个组合时。
std::move可以支持。

using namespace std;

int main(int argc, char** argv)
{
string str = "Hello World";
vector s_vet;
vector::iterator  it;

s_vet.push_back(str);
for(it = s_vet.begin(); it!=s_vet.end(); ++it)
{
cout << endl << *it << endl;
}

s_vet.push_back(std::move(str));
for(it = s_vet.begin(); it!=s_vet.end(); ++it)
{
cout << endl << *it << endl;
}

return 0;
}

可以看到,它在编译时,在/opt/android-ndk-r17b/sources/cxx-stl/gnu-libstdc++/4.9/include目录查找头文件。

1.2: clang + stlport:
不支持std::move。
可以看到,它在编译时,在 /opt/android-ndk-r17b/sources/cxx-stl/stlport/stlport目录中查找头文件。
它不支持std::move
网络文章说,NDK 下STLPort不支持C++11特性。


1.3: clang + C++_Shared
支持std::move
它在编译时,在 /opt/android-ndk-r17b/sources/cxx-stl/llvm-libc++/include目录中查找头文件。




 

Valgrind在Android平台的使用

$
0
0
作者: sam(甄峰)   sam_code@hotmail.com

几年前,Sam听同事说使用内存使用分析软件。但没有真正使用过。这次又有同事推荐Valgrind. 所以学习之。


 

利用ndk-gdb调试Android纯NativeC程序

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com



 

AndroidJNI记录

$
0
0
作者: Sam (甄峰)   sam_code@hotmail.com

很早之前,在Android 下使用OpenGL时,用到了JNI。当前看到同事使用JNI,生成方式不同。所以记录之。

1. 静态注册+NDK编译:
在NDK Sample中,有gles3jni , hello-gl2, hello-jni等例子程序。他们分别是:OpenGLES3.0, OpenGLES2.0等使用例子。无一例外都是Android 下创建JNI库和使用JNI库的例子。

这些例子采用静态注册,在C++代码中,通过 创建 的函数,提供给Java代码调用。并生成动态库。
; 载入库。
使用关键字 native声明该函数。并使用函数名()调用之。

这个方法的思路是: 首先提供C++代码,生成动态库,然后供Java代码使用。
使用Android.mk, Application.mk编译之。

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE     := libgles3jni
LOCAL_CFLAGS     := -Werror -DDYNAMIC_ES3
LOCAL_SRC_FILES  := xxx.cpp
LOCAL_LDLIBS     := -llog -lGLESv2 -lEGL

include $(BUILD_SHARED_LIBRARY)

这里生成了动态库。libgles3jni.so. 供Java 层使用。

2. 静态注册+javah方式
这个方法的思路是: 首先生成Java代码,再通过javah 生成头文件, 依据头文件编写C++代码,最后生成动态库。
A:生成java文件
package test;
public class HelloWorld {
    public static void main(String[] args){
        System.loadLibrary("HelloWorld");
        printHello();
    }
    public static native final void printHello();
}

B: 生成Class文件:
javac test/HelloWorld.java
test目录下将生成HelloWorld.class

C:生成头文件:
/javah -o test/Hello.h test.HelloWorld
test目录下将生成Hello.h

D: 创建C++源码文件:
#include "Hello.h"
#include

Void {
    printf("helloworld");
}

E: 生成库文件: 
 g++ test/HelloWorld.cpp -I ../jdk1.5.0_22/include/ -I ../jdk1.5.0_22/include/linux/ -fPIC -shared -o test/libHelloWorld.so
 test目录下将会生成libHelloWorld.so

F: 运行: 
java test.HelloWorld 
  屏幕上会打印出helloworld.

当然,这个方法是在X86机器上的路线图。 在Android ARM平台。则A,B,C,D可以照做。 但后面如何生成APK,则交给Eclipse或AS吧。

3.  动态加载+NDK编译
java上层的调用和静态方式是相同的,关键是native层的调用有所不同。
1.JNI_OnLoad()内容有所不同。
2.C++内方法名命名方式不同。


JNI_OnLoad是java jni技术的一个实现,每次java层加载System.loadLibrary之后,
自动会查找改库一个叫JNI_OnLoad的函数,动态注册的时候,cpp可以通过实现JNI_OnLoad而完成jni的动态注册。
//回调函数 在这里面注册函数
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){
    JNIEnv* env = NULL;
    //判断虚拟机状态是否有问题
    if(vm->GetEnv((void**)&env,JNI_VERSION_1_6)!= JNI_OK){
        return -1;
    }
    assert(env != NULL);
    //开始注册函数 registerNatives ->registerNativeMethods ->env->RegisterNatives
    if(!registerNatives(env)){
        return -1;
    }
    //返回jni 的版本
    return JNI_VERSION_1_6;
}

static int registerNatives(JNIEnv* env){
    //指定类的路径,通过FindClass 方法来找到对应的类
    const char* className  = "com/zienon/airmovesocketconnect/AirMoveGestureJni";
    return registerNativeMethods(env,className,getMethods, sizeof(getMethods)/ sizeof(getMethods[0]));
}

//此函数通过调用JNI中 RegisterNatives 方法来注册我们的函数
static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* getMethods,int methodsNum){
    jclass clazz;
    //找到声明native方法的类
    clazz = env->FindClass(className);
    if(clazz == NULL){
        return JNI_FALSE;
    }
    //注册函数 参数:java类 所要注册的函数数组 注册函数的个数
    if(env->RegisterNatives(clazz,getMethods,methodsNum) < 0){
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

这样,修改PackageName,ClassName就方便很多。函数名可以很短。


同样,使用NDK(Android.mk, Application.mk编译之)。 供Java使用。 


4. 动态加载 + AS CMake:




 

OpenCV底层对Camera的操作

$
0
0
作者: Sam (甄峰)   sam_code@hotmail.com

OpenCV提供接口,可以对Camera进行操作。


0.  OpenCV对Camera的操作所属目录文件:
OpenCV对Camera的操作,有多种方式。从VideoCapture class开始。

在modules/videoio/include/opencv2/videoio.hpp中,声明了一个class:
class CV_EXPORTS_W VideoCapture
{
};

它利用__attribute__ ((visibility ("default"))) 把class作为可见。


1. Camera读取接口:
OpenCV读取Camera的方法随OpenCV版本升级而提供了多种方式,我们先从VideoCapture class的成员函数看起:

1.1: VideoCapture的构造函数:
VideoCapture::VideoCapture(int index)
{
    open(index);
}


1.2:VideoCapture的open()函数:
1.2.1:打开/dev/videoX:
bool VideoCapture::open(int index)
{
    if (isOpened()) release();
    icap = IVideoCapture_create(index);
    if (!icap.empty())
        return true;
    cap.reset(cvCreateCameraCapture(index));
    return isOpened();
}
这里会调用cap.reset(cvCreateCameraCapture(index));

其中cvCreateCameraCapture(index)会创建capture.
它会根据编译OpenCV时的设置, 依次判断采用哪个硬件或架构:
HAVE_MSMF,Microsoft Media Foundation
HAVE_VFW, platform native
HAVE_CAMERAVL2, HAVE_LIBV4L, 采用libv4l,或者v4l2.
HAVE_GSTREAMER, GStreamer,
CV_CAP_FIREWIRE, 1394等等。


1.2.2: 打开文件:
bool VideoCapture::open(const String& filename, int apiPreference)
{
    if (isOpened()) release();
    icap = IVideoCapture_create(filename);
    if (!icap.empty())
        return true;

    cap.reset(cvCreateFileCaptureWithPreference(filename.c_str(), apiPreference));
    return isOpened();
}

cvCreateFileCaptureWithPreference() 这创建capture时,可以选择ffmpeg等。


1.3: open()的内部细节:
当选中了某个设置时,则capture由这个设置的函数具体构建,例如,我们选择在Linux下使用v4l2做OpenCV Camera接口的实现。则:
CvCapture* cvCreateCameraCapture_V4L( int index )
{
    cv::CvCaptureCAM_V4L* capture = new cv::CvCaptureCAM_V4L();

    if(capture->open(index))
        return capture;

    delete capture;
    return NULL;
}

其中:
struct CvCaptureCAM_V4L : public CvCapture, CvCaptureCAM_V4L结构体继承自CvCapture

这里会打开/dev/videoX设备(try_init_v4l2();), 并利用V4L2--ioctl()设置Camera。
获取Camera 各设置Range(v4l2_scan_controls())
设置帧数 (v4l2_set_fps())
设置Buffer, mmap等。


1.3:VideoCapture的read()函数:

bool VideoCapture::read(OutputArray image)
{
    if(grab())
        retrieve(image);
    else
        image.release();
    return !image.empty();
}
依次调用grab(), retrieve()函数。


bool VideoCapture::grab()
{
    if (!icap.empty())
        return icap->grabFrame();
    return cvGrabFrame(cap) != 0;
}


CV_IMPL int cvGrabFrame( CvCapture* capture )
{
    return capture ? capture->grabFrame() : 0;
}
这里,capture根据不同设置,由不同模块创建,我们使用V4L2.所以看V4L2(cap_v4l.cpp)的对应函数。
bool CvCaptureCAM_V4L::grabFrame()
{
    return icvGrabFrameCAM_V4L( this );
}

最终拿数据的是:
static void mainloop_v4l2(CvCaptureCAM_V4L* capture) {
    unsigned int count;

    count = 1;

    while (count-- > 0) {
        for (;;) {
            fd_set fds;
            struct timeval tv;
            int r;

            FD_ZERO (&fds);
            FD_SET (capture->deviceHandle, &fds);

           
            tv.tv_sec = 10;
            tv.tv_usec = 0;

            r = select (capture->deviceHandle+1, &fds, NULL, NULL, &tv);

            if (-1 == r) {
                if (EINTR == errno)
                    continue;

                perror ("select");
            }

            if (0 == r) {
                fprintf (stderr, "select timeout\n");

               
                break;
            }

            if (read_frame_v4l2 (capture))
                break;
        }
    }
}


这个函数很有玄机,它并不是直接DQBUF, 而是使用一个循环,这样就可以保证,每次获取的数据都是最新数据。

static int read_frame_v4l2(CvCaptureCAM_V4L* capture) {
    v4l2_buffer buf = v4l2_buffer();

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;

    if (-1 == ioctl (capture->deviceHandle, VIDIOC_DQBUF, &buf)) {
        switch (errno) {
        case EAGAIN:
            return 0;

        case EIO:
        if (!(buf.flags & (V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE)))
        {
          if (ioctl(capture->deviceHandle, VIDIOC_QBUF, &buf) == -1)
          {
            return 0;
          }
        }
        return 0;

        default:
           
            perror ("VIDIOC_DQBUF");
            return 1;
        }
   }

   assert(buf.index < capture->req.count);

   memcpy(capture->buffers[MAX_V4L_BUFFERS].start,
      capture->buffers[buf.index].start,
      capture->buffers[MAX_V4L_BUFFERS].length );
   capture->bufferIndex = MAX_V4L_BUFFERS;
   //printf("got data in buff %d, len=%d, flags=0x%X, seq=%d, used=%d)\n",
   //   buf.index, buf.length, buf.flags, buf.sequence, buf.bytesused);

   if (-1 == ioctl (capture->deviceHandle, VIDIOC_QBUF, &buf))
       perror ("VIDIOC_QBUF");

   //set timestamp in capture struct to be timestamp of most recent frame
   capture->timestamp = buf.timestamp;

   return 1;
}


 

TensorflowNDK编译

$
0
0
作者: Sam (甄峰)   sam_code@hotmail.com

0. TensorFlow简介:
TensorFlow™ 是一个开放源代码软件库,用于进行高性能数值计算。借助其灵活的架构,用户可以轻松地将计算工作部署到多种平台(CPU、GPU、TPU)和设备(桌面设备、服务器集群、移动设备、边缘设备等)。TensorFlow™ 最初是由 Google Brain 团队(隶属于 Google 的 AI 部门)中的研究人员和工程师开发的,可为机器学习和深度学习提供强力支持,并且其灵活的数值计算核心广泛应用于许多其他科学领域。


1. TensorFlow Lite简介:
https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/lite
TensorFlow Lite是 TensorFlow为移动设备和嵌入式设备准备的轻量级解决方案。 它可以实现在嵌入式板子上的机器学习,低延迟和较小的size. 它也支持硬件加速通过(Android Neural Networks API) Android神经网络API。

TensorFlow Lite 使用一些技巧如优化Kernel,让它可以做到低延迟。



 

Linux平台使用FFMPEG获取USBCamera数据

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

最近有同事在Windows上使用ffmpeg获取Camera数据。提到ffmpeg获取Camera信息和数据方便易用,提供更多信心。 比OpenCV好用多了。 于是在Linux平台也试用之。



 

Linux下IO多路复用Linux下的IO模型

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

0. Linux下的IO模型
I/O操作, I/O操作通常包括两个阶段:
A. 等待数据准备好。
B. 和内核交换数据。(读写)
对socket上的输入操作来说,
A步骤就是等待数据从网络中到达,此时,数据被存放在内核缓冲区。
B操作则是利用系统调用read, 把数据从内核缓冲区读取到应用程序层。

0.1: Linux下各I/O模型: 
Linux下的I/O模型有以下几种:
阻塞式I/O.
非阻塞I/O.
I/O复用。
信号驱动I/O。
异步I/O。

0.1.1: 阻塞式I/O模型
在低并发情况下,最常用的I/O模型,缺省条件下,所有Socket都是阻塞的。因为逻辑最清晰。

调用recvfrom ,read等系统调用时,如果数据还未准备好,就会一直阻塞,直到有了数据,并获取到数据才返回。

0.1.2: 非阻塞式I/O
在打开socket时,可以指定为非阻塞式I/O, 此时,会告知Kernel, 当数据没有准备好时,不要阻塞(进程不要被放入休眠队列)。 而是立刻返回错误码。

recvfrom调用时,若没有数据,则立刻返回EWOULDBLOCK. 若有数据,则反正Success,并复制数据。

0.1.3: I/O复用:
I/O复用的意思是: 多个设备同时监控,哪个设备准备好了,就对它进行数据处理和交换。
Linux2.4时代,有select() poll()两个系统调用。 linux2.6则引入了epoll.
select(), poll()本身是阻塞的。它们像Kernel注册要关注那些文件描述符的哪些状况(如有数据可读写,断连,异常)。 若发生注册的状况。则返回。此时调用对应系统调用来处理。
注意:阻塞是在select()或者poll()上,而非阻塞在I/O系统调用上。



它最大的优势,在于可以等待多个描述符就绪。


0.1.4: 信号驱动I/O。
可以用信号,让内核在描述符就绪时发送SIGIO信号通知我们。称为信号驱动式I/O .我们首先开启套接字的信号驱动式I/O功能,并通过sigaction系统调用安装一个信号处理函数。该系统调用将立即返回,我们的进程继续工作,也就是说它没有被阻塞。当数据报准备好读取时,内核就为该进程产生一个SIGIO信号。我们随后既可以在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已准备好待处理。也可以立即通知循环,让它读取数据报。



         无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达期间进程不被阻塞。主循环可以继续执行,只要等待来自信号处理函数的通知:既可以是数据已准备好被处理,也可以是数据报已准备好被读取。



0.1.5: 异步I/O
告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成后通知我们。这种模型与前一节介绍的信号驱动模型的主要区别在于:信号驱动I/O是由内核通知我们如何启动一个I/O操作,而异步I/O模型是由内核通知我们IO操作何时完成:











 

Linux下IO多路复用select,poll,epoll

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

1. I/O多路复用:
IO多路复用使得程序可以同时监听多个文件描述符(socket也是一种文件描述符),这对提高程序性能至关重要。

通常,网络程序在下列情况下需要使用I/O复用技术: 
A. 客户端程序要同时处理多个socket.
B. 客户端程序要同时处理用户输入和网络连接。
C. TCP服务器要同时处理监听Socket连接和socket数据。这是I/O复用最常用的场景。
D. 服务器要同时处理TCP请求和UDP请求。
E: 服务器要同时监听多个端口,或处理多种服务。如xinetd.

Linux下实现多路复用的系统调用主要包括: select, poll, epoll.

I/O多路复用虽然可以监听多路文件描述符,但它本身却是阻塞的。


2. Linux并发网络开发模型:
Linux下设计并发网络程序,方法很多,如PPC(Process Per Connect)模型, TPC(Thread Per Connect)模型, select模型和poll模型。Epoll也在2.6Kernel时被引入。

PPC /TPC模型:
这两种模型思路类似,都是每个连接建立一个Process/Thread. 让他们自我维护。 但Process/Thread都需要资源。再加上Process/Thread切换。 开销会较大。
所以此类模型能够接受的最大连接数都不大,几百个左右。


3. select和poll:
多路复用IO,2.4内核前主要是select和poll。
select(2)函数介绍:
select(2)使我们在SVR4和4.3+BSD之下可以执行I/O多路转接。 

int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

select(2)函数告知Kernel: a.我们所关心的描述符。b.对于每个描述符,我们所关心的条件(是否可读,是否可写,是否有异常)。  c. 希望等待的时间。

select(2)返回时,Kernel会负责告诉我们:
a. 已经准备好的描述符数量。
b. 哪一个描述符准备好读,写,或异常条件。

当参数5 timeout为NULL时,select(2)会永远等待,直到所指定的描述符中的一个已经准备好或捕捉到一个信号。
参数2,3,4:readfds, writefds, exceptfds是指向描述符集合的指针。它们分别放置了我们设定的可读,可写,处于异常条件的各描述符。
他们可以为NULL,表明对相应的条件并不关心。
参数1:nfds, 最大的fd+1. 指三个描述符集中找到最高的描述符编号值,然后加1。 有人喜欢把这个值直接设置为:FD_SETSIZE. (sys/types.h中设置的常数,表明最大的描述符数)。但这并不是好办法。因为Kernel会根据这个值在一定范围内查询。如果值太大,效率会降低。 
#define        __FD_SETSIZE            1024
#define    FD_SETSIZE              __FD_SETSIZE

返回值:
-1:以上描述符都没有准备好时就捕捉到一个信号。
0: 以上描述符都没有准备好,但Timeout。
正数:已经准备好的描述符数量。此时,描述符集中打开的对应位就是已经准备好的描述符位。

文件描述符集合的使用
select(2)中的2,3,4三个参数readfds, writefds, exceptfds都是指向描述符集的指针(fd_set*)。这个类型对用户并不透明,需要使用宏进行操作。

void FD_ZERO(fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
       
void FD_ZERO(fd_set *set);
初始化文件描述符集,其中所有位均归零。

void FD_SET(int fd, fd_set *set);
设置文件描述符集中对应fd的位为1。也就是把fd加入了文件描述符集合。

void FD_CLR(int fd, fd_set *set);
把fd移除文件描述符集合。

int  FD_ISSET(int fd, fd_set *set);
查看fd是否存在于文件描述符集合set中。

当关心的文件描述符发生了关心的事件时,我们通过select()返回值获取到个数,但必须使用FD_ISSET()一个一个检查是谁。在关心的文件描述符较大时,效率会很低。

最大并发数限制,因为一个进程所打开的FD(文件描述符)是有限制的
效率问题,select每次调用都会线性扫描全部的FD集合,这样效率就会呈现线性下降
内核/用户空间 内存拷贝问题,如何让内核把FD消息通知给用户空间呢?在这个问题上select采取了内存拷贝方法

poll(2):
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
poll和select用法思路很像, 也是提供一组关心的文件描述符及其事件给Kernel。 当有对应事件发生时,则返回事件个数。
用户也必须逐个查看fds数据中内容。才能确认哪个文件描述符发生了那个Event。


通过注册一堆事件组,当有事件请求时返回,然后仍然需要轮询一遍pollfd才能知道查找到对应的文件描述符,数据也需要在内核空间和用户空间来回拷贝




4. epoll:
Linux 2.6内核正式引入epoll以来,epoll已经成为了目前实现高性能网络服务器的必备技术。

为何会引入epoll. 肯定是select和poll有什么不足之处。现分析入下:

高并发的核心解决方案是1个线程处理所有连接的“等待消息准备好”。
但select和poll预估错误了一件事,当数十万并发连接存在时,可能每一毫秒只有数百个活跃的连接,同时其余数十万连接在这一毫秒是非活跃的。select的使用方法是这样的:
      返回的活跃连接 = select(全部待监控的连接)。
      什么时候会调用select方法呢?在你认为需要找出有报文到达的活跃连接时,就应该调用。所以,调用select在高并发时是会被频繁调用的。这样,这个频繁调用的方法就很有必要看看它是否有效率,因为,它的轻微效率损失都会被“频繁”二字所放大。它有效率损失吗?显而易见,全部待监控连接是数以十万计的,返回的只是数百个活跃连接,这本身就是无效率的表现。被放大后就会发现,处理并发上万个连接时,select就完全力不从心了。
      此外,在Linux内核中,select所用到的FD_SET是有限的,即内核中有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数。
#define __FD_SETSIZE         1024

其次,内核中实现 select是用轮询方法,即每次检测都会遍历所有FD_SET中的句柄,显然,select函数执行时间与FD_SET中的句柄个数有一个比例关系,即 select要检测的句柄数越多就会越费时.

epoll在以上三个问题的优势:
1. epoll没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于2048, 一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看
2. 效率提升,Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll.
3. 内存拷贝,Epoll在这点上使用了“共享内存”,这个内存拷贝也省略了





 

Linux下IO多路复用epoll的使用初探

$
0
0
作者: Sam (甄峰) sam_code@hotmail.com

1. 基础介绍:
epoll 是2.6.8 kernel 加入的IO复用模型,它从三个方面改进了select和poll模型。现在我们先尝试使用之。

epoll在实现和使用上与select, poll有很大差异。
A. epoll使用一组而不是一个函数来实现模型。 
B. epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中。无需像select和poll那样每次调用都要重复传递文件描述符集与事件集。(所以需要创建一个文件描述符,来唯一标识内核中的这个事件表)。
C. 将增加减少文件描述符上的事件这个低频率动作(epoll_ctl)和获取有事件的文件描述符这个高频率(epoll_wait)分开。
D. epoll_wait()返回值表示就绪的文件描述符个数。并把就绪的事件递交给参数。这个参数中所有数据,均为就绪的文件描述符。不像select或poll是全部文件描述符。


2. epoll系列函数介绍
2.1: 创建内核事件表文件描述符
int epoll_create(int size);
它创建一个epoll 实例。 参数size虽然被忽视,但必须大于0。
epoll_create()返回一个文件描述符,它代表一个新的epoll实体。  这个文件描述符需要在所有epoll interface中被作为参数使用。
如果不再被使用了,必须被close(2)所关闭。

失败时,返回 -1.  errno被设备。 
正常时,返回文件描述符。


2.2: 对epoll文件描述符进行操作:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
它对参数一epfd所代表的epoll实体进行操作。
参数二op所代表的操作执行到参数三fd代表的用户关注文件描述符上。

操作op:
EPOLL_CTL_ADD
              Register  the  target  file  descriptor  fd  on the epoll instance referred to by the file
              descriptor epfd and associate the event event with the internal file linked to fd.
注册fd上发生的事件(参数4-event) 到epoll实例.

       EPOLL_CTL_MOD
              Change the event event associated with the target file descriptor fd.

       EPOLL_CTL_DEL
              Remove (deregister) the target file descriptor fd from the epoll instance referred  to  by
              epfd.  The event is ignored and can be NULL (but see BUGS below).


参数四: 
typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;

           struct epoll_event {
               uint32_t     events;      
               epoll_data_t data;        
           };

其中:
uint32_t     events;      
包含:
EPOLLIN
              The associated file is available for read(2) operations.
  有数据可读

       EPOLLOUT
              The associated file is available for write(2) operations.
有数据可写

       EPOLLRDHUP (since Linux 2.6.17)
              Stream socket peer closed connection, or shut down writing half of connection.  (This flag
              is especially useful for writing simple code to detect peer shutdown when using Edge Trig‐
              gered monitoring.)

       EPOLLPRI
              There is urgent data available for read(2) operations.

       EPOLLERR
              Error condition happened on the associated file  descriptor.   epoll_wait(2)  will  always
              wait for this event; it is not necessary to set it in events.

       EPOLLHUP
              Hang  up  happened  on the associated file descriptor.  epoll_wait(2) will always wait for
              this event; it is not necessary to set it in events.  Note that when reading from a  chan‐
              nel  such  as  a pipe or a stream socket, this event merely indicates that the peer closed
              its end of the channel.  Subsequent reads from the channel will return  0  (end  of  file)
              only after all outstanding data in the channel has been consumed.

       EPOLLET
              Sets the Edge Triggered behavior for the associated file descriptor.  The default behavior
              for epoll is Level Triggered.  See epoll(7) for more detailed information about  Edge  and
              Level Triggered event distribution architectures.


2.3: 等待关注的文件描述符上的I/O Event:
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
等待epfd所代表的epoll实体中发生 所关注的event.
参数一epfd: epoll 实体 文件描述符。
参数二events: 就绪数组。由Kernel copy过来的。
参数三maxevents:  最多监听的事件数。
参数四timeout:


3. LT和ET模式:
epoll(7) 对文件描述符的操作有两种模式: LT(Level Trigger: 电平触发)和ET(Edge Trigger: 边沿触发)。
LT模式是缺省模式。

LT模式下,只要在关注的文件描述符上,有符合条件的事件发生,epoll_wait()就会返回并通知。
例如:epoll_ctl()注册了某个文件描述符关心EPOLLIN。 当有数据时,epoll_wati()返回并通知程序有数据。程序读取一部分数据(没有读完)后。 再次调用epoll_wait().  则立刻返回并通知程序有数据。
因为它是按照Level 来决定触发。 所有有数据,就触发。一直触发到没有数据为止。(当然TCP并不是有数据就触发,而是有个限定值)。


ET模式下,只有在关注的文件描述符上,在状态改变那一瞬间,epoll_wait()会返回并发出通知。
例如:epoll_ctl()注册了某个文件描述符关心EPOLLIN。当有数据时,epoll_wati()返回并通知程序有数据。程序读取一部分数据(没有读完)后。 再次调用epoll_wait().  则它不会返回并报告有数据
因为它是按照边沿触发的。所以一次没读完全。 后续的数据不会再报告。


在noblock模式下:
1. 某个TCP连接。 socket的对端发送了1024个字节。
LT模式下,epoll_wait()返回并通知有数据。recv(2)或read(2)选择读取10个数据。
此时再调用epoll_wait(), 会立刻通知有数据。直到1024个数据读完。则recv(2) 或read(2)返回-1. errno=EAGAIN或者EWOULDBLOCK. 表明暂时没数据了。

ET模式下,epoll_wait()返回并通知有数据。recv(2)或read(2)选择读取10个数据。
此时再调用epoll_wait(), 不会返回。
所以,在ET模式下,需要程序确保把数据读完。

2. 某个TCP连接。 socket的对端发送了1024个字节。应用程序正在读取阶段,又来了1024个数据。
LT模式下,epoll_wait()返回并通知有数据。每次读取10字节。
此时再调用epoll_wait(), 会立刻通知有数据。直到第二个1024字节数据依次读取完成。则recv(2) 或read(2)返回-1. errno=EAGAIN或者EWOULDBLOCK. 表明暂时没数据了。

ET模式下,应用程序正在读取时,第二个1024字节到达,epoll_wait()也会返回并通知有数据到达。


LT,ET模式转换:
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN;

event.events |= EPOLLET;

epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);



4. EPOLLONESHOT:
考虑这种情况,Nobloack模式+ET模式。 每次epoll_wait()返回时,就创建一个Thread()去接收数据并处理数据。
但如果正在处理数据时,这个socket()又有一个数据到了。则创建另一个Thread去接收和处理数据。
这就导致,同一个socket的数据,同时被两个socket 处理。 在某些有数据相关性的情况下,这会造成问题。

怎样才能保证同一个socket的数据,在同一时刻,只被一个thread处理呢?这就有了EPOLLONESHOT。

当一个文件描述符(Nobloack模式+ET模式)设置了EPOLLONESHOT后,操作系统最多只触发一次。直到我们用epoll_ctl()重置该文件描述符。才会再次触发。

epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN;

event.events |= EPOLLET;
event.events |= EPOLLONESHOT;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);






 

Linux线程间同步

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

Linux下线程间同步,之前只用到mutex.  这次把互斥锁(mutex),条件变量(cond)和读写锁(rwlock)都记录下来。

1. 互斥锁(mutex):
互斥锁的基本想法是: 某个Thread 把mutex锁住后,其它Thread(或本Thread) 再调用lock时,会被告知已经锁住。直到mutex被unlock.
它的主要作用是: 同一时刻,只允许一个Thread执行一个关键部分的代码。

1.1: 初始化和销毁:
在使用mutex 之前,需要初始化它。不再使用时,需要销毁它。
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

1.1.1: 初始化:
方法一 动态初始化:
pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr)
将要初始化参数一所指向mutex object.  
参数二指向attr是这个mutex object的特性。可以为NULL,此时mutex attribute使用缺省设置。
当pthread_mutex_init()执行成功时,mutex object是初始化过的,同时也是unlock的。
未定义的行为:
A. 只有互斥体本身(mutex object)可以用来执行同步操作。 pthread_mutex_lock(), pthread_mutex_trylock(), pthread_mutex_unlock(), pthread_mutex_destroy()使用mutex object 的拷贝的行为,都是未定义的。隐含着危险。
B. 尝试初始化已经初始化过的mutex object 是未定义行为
顺利完成,则返回0. 否则,返回error号。


方法二 静态初始化: 
pthread_mutex_t mutex_read = PTHREAD_MUTEX_INITIALIZER;
与pthread_mutex_init(&mutex_read, NULL)作用完全相同。


1.1.2: 销毁
pthread_mutex_destroy(pthread_mutex_t *mutex)
它将销毁参数所指向的mutex object.
一个被销毁的mutex object可以被重新初始化。
pthread_mutex_destroy()可以安全的销毁unlock的mutex object. 销毁未unlock的mutex会导致未知结果。
顺利完成,则返回0. 否则,返回error号。


1.2:加锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);

1.2.1:pthread_mutex_lock():
调用pthread_mutex_lock()去锁一个mutex object, 则这个mutex object 会被锁住, 函数会返回 0或者[EOWNERDEAD].  
如果这个mutex object已经被其它Thread 锁住。 则当前调用pthread_mutex_lock()的Thread被阻塞,直到mutex object变得可用(unlock). 这就保证了在另一个Thread unlock之前。 此Thread无法执行下面的指定代码。


1.2.1:pthread_mutex_trylock():
其它与pthread_mutex_lock()类似。唯一区别在于:
当mutex object已经被锁住时, 它不是被阻塞,而是立刻返回(EBUSY =16)。



1.3:解锁:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
释放Mutex Object. 它变成unlock状态。此时,如果有Thread被这个mutex阻塞,scheduling policy将会检查有多少个Thread被阻塞,它会挑选一个解除阻塞。



2. 条件变量(Cond): 
条件变量是利用线程间共享全局变量进行同步的一种机制.
某个Thread使用Cond,阻塞Thread, 来等待某个触发条件的发生。
另一个Thread触发之。

2.1: 初始化和销毁
2.1.1: 初始化Cond:
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);
参数二: Linux 未能实现,所以为NULL。使用缺省设置。

2.1.2:销毁Cond:
int pthread_cond_destroy(pthread_cond_t *cond);
只有在没Thread等待的情况下,才可以销毁这个cond。


2.2: 等待触发函数
2.2.1: 无限时等待:
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
这个等待函数需要和mutex配合使用。
先讲解它本身:
在执行pthread_cond_wait()时,它会做两件事:
1. 首先把参数二中指定的Mutex Object 自动unlock。
2. 阻塞住Thread. 等待条件变量(cond)被触发。


当条件变量真被触发时, pthread_cond_wait()在返回之前,会把参数二中指定的Mutex Object 自动 lock住。

在调用pthread_cond_wait()前,需要使用pthread_mutex_lock()锁住参数二所指向的Mutex Object.
在调用pthread_cond_wait()后,需要使用pthread_mutex_unlock()解开参数二所指向的Mutex Object.

用法如下:
pthread_mutex_lock(&mutex);
  1. pthread_cond_wait(&cond,&mutex);
  2. pthread_mutex_unlock(&mutex);



2.2.2: 限时等待:
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
除了增加time外,其它与pthread_cond_wait()相同。



2.3:激活触发:
2.3.1: 激活一个等待该条件的线程(存在多个等待线程时按入队顺序激活其中一个)
int pthread_cond_signal(pthread_cond_t *cond);

2.3.2:激活所有等待线程
int pthread_cond_broadcast(pthread_cond_t *cond);




3. 读写锁:
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
 
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
 
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout);





 

LinuxSignal详解

$
0
0
作者: Sam (甄峰)   sam_code@hotmail.com

0. 信号背景:
信号是由用户,系统或者进程发送给目标进程的信息。以通知目标进程某个状态的改变或系统异常。















注1: 线程组
每个进程都会属于一个进程组(Process Group),  一个进程组可以包含多个进程。 
进程组会有一个领导进程(Process group Leader).  领导进程的PID成为进程组ID(Process Group ID.  PGID).

ps -o pid,pgid,ppid,comm -A | cat

PID  PGID  PPID COMMAND
    1     1     0 systemd
    2     0     0 kthreadd
    4     0     2 kworker/0:0H
    6     0     2 mm_percpu_wq
    7     0     2 ksoftirqd/0
    8     0     2 rcu_sched
    9     0     2 rcu_bh




 

Linux平台assert()使用详解

$
0
0
作者: Sam (甄峰)   sam_code@hotmail.com

0. 基础知识:
assert()用来检查不可能发生的行为, 以确保开发者在调试阶段尽早发现不可能发生的事件是否真的发生了。 若真的发生了,则表明这里逻辑有问题。
assert()最好的地方是,它只在debug模式下其效果,在Release模式下无效。保证了程序效率。


assert()  协助开发者发现程序bug.  当参数为false时,assert() 输出错误信息到标准错误. 并且调用abort(3) 终止程序。 

错误信息包括调用者的文件名和函数名,程序行号。

如果宏 NDEBUG 被defined 在include 头文件 assert.h之前,assert()不做任何动作。
#define NDEBUG
#include <<span style="margin: 0px; padding: 0px; font-family: "Courier New" !important;">assert.h>


assert()使用总结:
1. 在函数开始阶段,检验传入参数的合法性。
例如: 
MsgCpy(msg*  dst, msg* src)
{
assert(dst != NULL);
assert(src != NULL);
.......
}

2. 每个assert()仅仅检验一个条件。否则出错了不知道是哪个条件出错。

3. 不能在assert()中对变量做出改变因为assert()仅在Debug模式下起作用。若改变了变量,则Release和Debug模式下会有不同。
例如:
assert(i++ < 1024)
则在Debug下,i++被执行了。 Release模式下,i++没被执行。

4. assert()应自成一行,上下有空行。

5. assert()与条件过滤各自有自己的适用点。不能混淆


2. assert()与条件过滤的思考:
Sam在之前的开发中,对一个问题一直有疑虑:应该在函数或模块内部检测环境和参数是否正常,还是在函数或模块的调用之前由调用者来检测呢?
后来有同事建议:为了稳妥起见,所有的检测和验证,都在函数或模块内部实现。即:不管用户怎样无理的使用此函数和模块,都应该能够正常工作。
但在实际工作中,这个方法有以下几个缺陷:
A. 运行环境和参数的检测耗费了大量的精力。
B. 这部分代码也占用了不少资源。

如果责任划分不明确,则我们或者需要设置太多的检查——每一方都要检查,或者需要太少的检查——每一方都期望对方进行检查。检查太多是很糟糕的,因为它会造成大量重复的代码,增加系统的复杂程度。所以需要明确哪一方应该进行检查。

后来看到契约式设计文档中的说法,感觉很贴切:
如果把程序库和组件库被类比为server,而使用程序库、组件库的程序被视为client。根据这种C/S关系,我们往往对库程序和组件的质量提出很严苛的要求,强迫它们承担本不应该由它们来承担的责任。这就造成程序库或者每个函数做了大量检验工作。以适应调用者。 这就造成Server提供者负责了太多任务。而client的开发者又随便使用,导致代码质量下降。

引入契约观念之后,这种C/S关系被打破,大家都是平等的,你需要我正确提供服务,那么你必须满足我提出的条件,否则我没有义务“排除万难”地保证完成任务。


模块中检查错误状况并且上报,是模块本身的义务。而在契约体制下,对于契约的检查并非义务,实际上是在履行权利。一个义务,一个权利,差别极大。例:
if (dest == NULL) { ... }
这就是义务,其要点在于,一旦条件不满足,我方(义务方)必须负责以合适手法处理这尴尬局面,或者返回错误值,或者抛出异常。而:
assert(dest != NULL);
检查契约,履行权利。如果条件不满足,那么错误在对方而不在我。


确保必须条件:
契约所核查的,是“为保证正确性所必须满足的条件”,因此,当契约被破坏时,只表明一件事:软件系统中有bug。其意义是说,某些条件在到达我这里时,必须已经确保为“真”。
assert(dest != NULL);
报错时,你要做的不是去修改你的string_copy函数,而是要让任何代码在调用string_copy时确保dest指针不为空

 “函数”和“过程”的理解:
我们以往对待“过程”或“函数”的理解是:完成某个计算任务的过程,这一看法只强调了其目标,没有强调其条件 。引入契约之后,“过程”和“函数”被定义为:完成契约的过程。基于契约的相互性,如果这个契约的失败是因为其他模块未能履行契约 ,本过程只需报告,无需以任何其他方式做出反应。而真正的异常状况是“对方完全满足了契约,而我依然未能如约完成任务”的情形。这样以来,我们就给“异常”下了一个清晰、可行的定义。



3. 契约式设计:
assert()是用来避免显而易见的错误的,而并非处理异常的。
所谓错误,是不应该出现的。 而异常,则是不可避免的。如上面第五条所说:
assert()处理错误。if() 条件过滤处理异常。
比如: 
文件打开不成功,是(如文件不存在,权限不足)异常。 可以用条件变量处理。
而传递给函数的参数为文件描述符,这个文件描述符为NULL。则为错误。

具体什么时候使用assert(), 什么时候使用条件过滤。这就引出了一个概念: 契约式设计(契约式编程)。

契约式编程/契约式设计(Design by Contract(DbC)/Programming by Contract)是一种设计计算机软件的方法。这种方法描述了,软件设计者应该为软件组件定义正式的,准确的,可验证的接口规范。 它扩展了先验条件(Preconditions),后验条件(Postcondition),不变性(invariants)的一般定义。


DbC 的核心思想,用一个比喻就是:基于相互的“义务”与“权利”,一个软件系统的要素之间如何互相合作。这个比喻来自于商业活动,“客户”与“供应商”达成的“合约”而来。例如:

  • 供应商必须提供某种产品(这是供应商的义务),并且有权期望客户付费(这是供应商的权利)。
  • 客户必须支付费用(这是客户的义务),并且有权得到产品(这是客户的权利)。
  • 双方必须满足应用于合约的某些义务,如法律和规定。


同样地,如果在面向对象程序设计中的一个类的例程(方法)提供某些功能,那么,它要:

  • 期望所有调用它的客户模块都保证某个进入的条件:这就是例程(方法)的先验条件—客户的义务和供应商的权利(方法本身),这样,它就不用去处理不满足先验条件的情况。
  • 保证退出时给出某个属性:这就是例程(方法)的后验条件——供应商的义务,也是客户的权利(调用方法的主要权利
  • 进入时假设,退出时保证,维护某个属性:类的不变性。

契约(contract )就是这种义务和权利的形式化。可以用“三个问题”来概括 DbC,这也是软件设计者必须反复问的:

  • 期望什么?
  • 保证什么?
  • 维护什么?

很多语言都使用这样的断言。然而,DbC 认为契约对于软件的正确性至关重要,它们应当是设计过程的一部分。实际上,DbC  提倡首先写断言。

契约的概念可以扩展到方法/过程。每个方法的契约通常包含如下信息

  • 可接受和不可接受的输入的值或类型,以及它们的含义
  • 返回的值或类型,以及它们的含义
  • 错误和异常的值或类型,以及它们的含义
  • 副作用
  • 先验条件
  • 后验条件
  • 不变性
  • (不太常见)性能保证,例如所需的时间和空间





3. 前置条件和后置条件。
所谓前提条件,是指在执行操作之前,期望具备的环境。对求平方根操作来说,前提条件可以定义为input >= 0。根据该前提条件,对某个负数执行求平方根操作本身就是错误的,而且结果无可预料。

所谓“后继条件”,是指操作执行完之后的情况。举例来说,如果我们定义对某个数的“求平方根”操作,则该操作的后继条件为:input = result * result,这里的result是输出结果,而input是输入的数值。在描述“做什么”(what we do)而不涉及“怎样做”(how we do it)——换言之,将接口和实现分离开来——时,后继条件是非常有用的方法。











例子:
自己实现memcpy()
void*  memcpy(void* dst, void* src, size_t count)
{
assert(dst !=NULL);
assert(src !=NULL);\

assert(count > 0);

assert((dst>src) && (src+count < dst));
assert((dst < src) && (dst+count < src));


}




所以,Sam准备未来尝试如此使用assert():
函数内部使用先验assert()来验证参数。
函数内部使用后验assert()验证结果。
但是否去掉if()。则视条件而定。






附录1:
Android NativeC代码中使用assert():
当在Application.mk中 使用APP_OPTIM := release 时。 
ndk-build 会自动加上 -DNDEBUG. 此时,assert()不起作用。 
当 APP_OPTIM := debug时。 不包含 -DNDEBUG. 所以,assert()就有效了。







 

libevent使用记录一

$
0
0
作者: Sam  (甄峰)  sam_code@hotmail.com

之前使用epoll+threadpool来做高并发Server, 后决定使用libevent来做替换。现记录如下:

0. 背景知识
libevent是用于编写高速可移植非阻塞IO应用的库。它对select,poll,epoll等接口进行封装。通过设置回调函数的方式,监听文件描述符和socket, 还兼任定时器和信号接收的管理工作。

其设计目的是:

可移植性:使用libevent编写的程序应该可以在libevent支持的所有平台上工作。即使没有好的方式进行非阻塞IO,libevent也应该支持一般的方式,让程序可以在受限的环境中运行。

速度快:libevent尝试使用每个平台上最高速的非阻塞IO实现,并且不引入太多的额外开销。

可扩展性:libevent被设计为程序即使需要上万个活动套接字的时候也可以良好工作。

方便:无论何时,最自然的使用libevent编写程序的方式应该是稳定的、可移植的。


libevent由下列组件构成:

evutil:用于抽象不同平台网络实现差异的通用功能。

event和event_base:libevent的核心,为各种平台特定的、基于事件的非阻塞IO后端提供抽象API,让程序可以知道套接字何时已经准备好,可以读或者写,并且处理基本的超时功能,检测OS信号。

bufferevent:为libevent基于事件的核心提供使用更方便的封装。除了通知程序套接字已经准备好读写之外,还让程序可以请求缓冲的读写操作,可以知道何时IO已经真正发生。(bufferevent接口有多个后端,可以采用系统能够提供的更快的非阻塞IO方式,如Windows中的IOCP。)

evbuffer:在bufferevent层之下实现了缓冲功能,并且提供了方便有效的访问函数。

evhttp:一个简单的HTTP客户端/服务器实现。

evdns:一个简单的DNS客户端/服务器实现。

 

evrpc:一个简单的RPC实现。



编译Libevent代码后,会产生以下5个库:

libevent_core:所有核心的事件和缓冲功能,包含了所有的event_base、evbuffer、bufferevent和工具函数。

libevent_extra:定义了程序可能需要,也可能不需要的协议特定功能,包括HTTP、DNS和RPC。

libevent:这个库因为历史原因而存在,它包含libevent_core和libevent_extra的内容。不应该使用这个库,未来版本的libevent可能去掉这个库。

某些平台上可能安装下列库:

libevent_pthreads:添加基于pthread可移植线程库的线程和锁定实现。它独立于libevent_core,这样程序使用libevent时就不需要链接到pthread,除非是以多线程方式使用libevent。

libevent_openssl:这个库为使用bufferevent和OpenSSL进行加密的通信提供支持。它独立于libevent_core,这样程序使用libevent时就不需要链接到OpenSSL,除非是进行加密通信。



头文件

libevent公用头文件都安装在event2目录中,分为三类:

API头文件:定义libevent公用接口。这类头文件没有特定后缀。

兼容头文件:为已废弃的函数提供兼容的头部包含定义。不应该使用这类头文件,除非是在移植使用较老版本libevent的程序时。

结构头文件:这类头文件以相对不稳定的布局定义各种结构体。这些结构体中的一些是为了提供快速访问而暴露;一些是因为历史原因而暴露。直接依赖这类头文件中的任何结构体都会破坏程序对其他版本libevent的二进制兼容性,有时候是以非常难以调试的方式出现。这类头文件具有后缀“_struct.h”。

(还存在不在event2目录中的较老版本libevent的头文件,请参考下节:如果需要使用老版本libevent)




1. 基础知识

libevent有自己的一套概念体系,要使用libevent,就必须首先理解这套概念体系。


1.1: event_base:

event_base是libevent最基础,最重要的对象。在一个event_base中,可以配置设置,监听事件。


1.1.1:创建和销毁

创建和返回一个event_base. 使用缺省的配置。

struct event_base *event_base_new(void);


创建和返回一个event_base, 使用定制的config.

struct event_base *event_base_new_with_config(const struct event_config *);


销毁一个event_base:

void event_base_free(struct event_base *);

它仅仅负责释放event_base_new()所分配的资源。并不关闭任何文件描述符,或释放任何event所占资源。


1.1.2:运行loop,等待关注的事件发生:

int event_base_loop(struct event_base *base, int flags)

是个loop. 等待event_base中关注的event变成active,并调用event的callback函数。

这个loop会一直运行,直到发生以下情况:

A. event_base 中,已经没有active或pending的Event了。

B. 有人调用了event_base_loopbreak() or event_base_loopexit()


参数flags:

#define EVLOOP_ONCE 0x01

 

#define EVLOOP_NONBLOCK 0x02

#define EVLOOP_NO_EXIT_ON_EMPTY 0x04


EVLOOP_ONCE: event_base_loop()阻塞,直到一个event变成active. 在Active状态的Event的Callback函数执行后,就退出。

EVLOOP_NONBLOCK:loop不会阻塞,它仅仅是查看是否已经有event ready. 有则运行其callback.然后退出

EVLOOP_NO_EXIT_ON_EMPTY 

:哪怕event_base中没有active或者pending的Event了。也不退出。直到调用event_base_loopbreak() or event_base_loopexit().


当flags什么都不设置时,则loop一直运行,检查到event变为active时,运行其callback函数。当没有active或pending的Event后,退出loop。有人调用了event_base_loopbreak() or event_base_loopexit(),也退出loop.



返回值:

0: success. ()

-1: 出错了。

1: 没有Active或pending event了。



int event_base_dispatch(struct event_base *event_base)

它就是简化版的event_base_loop();

int event_base_dispatch(struct event_base *event_base)

{

return (event_base_loop(event_base, 0));

 

}



1.1.3: 停止loop:

若想在event_base中的所有event被移除前,退出loop.可以调用:

int event_base_loopbreak(struct event_base *);

int event_base_loopexit(struct event_base *event_base, const struct timeval *tv)


event_base_loopbreak()会尝试立刻退出loop。 如果正在运行active event的callback(),则等待callback()函数返回后退出loop.

event_base_loopexit()会在tv时间后尝试退出loop.如果正在运行active event的callback(),则等待callback()函数返回后退出loop.


若调用此两个函数时,loop还未运行。则行为不同。

后面详细看看。



若想知道event_base是正常退出loop的,还是调用函数退出的。可以看:

int event_base_got_exit(struct event_base *);

int event_base_got_break(struct event_base *);



int event_base_loopcontinue(struct event_base *);



1.2: event:

event是libevent操作的基本单元。

一个event代表的是一组条件的集和,它包含:文件描述符,这个文件描述符上所感兴趣的事件,回调函数。

1.2.1: event的状态

一个event有三种状态:

A. 已初始化(Initialized)

B. Pending.

C. 激活(Active)

当event被创建并被关联到event_base后,它就是initialized状态。

将event添加入event_base的Pending event列表。此时,它被event_base的loop所监控。

event在Pending状态下,若发生触发条件。则event进入active状态。用户提供的事件回调函数将被运行。

此时,event的状态。要看创建时的设置,如果设置为Persistent.则事件event保持为Pending状态。 否则,则不是Pending状态。 可以用删除将它变回Initialized状态。也可以继续添加为Pending状态。




1.2.2:创建event:

 struct event *event_new(struct event_base *base, evutil_socket_t fd, short events, void (*cb)(evutil_socket_t, short, void *), void *arg)

创建一个event.并与event_base关联。参数fd和events告知触发event的条件(哪些fd发生哪些event), 参数cb和arg告知libevent,当event变成active后该怎么做。


此时,event是initilized状态。

如果出错,返回NULL。




event flags:

#define EV_TIMEOUT 0x01

#define EV_READ 0x02

#define EV_WRITE         0x04

#define EV_SIGNAL         0x08

#define EV_PERSIST 0x10

#define EV_ET         0x20

#define EV_FINALIZE      0x40

#define EV_CLOSED 0x80

  The EV_PERSIST flag can also be passed in the events argument: it makes

  event_add() persistent until event_del() is called.


EV_TIMEOUT:这个标志表示某超时时间流逝后事件成为激活的.构造事件的时候, EV_TIMEOUT标志是被忽略的: 可以在添加事件的时候设置超时,也可以不设置.超时发生时,回调函数的what参数将带有这个标志.


EV_READ:表示指定的文件描述符已经就绪,可以读取的时候,事件将成为激活的.


EV_WRITE:表示指定的文件描述符已经就绪,可以写入的时候,事件将成为激活的.


EV_SIGNAL:用于实现信号检测,请看下面的"构造信号事件"节.


EV_PERSIST:表示事件是"持久的",请看下面的"关于事件持久性"节.


EV_ET:表示如果底层的event_base后端支持边沿触发事件,则事件应该是边沿触发的.这个标志影响EV_READ和EV_WRITE的语义.




1.2.3:设置event为Pending:

int event_add(struct event *ev, const struct timeval *timeout);

把指定的event状态改变为Pending. 这意味着将这个Event加入event_base Loop所关注的列表中。

返回0:Success。  返回-1:出错。



1.2.4: 设置event为Initilizated状态:

int event_del(struct event *);

当event被event_base_loop() 设置为Active,即意味着有触发条件发生。此时,回调函数被调用。event进入No-Pending状态。这时,可以把它设置回Pending状态(event_add())),也可以设置回Initilizated状态。

event_del()就是把event设置回Initilizated状态。 也就是说它不再被Loop所关注。



1.2.5:释放Event:

void event_free(struct event *);

释放event资源。





 

libevent使用记录二epoll概念对应

$
0
0
作者: Sam (甄峰)  sam_code@hotmail.com

libevent Linux版本是对Epoll进行封装, 以实现IO多路复用。所以,在使用libevent时,常会感觉遇到epoll的对应概念。现尝试研究匹配之。

1. listen socket的设置:(ET模式和NOBLOCK模式)
在使用Epoll方式时, 在listen Socket 被创建后, 会被设置为NOBlock模式。 且在epoll_ctl()函数中设置为ET模式(边缘触发)。

libevent方式下,也是建立一个socket,设置为NoBlock模式。
并使用event_new()创建消息处理器。设置参数为:EV_READ|EV_PERSIST|EV_ET, 意味着在listent socket这个句柄上,有数据读,则创建回调函数。
EV_PERSIST:


 

Android平台深度学习---信息摘抄

$
0
0
作者: Sam (甄峰) sam_code@hotmail.com

1. Android 8.1 (API-27) NNAPI:

人工智能神经网络API(如:TensorFlow)

神经网络 API 能够向设备内置机器学习框架,如 TensorFlow Lite —— Google 移动跨平台机器学习框架、Caffe2 等,提供加速运算和推理。TensorFlow Lite 现已对开发者开放,各位可移步 TensorFlow Lite 开源库进行下载和文档阅览。在 TensorFlow Lite 和神经网络 API 协同合作下,移动设备能够高效运行类似 MobileNets、Inception v3 和 Smart Reply 之类的模块。


2. Android Neural Networks API(NNAPI)
2.1: 简介:
NNAPI (神经网络API)是 NDK中的一套API。由C语言实现。头文件在:
android-ndk-r17b/sysroot/usr/include/android/NeuralNetworks.h
实际上, 在NDK-R16就有了。

设计NNAPI的初衷是, 扮演底层平台的角色,支撑上层的各种机器语言学习框架(TensorFlow List, Caffe2等)高效的完成推理计算,甚至是构建/训练模型。

2.2: NNAPI有两个显著特点
1. 内置于Android系统,从Android8.1系统开始出现在NDK中。
2. 能够利用硬件加速,使用Android设备的GPU/DSP或各种专门人工智能加速芯片完成神经网络的推理运算,大大提高计算效率。

2.3:NNAPI的应用场景(Android8.1, API-27):
NNAPI是给机器学习框架和工具库调用的,理想情况下,各种机器学习框架在内部将构建模型,训练模型,推理运算等操作交由NNAPI完成。
NNAPI会根据Android设备的硬件性能,适当的将这些繁重的计算部署到合适的计算单元(CPU & GPU &DSP &神经网络芯片). 从而使用硬件加速功能完成推理运算。

对应用程序开发者,Google官方不推荐直接使用NNAPI, 而是只与TF Lite之类的机器学习框架打交道,构建出适用于TF Lite的模型并部署到Andoroid设备上,其余的工作交给TF Lite和NNAPI 完成。

普通开发者也可以按照NDK开发流程来开发基于NNAPI的应用,或者利用其封装出自己的机器学习库。
https://github.com/googlesamples/android-ndk/tree/master/nn_sample
https://github.com/daquexian/DNNLibrary

2.4: NNAPI工作原理:
狭义上,NNAPI 单指一组 C 语言 API;广义上,NNAPI 由 NNAPI、NN Runtime、NN HAL 组成。

其中,NN Runtime 是 NNAPI 的执行引擎。和其他的 HAL 相同,NN HAL 提供了统一的接口,屏蔽了不同设备厂商的实现差异。从而使开发者只需开发一套代码,便能运行于带有各种加速芯片的设备上。


自顶往下,安卓应用调用机器学习框架的接口完成推理运算并将结果呈现在界面上,而机器学习框架内部以 NDK 的形式调用 NNAPI,NN Runtime 作为 NNAPI 的执行引擎,完成构建模型、装填模型数据、加载输入数据、推理运算等。此外,NN Runtime 还能对代码进行优化、生成为特殊加速芯片专用的机器代码等。

如果安卓设备上没有 GPU、DSP 或专用加速芯片,NNAPI 将退化到 CPU 执行




2.5:开发流程
NNAPI 里包含了 4 类抽象:

模型(Model):一个计算图,由数学运算符和运算数组成。这里的运算符是神经网络专用的一些操作,比如 2d 卷积运算、激活函数等。运算数是在模型训练过程中形成的常数。在 NNAPI 中,一个模型由 ANeuralNetworksModel 表示。

编译(Compilation):配置信息,用于将模型编译成底层代码,一个编译由 ANeuralNetworksCompilation 表示。

内存(Memory):内存缓冲区或内存映射文件。NNAPI 利用内存能更加高效的将数据传递给驱动。在代码中,一个Memory 用 ANeuralNetworksMemory 表示。

执行(Execution):输入数据、运行模型进行推理运算,收集推理结果。该过程为异步操作,过个线程可以同时等待同一个执行结果。在代码中一个执行用 ANeuralNetworksExecution 表示。





2.6:NNAPI 和 TF Lite 关系
NNAPI 具有机器学习框架的功能,只不过跟 TF Lite 比起来,NNAPI 更加底层,功能有限,没有那么多花哨的东西。不仅如此,NNAPI 还具有 NN Runtime 这个功能强大的执行引擎,能够利用安卓设备上各种 XPU,合理分配计算量,提高运算速度。

据 谷歌 IO 大会表述,TF Lite 后续开发将与 NNAPI 对接,使 TF Lite 的内部执行调用 NNAPI 完成
google 开源了 TensorFLow Lite,并且在 Android 8.1 版本上为 DSP、GPU、和神经网络芯片等硬件加速器支持了神经网络 API,为了在 Android 移动设备上全面支持 AI 做足了准备



3. Android AI 大事记:




3. MACE(Mobile AI Compute Engine):

https://media.readthedocs.org/pdf/mace/latest/mace.pdf
Github地址:https://github.com/xiaomi/mace

MACE 是小米公司自研的移动端深度学习框架 Mobile AI Compute Engine,2017年12月15日于公司内部正式发布。2018年6月28日,在“2018(第十三届)开源中国开源世界高峰论坛”上,小米公司人工智能与云平台副总裁崔宝秋博士宣布开源 MACE,赋能中国 AI 产业,以期推动移动端深度学习的发展。




模型格式

MACE 自定义了一种类似 Caffe2 的模型格式。Tensorflow 和 Caffe 训练得到的模型可以转换成 MACE 模型进行部署。MACE 采用 YAML 文件描述模型的部署细节。下一章会给出 YAML 文件的创建方法。

模型转换

目前,提供了Tensorflow、Caffe模型到MACE模型的转换工具,未来会支持更多的框架。







 
Viewing all 158 articles
Browse latest View live