android emulator虛擬設備分析第三篇之pipe上的qemud service

一、概述

本篇和第二篇是強相關的,需要結合第二篇一起看。

以boot-properties為例,注意不需要看ANDROID-QEMUD.TXT,這個是和guest os中的qemud進行相關的,已廢棄。

啟動emulator時,有一個參數-prop <key>=<value>,用于向guest os中添加屬性。


二、guest os中使用qemud service的方法

實現代碼是:http://androidxref.com/5.1.0_r1/xref/device/generic/goldfish/qemu-props/qemu-props.c,用到了頭文件:http://androidxref.com/5.1.0_r1/xref/hardware/libhardware/include/hardware/qemud.h


guest os中程序名為qemu-props,由/system/etc/init.goldfish.rc啟動。
啟動后循環幾次,嘗試打開boot-properties服務(qemud_fd = qemud_channel_open( "boot-properties" ))。
如果打開成功,發送list命令(qemud_channel_send(qemud_fd, "list", -1))給boot-properties。
然后在循環中讀取啟動emulator時通過-prop指定的屬性(qemud_channel_recv(qemud_fd, temp, sizeof temp - 1))。
并設置guest os中的屬性(property_set(temp, q))。
 
 
 
 
qemud_channel_open,先嘗試打開/dev/qemu_pipe,寫入pipe:qemud:boot-properties。
如果pipe方式失敗,才會去通過socket和qemud進程通信,寫入boot-properties,期待返回OK。
 
static __inline__ int
qemud_channel_open(const char*  name)
{int  fd;int  namelen = strlen(name);char answer[2];char pipe_name[256];/* First, try to connect to the pipe. */snprintf(pipe_name, sizeof(pipe_name), "qemud:%s", name);fd = qemu_pipe_open(pipe_name);if (fd < 0) {D("QEMUD pipe is not available for %s: %s", name, strerror(errno));/* If pipe is not available, connect to qemud control socket */fd = socket_local_client( "qemud",ANDROID_SOCKET_NAMESPACE_RESERVED,SOCK_STREAM );if (fd < 0) {D("no qemud control socket: %s", strerror(errno));return -1;}/* send service name to connect */if (qemud_fd_write(fd, name, namelen) != namelen) {D("can't send service name to qemud: %s",strerror(errno));close(fd);return -1;}/* read answer from daemon */if (qemud_fd_read(fd, answer, 2) != 2 ||answer[0] != 'O' || answer[1] != 'K') {D("cant' connect to %s service through qemud", name);close(fd);return -1;}}return fd;
}


qemud_channel_send和qemud_channel_recv是qemu-pipe和qemud所通用的,直接對fd進行讀寫,先讀寫4個字節,為size,然后讀取具體的內容。
 
static __inline__ int
qemud_channel_send(int  fd, const void*  msg, int  msglen)
{char  header[5];if (msglen < 0)msglen = strlen((const char*)msg);if (msglen == 0)return 0;snprintf(header, sizeof header, "%04x", msglen);if (qemud_fd_write(fd, header, 4) != 4) {D("can't write qemud frame header: %s", strerror(errno));return -1;}if (qemud_fd_write(fd, msg, msglen) != msglen) {D("can4t write qemud frame payload: %s", strerror(errno));return -1;}return 0;
}static __inline__ int
qemud_channel_recv(int  fd, void*  msg, int  msgsize)
{char  header[5];int   size, avail;if (qemud_fd_read(fd, header, 4) != 4) {D("can't read qemud frame header: %s", strerror(errno));return -1;}header[4] = 0;if (sscanf(header, "%04x", &size) != 1) {D("malformed qemud frame header: '%.*s'", 4, header);return -1;}if (size > msgsize)return -1;if (qemud_fd_read(fd, msg, size) != size) {D("can't read qemud frame payload: %s", strerror(errno));return -1;}return size;
}


三、注冊新的qemud service

所有的qemud service都使用pipe:qemud這個pipe service,是它的子服務。如何去實現這種子服務呢?

emulator里面有兩中結構體QemudService, QemudClient分別表示子服務,以及子服務的client。

QemudPipe和之前說的pipe類似,每次打開/dev/qemu_pipe時,kernel和emulator中都會產生一個pipe,對應一個CHANNEL,在guest os第一次通過/dev/qemu_pipe發送數據時,會創建一個QemudPipe,也就是peer,作為pipe:qemud funcs中的opaque。

pipeConnector_sendBuffers函數代碼片段:

        Pipe* pipe = pcon->pipe;void* peer = svc->funcs.init(pipe->hwpipe, svc->opaque, pipeArgs);if (peer == NULL) {D("%s: Initialization failed for pipe %s!", __FUNCTION__, pipeName);return PIPE_ERROR_INVAL;}/* Do the evil switch now */pipe->opaque = peer;pipe->service = svc;pipe->funcs  = &svc->funcs;pipe->args   = ASTRDUP(pipeArgs);AFREE(pcon);



3.1、pipe:qemud服務

代碼為external/qemu/android/emulation/android_qemud.cpp,我在android源碼中沒有找到,在另一個模擬器的repo中找到了。注意代碼中夾雜著一些guest os中qemud相關的東西,關鍵詞serial,不需要看。


初始化代碼如下,_qemudPipe_funcs就是第二篇中所說的svc->funcs,從第二次通信開始,qemu_pipe都使用這些funcs去讀寫。

/* QEMUD pipe functions.*/
static const AndroidPipeFuncs _qemudPipe_funcs = {_qemudPipe_init,_qemudPipe_closeFromGuest,_qemudPipe_sendBuffers,_qemudPipe_recvBuffers,_qemudPipe_poll,_qemudPipe_wakeOn,_qemudPipe_save,_qemudPipe_load,
};/* Initializes QEMUD pipe interface.*/
static void _android_qemud_pipe_init(void) {static bool _qemud_pipe_initialized = false;if (!_qemud_pipe_initialized) {android_pipe_add_type("qemud", looper_getForThread(), &_qemudPipe_funcs);_qemud_pipe_initialized = true;}
}static bool isInited = false;void android_qemud_init(CSerialLine* sl) {D("%s", __FUNCTION__);/* We don't know in advance whether the guest system supports qemud pipes,* so we will initialize both qemud machineries, the legacy (over serial* port), and the new one (over qemu pipe). Then we let the guest to connect* via one, or the other. */_android_qemud_serial_init(sl);_android_qemud_pipe_init();isInited = true;
}


_qemudPipe_init是建立連接后,初始化QemudPipe的代碼。

QemudMultiplexer中只有兩個鏈表有用。

先根據service name查找子服務QemudService,然后調用子服務的qemud_service_connect_client去創建QemudClient,然后去創建QemudPipe

/* This is a callback that gets invoked when guest is connecting to the service.** Here we will create a new client as well as pipe descriptor representing new* connection.*/
static void*
_qemudPipe_init(void* hwpipe, void* _looper, const char* args) {QemudMultiplexer* m = qemud_multiplexer;QemudService* sv = m->services;QemudClient* client;QemudPipe* pipe = NULL;char service_name[512];const char* client_args;size_t srv_name_len;/* 'args' passed in this callback represents name of the service the guest is* connecting to. It can't be NULL. */if (args == NULL) {D("%s: Missing address!", __FUNCTION__);return NULL;}/* 'args' contain service name, and optional parameters for the client that* is about to be created in this call. The parameters are separated from the* service name wit ':'. Separate service name from the client param. */client_args = strchr(args, ':');if (client_args != NULL) {srv_name_len = min(client_args - args, (intptr_t) sizeof(service_name) - 1);client_args++;  // Past the ':'if (*client_args == '\0') {/* No actual parameters. */client_args = NULL;}} else {srv_name_len = min(strlen(args), sizeof(service_name) - 1);}memcpy(service_name, args, srv_name_len);service_name[srv_name_len] = '\0';/* Lookup registered service by its name. */while (sv != NULL && strcmp(sv->name, service_name)) {sv = sv->next;}if (sv == NULL) {D("%s: Service '%s' has not been registered!", __FUNCTION__, service_name);return NULL;}/* Create a client for this connection. -1 as a channel ID signals that this* is a pipe client. */client = qemud_service_connect_client(sv, -1, client_args);if (client != NULL) {pipe = static_cast<QemudPipe*>(android_alloc0(sizeof(*pipe)));pipe->hwpipe = hwpipe;pipe->looper = _looper;pipe->service = sv;pipe->client = client;client->ProtocolSelector.Pipe.qemud_pipe = pipe;}return pipe;
}


_qemudPipe_sendBuffers是guest通過/dev/qemu_pipe寫數據時,將被調用的函數,也就是QemudClient接收到數據的函數,注意不要把send/recv的概念搞錯了。

代碼就是把guest發送的buffers拼起來,然后調用QemudClient的接收函數qemud_client_recv去處理。

/* Called when the guest has sent some data to the client.*/
static int
_qemudPipe_sendBuffers(void* opaque,const AndroidPipeBuffer* buffers,int numBuffers) {QemudPipe* pipe = static_cast<QemudPipe*>(opaque);QemudClient* client = pipe->client;size_t transferred = 0;if (client == NULL) {D("%s: Unexpected NULL client", __FUNCTION__);return -1;}if (numBuffers == 1) {/* Simple case: all data are in one buffer. */D("%s: %s", __FUNCTION__, quote_bytes((char*) buffers->data, buffers->size));qemud_client_recv(client, buffers->data, buffers->size);transferred = buffers->size;} else {/* If there are multiple buffers involved, collect all data in one buffer* before calling the high level client. */uint8_t* msg, * wrk;int n;for (n = 0; n < numBuffers; n++) {transferred += buffers[n].size;}msg = static_cast<uint8_t*>(malloc(transferred));wrk = msg;for (n = 0; n < numBuffers; n++) {memcpy(wrk, buffers[n].data, buffers[n].size);wrk += buffers[n].size;}D("%s: %s", __FUNCTION__, quote_bytes((char*) msg, transferred));qemud_client_recv(client, msg, transferred);free(msg);}return transferred;
}


_qemudPipe_recvBuffers是guest想從/dev/qemu_pipe讀取數據時被調用的。

QemudClient寫數據時是寫到自己的ProtocolSelector.Pipe.messages中的,在這個函數中把QemudClient中的ProtocolSelector.Pipe.messages倒騰到buffers中。

/* Called when the guest is reading data from the client.*/
static int
_qemudPipe_recvBuffers(void* opaque, AndroidPipeBuffer* buffers, int numBuffers) {QemudPipe* pipe = static_cast<QemudPipe*>(opaque);QemudClient* client = pipe->client;QemudPipeMessage** msg_list;AndroidPipeBuffer* buff = buffers;AndroidPipeBuffer* endbuff = buffers + numBuffers;size_t sent_bytes = 0;size_t off_in_buff = 0;if (client == NULL) {D("%s: Unexpected NULL client", __FUNCTION__);return -1;}msg_list = &client->ProtocolSelector.Pipe.messages;if (*msg_list == NULL) {/* No data to send. Let it block until we wake it up with* PIPE_WAKE_READ when service sends data to the client. */return PIPE_ERROR_AGAIN;}/* Fill in goldfish buffers while they are still available, and there are* messages in the client's message list. */while (buff != endbuff && *msg_list != NULL) {QemudPipeMessage* msg = *msg_list;/* Message data fiting the current pipe's buffer. */size_t to_copy = min(msg->size - msg->offset, buff->size - off_in_buff);memcpy(buff->data + off_in_buff, msg->message + msg->offset, to_copy);/* Update offsets. */off_in_buff += to_copy;msg->offset += to_copy;sent_bytes += to_copy;if (msg->size == msg->offset) {/* We're done with the current message. Go to the next one. */*msg_list = msg->next;free(msg);}if (off_in_buff == buff->size) {/* Current pipe buffer is full. Continue with the next one. */buff++;off_in_buff = 0;}}D("%s: -> %u (of %u)", __FUNCTION__, sent_bytes, buffers->size);return sent_bytes;
}


_qemudPipe_poll,PIPE_POLL_OUT總是有效,PIPE_POLL_IN需要看QemudClient的ProtocolSelector.Pipe.messages中是否有數據

static unsigned
_qemudPipe_poll(void* opaque) {QemudPipe* pipe = static_cast<QemudPipe*>(opaque);QemudClient* client = pipe->client;unsigned ret = 0;if (client != NULL) {ret |= PIPE_POLL_OUT;if (client->ProtocolSelector.Pipe.messages != NULL) {ret |= PIPE_POLL_IN;}} else {D("%s: Unexpected NULL client", __FUNCTION__);}return ret;
}


_qemudPipe_wakeOn,發現ProtocolSelector.Pipe.messages中有數據時,會調用android_pipe_wake,把pipe添加到dev->signaled鏈表中。

static void
_qemudPipe_wakeOn(void* opaque, int flags) {QemudPipe* qemud_pipe = (QemudPipe*) opaque;QemudClient* c = qemud_pipe->client;D("%s: -> %X", __FUNCTION__, flags);if (flags & PIPE_WAKE_READ) {if (c->ProtocolSelector.Pipe.messages != NULL) {android_pipe_wake(c->ProtocolSelector.Pipe.qemud_pipe->hwpipe,PIPE_WAKE_READ);}}
}



3.2、qemud service

代碼是external/qemu/android/boot-properties.c,也是在模擬器repo中的


boot_property_init_service去注冊一個QemudService,主要函數就一個boot_property_service_connect,用于創建新的QemudClient

void
boot_property_init_service( void )
{if (!_inited) {QemudService*  serv = qemud_service_register( SERVICE_NAME,1, NULL,boot_property_service_connect,boot_property_save,boot_property_load);if (serv == NULL) {derror("could not register '%s' service", SERVICE_NAME);return;}D("registered '%s' qemud service", SERVICE_NAME);_inited = 1;}
}


boot_property_service_connect創建新的QemudClient,channel一般都是-1,表示是pipe方式,而不是serial方式(使用guest qemud進程)

static QemudClient*
boot_property_service_connect( void*          opaque,QemudService*  serv,int            channel,const char*    client_param )
{QemudClient*  client;client = qemud_client_new( serv, channel, client_param, NULL,boot_property_client_recv,NULL, NULL, NULL );qemud_client_set_framing(client, 1);return client;
}


qemud_client_new會綁定QemudClient的讀寫函數,讀函數boot_property_client_recv(也就是qemud_client_recv)是在_qemudPipe_sendBuffers中調用的

循環執行qemud_client_send將數據(-prop指定的屬性值的列表)寫到QemudClient的ProtocolSelector.Pipe.messages中,當_qemudPipe_recvBuffers函數執行時,從QemudClient的ProtocolSelector.Pipe.messages中倒騰數據返回給guest

void
boot_property_client_recv( void*         opaque,uint8_t*      msg,int           msglen,QemudClient*  client )
{/* the 'list' command shall send all boot properties* to the client, then close the connection.*/if (msglen == 4 && !memcmp(msg, "list", 4)) {BootProperty*  prop;for (prop = _boot_properties; prop != NULL; prop = prop->next) {qemud_client_send(client, (uint8_t*)prop->property, prop->length);}/* Send a NUL to signal the end of the list. */qemud_client_send(client, (uint8_t*)"", 1);return;}/* unknown command ? */D("%s: ignoring unknown command: %.*s", __FUNCTION__, msglen, msg);
}




boot-properties服務的入口函數是boot_property_parse_option,emulator在解析-prop參數時,會調用這個函數。

獲得name和value后,調用boot_property_add2(name, namelen, value, valuelen)去添加屬性到屬性列表(_boot_properties)中

void
boot_property_parse_option( const char*  param )
{char* q = strchr(param,'=');const char* name;const char* value;int   namelen, valuelen, ret;if (q == NULL) {dwarning("boot property missing (=) separator: %s", param);return;}name    = param;namelen = q - param;value    = q+1;valuelen = strlen(name) - (namelen+1);ret = boot_property_add2(name, namelen, value, valuelen);if (ret < 0) {boot_property_raise_warning(ret, name, namelen, value, valuelen);}
}



boot_property_add2會檢查服務是否已初始化,如果沒有,將調用boot_property_init_service。如果屬性名和值沒有非法字符,將申請新的屬性:prop = boot_property_alloc(name, namelen, value, valuelen)并添加到屬性列表中

/* Appends a new boot property to the end of the internal list.*/
int
boot_property_add2( const char*  name, int  namelen,const char*  value, int  valuelen )
{BootProperty*  prop;/* check the lengths*/if (namelen > PROPERTY_MAX_NAME)return -1;if (valuelen > PROPERTY_MAX_VALUE)return -2;/* check that there are not invalid characters in the* property name*/const char*  reject = " =$*?'\"";int          nn;for (nn = 0; nn < namelen; nn++) {if (strchr(reject, name[nn]) != NULL)return -3;}/* init the service */boot_property_init_service();/* add to the end of the internal list */prop = boot_property_alloc(name, namelen, value, valuelen);*_boot_properties_tail = prop;_boot_properties_tail  = &prop->next;return 0;
}




boot_property_init_service先檢查是否已初始化,如果沒有,將進行初始化
QemudService* ?serv = qemud_service_register( SERVICE_NAME,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?1, NULL,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?boot_property_service_connect,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?boot_property_save,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?boot_property_load);
第二個參數是max_clients,最大客戶數量
第三個參數是serv_opaque,將傳遞給注冊的serv_connect函數的第一個參數
第四個參數是注冊的serv_connect函數
第五、第六是保存和恢復屬性鏈表的函數

void
boot_property_init_service( void )
{if (!_inited) {QemudService*  serv = qemud_service_register( SERVICE_NAME,1, NULL,boot_property_service_connect,boot_property_save,boot_property_load);if (serv == NULL) {derror("could not register '%s' service", SERVICE_NAME);return;}D("registered '%s' qemud service", SERVICE_NAME);_inited = 1;}
}



本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/542610.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/542610.shtml
英文地址,請注明出處:http://en.pswp.cn/news/542610.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

c#異常處理_C#異常處理能力問題和解答 套裝4

c#異常處理1) Which is not a valid keyword used in the context of exception handling? trycatchfinalfinally Answer & Explanation Correct answer: 3final The final keyword is not used to handle exceptions in C#.NET. 1)在異常處理的上下文中使用哪個無效關鍵字…

Castor xsd生成java_java – Castor可以處理從基礎XSD導入的多個XSD生成類嗎?

注意&#xff1a;我是EclipseLink JAXB (MOXy)領導者,也是JAXB 2 (JSR-222)專家組的成員.Can Castor do this? If so, what would be the Ant task syntax for it.If not, would perhaps JAXB be a better alternative?下面是如何使用JAXB完成此操作的示例&#xff1a;產品xm…

串口通信 校驗碼_一文讀懂S7-200 SMART自由口通信!

學習S7-200 SMART時了解到&#xff0c;基于RS485接口可實現一下幾種通信&#xff1a;1&#xff09;modbus RTU通信2&#xff09;PPI協議通信3&#xff09;USS協議通信4&#xff09;自由口通信何為自由口通信呢&#xff1f;前三種通信必須要PLC和與其通信的設備支持相同的通信協…

hbase 學習(十三)集群間備份原理

集群建備份&#xff0c;它是master/slaves結構式的備份&#xff0c;由master推送&#xff0c;這樣更容易跟蹤現在備份到哪里了&#xff0c;況且region server是都有自己的WAL 和HLog日志&#xff0c;它就像mysql的主從備份結構一樣&#xff0c;只有一個日志來跟蹤。一個master集…

python expect模塊_Python基礎教程:用Python怎么telnet到網絡設備

Python基礎教程&#xff1a;用Python怎么telnet到網絡設備0.前言Telnet協議屬于TCP/IP協議族里的一種&#xff0c;對于我們這些網絡攻城獅來說&#xff0c;再熟悉不過了&#xff0c;常用于遠程登陸到網絡設備進行操作&#xff0c;但是&#xff0c;它的缺陷太明顯了&#xff0c;…

Java實現動態加載頁面_[Java教程]動態加載頁面數據的小工具 javascript + jQuery (持續更新)...

[Java教程]動態加載頁面數據的小工具 javascript jQuery (持續更新)0 2014-05-07 18:00:06使用該控件&#xff0c;可以根據url&#xff0c;參數&#xff0c;加載html記錄模板(包含json參數對應&#xff0c;以及具體記錄位置Index根據參數描述加載對應的屬性&#xff0c;并可以…

馬哥linux第六周作業

1、復制/etc/rc.d/rc.sysinit文件至/tmp目錄&#xff0c;將/tmp/rc.sysinit文件中的以至少一個空白字符開頭的行的行首加#&#xff1b;[rootmageedu tmp]# cp /etc/rc.d/rc.sysinit . [rootmageedu tmp]# vim rc.sysinit :% s/^[[:space:]]/#&/ #按Esc進入vi…

Java ObjectInputStream enableResolveObject()方法與示例

ObjectInputStream類enableResolveObject()方法 (ObjectInputStream Class enableResolveObject() method) enableResolveObject() method is available in java.io package. enableResolveObject()方法在java.io包中可用。 enableResolveObject() method is used to enable th…

pygame render怎么顯示中文_PyGame開發游戲(2D)02.基礎圖元

這節將介紹PyGame的基礎架構。并學習如何在PyGame里繪制各種幾何圖形和顯示加載圖片。01.應用框架上一節的示例程序里&#xff0c;我們用到一個PyGame的應用程序框架。這是一個基礎框架&#xff0c;利用它我們可以很輕松的添加各類圖型繪制&#xff0c;鍵盤鼠標輸入處理和各類邏…

word+增加水印+java_為Word2019文檔添加水印的兩種方法

水印的類型包括文字水印和圖片水印兩種。在Word文檔中添加文字水印時&#xff0c;可以使用程序中預設的水印效果&#xff0c;而圖片水印則需要自定義添加。一、使用程序預設的文字水印Word 2019中預設了機密、緊急、免責聲明三種類型的文字水印&#xff0c;用戶可根據文件的類型…

如何設置CentOS 7獲取動態及靜態IP地址

自動獲取動態IP地址1.輸入“ip addr”并按回車鍵確定&#xff0c;發現無法獲取IP(CentOS 7默認沒有ifconfig命令)&#xff0c;記錄下網卡名稱&#xff08;本例中為ens33&#xff09;。2.輸入“cd /etc/sysconfig/network-scripts/”按回車鍵確定&#xff0c;繼續輸入“ls”按回…

請求列出指定服務器上的可用功能失敗_濫用 ESI 詳解(上)

在進行安全性評估時&#xff0c;我們注意到了標記語言 Edge Side Includes (ESI)中的一個意外行為&#xff0c;這種語言用于許多流行的 HTTP 代理(反向代理、負載平衡器、緩存服務器、代理服務器)。我們發現成功的 ESI 攻擊可以導致服務器端請求偽造(SSRF)、各種繞過 HTTPOnly …

Java ClassLoader setPackageAssertionStatus()方法與示例

ClassLoader類setPackageAssertionStatus()方法 (ClassLoader Class setPackageAssertionStatus() method) setPackageAssertionStatus() method is available in java.lang package. setPackageAssertionStatus()方法在java.lang包中可用。 setPackageAssertionStatus() metho…

java上傳kafka的方法_哪種方法是將所有數據從Kafka主題復制到接收器(文件或Hive表)的最佳方法?...

我正在使用Kafka Consumer API將所有數據從Kafka主題復制到Hive表 . 為此&#xff0c;我使用HDFS作為中間步驟 . 我使用唯一的組ID并將偏移重置為“最早”&#xff0c;以便從頭開始獲取所有數據&#xff0c;并在執行后忽略提交 . 然后我遍歷Kafka主題中的記錄&#xff0c;并將每…

openstack nova-network 的小bug的排錯經歷

環境是 nova-network vmwareflatdhcp錯誤表現為 開出來的虛擬機有一定幾率獲取不到dhcp地址&#xff0c;手工賦予ip則正常&#xff0c;用flat模式注入的ip正常&#xff0c;下面是排錯過程1首先找網絡防火墻已經把 dnsmasq對應的端口已經打開抓包結果&#xff1a;可以看到虛擬機…

anaconda base環境_anaconda中安裝packages:pip還是conda install?

conda install我就不說了&#xff0c;這都不會別學了就。Using command:$ which -a pip, the terminal will return:This indicates two different pip path to install packages[1].在tf23環境中pip install在base環境中pip install在windows下powershell內&#xff0c;進入到…

Java ClassLoader setDefaultAssertionStatus()方法與示例

ClassLoader類setDefaultAssertionStatus()方法 (ClassLoader Class setDefaultAssertionStatus() method) setDefaultAssertionStatus() method is available in java.lang package. setDefaultAssertionStatus()方法在java.lang包中可用。 setDefaultAssertionStatus() metho…

【風馬一族_xml】xmlp之dtd1

什么是XML約束&#xff1f;在xml技術里&#xff0c;可以編寫一個文檔來約束一個xml文檔的寫法&#xff0c;這稱之為xml約束 2. 為什么要使用xml約束&#xff1f; 參看提示欄 3. xml約束的作用&#xff1f; 約束xml的寫法對xml進行校驗4. 常見的xml約束技術 xml dtdxml Schema…

java ssm框架 緩存_SSM框架之MyBatis3專題4:查詢緩存

查詢緩存的使用&#xff0c;主要是為了提高查詢訪問速度。將用戶對同一數據的重復查詢過程簡化&#xff0c;不再每次均從數據庫中查詢獲取結果數據&#xff0c;從而提高訪問速度。MyBatis的查詢緩存機制&#xff0c;根據緩存區的作用域(聲明周期)可劃分為兩種&#xff1a;一級查…

matplotlib畫圖_漂亮,超詳細的matplotlib畫圖基礎

來自 | 逐夢erhttps://zhumenger.blog.csdn.net/article/details/106530281本文僅作技術交流&#xff0c;如有侵權&#xff0c;請聯系后臺刪除。數據可視化非常重要&#xff0c;因為錯誤或不充分的數據表示方法可能會毀掉原本很出色的數據分析工作。matplotlib 庫是專門用于開發…