博客
关于我
Android Binder分析
阅读量:128 次
发布时间:2019-02-27

本文共 20087 字,大约阅读时间需要 66 分钟。

Binder通信模型

Binder的优势

实现方式

        Binder使用Client-Server通信方式:一个进程作为Server提供诸如视频/音频解码,视频捕获,地址本查询,网络连接等服务;多个进程作为Client向Server发起服务请求,获得所需要的服务。要想实现Client-Server通信据必须实现以下两点:一是server必须有确定的访问接入点或者说地址来接受Client的请求,并且Client可以通过某种途径获知Server的地址;二是制定Command-Reply协议来传输数据。例如在网络通信中Server的访问接入点就是Server主机的IP地址+端口号,传输协议为TCP协议。对Binder而言,Binder可以看成Server提供的实现某个特定服务的访问接入点, Client通过这个‘地址’向Server发送请求来使用该服务;对Client而言,Binder可以看成是通向Server的管道入口,要想和某个Server通信首先必须建立这个管道并获得管道入口。

性能优化

        如果是传统的Linux IPC方式中,socket作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。

        举个例子如,Client要将一块内存数据传递给Server,一般的做法是,Client将这块数据从它的进程空间拷贝到内核空间中,然后内核再将这个数据从内核空间拷贝到Server的进程空间,这样,Server就可以访问这个数据了。但是在这种方法中,执行了两次内存拷贝操作。所以Binder设计时采用了折衷的方式,只需要把Client进程空间的数据拷贝一次到内核空间,然后Server与内核共享这个数据就可以了,整个过程只需要执行一次内存拷贝,提高了效率。同时这样更有C/S架构的模型,方便管理。

Binder 通信模型

        从英文字面上意思看,Binder具有粘结剂的意思,那么它把什么东西粘结在一起呢?在Android系统的Binder机制中,由一系统组件组成,分别是Client、Server、Service Manager和Binder驱动程序,其中Client、Server和Service Manager运行在用户空间,Binder驱动程序运行内核空间。Binder就是一种把这四个组件粘合在一起的粘结剂了,其中,核心组件便是Binder驱动程序了,Service Manager提供了辅助管理的功能,Client和Server正是在Binder驱动和Service Manager提供的基础设施上,进行Client-Server之间的通信。这四个角色的关系和互联网类似:Server是服务器,Client是客户终端,Service Manager是域名服务器(DNS),Binder驱动是路由器。

Binder驱动

        和路由器一样,Binder驱动虽然默默无闻,却是通信的核心。尽管名叫‘驱动’,实际上和硬件设备没有任何关系,只是实现方式和设备驱动程序是一样的:它工作于内核态,提供open(),mmap(),poll(),ioctl()等标准文件操作,以字符驱动设备中的misc设备注册在设备目录/dev下,用户通过/dev/binder访问该它。驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。驱动和应用程序之间定义了一套接口协议,主要功能由ioctl()接口实现,不提供read(),write()接口,因为ioctl()灵活方便,且能够一次调用实现先写后读以满足同步交互,而不必分别调用write()和read()。Binder驱动的代码每个分支位置不一样,再加上我也没有下内核的代码,先给个4.4的Binder.c的地址,有兴趣的可以自己研究。

ServiceManager 与实名Binder

        和DNS类似,ServiceManager的作用是将字符形式的Binder名字转化成Client中对该Binder的引用,使得Client能够通过Binder名字获得对Server中Binder实体的引用。注册了名字的Binder叫实名Binder,就象每个网站除了有IP地址外还有自己的网址。Server创建了Binder实体,为其取一个字符形式,可读易记的名字,将这个Binder连同名字以数据包的形式通过Binder驱动发送给ServiceManager,通知ServiceManager注册一个名叫张三的Binder,它位于某个Server中。驱动为这个穿过进程边界的Binder创建位于内核中的实体节点以及ServiceManager对实体的引用,将名字及新建的引用打包传递给ServiceManager。ServiceManager收数据包后,从中取出名字和引用填入一张查找表中。

        细心的读者可能会发现其中的蹊跷:ServiceManager是一个进程,Server是另一个进程,Server向ServiceManager注册Binder必然会涉及进程间通信。当前实现的是进程间通信却又要用到进程间通信,这就好象蛋可以孵出鸡前提却是要找只鸡来孵蛋。Binder的实现比较巧妙:预先创造一只鸡来孵蛋:ServiceManager和其它进程同样采用Binder通信,ServiceManager是Server端,有自己的Binder对象(实体),其它进程都是Client,需要通过这个Binder的引用来实现Binder的注册,查询和获取。ServiceManager提供的Binder比较特殊,它没有名字也不需要注册,当一个进程使用BINDER_SET_CONTEXT_MGR命令将自己注册成ServiceManager(会用到ioctl(fd, cmd, arg)函数,cmd为BINDER_SET_CONTEXT_MGR)时Binder驱动会自动为它创建Binder实体(这就是那只预先造好的鸡)。其次这个Binder的引用在所有Client中都固定为0而无须通过其它手段获得。也就是说,一个Server若要向ServiceManager注册自己Binder就必需通过0(即NULL指针)这个引用号和ServiceManager的Binder通信。类比网络通信,0号引用就好比域名服务器的地址,你必须预先手工或动态配置好。要注意这里说的Client是相对ServiceManager而言的,一个应用程序可能是个提供服务的Server,但对ServiceManager来说它仍然是个Client。

Client 获得实名Binder的引用

        Server向ServiceManager注册了Binder实体及其名字后,Client就可以通过名字获得该Binder的引用了。Client也利用保留的0号引用向ServiceManager请求访问某个Binder:我申请获得名字叫张三的Binder的引用。ServiceManager收到这个连接请求,从请求数据包里获得Binder的名字,在查找表里找到该名字对应的条目,从条目中取出Binder的引用,将该引用作为回复发送给发起请求的Client。从面向对象的角度,这个Binder对象现在有了两个引用:一个位于ServiceManager中,一个位于发起请求的Client中。如果接下来有更多的Client请求该Binder,系统中就会有更多的引用指向该Binder,就象java里一个对象存在多个引用一样。而且类似的这些指向Binder的引用是强类型,从而确保只要有引用Binder实体就不会被释放掉。通过以上过程可以看出,ServiceManager象个火车票代售点,收集了所有火车的车票,可以通过它购买到乘坐各趟火车的票-得到某个Binder的引用。

匿名 Binder

        并不是所有Binder都需要注册给ServiceManager广而告之的。Server端可以通过已经建立的Binder连接将创建的Binder实体传给Client,当然这条已经建立的Binder连接必须是通过实名Binder实现。如果我们是从事application开发,跨进程的自己手写AIDL文件,或者相同进程的bindService自己添加一个继承Binder的子类,那么这个Binder没有向ServiceManager注册名字,所以是个匿名Binder。Client将会收到这个匿名Binder的引用,通过这个引用向位于Server中的实体发送请求。匿名Binder为通信双方建立一条私密通道,只要Server没有把匿名Binder发给别的进程,别的进程就无法通过穷举或猜测等任何方式获得该Binder的引用,向该Binder发送请求。

Binder传递数据的载体Parcel

Parcel是一种数据的载体,用于承载希望通过IBinder发送的相关信息(包括数据和对象引用),正是基于Parcel这种跨进程传输数据的能力,进程间的IPC通信才能更加平滑可靠。

Parcel具备打包和重组的能力,它提供了非常丰富的接口以方便应用程序的使用。

class Parcel {    friend class IPCThreadState;public:    class ReadableBlob;    class WritableBlob;                        Parcel();                        ~Parcel();        const uint8_t*      data() const;    size_t              dataSize() const;//获取当前已经存储的数据大小    size_t              dataAvail() const;//当前Parcel中的可读数据的大小    size_t              dataPosition() const;//数据的当前位置值,有点类似于游标    size_t              dataCapacity() const;//当前Parcel的存储能力    status_t            setDataSize(size_t size);    void                setDataPosition(size_t pos) const;    status_t            setDataCapacity(size_t size);//设置Parcel空间的大小,显然存储的数据不                                                      //能大于这个值        status_t            setData(const uint8_t* buffer, size_t len);    status_t            appendFrom(const Parcel *parcel,                                   size_t start, size_t len);    int                 compareData(const Parcel& other);    bool                allowFds() const;    bool                pushAllowFds(bool allowFds);    void                restoreAllowFds(bool lastValue);

Parcel提供的读写操作时配套的,用哪种方式写入的数据就要用相应的方式正确读取。数据是按照host cpu的字节序来读写的

Primitive Arrays的读写操作通常是先写入用4个字节表示的数据大小值,接着写入数据本身。

如果写入数据时系统发现已经超出了Parcel的存储能力,它会自动申请所需的内存空间,并扩展dataCapacity;而且每次写入都是从dataPosition开始的。

遵从Parcelable协议的对象可以通过Parcel来存取。

Active Objects

Parcel的另一个强大武器就是可以读写Active Object.通常我们存入Parcel中的是对象的内容,而Active Objects写入的则是它们的特殊标志引用。所以从Parcel中读取这些对象时,大家看到的并不是重新创建的对象实例,而是原来那个被写入的实例。能够以这种方式传输的对象主要有两类:

1. Binder

Binder是Android系统IPC通信的核心机制之一,同时它也是一个对象。利用Parcel将Binder对象写入,读取时就能得到原始的Binder对象,或者是它的特殊代理实例(最终操作的还是原始的Binder对象)。

sp
readStrongBinder() const; status_t readStrongBinder(sp
* val) const; status_t writeStrongBinder(const sp
& val); status_t writeWeakBinder(const wp
& val);

2. FileDescriptor。FileDescriptor是Linux中的文件描述符,可以通过Parcel的如下方式进行传递

// Place a file descriptor into the parcel.  The given fd must remain    // valid for the lifetime of the parcel.    // The Parcel does not take ownership of the given fd unless you ask it to.    status_t            writeFileDescriptor(int fd, bool takeOwnership = false);    // Place a file descriptor into the parcel.  A dup of the fd is made, which    // will be closed once the parcel is destroyed.    status_t            writeDupFileDescriptor(int fd);

应用程序如何使用Parcel

通过Parcel.obtain()接口来获取一个Parcel对象

status_t            mError;    uint8_t*            mData;    size_t              mDataSize;    size_t              mDataCapacity;    mutable size_t      mDataPos;    binder_size_t*      mObjects;    size_t              mObjectsSize;    size_t              mObjectsCapacity;    mutable size_t      mNextObjectHint;    mutable bool        mFdsKnown;    mutable bool        mHasFds;    bool                mAllowFds;    release_func        mOwner;    void*               mOwnerCookie;

ServiceManagerNative.java (base\core\java\android\os) 

class ServiceManagerProxy implements IServiceManager {    public ServiceManagerProxy(IBinder remote) {        mRemote = remote;    }        public IBinder asBinder() {        return mRemote;    }        public IBinder getService(String name) throws RemoteException {        Parcel data = Parcel.obtain();//获取一个Parcel对象        Parcel reply = Parcel.obtain();        data.writeInterfaceToken(IServiceManager.descriptor);//用于写入IBinder接口标志,所带         的参数是String类型的,如:IServiceManager.descriptor = 'android.os.IServiceManager'        data.writeString(name);//写入需要向ServiceManager查询的Service名称        mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);        IBinder binder = reply.readStrongBinder();        reply.recycle();        data.recycle();        return binder;    }

Parcel在整个IPC的传递过程中比较繁琐,但有一点是不变的,那就是写入方和读取方所使用的协议必须是完全一致的

// Write RPC headers.  (previously just the interface token)status_t Parcel::writeInterfaceToken(const String16& interface){    writeInt32(IPCThreadState::self()->getStrictModePolicy() |               STRICT_MODE_PENALTY_GATHER);    // currently the interface identification token is just its name as a string    return writeString16(interface);}

读取方式Service_Manager.c中,这个ServiceManager是用C/C++编写的,也特别提醒的是,servicemanager所对应的c文件是service_manager.c和binder.c。源码工程目录中还有其他诸如ServiceManager.cpp的文件存在,但并不属于SM程序。

ServiceManager(Binder Server)就像TCP/IP协议中的DNS服务器,其IP地址就是默认的0;

int main(int argc, char** argv){    struct binder_state *bs;    union selinux_callback cb;    char *driver;    if (argc > 1) {        driver = argv[1];    } else {        driver = "/dev/binder";    }    bs = binder_open(driver, 128*1024);    if (!bs) {#ifdef VENDORSERVICEMANAGER        ALOGW("failed to open binder driver %s\n", driver);        while (true) {            sleep(UINT_MAX);        }#else        ALOGE("failed to open binder driver %s\n", driver);#endif        return -1;    }    if (binder_become_context_manager(bs)) {//将自己设置为Binder大管家,整个Android系统只允许一个ServiceManager存在,若后面还有人调用这个函数就会失败        ALOGE("cannot become context manager (%s)\n", strerror(errno));        return -1;    }    cb.func_audit = audit_callback;    selinux_set_callback(SELINUX_CB_AUDIT, cb);    cb.func_log = selinux_log_callback;    selinux_set_callback(SELINUX_CB_LOG, cb);#ifdef VENDORSERVICEMANAGER    sehandle = selinux_android_vendor_service_context_handle();#else    sehandle = selinux_android_service_context_handle();#endif    selinux_status_open(true);    if (sehandle == NULL) {        ALOGE("SELinux: Failed to acquire sehandle. Aborting.\n");        abort();    }    if (getcon(&service_manager_context) != 0) {        ALOGE("SELinux: Failed to acquire service_manager context. Aborting.\n");        abort();    }    binder_loop(bs, svcmgr_handler);//进入循环,等待客户的请求    return 0;}
int svcmgr_handler(struct binder_state *bs,                   struct binder_transaction_data *txn,                   struct binder_io *msg,                   struct binder_io *reply){    struct svcinfo *si;    uint16_t *s;    size_t len;    uint32_t handle;    uint32_t strict_policy;    int allow_isolated;    //ALOGI("target=%p code=%d pid=%d uid=%d\n",    //      (void*) txn->target.ptr, txn->code, txn->sender_pid, txn->sender_euid);    if (txn->target.ptr != BINDER_SERVICE_MANAGER)        return -1;    if (txn->code == PING_TRANSACTION)        return 0;    // Equivalent to Parcel::enforceInterface(), reading the RPC    // header with the strict mode policy mask and the interface name.    // Note that we ignore the strict_policy and don't propagate it    // further (since we do no outbound RPCs anyway).    strict_policy = bio_get_uint32(msg);    s = bio_get_string16(msg, &len);    if (s == NULL) {        return -1;    }    if ((len != (sizeof(svcmgr_id) / 2)) ||        memcmp(svcmgr_id, s, sizeof(svcmgr_id))) {        fprintf(stderr,"invalid id %s\n", str8(s, len));        return -1;    }    if (sehandle && selinux_status_updated() > 0) {        struct selabel_handle *tmp_sehandle = selinux_android_service_context_handle();        if (tmp_sehandle) {            selabel_close(sehandle);            sehandle = tmp_sehandle;        }    }    switch(txn->code) {    case SVC_MGR_GET_SERVICE:    case SVC_MGR_CHECK_SERVICE:        s = bio_get_string16(msg, &len);        if (s == NULL) {            return -1;        }        handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);        if (!handle)            break;        bio_put_ref(reply, handle);        return 0;    case SVC_MGR_ADD_SERVICE:

这其中包含Binder驱动与用户空间的各种交互,后续讲解

Binder Server(SM)是基于Native代码实现的,那么获取SM服务是不是也必须使用Native语言来实现呢?其实它们之间没有必然的联系。所有的Binder Client或者Binder Server都是基于Binder驱动展开的,因此只要能正常使用Binder驱动,采用哪种编程语言都是可以的。

如果要访问SM(Binder server)的服务,无非就是下面几部

1.打开Binder设备

2.执行mmap

3.通过Binder驱动向SM发送请求(SM的handle为0)

4.获得结果

真正实现的时候,不是每个线程都要打开binder驱动执行mmap,其后果是消耗的系统资源会越来越多,直到崩溃。有效的解决方法就是每个进程只允许打开一次Binder设备,且只做一次内存映射,所有需要使用Binder驱动的线程共享这一资源

这就涉及到ProcessState和IPCThreadState这两个类

这两个类在MediaPlayerService中有简单的分析

首先需要对SM的服务进行封装,叫做ServiceManagerProxy 其代码位于ServiceManagerNative.java (base\core\java\android\os) ServiceManagerProxy获得WindowsManagerService的服务,假设一个应用程序要通过SMProxy,获得WMS的Binder句柄,可以用以下方式(真实的实现不是这样的,Android系统中真实的实现与此基本类似,只不过它在ServiceManagerProxy上又加了一层封装,即ServicManager.java)

/*应用程序获取SM服务的示例*///step 1,创建ServiceManagerProxyServiceManagerProxy sm = new ServiceManagerProxy(new BpBinder(HANDLE));//step 2,通过ServiceManagerProxy 获取SM的某项服务IBinder wms_binder = sm.gerService("window")

ServiceManagerProxy所提供的服务和服务端的SM必须是一致的。把这些方法提取出来,就是ServiceManagerProxy的接口,取个名字叫做IServiceManager。

public interface IServiceManager extends IInterface{    /**     * Retrieve an existing service called @a name from the     * service manager.  Blocks for a few seconds waiting for it to be     * published if it does not already exist.     */    public IBinder getService(String name) throws RemoteException;        /**     * Retrieve an existing service called @a name from the     * service manager.  Non-blocking.     */    public IBinder checkService(String name) throws RemoteException;    /**     * Place a new @a service called @a name into the service     * manager.     */    public void addService(String name, IBinder service, boolean allowIsolated)                throws RemoteException;    /**     * Return a list of all currently running services.     */    public String[] listServices() throws RemoteException;    /**     * Assign a permission controller to the service manager.  After set, this     * interface is checked before any services are added.     */    public void setPermissionController(IPermissionController controller)            throws RemoteException;        static final String descriptor = "android.os.IServiceManager";    int GET_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;    int CHECK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+1;    int ADD_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+2;    int LIST_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3;    int CHECK_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4;    int SET_PERMISSION_CONTROLLER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+5;}
class ServiceManagerProxy implements IServiceManager {    public ServiceManagerProxy(IBinder remote) {        mRemote = remote;    }        public IBinder asBinder() {        return mRemote;    }        public IBinder getService(String name) throws RemoteException {        Parcel data = Parcel.obtain();        Parcel reply = Parcel.obtain();        data.writeInterfaceToken(IServiceManager.descriptor);        data.writeString(name);        mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);        IBinder binder = reply.readStrongBinder();        reply.recycle();        data.recycle();        return binder;    }    public IBinder checkService(String name) throws RemoteException {        Parcel data = Parcel.obtain();        Parcel reply = Parcel.obtain();        data.writeInterfaceToken(IServiceManager.descriptor);        data.writeString(name);        mRemote.transact(CHECK_SERVICE_TRANSACTION, data, reply, 0);        IBinder binder = reply.readStrongBinder();        reply.recycle();        data.recycle();        return binder;    }    public void addService(String name, IBinder service, boolean allowIsolated)            throws RemoteException {        Parcel data = Parcel.obtain();        Parcel reply = Parcel.obtain();        data.writeInterfaceToken(IServiceManager.descriptor);        data.writeString(name);        data.writeStrongBinder(service);        data.writeInt(allowIsolated ? 1 : 0);        mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0);        reply.recycle();        data.recycle();    }        public String[] listServices() throws RemoteException {        ArrayList
services = new ArrayList
(); int n = 0; while (true) { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IServiceManager.descriptor); data.writeInt(n); n++; try { boolean res = mRemote.transact(LIST_SERVICES_TRANSACTION, data, reply, 0); if (!res) { break; } } catch (RuntimeException e) { // The result code that is returned by the C++ code can // cause the call to throw an exception back instead of // returning a nice result... so eat it here and go on. break; } services.add(reply.readString()); reply.recycle(); data.recycle(); } String[] array = new String[services.size()]; services.toArray(array); return array; } public void setPermissionController(IPermissionController controller) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IServiceManager.descriptor); data.writeStrongBinder(controller.asBinder()); mRemote.transact(SET_PERMISSION_CONTROLLER_TRANSACTION, data, reply, 0); reply.recycle(); data.recycle(); } private IBinder mRemote;}

应用程序使用ServiceManager更加方便,连ServiceManagerProxy都不用创建了,因为ServiceManager的所有接口都是static的,可以直接使用ServiceManager的功能

ServiceManager.java (base\core\java\android\os) 

/**     * Returns a reference to a service with the given name.     *      * @param name the name of the service to get     * @return a reference to the service, or null if the service doesn't exist     */    public static IBinder getService(String name) {        try {            IBinder service = sCache.get(name);//查询缓存            if (service != null) {                return service;//从缓存中找到结果,直接返回            } else {                return Binder.allowBlocking(getIServiceManager().getService(name));//向SM发起查询            }        } catch (RemoteException e) {            Log.e(TAG, "error in getService", e);        }        return null;    }
private static IServiceManager getIServiceManager() {        if (sServiceManager != null) {            return sServiceManager;// 返回一个IServiceManager对象        }        // Find the service manager        sServiceManager = ServiceManagerNative                .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));        return sServiceManager;    }
public abstract class ServiceManagerNative extends Binder implements IServiceManager{    /**     * Cast a Binder object into a service manager interface, generating     * a proxy if needed.     */    static public IServiceManager asInterface(IBinder obj)    {        if (obj == null) {            return null;        }        IServiceManager in =            (IServiceManager)obj.queryLocalInterface(descriptor);        if (in != null) {            return in;        }                return new ServiceManagerProxy(obj);    }        public ServiceManagerNative()    {        attachInterface(this, descriptor);    }

ServiceManagerProxy终于出现了,作为SM的代理,其必定要与Binder驱动进行通信,其只是简单记录了传入的Binder对象

 

public ServiceManagerProxy(IBinder remote) {        mRemote = remote;    }
public IBinder getService(String name) throws RemoteException {        Parcel data = Parcel.obtain();        Parcel reply = Parcel.obtain();        data.writeInterfaceToken(IServiceManager.descriptor);        data.writeString(name);        mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);//利用IBinder对象执行命令        IBinder binder = reply.readStrongBinder();        reply.recycle();        data.recycle();        return binder;    }

IBinder.transact:利用IBinder的transact将请求发出去,而不用理会Binder驱动Open mmap以及一大堆具体的Binder协议中的命令。所以这个IBinder一定会再内部使用ProcessState和IPCThreadState来与Binder驱动进行通信。

int GET_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
public interface IBinder {    /**     * The first transaction code available for user commands.     */    int FIRST_CALL_TRANSACTION  = 0x00000001;

Client要与Server所使用的业务码一致,如上面的GET_SERVICE_TRANSACTION

所以这个业务码是1,那Service_Manager.c中的业务码说明

enum {    /* Must match definitions in IBinder.h and IServiceManager.h */    PING_TRANSACTION  = B_PACK_CHARS('_','P','N','G'),    SVC_MGR_GET_SERVICE = 1,    SVC_MGR_CHECK_SERVICE,    SVC_MGR_ADD_SERVICE,    SVC_MGR_LIST_SERVICES,};

最终会到Service_Manager中去处理

转载地址:http://ibjb.baihongyu.com/

你可能感兴趣的文章
Mysql InnoDB存储引擎中缓冲池Buffer Pool、Redo Log、Bin Log、Undo Log、Channge Buffer
查看>>
MySQL InnoDB引擎的锁机制详解
查看>>
Mysql INNODB引擎行锁的3种算法 Record Lock Next-Key Lock Grap Lock
查看>>
mysql InnoDB数据存储引擎 的B+树索引原理
查看>>
mysql innodb通过使用mvcc来实现可重复读
查看>>
mysql insert update 同时执行_MySQL进阶三板斧(三)看清“触发器 (Trigger)”的真实面目...
查看>>
mysql interval显示条件值_MySQL INTERVAL关键字可以使用哪些不同的单位值?
查看>>
Mysql join原理
查看>>
MySQL Join算法与调优白皮书(二)
查看>>
Mysql order by与limit混用陷阱
查看>>
Mysql order by与limit混用陷阱
查看>>
mysql order by多个字段排序
查看>>
MySQL Order By实现原理分析和Filesort优化
查看>>
mysql problems
查看>>
mysql replace first,MySQL中处理各种重复的一些方法
查看>>
MySQL replace函数替换字符串语句的用法(mysql字符串替换)
查看>>
mysql replace用法
查看>>
Mysql Row_Format 参数讲解
查看>>
mysql select, from ,join ,on ,where groupby,having ,order by limit的执行顺序和书写顺序
查看>>
MySQL Server 5.5安装记录
查看>>