Bn Bp Binder native层关系
Servicemanager
源码在/frameworks/base/cmds/servicemanager/service_manager.c
编译成 systemmanager 可执行文件
systemmanager是以binder为主要通信手段,为系统各种各样服务的进行登记查询管理的服务。
系统服务的框架里有3个主要角色,服务提供者(server),服务使用者(client),和服务管理者(system manager),通信方式是Binder,在系统启动流程中的先后顺序是:服务管理者,服务提供者,服务使用者。
成为system manager
该进程会常驻在系统中,先通过binder_become_context_manager()注册为系统中服务管理器(本质上是通过ioctl对打开的/dev/binder的fd发送BINDER_SET_CONTEXT_MGR),然后通过binder_loop() 进入死循环,来持续为系统系统各种服务的查询/添加功能。各个服务本身运行在自己的进程中,但是通过systemmanager这样一个枢纽,提供真实服务的进程,将自己的服务向systemmanager注册,然后其他需要使用服务的进程通过先从/dev/binder中获取到systemmanager,然后再通过systemmanager查询并获取到真正想要的服务的stub,然后包装成binderPoxy(bpbinder),然后通过这个bpbinder与真正的服务进行IPC通信。
用在服务提供者和服务使用者角色中,与system manager 进行通信的模块:IPCThreadState 和 ProcessState
其他服务使用者通过在自己的进程中调用ProcessState::self()获得一个ProcessState对象(进程级单例,单例对象定义在binder/Static.cppg Process;)(一般是使用IPCThreadState对象时间接初始化,IPCThreadState的使用非常广泛,如Looper的native层,BPBinder中,bootanimation服务,app_process中当虚拟机状态发生改变后用来关闭binder的fd等,只要是native层,几乎都会使用到这个类来进行binder的IPC通信),在构造函数中ProcessState.cpp 中open_driver()打开/dev/binder 的fd,然后通过mmap映射这个fd的BINDER_VM_SIZE 大小的到自己进程中。
之后,往往通过调用joinThreadPool(isMain = 默认true)方法和binder的驱动进行交互并初始化,让当前进程在serviciemanager处注册服务,方法内部通过mOut和mIn 这2个Parcel数据对象(提供了将数据按规定格式(如Int32,float等)进行读写的方法,各种通信状态的记录等)来向binder收发数据,首先构造mOut写入Int32 BC_ENTER_LOOPER,然后进入一个循环中,先对mIn的数据处理,然后调用talkWithDriver()(本质上是通过ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)与binder通信,而bwr结构是又一次对mIn和mOut的数据转换,是直接和binder驱动进行IO的数据结构,一次这样的ioctl既可以用作向binder写数据,也可以从binder读数据,是否存在读的数据需要看上下文以及外部环境是否有信息发送过来) 来和binder驱动通信),获得状态对象,然后从mIn读取cmd,然后执行executeCommand(cmd);根据cmd的类型执行回馈处理命令,将mIn的数据出,将处理好的数据放入mOut打包,等下次talkWithDriver()的时候和binder回复以及接受新命令(mIn头部的int32大小的数据存放cmd)。joinThreadPool中的这个处理binder数据的循环会运行到executeCommand() 返回 TIMED_OUT 或者ECONNREFUSED等失败退出的状态为止,退出循环,最后通过向mOut写入 (BC_EXIT_LOOPER) 并调用talkWithDriver(false) 来结束通信。
SystemServer的分析,主要流程
Main()
加载 android_servers 动态库
1. 进入 init1(args) native方法(主要启动本机服务SurfaceFlinger(屏幕),AudioFlinger,传感器等,设置通过binder监听随着servicemanager死亡的而自杀)
2. 之后调用init2()启动android服务
a) 设置进程,线程优先级
b) 检查 sys.shutdown.requested, =1重启,或关机
c) 启动一堆核心服务,并注册(如PowerManagerService,ActivityManager等)
若设置了 vold.decrypt 为 ENCRYPTING 或 ENCRYPTED(加密状态) 则设置只运行 coreApp,以此启动packageManager
。。。服务。。。。
d) 启动一些需要UI的非核心服务,并注册,大多数服务类似am,驻留在当前system_service 进程里,不像c层服务,大多数是自己独立一个进程
e) 根据当前是否为安全模式,进行不同的初始化
f) 启动其他app 进程,如devicePolicy
g) 注册am中的一个回调systemReady,在回调中 调用一堆服务的 systemReady方法
3. 循环处理binder命令(注册了自己的服务?)
Init1 对应源码在
/frameworks/base/services/jni/com_android_server_SystemServer.cpp
省略源码+注释若干
下面是java层的system_server的总结
java层的system_server启动了并注册和启动了很多java层服务(各个服务自己有一条线程来维持与binder通信和执行业务),进入了一个Looper循环(作用?)
System_server 中的很多服务都是在java层初始化,并驻留在system_server进程中(通过一条单独处理binder通信的线程来维持运作,如activityManager),然后注册到serviceManager
而init中也有服务的概念,这些服务中有一部分不会注册到servcieManager如ueventd,healthd ,但也有一部分会启动后以c层代码的方式注册到服务,如bootanimation
所以说关于服务有3类
1. Init中启动的native层服务,不注册到serviceManager
2. Init中启动的native层服务,会注册到serviceManager
3. System_server中启动的java层服务,会注册到serviceManager
Binder
IPCThreadState中joinThreadPool 与binder通信某些地方有点类似与TCP通信,一方发送一个Command(字符串BC_*的命令),另一方接收到并处理完后,返回一个return(字符串BR_*)状态,类似TCP的发送某SEQ帧后,接收方收到后回馈一个对应的ACK帧
一次向servicemanager添加注册服务需要经过多层
Binder驱动层,binder驱动通信层,servicemanager业务层,具体的执行注册服务的函数。4层
关键通信函数及一些常量
Binder驱动,ProcessState, ioctl() |
IPCThreadState::executeCommand() |
svcmgr_handler() |
do_add_service() |
BINDER_VERSION BINDER_WRITE_READ----> BINDER_THREAD_EXIT BINDER_SET_CONTEXT_MGR …… |
BR_TRANSACTION-----------> …… |
SVC_MGR_GET_SERVICE SVC_MGR_CHECK_SERVICE SVC_MGR_ADD_SERVICE----> …… |
svc_can_register()呼叫者uid权限校验 …… 加入服务到svclist链表中 …… |
小结:
起点:
I##自定义INTERFACE服务类接口 : IInterface
。。。
上面画的线图,格式毁了,看截图把
浓缩:
IBinder有2个主要的子类BBinder 和 BpBinder
1.服务端继承BBinder实现transact用于在onTransact里根据cmd调用实际的接口实现的方法然后通过IPCThreadState的talkWithDriver写回结果给binder
2.客户端继承BpBinder实现transact用于根据调用的业务方法产生对应cmd和参数调用IPCThreadState::self()->transact(),然后通过waitForResponse() -> talkWithDriver() 和binder通信
3.Binder驱动和servicemanager做的工作主要是使用内存共享完成IPC,另外一个是在客户端获取服务的IBinder时,用BpBinder*的实现,而不是服务器端添加服务时给出的BBinder*。
4.当前是否服务端还是客户端?
从servicemanager通过binder通信获取到某个服务的IBinder指针后,是在interface_cast()调用的asInterface()中根据这个IBinder的queryLocalInterface() 返回IInterface* 是否为0指针来判断的
queryLocalInterface()返回是否为0指针
queryLocalInterface 是声明在IBinder中的一个虚方法,有一个默认实现(客户端中的情况)在Binder.cpp 中实现 IBinder::queryLocalInterface() (总是返回NULL)
queryLocalInterface 在服务端在BnInterface中覆盖默认的实现
服务端是调用方法IBinder->BBinder->BnInterface::queryLocalInterface(),查询用的 (用MediaPlayService举例)IMediaPlayService::descriptor == MediaPlayService::descriptor (MediaPlayService::descriptor是MediaPlayService通过继承IMediaPlayService得来,是同一个类中的成员),所以返回 this对象的指针地址,不为0
这一部分和java层的binder机制是类似的(为了方便程序员使用Binder,android提供的AIDL的主要功能就是在此层,根据自定义方法的数量生成等量的常量,用在BINDER_WRITE_READ -> BR_TRANSACTION 与binder通信时确定远端让本方调用某个本地方法的 COMMAND 常量,然后根据command常量调用对应的方法。类似svcmgr_handler()中做的工作。)
对象转换过程:
IserviceManager <- IBinder <- BpBinder
服务使用:
IPCThreadState,ProcessState,android::Thread, android::Thread::threadLoop(), binder. 这一切是有关联的
从binder – ProcessState - android::Thread – IPCThreadState
简要流程
Binder
打开/dev/binder ,用ioctl 循环的BINDER_WRITE_READ 进行数据读写并执行对应的命令
ProcessState
主要持有/dev/binder的fd,设置与binder通信的基本参数
分析一个 开机动画的例子 bootanimation
在void SurfaceFlinger::startBootAnim()中
调用property_set("ctl.start", "bootanim"); 通过属性服务 发送控制命令 启动bootanim 服务,播放启动动画,之后当SurfaceFlinger::bootFinished()启动完成后,调用property_set("service.bootanim.exit", "1"); 设置动画退出属性为1,bootanimation会轮询检测该标志,若读取到1,则退出播放动画。
- 打开/dev/binder,用ioctl 检查BINDER_VERSION,设置最大线程数量 BINDER_SET_MAX_THREADS
- startThreadPool() 打开线程池 -> spawnPooledThread(true)
1. new 一个PoolThread 线程对象(继承android::Thread ,class Thread : virtual public RefBase, android::Thread的线程功能实际上是用pthread实现的),设置线程名称为Binder Thread 1
2. 调用PoolThread 的run方法,实际上是调用了android::Thread::run() -> androidSetCreateThreadFunc() -> pthread_create() -> Thread::_threadLoop() -> PoolThread::readyToRun() -> PoolThread::threadLoop()
mCanCallJava = isMain = true
androidCreateThreadEtc()
默认是 gCreateThreadFn = androidCreateRawThreadEtc;
有个别地方 如 AndroidRuntime.cpp 中通过 androidSetCreateThreadFunc() 将 gCreateThreadFn 指向了 javaCreateThreadEtc() ,但其实际上做了一些对JVM的额外处理后又进入了androidCreateRawThreadEtc
而 androidCreateRawThreadEtc 的实现分为 LINUX 下的 PTHREAD 实现 和 WIN32_THREAD 实现,这里是LINUX的实现
最关键的则是int result = pthread_create(&thread, &attr, (android_pthread_entry)entryFunction, userData);
而入口函数entryFunction 则是在 Thread::run() 中指定的Thread::_threadLoop(),此时子线程才真正跑起来
之后做了一些初始化如gettid()等,进入了一个循环,第一次进入循环则调用readyToRun()虚方法(一般由继承Thread的子类自己根据业务逻辑实现,比如BootAnimation::readyToRun()中当图形绘制的初值化工作完成后返回NO_ERROR,否则返回NO_INIT),然后android Thread框架检测,如果是 NO_ERROR 并且exitPending()未返回true,那么就调用 虚方法threadLoop();(注意,该方法不是_threadLoop()),该方法内一般让子类实现关键的业务逻辑,如BootAnimation::threadLoop()中就在进入一个死循环,播放开机动画,直到exitPending()返回true(当调用了requestExit()后exitPending会返回true,本质上是将mExitPending 变量赋值为true)后退出循环停止播放动画。
因为这里的特定子类是PoolThread所以Thread::_threadLoop()这个框架方法调用了PoolThread::readyToRun(),但PoolThread没去重写它,所以默认返回NO_ERROR,继续,调用PoolThread::threadLoop(), 调用IPCThreadState::self()->joinThreadPool(mIsMain = true); ,self()方法 主要是通过pthread_getspecific 获取保存的线程私有数据中IPCThreadState 对象的指针,如果没有获取到则新建一个IPCThreradState 对象并用pthread_key_create() 和pthread_setspecific() 对象指针存入线程私有数据集合,然后进入joinThreadPool()
IPCThreadState的executeCommand() 主要实现了和通过binder通信的一些常用命令,如获取/释放 自身服务的BBinder对象指针,增/减引用计数器,这里主要是对BR_*的反馈命令进行处理。BC_*发给binder,binder或远端服务返回一个BR_*。
Bootnimation 被 new 出来后,被第一次引用时,会触发onFirstRef()的回调,这里是bootanimation对象业务逻辑的的入口,在里面调用了run() 启动Bootnimation这个继承了Thread类的子线程,并进入threadLoop()根据当前启动阶段展示开机动画
总结:
ProcessState – PoolThread – IPCThreadState 这几个类常用在底层做binder通信使用
常见用法:
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
- ProcessState::self(),创建ProcessState对象,打开binder驱动,并设置基本参数
- ProcessState::startThreadPool(), 启动一个PoolThread子线程,调用run()让子线程初始化并进入ProcessState::threadLoop() 该方法中调用IPCThreadState::self()->joinThreadPool(mIsMain) ,让子线程在一个循环内通过talkWithDriver() 和binder、servicemanager、服务进程进行通信,并用executeCommand() 根据具体的反馈cmd执行处理命令,让这个子线程成为与binder通信的专用线程。
ProcessState::startThreadPool() =
-> new PoolThread子线程 并让它
-> Thread::run()
-> PoolThread::threadLoop()
-> IPCThreadState::self()->joinThreadPool(mIsMain) 循环的与binder通信
- 主线程也调用了一次 IPCThreadState::self()->joinThreadPool(); 那么主线程也进入与binder通信并执行命令的循环中
.doc文件的格式直接复制过来,有些对不上,所以截图
小结:
起点:
I##自定义INTERFACE服务类接口 : IInterface
|
V
在.h文件中DECLARE_META_INTERF asBinder()
ACE(INTERFACE) 声明asInterface()
以及声明自定义的业务方法。
在.c 文件中使用
IMPLEMENT_META_INTERFACE 实现
AsInterface()方法
此处调用发起是,IPCThreadState
调用了joinThread后(PoolThread)
,在一个循环中处理binder发来
的消息,并进入executeCommand()
,其中cmd为BR_TRANSACTION的
case里,会调用the_context_object
法(在服务端的该方法实现是调用
onTransact,客户端的BpBinder
是直接与binder用ioctl通信)
^
|
实现BBinder::onTransact 传递给addService
根据传入参数解cmd(code)
真正实现接口(I##自定义 调用当前对象中自定义INTE
INTERFACE服务类接口)中 RFACE服务类对应的方法,拿
的方法的业务逻辑 到返回值后写入reply对象
^ß---------------------------/ ^
| |
自定义INTERFACE服务类 : Bn自定义INTERFACE服务 : BnInterface : I##自定义INTERFACE服务类接口:(IInterface) : BBinder : IBinder : RefBase
Bp自定义INTERFACE服务 : BpInterface : I##自定义INTERFACE服务类接口:(IInterface) : BpRefBase -- BpBinder : IBinder
| |
V V
实现INTERFACE方法,但 客户端拿到servicemanager通过binder返回过来的 保存binder返回的IBin
是通过调用内部保存的 IBinder后,调用asInterface转换为I##自定义 der(BpBinder)指针
IBinder(BpBinder) INTERFACE服务类接口对象,直接调用想用的业务方法
的transact方法通过 《----------------------- Bp自定义INTERFACE服务 中的实现
Binder通信(ioctl)
完成数据转发给服务
端BBinder的
onTransact处理并返回
浓缩:
IBinder有2个主要的子类BBinder 和 BpBinder
1.服务端继承BBinder实现transact用于在onTransact里根据cmd调用实际的接口实现的方法然后通过IPCThreadState的talkWithDriver写回结果给binder
2.客户端继承BpBinder实现transact用于根据调用的业务方法产生对应cmd和参数调用IPCThreadState::self()->transact(),然后通过waitForResponse() -> talkWithDriver() 和binder通信
3.Binder驱动和servicemanager做的工作主要是使用内存共享完成IPC,另外一个是在客户端获取服务的IBinder时,用BpBinder*的实现,而不是服务器端添加服务时给出的BBinder*。
4.当前是否服务端还是客户端?
从servicemanager通过binder通信获取到某个服务的IBinder指针后,是在interface_cast()调用的asInterface()中根据这个IBinder的queryLocalInterface() 返回IInterface* 是否为0指针来判断的
queryLocalInterface()返回是否为0指针
queryLocalInterface 是声明在IBinder中的一个虚方法,有一个默认实现(客户端中的情况)在Binder.cpp 中实现 IBinder::queryLocalInterface() (总是返回NULL)
queryLocalInterface 在服务端在BnInterface中覆盖默认的实现
服务端是调用方法IBinder->BBinder->BnInterface::queryLocalInterface(),查询用的 (用MediaPlayService举例)IMediaPlayService::descriptor == MediaPlayService::descriptor (MediaPlayService::descriptor是MediaPlayService通过继承IMediaPlayService得来,是同一个类中的成员),所以返回 this对象的指针地址,不为0
这一部分和java层的binder机制是类似的(为了方便程序员使用Binder,android提供的AIDL的主要功能就是在此层,根据自定义方法的数量生成等量的常量,用在BINDER_WRITE_READ -> BR_TRANSACTION 与binder通信时确定远端让本方调用某个本地方法的 COMMAND 常量,然后根据command常量调用对应的方法。类似svcmgr_handler()中做的工作。)
对象转换过程:
IserviceManager <- IBinder <- BpBinder
服务注册:
服务使用:
IPCThreadState,ProcessState,android::Thread, android::Thread::threadLoop(), binder. 这一切是有关联的
从binder – ProcessState - android::Thread – IPCThreadState
简要流程
Binder
打开/dev/binder ,用ioctl 循环的BINDER_WRITE_READ 进行数据读写并执行对应的命令
ProcessState
主要持有/dev/binder的fd,设置与binder通信的基本参数
分析一个 开机动画的例子 bootanimation
在void SurfaceFlinger::startBootAnim()中
调用property_set("ctl.start", "bootanim"); 通过属性服务 发送控制命令 启动bootanim 服务,播放启动动画,之后当SurfaceFlinger::bootFinished()启动完成后,调用property_set("service.bootanim.exit", "1"); 设置动画退出属性为1,bootanimation会轮询检测该标志,若读取到1,则退出播放动画。
1. 打开/dev/binder,用ioctl 检查BINDER_VERSION,设置最大线程数量 BINDER_SET_MAX_THREADS
2. startThreadPool() 打开线程池 -> spawnPooledThread(true)
1. new 一个PoolThread 线程对象(继承android::Thread ,class Thread : virtual public RefBase, android::Thread的线程功能实际上是用pthread实现的),设置线程名称为Binder Thread 1
2. 调用PoolThread 的run方法,实际上是调用了android::Thread::run() -> androidSetCreateThreadFunc() -> pthread_create() -> Thread::_threadLoop() -> PoolThread::readyToRun() -> PoolThread::threadLoop()
mCanCallJava = isMain = true
androidCreateThreadEtc()
默认是 gCreateThreadFn = androidCreateRawThreadEtc;
有个别地方 如 AndroidRuntime.cpp 中通过 androidSetCreateThreadFunc() 将 gCreateThreadFn 指向了 javaCreateThreadEtc() ,但其实际上做了一些对JVM的额外处理后又进入了androidCreateRawThreadEtc
而 androidCreateRawThreadEtc 的实现分为 LINUX 下的 PTHREAD 实现 和 WIN32_THREAD 实现,这里是LINUX的实现
最关键的则是int result = pthread_create(&thread, &attr, (android_pthread_entry)entryFunction, userData);
而入口函数entryFunction 则是在 Thread::run() 中指定的Thread::_threadLoop(),此时子线程才真正跑起来
之后做了一些初始化如gettid()等,进入了一个循环,第一次进入循环则调用readyToRun()虚方法(一般由继承Thread的子类自己根据业务逻辑实现,比如BootAnimation::readyToRun()中当图形绘制的初值化工作完成后返回NO_ERROR,否则返回NO_INIT),然后android Thread框架检测,如果是 NO_ERROR 并且exitPending()未返回true,那么就调用 虚方法threadLoop();(注意,该方法不是_threadLoop()),该方法内一般让子类实现关键的业务逻辑,如BootAnimation::threadLoop()中就在进入一个死循环,播放开机动画,直到exitPending()返回true(当调用了requestExit()后exitPending会返回true,本质上是将mExitPending 变量赋值为true)后退出循环停止播放动画。
因为这里的特定子类是PoolThread所以Thread::_threadLoop()这个框架方法调用了PoolThread::readyToRun(),但PoolThread没去重写它,所以默认返回NO_ERROR,继续,调用PoolThread::threadLoop(), 调用IPCThreadState::self()->joinThreadPool(mIsMain = true); ,self()方法 主要是通过pthread_getspecific 获取保存的线程私有数据中IPCThreadState 对象的指针,如果没有获取到则新建一个IPCThreradState 对象并用pthread_key_create() 和pthread_setspecific() 对象指针存入线程私有数据集合,然后进入joinThreadPool()
IPCThreadState的executeCommand() 主要实现了和通过binder通信的一些常用命令,如获取/释放 自身服务的BBinder对象指针,增/减引用计数器,这里主要是对BR_*的反馈命令进行处理。BC_*发给binder,binder或远端服务返回一个BR_*。
Bootnimation 被 new 出来后,被第一次引用时,会触发onFirstRef()的回调,这里是bootanimation对象业务逻辑的的入口,在里面调用了run() 启动Bootnimation这个继承了Thread类的子线程,并进入threadLoop()根据当前启动阶段展示开机动画
总结:
ProcessState – PoolThread – IPCThreadState 这几个类常用在底层做binder通信使用
常见用法:
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
1. ProcessState::self(),创建ProcessState对象,打开binder驱动,并设置基本参数
2. ProcessState::startThreadPool(), 启动一个PoolThread子线程,调用run()让子线程初始化并进入ProcessState::threadLoop() 该方法中调用IPCThreadState::self()->joinThreadPool(mIsMain) ,让子线程在一个循环内通过talkWithDriver() 和binder、servicemanager、服务进程进行通信,并用executeCommand() 根据具体的反馈cmd执行处理命令,让这个子线程成为与binder通信的专用线程。
ProcessState::startThreadPool() =
-> new PoolThread子线程 并让它
-> Thread::run()
-> PoolThread::threadLoop()
-> IPCThreadState::self()->joinThreadPool(mIsMain) 循环的与binder通信
3. 主线程也调用了一次 IPCThreadState::self()->joinThreadPool(); 那么主线程也进入与binder通信并执行命令的循环中
Bn Bp Binder native层关系的更多相关文章
- Binder Native 层(二)
Binder 框架及 Native 层 Binder机制使本地对象可以像操作当前对象一样调用远程对象,可以使不同的进程间互相通信.Binder 使用 Client/Server 架构,客户端通过服务端 ...
- Android Framework 分析---2消息机制Native层
在Android的消息机制中.不仅提供了供Application 开发使用的java的消息循环.事实上java的机制终于还是靠native来实现的.在native不仅提供一套消息传递和处理的机制,还提 ...
- Native层和so接口和Java层
一.Java层加载so文件 Android在Java层加载so的接口是System.loadLibrary()逐级调用的过程: System.loadLibrary()系统源码: 987 pub ...
- Android Java层,Native层,Lib层打印Log简介【转】
本文转载自:https://blog.csdn.net/AndroidMage/article/details/52225068 说明: 这里我根据个人工作情况说明在各个层打印log.如有问题欢迎拍砖 ...
- 在Android Native层中创建Java虚拟机实例
前言 Android应用中JNI代码,是作为本地方法运行的.而大部分情况下,这些JNI方法均需要传递Dalvik虚拟机实例作为第一个参数.例如,你需要用虚拟机实例来创建jstring和其他的Java对 ...
- Android Native层异步消息处理框架
*本文系作者工作学习总结,尚有不完善及理解不恰当之处,欢迎批评指正* 一.前言 在NuPlayer中,可以发现许多类似于下面的代码: //============================== ...
- 在Visual Studio中使用层关系图描述系统架构、技术栈
当需要描述项目的架构或技术栈的时候,可以考虑使用层关系图. 在解决方案下添加一个名称为"TailspinToys.DesignModel"的建模项目. 在新建的建模项目下添加一个名 ...
- Android逆向之旅---Native层的Hook神器Cydia Substrate使用详解
一.前言 在之前已经介绍过了Android中一款hook神器Xposed,那个框架使用非常简单,方法也就那几个,其实最主要的是我们如何找到一个想要hook的应用的那个突破点.需要逆向分析app即可.不 ...
- SQLite数据库学习小结——native层实现
1. SQlite概述 SQLite是一款轻量.快速.跨平台的嵌入式数据库,是遵守ACID(注:ACID指数据库事务正确执行的四个基本要素的缩写.包含:原子性(Atomicity).一致性(Consi ...
随机推荐
- RDIFramework.NET敏捷开发框架 ━ 工作流程组件Web业务平台
接前两篇: RDIFramework.NET敏捷开发框架 ━ 工作流程组件介绍 RDIFramework.NET敏捷开发框架 ━ 工作流程组件WinForm业务平台 1.RDIFramework.NE ...
- 单词倒序(java)
如何将一串单词组成的字符串倒序呢?如:" we go to school" 变成"school to go we "java代码实现: public stati ...
- 数据结构笔记2(c++)_跨函数使用内存的问题
预备知识 1.所有的指针变量只占4个子节 用第一个字节的地址表示整个变量的地址 //1.cpp 所有的指针变量只占4个子节 用第一个字节的地址表示整个变量的地址 # include <stdi ...
- 苏州市java岗位的薪资状况(2)
上一篇已经统计出了起薪最高的top 10: 接着玩,把top 10 中所有职位的详细信息爬取下来.某一职位的详情是这样: 我们需要把工作经验.学历.职能.关键字爬取下来. from urllib.re ...
- Java总结转载,持续更新。。。
1.Java中内存划分 https://www.cnblogs.com/yanglongbo/p/10981680.html
- Windows10 安装grpc-go 详细步骤
准备依赖 git clone https://github.com/grpc/grpc-go.git $env:GOPATH\src\google.golang.org\grpc git clone ...
- Codeforces Round #602 (Div. 2, based on Technocup 2020 Elimination Round 3) C. Messy 构造
C. Messy You are fed up with your messy room, so you decided to clean it up. Your room is a bracket ...
- Python连载44-XML其他注意点
一.XML文件注意点 1.内容中不能出现尖括号 例如:下面是不合法的 <grade>成绩<90</grade> 解决方案:使用实体引用<EntityReferenc ...
- 【STM32H7教程】第27章 STM32H7的TCM,SRAM等五块内存的动态内存分配实现
完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第27章 STM32H7的TCM,SRAM等五块内 ...
- tomcat程序生成的日志文件不可读问题 - 运维总结
现象描述:线上机器的程序文件(包括TOMCAT自身)使用APP账号作为属主运行,同时禁止了APP账号的BASH.登录系统使用了统一认证,这样每个人都有自己的账号登录系统.为了方便开发人员登录查看日志, ...