In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-19 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/01 Report--
Editor to share with you the example analysis of Binder, I believe that most people do not know much about it, so share this article for your reference, I hope you can learn a lot after reading this article, let's go to know it!
1. Why Binder?
1.1 shortcomings of traditional IPC mechanisms
As we all know, the Android system is based on the Linux kernel. Linux already provides a variety of inter-process communication mechanisms, such as pipes, message queues, shared memory, sockets (Socket) and so on. Why implement another IPC mechanism? It is mainly based on two reasons:
1.1.1 performance Angle
Pipeline, message queue and Socket all need two copies of memory for one process communication, which is too inefficient; shared memory does not need to copy memory, but its management is complex; Binder only needs one copy of memory, which is lower than shared memory and superior to other methods from a performance point of view.
1.1.2 Security considerations
The traditional IPC mechanism has no security measures, and the receiver can not get the other party's reliable process ID or user ID, which is completely protected by the upper layer protocol. For example, the IP address of Socket communication is filled in by the client and is likely to be tampered with by malicious programs. As an open source platform for end users, Android has a large number of applications for users to choose from in the application market, so security is very important. The Android system assigns a user ID (UID) to each installed App. UID is an important identification of the process, and a series of permissions can be verified through UID. On the other hand, the access point of the traditional IPC is open, and any program can access according to the protocol, which can not prevent the access of malicious programs. Android needs an IPC mechanism based on the Cripple S architecture, and the server needs to be able to verify the identity of the Client request to ensure the security of the data.
1.2 some basic knowledge of Linux
To understand how Binder communicates across processes with only one memory copy, you first need to figure out why traditional IPC mechanisms need two memory copies, which requires some basic knowledge of the operating system.
1.2.1 process isolation
Let's take a look at Wikipedia's definition of "process isolation":
Process isolation is a set of different hardware and software technologies designed to protect processes from interfering with each other in the operating system. The purpose of this technique is to prevent process A from writing to process B. The isolation implementation of the process uses the virtual address space. The virtual address of process An is different from that of process B, which prevents process A from writing data information to process B.
In other words, the data between processes is not shared, A process can not directly access the data of B process, in order to ensure the security of the data. In a process-isolated operating system, the interaction between processes must be through the IPC mechanism.
The implementation of process isolation uses a virtual address space. What is a virtual address space? First of all, we need to understand the concept of virtual memory in the operating system, which is a technology to improve programming efficiency and physical memory utilization efficiency. To put it simply, the application sees a continuous and complete memory address space, when in fact The Temple of Earth space is mapped into fragmented physical memory, the mapping process is transparent to the application. This concept is very important. For a more in-depth understanding of virtual memory, please refer to this article: Linux understanding of virtual memory and physical memory.
1.2.2 process space: user space / kernel space
Today's operating systems use virtual memory. For 32-bit operating systems, the addressing space is 2 to the 32th power, that is, 4G. The core of the operating system is the kernel, which has all access rights to the underlying devices, so it needs to be independent from ordinary applications, and user processes cannot directly access kernel processes. The operating system logically divides the virtual address space into user space (User Space) and kernel space (Kernel Space). In the 32-bit Linux operating system, the high-order 1GB bytes are used by the kernel, which is called kernel space, and the remaining 3GB bytes are used by user processes, called user space.
1.2.3 system call: user mode / kernel mode
Because the permissions of user space are lower than that of kernel space, it is inevitable that user space needs to access resources in kernel space, such as reading and writing files and network access. The only way is through the system call interface provided by the operating system, through the system call interface, the user program can achieve limited access to kernel resources under the control of the kernel, which can not only meet the resource requests of the application program, but also ensure the security and stability of the system.
When the user process executes its own code, the process is currently in the user running state (user mode), and the processor executes the user code with low privileges. When the user process executes kernel code through the system call, the process temporarily enters the kernel running state (kernel state). At this time, the processor permissions *, you can execute privileged instructions.
1.2.4 Kernel module / driver
As mentioned earlier, user space can access kernel space through system calls, so how can user space communicate with each other (between processes)? Traditional IPC mechanisms are supported by the kernel, and so is Binder. There is a Binder driver running in the kernel that is responsible for Binder communication between processes.
Driver generally refers to the device driver (Device Driver), which is a special program that enables computers and devices to communicate. Equivalent to the hardware interface, the operating system can only pass through this interface.
Binder driver is a virtual character device registered in / dev/binder. It defines a set of Binder communication protocol, which is responsible for establishing Binder communication between processes and provides a series of low-level support for packet transmission between processes. The application process accesses the Binder driver through system calls.
1.3 Communication principle of traditional IPC mechanism
With the basics above, let's take a look at how the traditional IPC mechanism is implemented, usually in the following two steps (except for the shared memory mechanism):
The sender process copies the data to be sent to the kernel cache through a system call (copy_from_user).
The receiver opens up a memory space, and the kernel copies the data in the kernel cache to the receiver's memory cache through system calls (copy_to_user).
There are two problems with this traditional IPC mechanism:
Two copies of the data are required, the first from the sender's user space to the kernel cache and the second from the kernel cache to the receiver's user space.
The receiver process does not know how much space to allocate in advance to receive data, which may be a waste of space.
2. The basic principle of Binder
2.1 the underlying principle of Binder
The traditional IPC mechanism needs to copy memory twice. How does Binder achieve inter-process communication with only one memory copy? As we have learned earlier, Linux uses virtual memory addressing, the virtual memory address of user space is mapped to physical memory, and reading and writing to virtual memory is actually reading and writing to physical memory, this process is memory mapping, which is realized by the system call mmap ().
With the help of the method of memory mapping, Binder makes a layer of memory mapping between the kernel space and the data cache of the receiver user space. In this way, the data copied from the sender's user space to the kernel space cache is equivalent to being copied directly to the receiver's user space's data cache, thus reducing one data copy.
2.2 Binder communication model
Binder is based on Binder S architecture. For both sides of the communication, the process that initiates the request belongs to Client, and the process that receives the request belongs to Server. Due to the existence of process isolation, the two sides can not communicate directly. How is Binder realized?
The example of network communication given in the analysis of the principle of Binder written to the Android application engineer is very appropriate. The communication process of Binder is similar to the network request, and the network communication process can be simplified to four roles: Client, Server, DNS server and router. A complete network communication process is roughly as follows:
A, Client enter the domain name of Server
B. DNS resolves domain names
It is impossible to find the corresponding Server directly through the domain name. You must first convert the Server domain name into a specific IP address through the DNS server.
C. Send the request to Server through the router
After the Client resolves to the IP address of the Server through the DNS server, it cannot directly initiate a request to the Server, and it needs to go through the layers of the router to reach the Server.
D, Server return data
Server receives the request and processes it, and then returns the data to Client through the router.
In the Binder mechanism, four roles are also defined: Client, Server, Binder driver and ServiceManager.
Binder driver: similar to a router in network communication, it is responsible for forwarding the request of Client to a specific Server for execution and sending the data returned by Server back to Client.
ServiceManager: similar to the DNS server in network communication, it is responsible for translating the Binder descriptor of the Client request into a specific Server address so that the Binder driver can forward it to the specific Server. If Server needs to provide Binder services, you need to register with ServiceManager.
The specific communication process is as follows:
A, Server registers with ServiceManager
Server registers with ServiceManager through the Binder driver and declares that services can be provided externally. A mapping table is retained in ServiceManager: the Binder reference for a Server with the name zhangsan is 0x12345.
B. Client requests Binder reference of Server from ServiceManager
When Client wants to request data from Server, it needs to request the Binder reference of Server from ServiceManager through the Binder driver: I want to communicate to Server named zhangsan, please tell me the Binder reference of Server.
C. send a request to a specific Server
Once Client gets the Binder reference, it can communicate with Server through the Binder driver.
D, Server returns the result
After the Server responds to the request, the result needs to be returned to Client again through the Binder driver.
It can be seen that the communication among Client, Server and ServiceManager is all bridged by the Binder driver, which shows the importance of the Binder driver. You may also have a little doubt that the ServiceManager and Binder drivers belong to two different processes, which serve the interprocess communication between Client and Server, that is to say, the interprocess communication between Client and Server depends on the interprocess communication between ServiceManager and Binder drivers, which is like: "Egg laying chicken, chicken laying eggs, but * eggs have to be hatched by one chicken." How does the Binder mechanism create an egg-laying chicken?
When the Android system starts, it creates a process called servicemanager, which registers with the Binder driver through an agreed command BINDERSETCONTEXT_MGR, and applies to be a Binder entity for the ServiceManager,Binder driver that automatically creates a Binder entity for the ServiceManager (* only laying chickens).
And the reference to this Binder entity is 0 in all Client, which means that each Client can communicate with ServiceManager through this reference of 0. Server registers with ServiceManager with reference 0, and Client can get the Binder reference of the Server to communicate with through reference 0.
Android Binder Design and implementation-Client, Server, Binder driver, and ServiceManager are introduced in more detail in the design section.
2.3Agent mechanism of Binder
Through the above analysis, we already know the basic communication process of Binder: Client gets the Binder reference of Server from SerivceManger, and Client makes a specific request to Server through Binder reference. How exactly does Client call the Server method through this Binder reference?
For example, a Server provides an add method. The process of Client actually requesting add is as follows: Client first obtains the Binder reference of Server from ServiceManager through the Binder driver. This reference is a Java Object, and the Object has an add method. After Cient gets the Object, it can request the add method directly.
In fact, the Object that Client gets is not the real Binder entity of Server. The Binder driver does a layer of object conversion, wrapping the Object into a proxy object ProxyObject, which has the same method signature as the real Binder entity. When Client requests the add method through this ProxyObject, the Binder driver will automatically forward the request to the specific Binder entity for execution. This is the Binder proxy mechanism. Because ProxyObject and the real Binder entity have the same method signature, Client doesn't need to care about whether it's ProxyObject or the real Object.
For ease of description, the real Binder entity of Server is called the Binder local object; the Binder reference in the Client, or ProxyObject, is called the Binder proxy object.
Re-understanding of the concept of Binder
After the above analysis, we have a general understanding of the basic communication principles of the Binder mechanism, and now let's go back to our understanding of the Binder mechanism:
Generally speaking, Binder is an object-oriented IPC mechanism based on Cramp S structure. Including: Client, Server, Binder driver and ServiceManager four major components. The meaning of Binder in each component is different:
For Client
Binder is a reference to the local object of Server, which is actually a proxy object through which Client indirectly accesses the local object of Server
For Server
Binder is a local object that provides a specific implementation, and needs to be registered with ServiceManager
For Binder drivers
It is the bridge between Client and Server, which is responsible for transforming the proxy object into a local object and returning the execution result of Server to Client.
For ServiceManager
It saves the mapping of Server Binder character names and Binder references, which Client uses to find the Binder references of Server.
The specific structure of Binder proxy object and Binder local object is retained in the Binder driver. Because we are only concerned with the basic communication mechanism of Binder, the underlying implementation is not introduced too much. Students who want to know more can refer to Android Binder Design and implementation-Design.
3. Understand Binder through code
The above introduction is a bit abstract, now let's understand Binder through concrete examples.
Understand the usage of Binder through AIDL examples
Implement ActivityManagerService by manual coding
3.1 understand the usage of Binder through AIDL examples
The most common way to achieve Binder communication is to define the interface between Client and Server through the aidl,aidl interface. For students who do not know aidl, please refer to the official document Android Interface definition language (AIDL).
3.1.1 responsibilities of several classes related to Binder
Before we can analyze specifically, we need to understand the responsibilities of several classes related to Binder:
IBinder
The Base interface of cross-process communication, which declares a series of abstract methods that need to be implemented in cross-process communication. The implementation of this interface means that cross-process communication can be carried out. Both Client and Server should implement this interface.
IInterface
This is also a Base interface, which is used to indicate what capabilities Server provides and is the protocol for Client and Server communication.
Binder
The base class of the local object that provides the Binder service, which implements the IBinder interface, and all local objects inherit this class.
BinderProxy
A BinderProxy class is also defined in the Binder.java file, which represents the Binder proxy object, which also implements the IBinder interface, but many of its implementations are handled by the native layer. What you get in Client is actually this proxy object.
Stub
This class is automatically generated after compiling the aidl file, and it inherits from Binder, indicating that it is a Binder native object; it is an abstract class that implements the IInterface interface, indicating that its subclass needs to implement the specific capabilities that Server will provide (that is, the methods declared in the aidl file).
Proxy
It implements the IInterface interface, indicating that it is part of the Binder communication process; it implements the methods declared in aidl, but ultimately leaves it to the mRemote member to handle, indicating that it is a proxy object, and the mRemote member is actually BinderProxy.
3.1.2 AIDL instance
First define an aidl file in which a getPid method is declared:
/ / IRemoteService.aidl package com.rush.demo.aidltest; interface IRemoteService {int getPid ();}
The following is the java class generated after compiling IRemoteService.aild:
/ / IRemoteService.java package com.rush.demo.aidltest; public interface IRemoteService extends android.os.IInterface {public static abstract class Stub extends android.os.Binder implements com.rush.demo.aidltest.IRemoteService {/ / Binder descriptor private static final java.lang.String DESCRIPTOR = "com.rush.demo.aidltest.IRemoteService"; public Stub () {this.attachInterface (this, DESCRIPTOR) } public static com.rush.demo.aidltest.IRemoteService asInterface (android.os.IBinder obj) {if ((obj = = null)) {return null;} android.os.IInterface iin = obj.queryLocalInterface (DESCRIPTOR) If ((iin! = null) & & (iin instanceof com.rush.demo.aidltest.IRemoteService)) {return ((com.rush.demo.aidltest.IRemoteService) iin);} return new com.rush.demo.aidltest.IRemoteService.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_getPid: {data.enforceInterface (DESCRIPTOR); java.lang.String _ arg0; _ arg0 = data.readString (); int _ result = this.getPid (_ arg0); reply.writeNoException () Reply.writeInt (_ result); return true;}} return super.onTransact (code, data, reply, flags);} private static class Proxy implements com.rush.demo.aidltest.IRemoteService {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 getPid (java.lang.String name) 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.writeString (name); mRemote.transact (Stub.TRANSACTION_getPid, _ data, _ reply, 0); _ reply.readException (); _ result = _ reply.readInt () } finally {_ reply.recycle (); _ data.recycle ();} return _ result;} static final int TRANSACTION_getPid = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);} public int getPid (java.lang.String name) throws android.os.RemoteException }
There are three classes in this file:
A 、 IRemoteService
Inherits to the IInterface interface and declares the getPid method declared in IRemoteService.aidl, which is the interface for Client and Service communication.
B 、 IRemoteService.Stub
The static abstract inner class of IRemoteService inherits from Binder. Its subclass needs to implement the IRemoteService interface, indicating that it is the Binder local object of Server and needs to implement the getPid interface.
C 、 IRemoteService.Stub.Proxy
The static inner class of IRemoteService.Stub, which does not inherit from Binder, but contains an IBinder object, which is actually BinderProxy, indicating that it is the local proxy object of Server in Client. Proxy implements the getPid interface, serializing the parameters and handing them over to mRemote (BinderProxy), which is actually handed over to the Binder driver to communicate with the remote Stub.
First, let's take a look at the asInterface method in Stub. This method is usually called by Client after Client succeeds in bindService. The function is to convert the IBinder object returned after successful binding into a specific IInterface interface. After Client gets this IInterface interface, it is free to call the method provided by Server.
Public static com.rush.demo.aidltest.IRemoteService asInterface (android.os.IBinder obj) {if ((obj = = null)) {return null;} android.os.IInterface iin = obj.queryLocalInterface (DESCRIPTOR); if (iin! = null) & & (iin instanceof com.rush.demo.aidltest.IRemoteService) {return ((com.rush.demo.aidltest.IRemoteService) iin) } return new com.rush.demo.aidltest.IRemoteService.Stub.Proxy (obj);}
Why is it possible to either return an IRemoteService object of Stub itself or create a Proxy object in the asInterface method? Because although Binder is a cross-process communication mechanism, it can also serve this process, that is to say, Client and Server may be in the same process, so there is no need to transfer through the Binder driver in the same process, just access it directly; if Client and Server are in different processes, you need to transfer through the Binder proxy object. In other words:
Client and Server are in the same process. Obj is the Binder local object (a subclass of Stub), and the asInterface method returns the Binder local object.
Client and Server are in different processes, obj is actually a Binder proxy object, and asInterface returns a Proxy object.
/ / Binder.java / * obj.queryLocalInterface how to find whether there is a local IInterface. As you can see from the Binder code, simply compare whether the descriptor of Binder matches the descriptor you are looking for, and return mOwner directly if there is a match. This mOwner is the this parameter passed by calling the attachInterface method in the Stub constructor. * / public IInterface queryLocalInterface (String descriptor) {if (mDescriptor.equals (descriptor)) {return mOwner;} return null;} final class BinderProxy implements IBinder {public IInterface queryLocalInterface (String descriptor) {return null }} public static abstract class Stub extends android.os.Binder implements com.rush.demo.aidltest.IRemoteService {/ / Binder descriptor, the value is the full name of the interface class name private static final java.lang.String DESCRIPTOR = "com.rush.demo.aidltest.IRemoteService"; public Stub () {/ / binds owner and descriptor this.attachInterface (this, DESCRIPTOR) to Binder;}}
How does obj.queryLocalInterface find out whether there is a local IInterface? as you can see from the code of Binder, simply compare whether the descriptor of Binder matches the descriptor you are looking for, and return mOwner directly if there is a match. This mOwner is the this parameter passed in by calling the attachInterface method in the Stub constructor. The queryLocalInterface method of BinderProxy returns null directly.
There are two scenarios for calling the Server method through Binder in Client:
1. Client and Server are in the same process
The Stub.asInterface method returns the Stub object, which is the Binder local object. In other words, it has nothing to do with Binder cross-process communication, it can be called directly, and the Client caller and the Server responder are in the same thread.
2. Client and Server are in different processes
The Stub.asInterface method returns a Binder proxy object, which requires cross-process communication through the Binder driver. In this scenario, the Client caller thread is suspended (Binder also provides an asynchronous way, which is not discussed here), waits for the Server response and returns data. Note here that the Server response is processed in the Binder thread pool of the Server process, not the main thread.
Next, analyze the specific process of Client calling the getPid method in a cross-process scenario:
1. Client calls the Binder proxy object, and the Client thread hangs
The IRemoteService reference you get in Client is actually Proxy, and calling the getPid method is actually calling Proxy's getPid method, which simply calls the transact method of the mRemote member after serializing the parameters. The method number is defined for each method in the IRemoteService in the Stub class, and the number of the getPid method is passed in the transact method. At this point, the Client caller thread suspends, waiting for the Server response data.
/ / Stub.Proxy public int getPid (java.lang.String name) throws android.os.RemoteException {... _ data.writeInterfaceToken (DESCRIPTOR); _ data.writeString (name); mRemote.transact (Stub.TRANSACTION_getPid, _ data, _ reply, 0); _ reply.readException (); _ result = _ reply.readInt ();... Return _ result;}
2. The Binder proxy object dispatches the request to the Binder driver
The mRemote member in Proxy is actually BinderProxy, and the transact method in BinderProxy is finally called in the transactNative method, which means that Client's request is sent to the Binder driver for processing.
3. The Binder driver dispatches the request to Server
After a series of processing, the Binder driver dispatches the request to Server, that is, the onTransact method that calls the Server native Binder object (Stub) finally completes the specific call to the getPid method in this method. In the onTransact method, the specific method to be processed is distinguished according to the method number passed in when the transact is called in Proxy.
/ / Stub public boolean onTransact (int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {switch (code) {... Case TRANSACTION_getPid: {data.enforceInterface (DESCRIPTOR); / / get the method parameter java.lang.String _ arg0 = data.readString (); / / call the getPid method, which implements int _ result = this.getPid (_ arg0) in Server, a subclass of Stub; reply.writeNoException (); reply.writeInt (_ result) Return true;}} return super.onTransact (code, data, reply, flags);}
4. Wake up the client thread and return the result
After onTransact processing, the result is written to reply and returned to the Binder driver, which wakes up the suspended client thread and returns the result. At this point, a cross-process communication is completed.
3.2 Manual coding to implement ActivityManagerService
Through the previous example, we already know that the aidl file is only used to define the interface of the Proxy S interaction, Android will automatically generate the corresponding Java class when compiling, and the generated class contains Stub and Proxy static inner classes, which are used to encapsulate the data conversion process. In practice, you only care about the specific Java interface class. Why are Stub and Proxy static inner classes? This is really just to put the three classes in one file to improve the aggregation of the code. Through the above analysis, we can actually achieve the communication of Binder without manual coding through aidl. Let's implement ActivityManagerService by coding.
First, define the IActivityManager interface:
Public interface IActivityManager extends IInterface {/ / binder descriptor String DESCRIPTOR = "android.app.IActivityManager"; / / method number int TRANSACTION_startActivity = IBinder.FIRST_CALL_TRANSACTION + 0; / / declare a method to start activity. For simplicity, only pass in the intent parameter int startActivity (Intent intent) throws RemoteException;}
Second, implement the local Binder object base class on the ActivityManagerService side:
/ / the name is random and inconsistent is called Stub public abstract class ActivityManagerNative extends Binder implements IActivityManager {public static IActivityManager asInterface (IBinder obj) {if (obj = = null) {return null;} IActivityManager in = (IActivityManager) obj.queryLocalInterface (IActivityManager.DESCRIPTOR); if (in! = null) {return in } / / proxy object, see the following code return new ActivityManagerProxy (obj);} @ Override public IBinder asBinder () {return this } @ Override protected boolean onTransact (int code, Parcel data, Parcel reply, int flags) throws RemoteException {switch (code) {/ / get the binder descriptor case INTERFACE_TRANSACTION: reply.writeString (IActivityManager.DESCRIPTOR); return true / / start activity. After deserializing the intent parameter from data, call the subclass startActivity method directly to start activity. Case IActivityManager.TRANSACTION_startActivity: data.enforceInterface (IActivityManager.DESCRIPTOR); Intent intent = Intent.CREATOR.createFromParcel (data); int result = this.startActivity (intent); reply.writeNoException (); reply.writeInt (result); return true;} return super.onTransact (code, data, reply, flags);}}
Thirdly, implement the proxy object on the Client side:
Public class ActivityManagerProxy implements IActivityManager {private IBinder mRemote; public ActivityManagerProxy (IBinder remote) {mRemote = remote;} @ Override public IBinder asBinder () {return mRemote;} @ Override public int startActivity (Intent intent) throws RemoteException {Parcel data = Parcel.obtain (); Parcel reply = Parcel.obtain (); int result Try serializes the intent parameter and writes it to intent.writeToParcel (data, 0) in data; / / calls the transact method of the BinderProxy object, which is processed by the Binder driver. MRemote.transact (IActivityManager.TRANSACTION_startActivity, data, reply, 0); reply.readException (); / wait for the end of server execution, read the execution result result = reply.readInt ();} finally {data.recycle (); reply.recycle ();} return result;}}
* to implement Binder local object (IActivityManager API):
Public class ActivityManagerService extends ActivityManagerNative {@ Override public int startActivity (Intent intent) throws RemoteException {/ / launch activity return 0;}}
The simplified version of ActivityManagerService has been implemented here, and all that is left is that Client needs to get IActivityManager, the proxy object of AMS, to communicate. In the actual development process, the intermediate code can be compiled automatically through the aidl file, which does not need to be implemented manually, but manual coding can deepen the understanding of the Binder mechanism. We don't use AMS directly in the development process, but understanding the principles of AMS implementation is essential for us to be familiar with Framework.
The above is all the content of this article "sample Analysis of Binder". Thank you for reading! I believe we all have a certain understanding, hope to share the content to help you, if you want to learn more knowledge, welcome to follow the industry information channel!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.