Binder解析-程序员宅基地

技术标签: java binder  binder架构解析  service manager  native binder  

相关源码路径

/framework/base/core/java/Android/os/IInterface.java
/framework/base/core/java/Android/os/IServiceManager.java
/framework/base/core/java/Android/os/ServiceManager.java
/framework/base/core/java/Android/os/ServiceManagerNative.java(包含内部类ServiceManagerProxy)

/framework/base/core/java/Android/os/IBinder.java
/framework/base/core/java/Android/os/Binder.java(包含内部类BinderProxy)
/framework/base/core/java/Android/os/Parcel.java
/framework/base/core/java/com/Android/internal/os/BinderInternal.java

/framework/base/core/jni/Android_os_Parcel.cpp
/framework/base/core/jni/AndroidRuntime.cpp
/framework/base/core/jni/Android_util_Binder.cpp

       Android系统中,多进程间的通信都是依赖于底层Binder IPC机制,Binder机制是一种RPC方案例如:当进程A中的Activity与进程B中的Service通信时,就使用了binder机制。为了完成进程间的通信,binder使用AIDL来描述进程间的接口。此外,整个Android系统架构中,采用了大量的binder机制。

       Android中的Binder通信采用C/S架构,从组件视角来说,包含ClientServerService Manager以及binder驱动,其中Service Manager用于管理系统中的各种服务。Binder整体通信架构如下图所示:

 

(1)注册服务(addService):Server进程要先注册Service到Service Manager。该过程:Server是客户端,Service Manager是服务端;

(2)获取服务(getService):Client进程使用某个Service前,须先向Service Manager中获取相应的Service。该过程:Client是客户端,Service Manager是服务端;

(3)使用服务:Client根据得到的Service信息建立与Service所在的Server进程通信的通路,然后就可以直接与Service交互。该过程:client是客户端,server是服务端。

       

        binder相对来说还是比较复杂的,有framework层与native层的binder。framework层的binder采用JNI技术来调用native(C/C++ framework)层的binder架构,从而为上层应用程序提供服务。需要注意的是:

(1) framework层的Binder是建立在Native层架构基础之上的,核心逻辑都是交予Native层方法来处理

(2)framework层的Service Manager类与Native层的功能并不完全对应,java层的Service Manager类的实现最终是通过BinderProxy传递给Native层来完成的。

 

       图中红色代表整个frameworkbinder架构相关组件;Binder类代表Server端,BinderProxy类代表Client端;图中蓝色代表NativeBinder架构相关组件。


1 binder介绍

         Android源码中,binder的核心库是在native层实现,但在java层和native层都有接口供应用程序使用。如果单从binder角度出发,Binder架构图如下:

 

(1) binder驱动层

         Android因此添加了binder驱动,其设备节点为/dev/binder,主设备号为10binder驱动程序在内核中的头文件和代码路径如下:

   kernel/drivers/staging/binder.h

         kernel/drivers/staging/binder.c

         binder驱动层的主要作用是完成实际的binder数据传输。

(2) binder adapter

       主要是IPCThreadState.cppProcessState.cpp,源码位于frameworks/native/libs/binder目录下,这两个类都采用了单例模式,主要负责和驱动直接交互。

        a、ProcessState,进程相关的类,负责打开binder设备,进行一些初始化设置并做内存映射;

void ProcessState::startThreadPool()该方法启动的新线程,并通过joinThreadPool读取binder设备,查看是否有请求。

        b、IPCThreadState,线程相关的类,负责直接和binder设备通信,使用ioctl读写binder驱动数据。 

status_t IPCThreadState::talkWithDriver(bool doReceive) 该方法调用的是ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)从/dev/binder读取。
status_t IPCThreadState::executeCommand(int32_t cmd)该方法是对talkWithDriver返回的数据进行处理。
void IPCThreadState::joinThreadPool(bool isMain)该方法创建线程并进行talkWithDriver以及executeCommand。

(3) Binder核心层

       Binder核心层主要是IBinder及它的两个子类,即BBinderBpBinder,分别代表了最基本的服务端及客户端。源码位于frameworks/native/libs/binder目录下。

       binder service服务端实体类会继承BnInterface,而BnInterface会继承自BBinder,服务端可将BBinder对象注册到servicemananger进程。

       客户端程序和驱动交互时只能得到远程对象的句柄handle,它可以调用调用ProcessStategetStrongProxyForHandle函数,利用句柄handle建立BpBinder对象,然后将它转为IBinder指针返回给调用者。这样客户端每次调用IBinder指针的transact方法,其实是执行BpBindertransact方法。

(4) Binder框架层

      a、Native Binder框架层包含以下类(frameworks/native/libs/binder)IInterfaceBnInterfaceBpInterface,等。

      b、Java框架层包含以下类(frameworks/base/core/java/android/os):

      IBinderBinderIInterfaceServiceManagerNativeServiceManagerBinderInternalIServiceManagerServiceManagerProxy

     Java框架层的类的部分方法的实现在本地代码里(frameworks/base/core/jni)


1.1 Binder类分层

        整个Binderkernel至,nativeJNIFramework层所涉及的类如下:

【初始化】

         Zygote启动时会有一个虚拟机注册过程,该过程调用AndroidRuntime::startReg方法来完成jni方法的注册。调用register_Android_os_Binder完成binder的native方法注册。
int register_Android_os_Binder(JNIEnv* env)
{
    // 注册Binder类的jni方法
    if (int_register_Android_os_Binder(env) < 0)  
        return -1;

    // 注册BinderInternal类的jni方法
    if (int_register_Android_os_BinderInternal(env) < 0)
        return -1;

    // 注册BinderProxy类的jni方法
    if (int_register_Android_os_BinderProxy(env) < 0)
        return -1;              
    ...
    return 0;
}


1.2 binder调用顺序

          使用AIDL进行跨进程的通信,实际上就是使用binder,必须要继承binder接口,java和C++都有对应的IBinder,proxy,stub等,通过jni进程数据的交互,binder的核心在native层。整个调用关系如下。




2 Binder的Proxy-Stub模式


2.1 Java空间      


        IBinder/Binder/BinderProxy是Binder机制的核心api, 而IInterface和AIDL就是为了方便开发者使用Binder进行进程间通信。先看IBinder接口:

public interface IBinder 
{
    . . . . . .
    public String getInterfaceDescriptor() throws RemoteException;
    public boolean pingBinder();
    public boolean isBinderAlive();
    public IInterface queryLocalInterface(String descriptor);  //返回IInterface类型
    public void dump(FileDescriptor fd, String[] args) throws RemoteException;
    public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException;
    public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException;  //通信函数
    
    public interface DeathRecipient 
    {
        public void binderDied();
    }
    public void linkToDeath(DeathRecipient recipient, int flags)throws RemoteException;
    public boolean unlinkToDeath(DeathRecipient recipient, int flags);
}

Binder与BinderProxy的定义如下,都实现了IBinder接口
public class Binder implements IBinder {

    ...
    public Binder() {
        init();

        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Binder> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Binder class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }
    }
    
    public void attachInterface(IInterface owner, String descriptor) {
        mOwner = owner;
        mDescriptor = descriptor;
    }
    
    public String getInterfaceDescriptor() {
        return mDescriptor;
    }

    public IInterface queryLocalInterface(String descriptor) {
        if (mDescriptor.equals(descriptor)) {
            return mOwner;
        }
        return null;
    }

    protected boolean onTransact(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException {
        if (code == INTERFACE_TRANSACTION) {
            reply.writeString(getInterfaceDescriptor());
            return true;
        } else if (code == DUMP_TRANSACTION) {
            ParcelFileDescriptor fd = data.readFileDescriptor();
            String[] args = data.readStringArray();
            if (fd != null) {
                try {
                    dump(fd.getFileDescriptor(), args);
                } finally {
                    try {
                        fd.close();
                    } catch (IOException e) {
                        // swallowed, not propagated back to the caller
                    }
                }
            }
            // Write the StrictMode header.
            if (reply != null) {
                reply.writeNoException();
            } else {
                StrictMode.clearGatheredViolations();
            }
            return true;
        }
        return false;
    }
    
    public final boolean transact(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException {
        if (false) Log.v("Binder", "Transact: " + code + " to " + this);
        if (data != null) {
            data.setDataPosition(0);
        }
        boolean r = onTransact(code, data, reply, flags);//调用onTransact()函数
        if (reply != null) {
            reply.setDataPosition(0);
        }
        return r;
    }
    
    public void linkToDeath(DeathRecipient recipient, int flags) {
    }

    public boolean unlinkToDeath(DeathRecipient recipient, int flags) {
        return true;
    }
    
    protected void finalize() throws Throwable {
        try {
            destroy();
        } finally {
            super.finalize();
        }
    }

    private native final void init();
    private native final void destroy();

    // Entry point from android_util_Binder.cpp's onTransact
    private boolean execTransact(int code, long dataObj, long replyObj, int flags) {
        ...
        try {
            res = onTransact(code, data, reply, flags);//调用onTransact()函数
        } catch (RemoteException e) {
            if ((flags & FLAG_ONEWAY) != 0) {
                Log.w(TAG, "Binder call failed.", e);
            } else {
                reply.setDataPosition(0);
                reply.writeException(e);
            }
            res = true;
        } 
        ...
        return res;
    }
}

final class BinderProxy implements IBinder {
    ...
    public IInterface queryLocalInterface(String descriptor) {
        return null;
    }

    public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
        return transactNative(code, data, reply, flags);
    }

    public native String getInterfaceDescriptor() throws RemoteException;
    public native boolean transactNative(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException;
    public native void linkToDeath(DeathRecipient recipient, int flags)
            throws RemoteException;
    public native boolean unlinkToDeath(DeathRecipient recipient, int flags);

    BinderProxy() {
        mSelf = new WeakReference(this);
    }    
   ...
}
最后,在来看IInterface接口,很简单,就一个方法,返回值是IBinder。
public interface IInterface
{
    public IBinder asBinder();
}
四者的UML类图如下所示:


       这就是BInder机制Proxy-Stub模式原始配方。
       在这个类图的最顶层,有两个接口,IInterface和IBinder。IBinder代表跨进程传输的能力,而IInterface则用来辅助实现具体的传输业务。Binder是IBinder的实现类,因此它具备跨进程传输的能力,它实际上就是远程Server端的Binder对象本身。与此对应的BinderProxy则是远程Binder的代理对象,给Client进程用的。在跨越进程的时候,Binder驱动会自动完成这两个对象的转换。

当我们写了一个AIDL文件之后,如下:
interface ICompute {
    int add(int a,int b);
}
此时,会生成一个相应的java文件,如下:
/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: D:\\Code\\Github\\Android\\Demo\\src\\com\\tfygg\\demo\\service\\ICompute.aidl
 */
package com.tfygg.demo.service;

public interface ICompute extends android.os.IInterface {
	/** Local-side IPC implementation stub class. */
	public static abstract class Stub extends android.os.Binder implements com.tfygg.demo.service.ICompute {
		private static final java.lang.String DESCRIPTOR = "com.tfygg.demo.service.ICompute";

		/** Construct the stub at attach it to the interface. */
		public Stub() {
			this.attachInterface(this, DESCRIPTOR);
		}

		/**
		 * Cast an IBinder object into an com.tfygg.demo.service.ICompute
		 * interface, generating a proxy if needed.
		 */
		public static com.tfygg.demo.service.ICompute asInterface(android.os.IBinder obj) {
			if ((obj == null)) {
				return null;
			}
			android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
			if (((iin != null) && (iin instanceof com.tfygg.demo.service.ICompute))) {
				return ((com.tfygg.demo.service.ICompute) iin);
			}
			return new com.tfygg.demo.service.ICompute.Stub.Proxy(obj);
		}

		@Override
		public android.os.IBinder asBinder() {
			return this;
		}

		@Override
		public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
				throws android.os.RemoteException {
			switch (code) {
			case INTERFACE_TRANSACTION: {
				reply.writeString(DESCRIPTOR);
				return true;
			}
			case TRANSACTION_add: {
				data.enforceInterface(DESCRIPTOR);
				int _arg0;
				_arg0 = data.readInt();
				int _arg1;
				_arg1 = data.readInt();
				int _result = this.add(_arg0, _arg1);
				reply.writeNoException();
				reply.writeInt(_result);
				return true;
			}
			}
			return super.onTransact(code, data, reply, flags);
		}

		private static class Proxy implements com.tfygg.demo.service.ICompute {
			private android.os.IBinder mRemote;

			Proxy(android.os.IBinder remote) {
				mRemote = remote;
			}

			@Override
			public android.os.IBinder asBinder() {
				return mRemote;
			}

			public java.lang.String getInterfaceDescriptor() {
				return DESCRIPTOR;
			}

			@Override
			public int add(int a, int b) throws android.os.RemoteException {
				android.os.Parcel _data = android.os.Parcel.obtain();
				android.os.Parcel _reply = android.os.Parcel.obtain();
				int _result;
				try {
					_data.writeInterfaceToken(DESCRIPTOR);
					_data.writeInt(a);
					_data.writeInt(b);
					mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
					_reply.readException();
					_result = _reply.readInt();
				} finally {
					_reply.recycle();
					_data.recycle();
				}
				return _result;
			}
		}

		static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
	}

	public int add(int a, int b) throws android.os.RemoteException;
}
对照着生成出来的ICompute.java文件,绘制如下Binder模型图:

     上图中绿色部分就是生成的java文件的内容,我们发现AIDL的Binder模型是基于原始配方的扩展。当我们写完ICompute.aidl之后,ICompute,Stub,Proxy已经自动生成出来,其作用如下:
(1)ICompute接口继承了IInterface,并写了add(a,b)的方法,代表Server端进程具备计算两数相加的能力。此方法将在Stub的具体实现类中重写。
(2)Stub是一个抽象类,他本质是一个Binder,他存在的目的之一是约定好asInterface,asBinder,onTransact方法,减少我们开发具体Binder对象的工作量。此外,Stub即需要跨进程传输,又需要约定远端服务端具备的能力,因此他需要同时实现IInterface和IBinder接口,通过上文的类图可以看得出来。
(3)Proxy是代理对象,它在Client进程中使用,它持有BinderProxy对象,BinderProxy能帮我们访问服务端Binder对象的能力。

2.1 C空间

       在C空间中,仍然是一样的Binder配方,不同的是,C空间没有了AIDL,而是使用模板辅助实现了Proxy-stub。所以,在C空间中也有IBinder.h,Binder。UML类图如下:


      先看IBinder,其定义截选如下:
class IBinder : public virtual RefBase
{
public:
    . . . . . .
    IBinder();
    virtual sp<IInterface>  queryLocalInterface(const String16& descriptor);
    virtual const String16& getInterfaceDescriptor() const = 0;

    virtual bool            isBinderAlive() const = 0;
    virtual status_t        pingBinder() = 0;
    virtual status_t        dump(int fd, const Vector<String16>& args) = 0;
    virtual status_t        transact(uint32_t code, const Parcel& data,
                                     Parcel* reply, uint32_t flags = 0) = 0;

    class DeathRecipient : public virtual RefBase
    {
    public:
        virtual void binderDied(const wp<IBinder>& who) = 0;
    };
    virtual status_t        linkToDeath(const sp<DeathRecipient>& recipient,
                                        void* cookie = NULL, uint32_t flags = 0) = 0;
    virtual status_t        unlinkToDeath(const wp<DeathRecipient>& recipient,
                                          void* cookie = NULL, uint32_t flags = 0,
                                          wp<DeathRecipient>* outRecipient = NULL) = 0;

    virtual bool            checkSubclass(const void* subclassID) const;
    
    typedef void (*object_cleanup_func)(const void* id, void* obj, void* cleanupCookie);
    virtual void            attachObject(const void* objectID, void* object,
                                         void* cleanupCookie, object_cleanup_func func) = 0;
    virtual void*           findObject(const void* objectID) const = 0;
    virtual void            detachObject(const void* objectID) = 0;

    virtual BBinder*        localBinder();
    virtual BpBinder*       remoteBinder();

protected:
    virtual          ~IBinder();
private:
};
有接口必然有实现,BBinder和BpBinder都是IBinder的实现类,这里就总结一下他们的区别:

(1)pingBinder, BBinder直接返回OK,而BpBinder需要运行一个transact函数,这个函数很重要。
(2)linkToDeath()是用来在服务挂的时候通知客户端的,那服务端当然不需要自己监视自己咯,所以BBinder直接返回非法,而Bpbinder需要通过requestDeathNotification()要求某人完成这个事情。

        奇怪的是BBinder和BpBinder都没有实现queryLocalInterface() 接口啊,那肯定另有他人实现这个类了,这个人就是IInterface.h。客户程序通过queryLocalInterface() 可以知道服务端都提供哪些服务。
       在IInterface.h中定义的BnInterface和BpInterface是两个重要的模版,这是为各种程序中使用的。
template  
class BnInterface : public INTERFACE, public BBinder  
{  
public:  
    virtual sp  queryLocalInterface(const String16& _descriptor);  
    virtual String16        getInterfaceDescriptor() const;  
protected:  
    virtual IBinder*        onAsBinder();  
};  
     BnInterface模版的定义如下所示:  
template  
class BpInterface : public INTERFACE, public BpRefBase  
{  
public:  
                            BpInterface(const sp& remote);  
protected:  
    virtual IBinder*    onAsBinder();  
};  
        这两个模版在使用的时候,起到得作用实际上都是双继承:使用者定义一个接口INTERFACE,然后使用BnInterface和BpInterface两个模版结合自己的接口,构建自己的BnXXX和BpXXX两个类。
        DECLARE_META_INTERFACE和IMPLEMENT_META_INTERFACE两个宏用于帮助BpXXX类的实现:
#define DECLARE_META_INTERFACE(INTERFACE)                               /  
    static const String16 descriptor;                                   /  
    static sp asInterface(const sp& obj);        /  
    virtual String16 getInterfaceDescriptor() const;                    /  
#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                       /  
    const String16 I##INTERFACE::descriptor(NAME);                      /  
    String16 I##INTERFACE::getInterfaceDescriptor() const {             /  
        return I##INTERFACE::descriptor;                                /  
    }                                                                   /  
    sp I##INTERFACE::asInterface(const sp& obj)  /  
    {                                                                   /  
        sp intr;                                          /  
        if (obj != NULL) {                                              /  
            intr = static_cast(                          /  
                obj->queryLocalInterface(                               /  
                        I##INTERFACE::descriptor).get());               /  
            if (intr == NULL) {                                         /  
                intr = new Bp##INTERFACE(obj);                          /  
            }                                                           /  
        }                                                               /  
        return intr;                                                    /  
    }  
       在定义自己的类的时候,只需要使用DECLARE_META_INTERFACE和IMPLEMENT_META_INTERFACE两个接口,并结合类的名称,就可以实现BpInterface中asInterface()和getInterfaceDescriptor()两个函数。
       此外,IInterface还起到了转换的作用,IXXXService继承自IInterface,而IInterface中的asBinder()方法,会将自身,也就是IXXXService转换成一个IBinder对象,而asInterface接口则是将IIBinder转换为IXXXService接口。
template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
return INTERFACE::asInterface(obj);
}
      由此可知,使用interface_cast的作用就是将IBinder实例转化成IXXXService实例。
      
      可以看出,C空间的IBinder接口和java空间类似,下图可以看出C空间的binder的Proxy-stub模式








版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/tfygg/article/details/51626632

智能推荐

EXP-00008: 遇到 ORACLE 错误 904_dummyflag-程序员宅基地

文章浏览阅读4k次。今天在用exp工具导出数据时出现如下提示:连接到: Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - 64bit ProductionWith the Partitioning, Automatic Storage Management, OLAP, Data Miningand Real Application T_dummyflag

linux中bond网卡是什么意思,linux 网卡bond-程序员宅基地

文章浏览阅读127次。SCC(超级计算集群)简介 SCC概述 超级计算集群(Super Computing Cluster,SCC)使用高速RDMA网络互联的CPU以及GPU等异构加速设备,面向高性能计算、人工智能/机器学习、科学/工程计算、数据分析、音视频处理等应用,提供极致计算性能和并行效率的计算集群服务。SCC实例类型 类型 CPU Memory 网络 存储 适用场景 ecs.scch5.16xlarge 64核..._跨rdma网卡bond

PCM和WAV格式,Linux下使用ffmpeg命令采集音频数据的方法_linux pcm short数组-程序员宅基地

文章浏览阅读1.2k次,点赞3次,收藏2次。音频采集量化概念1.采样大小(位深):一个采样用多少bit存放,常用的是16bit。2.采样率:采样频率,8K, 16K, 32K, 44.1K, 48K3.声道数:单声道,双声道,多声道码率计算PCM数据音频流码率=采样率x采样大小x声道数WAVWAV为微软公司(Microsoft)开发的一种声音文件格式,它符合RIFF(Resource Interchange File Format)文件规范,用于保存Windows平台的音频信息资源,被Windows平台及其应用程序所广泛支持。WAVE文_linux pcm short数组

Java中使用Exchanger类进行线程间的数据交换_exchanger类交换变量时值交换吗-程序员宅基地

文章浏览阅读648次。import java.util.concurrent.Exchanger;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ExchangerTest{ public static void main(String[] args) _exchanger类交换变量时值交换吗

bootcamp空间不足_mac安装双系统 提示磁盘上没有足够的可用空间的解决方法-程序员宅基地

文章浏览阅读1.2w次。mac安装双系统的坑安装环境:2017 macbook air 128g(是的,空间很小)macos 10.14.4准备:1。win10镜像。安装步骤为:1。打开boot camp2。分区3。安装出现错误:windows支持软件未能存储到所选驱动器,磁盘上没有足够的可用空间。删了很多东西,把win的分区加大都无法解决问题。折腾了很久终于找到了原因。原因:boot camp只认fat32,fat32..._bootcamp磁盘上没有足够的可用空间

GDOI2016集训总结 —— Part 2_四会集训总结-程序员宅基地

文章浏览阅读509次。写在前面GDOI赛前集训Part2,今天结束了,当然作为一个弱菜,还是垫了一波底。_四会集训总结

随便推点

Python爬虫学习路线(非常详细)_爬虫学习路线u,2024年最新滴滴 hr面试-程序员宅基地

文章浏览阅读495次,点赞23次,收藏8次。我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了,只是里面的项目比较多,水平也是参差不齐,大家可以挑自己能做的项目去练练。这是我花了几天的时间去把Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。这是一套 100% 原创的学习路线,它坚持实用主义,追求前沿技术,不但为初学者规划好了具体的学习步骤,还指明了常见的陷阱和困难。

git提交本地文件到远程仓库-程序员宅基地

文章浏览阅读99次。建立远程仓库进入需要的建仓库的文件夹,初始化本地仓库将所有变化提交到暂存区4. 提交代码到本地仓库5. 关联远程仓库6. 拉取远程仓库7.强制将本地分支推送到远程分支8.推送成功...

docker登录报WARNING! Using --password via the CLI is insecure. Use --password-stdin.-程序员宅基地

文章浏览阅读1.7w次,点赞5次,收藏3次。当使用docker login -u xxx -p xx时,报WARNING! Using --password via the CLI is insecure. Use --password-stdin.提示解决:1. 将密码写入到一个文件中,例如/etc/docker_passwd文件2.使用以下命令执行登录:cat /etc/docker_passwd | docker login --username 用户名--password-stdin..._warning! using --password via the cli is insecure. use --password-stdin.

博客更新说明_马鸿凯_新浪博客-程序员宅基地

文章浏览阅读73次。由于新浪博客书写保存问题,以后博客所在目录为CSDNMa_Hong_Kaihttps://blog.csdn.net/Ma_Hong_Kai/article/details/82734720(至于剩下的未写完的会在CSDN上更新) ...

同样的工作、同样的做需求,为什么他们能进阿里-程序员宅基地

文章浏览阅读117次。引言古人云:“活到老,学到老。”互联网算是最辛苦的行业之一,“加班”对工程师来说已是“家常便饭”,同时互联网技术又日新月异,很多工程师都疲于应付,叫苦不堪。以至于长期以来流传一个很广的误解:35岁是程序员工作的终点。如何在繁忙的工作中做好技术积累,构建个人核心竞争力,相信是很多工程师同行都在思考的问题。同样的工作、同样的做需求,为什么有的人只能在现有岗位上缓慢前行,而有的人却能进阿里,本文..._15096683069

多媒体架构----Audio介绍_audio架构-程序员宅基地

文章浏览阅读1.3k次。前言:_audio架构