当前位置:网站首页>Overview of AOSP ~ WiFi architecture
Overview of AOSP ~ WiFi architecture
2022-06-25 02:47:00 【Nanke is cute】
Android WiFi Architectural Overview
In this paper, Android Source project (AOSP) in WiFi Functional software architecture and each module ( Executable file 、 Dynamic link library ) The interface between .
SDK API
Android SDK For developers WiFi Programming interface (API) , It is very convenient to use .
Related packages :
android.net.wifi( Write App Just import The package , You can use WiFi Related functions )
Main related categories :
- WifiManager WIFI Programming entry ,WIFI Most of the functionality of is provided in the form of methods of this class
- WifiInfo Used to describe WIFI The state of the connection
- ScanResult Used to describe a AP, Such as SSID, Signal strength , Safety mode, etc
Overview

WifiManager It's all about managing Wifi Basic of connection API, Can pass :
android.content.Context.getSystemService(Context.WIFI_SERVICE)
Get an example of it .
Specifically IPC(Inter-Process communication)
App & system_server(WifiManager & WifiService)
if Binder Is the connection App and system_server The bridge , that WifiManager and WiFiService The two ends of the bridge .
framework Code and wifi dependent package be located :
frameworks/base/wifi/java (WIFI Some related packages )
frameworks/base/services/java ( Various Service,WIFI Related packages are :com.android.server.wifi )
frameworks In the code , and wifi The relationships of several related classes are as follows :
- WifiService Inherited from IWifiManager.Stub;
- IWifiManager.Stub And inherit from Binder, At the same time IWifiManager Interface ;
- WifiManager.Stu.proxy It has also been realized. IWifiManager Interface ;
Pictured :
among ,IWifiManager, IWifiManager.Stub, IWifiManager.Stub.Proxy All by IWifiManger.aidl Generate ;
aidl Automatically generate related java Code , Simplify the use of Binder Realization RPC The process of .
IWifiManager.Stub.Proxy,WifiManager,BinberProxy For clients (App process );
and IWifiManager.Stub,WifiService,Binder For the server (SystemServer process ).
App And system_server adopt Binder signal communication , but Binder In itself, it only realizes IPC, It's like socket The ability to communicate . and App Terminal WifiManager and system_server Terminal WifiService And Binder And so on RPC(remote procedure call).
WifiManager Only the system is app Provided interface .Context.getSystemService(Context.WIFI_SERVICE)
The actual object type returned is IWifiManager.Stub.Proxy.
IWifiManager.Stub.Proxy An example of is located at App A proxy on the end , Proxy image IWifiManager.Stub.Proxy
take WifiManager The parameters of the method are serialized to Parcel, Re menstruation Binder Send to system_server process .
system_server Internal WifiService closed App From the WifiManager call , Complete the actual work .
such , The actual work of communicating with the lower layer is done by App Transferred to system_server process (WifiService object ).
WifiStateMachine
in addition , You can see WifiStateMachine yes wifi Functional hub , The control flow of several different modes flows down through it .
When WIFI be in STA Pattern ( or P2P Pattern ) when , adopt WifiNative And wpa_supplicant Interaction .
WifiNative Several definitions Native Method :
public native static boolean setMaxTxPower(int txpower, boolean sapRunning);
public native static boolean loadDriver();
public native static boolean isDriverLoaded();
public native static boolean unloadDriver();
public native static boolean startSupplicant(boolean p2pSupported, int firstScanDelay);
/* Sends a kill signal to supplicant. To be used when we have lost connection or when the supplicant is hung */
public native static boolean killSupplicant(boolean p2pSupported);
private native boolean connectToSupplicantNative();
private native void closeSupplicantConnectionNative();
/** * Wait for the supplicant to send an event, returning the event string. * @return the event string sent by the supplicant. */
private native String waitForEventNative();
private native boolean doBooleanCommandNative(String command);
private native int doIntCommandNative(String command);
private native String doStringCommandNative(String command);
When WIFI be in AP Pattern . adopt NetworkManagementService And netd Interaction , Specifically through LocalSocket(Framework Packaged UNIX Domain socket) And netd Process of communication .
such as ,NetworkManagementService.startAccessPoint Method :
@Override
public void startAccessPoint(
WifiConfiguration wifiConfig, String wlanIface) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
try {
wifiFirmwareReload(wlanIface, "AP");
if (wifiConfig == null) {
mConnector.execute("softap", "set", wlanIface); // towards netd Send control command
} else {
mConnector.execute("softap", "set", wlanIface, wifiConfig.SSID,
wifiConfig.hiddenSSID ? "hidden" : "broadcast",
"1", getSecurityType(wifiConfig),
new SensitiveArg(wifiConfig.preSharedKey));
}
mConnector.execute("softap", "startap");
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
}
WifiNative
functionally ,WifiNative yes system_server and wpa_supplicant Dialogue window , In fact, it mainly depends on wpa_supplicant The dynamic library compiled by the project libwpa_client.so.
WifiNative Several native The concrete implementation of the method is in :
frameworks/base/core/jni/android_net_wifi_WifiNative.cpp
WifiNative Of native Method implementation :
static JNINativeMethod gWifiMethods[] = {
/* name, signature, funcPtr */
{
"loadDriver", "()Z", (void *)android_net_wifi_loadDriver },
{
"isDriverLoaded", "()Z", (void *)android_net_wifi_isDriverLoaded },
{
"unloadDriver", "()Z", (void *)android_net_wifi_unloadDriver },
{
"startSupplicant", "(ZI)Z", (void *)android_net_wifi_startSupplicant },
{
"killSupplicant", "(Z)Z", (void *)android_net_wifi_killSupplicant },
{
"connectToSupplicantNative", "()Z", (void *)android_net_wifi_connectToSupplicant },
{
"closeSupplicantConnectionNative", "()V", (void *)android_net_wifi_closeSupplicantConnection },
{
"waitForEventNative", "()Ljava/lang/String;", (void*)android_net_wifi_waitForEvent },
{
"doBooleanCommandNative", "(Ljava/lang/String;)Z", (void*)android_net_wifi_doBooleanCommand },
{
"doIntCommandNative", "(Ljava/lang/String;)I", (void*)android_net_wifi_doIntCommand },
{
"doStringCommandNative", "(Ljava/lang/String;)Ljava/lang/String;", (void*) android_net_wifi_doStringCommand },
{
"setMaxTxPower", "(IZ)Z", (void *)android_net_wifi_setMaxTxPower },
};
android_net_wifi_WifiNative.cpp Not much practical work done , Most of them call directly wifi.h wifi_maxtxpower.h Defined function :
wifi.h The concrete realization of is hardware/libhardware_legacy/wifi/wifi.c ( Will be compiled as libhardware_legacy.so)
wifi_maxtxpower.h The concrete realization of is hardware/qcom/wlan/libmaxtxpower/wifi_maxtxpower.c ( Will be compiled as libmaxtxpower.so)
therefore native Code dependency libhardware_legacy.so modular 、libmaxtxpower.so modular .
WIFI HAL
Realization SystemServer And wpa_supplicant(hostapd) communication , namely Wifi HAL.
Wifi HAL Encapsulates the UNIX Domain socket,SystemServer adopt UNIX Domain socket And wpa_supplicant(hostapd)
signal communication ;SystemServer Messages sent and wpa_supplicant The response messages are all for ASCII character string .
Wifi HAL The code is mainly distributed in :
hardware/libhardware_legacy/wifi
hardware/qcom/wlan/libmaxtxpower
Are compiled as :
hardware/libhardware_legacy/wifi -> libhardware_legacy.so
hardware/qcom/wlan/libmaxtxpower -> libmaxtxpower.so
wifi.h Defined WIFI HAL The interface of , The specific functions are :
// Drive related :
int wifi_load_driver();
int wifi_unload_driver();
int is_wifi_driver_loaded();
// supplicant relevant :
int wifi_start_supplicant(int p2pSupported, int first_scan_delay);
int wifi_stop_supplicant(int p2pSupported);
int wifi_connect_to_supplicant();
void wifi_close_supplicant_connection();
// wait for WIFI What happened , This function blocks the current call , Until there is wifi Event time , Returns a representation of wifi The character of the event
int wifi_wait_for_event(char *buf, size_t len);
// towards wifi Drive sends a life ,( Most functions send commands to the lower layer through this function )
int wifi_command(const char *command, char *reply, size_t *reply_len);
// Initiate one dhcp request
int do_dhcp_request(int *ipaddr, int *gateway, int *mask,
int *dns1, int *dns2, int *server, int *lease);
// Return to one do_dhcp_request() Error string for
const char *get_dhcp_error_string();
#define WIFI_GET_FW_PATH_STA 0
#define WIFI_GET_FW_PATH_AP 1
#define WIFI_GET_FW_PATH_P2P 2
// Return an invitation firmware route
const char *wifi_get_fw_path(int fw_type);
// by wlan Drive change firmware route
int wifi_change_fw_path(const char *fwpath);
#define WIFI_ENTROPY_FILE "/data/misc/wifi/entropy.bin"
int ensure_entropy_file_exists();
wifi_maxtxpower.h Only one function is defined :
int set_max_tx_power(int power, int sap_running);
android_net_wifi_WifiNative.cpp These functions are called .
wifi.c Called wpa_ctrl.h Some functions defined , and wpa_ctrl.h The function in
stay external/wpa_supplicant_8 Project implementation , And compiled as libwpa_client.so,
See external/wpa_supplicant_8/Android.mk.
wpa_supplicant(hostapd)
Code is located :
external/wpa_supplicant_8
The project contains two interrelated open source projects wpa_supplicant and hostapd, They will generate two executables :
wpa_supplicant and hostapd, Respectively STA Patterns and AP Mode daemon .
besides , Also generated for testing wpa_cli,hostapd_cli,
as well as WIFI HAL Rely on the wpa_client.so, The details can be found in Android.mk Find .
wpa_supplicant Source code structure and hostapd similar , I'll just introduce wpa_supplicant.
wpa_ctrl.h Defines and wpa_supplicant( or hostapd) Interface for process communication :
struct wpa_ctrl * wpa_ctrl_open(const char *ctrl_path);
// Close a control interface to wpa_supplicant/hostapd
void wpa_ctrl_close(struct wpa_ctrl *ctrl);
// Send a command to wpa_supplicant/hostapd
int wpa_ctrl_request(struct wpa_ctrl *ctrl, const char *cmd, size_t cmd_len,
char *reply, size_t *reply_len,
void (*msg_cb)(char *msg, size_t len));
// Register as an event monitor for the control interface
int wpa_ctrl_attach(struct wpa_ctrl *ctrl);
// Unregister event monitor from the control interface
int wpa_ctrl_detach(struct wpa_ctrl *ctrl);
// Receive a pending control interface message
int wpa_ctrl_recv(struct wpa_ctrl *ctrl, char *reply, size_t *reply_len);
// Check whether there are pending event messages
int wpa_ctrl_pending(struct wpa_ctrl *ctrl);
// Get file descriptor used by the control interface
int wpa_ctrl_get_fd(struct wpa_ctrl *ctrl);
char * wpa_ctrl_get_remote_ifname(struct wpa_ctrl *ctrl);
wpa_ctrl_open Create a UNIX Domain socket And wpa_supplicant( or hostapd) Processes are connected ,
wpa_ctrl_close Used to turn off wpa_ctrl_open Created connection ,
wpa_ctrl_request Used to direct to wpa_supplicant/hostapd Send control command , And block ,
until wpa_supplicant/hostapd Return the command response . Control commands and responses are ASCII character string .
wpa_ctrl.h In addition to declaring these functions , Also defined wpa_supplicant/hostapd Some news from , There is no detailed list of .
Source code wpa_supplicant_global_ctrl_iface_receive
Be responsible for dispatching control commands from the upper level , And then call the specific processing function :
"ATTACH" -> wpa_supplicant_ctrl_iface_attach
"DETACH" -> wpa_supplicant_ctrl_iface_detach
else -> wpa_supplicant_global_ctrl_iface_process:
"IFNAME="(prefix) // Most control commands are in the form of IFNAME= start
-> wpas_global_ctrl_iface_ifname
-> wpa_supplicant_ctrl_iface_process # "IFNAME=" Processing of the first command
wpas_global_ctrl_iface_redir
-> wpas_global_ctrl_iface_redir_p2p
-> wpa_supplicant_ctrl_iface_process # "IFNAME=" Processing of the first command
-> wpas_global_ctrl_iface_redir_wfd
-> wpa_supplicant_ctrl_iface_process # "IFNAME=" Processing of the first command
"PING" -> "PONG"
"INTERFACE_ADD" -> wpa_supplicant_global_iface_add
"INTERFACE_REMOVE" -> wpa_supplicant_global_iface_remove
"INTERFACE_LIST" -> wpa_supplicant_global_iface_list
"INTERFACES" -> wpa_supplicant_global_iface_interfaces
"TERMINATE" -> wpa_supplicant_terminate_proc
"SUSPEND" -> wpas_notify_suspend
"RESUME" -> wpas_notify_resume
"SET" -> wpas_global_ctrl_iface_set
"SAVE_CONFIG" -> wpas_global_ctrl_iface_save_config
"STATUS" -> wpas_global_ctrl_iface_status
The function is in wpa_supplicant In the catalog
ctrl_iface_unix.c and ctrl_iface_udp.c It can be realized in , It can be accessed from the android.config Switch
(android.config By Android.mk contain , Specific see Android.mk)
wpa_supplicant Communicate with the kernel
wpa_supplicant The running model of is single process and single thread Reactor(IO multiplexing).wpa_supplicant adopt NETLINK socket Communicate with the kernel .
wpa_supplicant The project supports multiple driver programming interfaces , stay Android It uses nl80211;
nl80211 It's new 802.11netlink Interface common header , And cfg80211 Together form Wireless-Extensions alternatives .
cfg80211 yes Linux 802.11 To configure API, nl80211 Used for configuration cfg80211 equipment , It is also used for kernel to user space communication .
The actual use nl80211 when , Just include the header file in the program
wireless module(in kernel)
Code is located :
kernel/net/wireless
nl80211.c Medium nl80211_init Use genl_register_family_with_ops Registered the response application
struct genl_ops nl80211_ops[], This array defines the response NETLINK Function of message .
and nl80211_init stay cfg80211_init Inside is called ,cfg80211_init Be being subsys_initcall Registered
Subsystem initialization program , Compiled into cfg80211.ko.
nl80211_ops[] excerpts :
static struct genl_ops nl80211_ops[] = {
// ...
{
.cmd = NL80211_CMD_TRIGGER_SCAN,
.doit = nl80211_trigger_scan,
.policy = nl80211_policy,
.flags = GENL_ADMIN_PERM,
.internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
NL80211_FLAG_NEED_RTNL,
},
{
.cmd = NL80211_CMD_GET_SCAN,
.policy = nl80211_policy,
.dumpit = nl80211_dump_scan,
},
{
.cmd = NL80211_CMD_START_SCHED_SCAN,
.doit = nl80211_start_sched_scan,
.policy = nl80211_policy,
.flags = GENL_ADMIN_PERM,
.internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
NL80211_FLAG_NEED_RTNL,
},
{
.cmd = NL80211_CMD_STOP_SCHED_SCAN,
.doit = nl80211_stop_sched_scan,
.policy = nl80211_policy,
.flags = GENL_ADMIN_PERM,
.internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
NL80211_FLAG_NEED_RTNL,
},
// ...
};
wlan driver
Code is located :
vendor/qcom/opensource/wlan/prima
Module initialization (module_init), Module exit (module_exit):
CORE/HDD/src/wlan_hdd_main.c
Model :
Multithreading + queue
Create kernel thread :
CORE/VOSS/src/vos_sched.c Of vos_sched_open()
Thread task (vos_sched.c):
- VosMcThread() - The VOSS Main Controller thread
- VosWdThread() - The VOSS Watchdog thread
- VosTXThread() - The VOSS Main Tx thread
- VosRXThread() - The VOSS Main Rx thread
Thread environment (context) (vos_sched.h):
typedef struct _VosSchedContext
{
/* Place holder to the VOSS Context */
v_PVOID_t pVContext;
/* WDA Message queue on the Main thread*/
VosMqType wdaMcMq;
/* PE Message queue on the Main thread*/
VosMqType peMcMq;
/* SME Message queue on the Main thread*/
VosMqType smeMcMq;
/* TL Message queue on the Main thread */
VosMqType tlMcMq;
/* SYS Message queue on the Main thread */
VosMqType sysMcMq;
/* WDI Message queue on the Main thread*/
VosMqType wdiMcMq;
/* WDI Message queue on the Tx Thread*/
VosMqType wdiTxMq;
/* WDI Message queue on the Rx Thread*/
VosMqType wdiRxMq;
/* TL Message queue on the Tx thread */
VosMqType tlTxMq;
/* TL Message queue on the Rx thread */
VosMqType tlRxMq;
/* SYS Message queue on the Tx thread */
VosMqType sysTxMq;
VosMqType sysRxMq;
// ...
struct task_struct* McThread;
/* TX Thread handle */
struct task_struct* TxThread;
/* RX Thread handle */
struct task_struct* RxThread;
// ...
} VosSchedContext, *pVosSchedContext;
Qualcomm data :
80-Y0513-1_G_QCA_WCN36x0_Software_Architecture.pdf
chapter: Android WLAN Host Software Architecture
SCAN Process tracking
App
WifiManager wifiManager = (WifiManager)Context.getService(Contex.WIFI_SERVICE);
wifiManager.startScan();
//wifiManager --> IWifiManager.Stub.Proxy
IWifiManager.Stub.Proxy implements android.net.wifi.IWifiManager
wifiManager.startScan()
-> IWifiManager.Stub.Proxy.startScan(WorkSource=null);
-> BinderProxy.transact(Stub.TRANSACTION_startScan, _data, _reply, 0);
systerm_server
wifi –> WifiService
WifiService extends IWifiManager.Stub
IWifiManager.Stub extends android.os.Binder
implements android.net.wifi.IWifiManager
-> IWifiManager.Stub.onTransact(int code, Parcel data, Parcel reply, int flags);
case TRANSACTION_startScan:
-> WifiService.startScan(WorkSource workSource);
-> WifiStateMachine.startScan(int callingUid, WorkSource workSource);
-> StateMachine.sendMessage(CMD_START_SCAN, callingUid, 0, workSource);
case CMD_START_SCAN:
-> handleScanRequest(WifiNative.SCAN_WITHOUT_CONNECTION_SETUP, message);
-> startScanNative(type, freqs)
-> WifiNative.scan(type, freqs)
-> doBooleanCommand("SCAN ..."); // AF_UNIX socket, send to wpa_supplicant
wpa_supplicant
external/wpa_supplicant_8/wpa_supplicant$ grep -nr "\"SCAN " .
./ChangeLog:197: - "SCAN freq=<freq list>" can be used to specify which channels are
./ChangeLog:199: - "SCAN passive=1" can be used to request a passive scan (no Probe
./ChangeLog:201: - "SCAN use_id" can be used to request a scan id to be returned and
./ChangeLog:203: - "SCAN only_new=1" can be used to request the driver/cfg80211 to
./ctrl_iface.c:6986: } else if (os_strncmp(buf, "SCAN ", 5) == 0) {
./src/drivers/driver_test.c:1289: ret = os_snprintf(pos, end - pos, "SCAN " MACSTR,
./src/drivers/driver_test.c:1994: } else if (os_strncmp(buf, "SCAN ", 5) == 0) {
./ctrl_iface.c:6986: } else if (os_strncmp(buf, "SCAN ", 5) == 0) {
refer to ./ctrl_iface.c:6986
} else if (os_strcmp(buf, "SCAN") == 0) {
wpas_ctrl_scan(wpa_s, NULL, reply, reply_size, &reply_len);
} else if (os_strncmp(buf, "SCAN ", 5) == 0) {
wpas_ctrl_scan(wpa_s, buf + 5, reply, reply_size, &reply_len);
wpas_ctrl_scan -> wpa_supplicant_req_scan
int res = eloop_deplete_timeout(sec, usec, wpa_supplicant_scan, wpa_s,
NULL);
if (res == 1) {
wpa_dbg(wpa_s, MSG_DEBUG, "Rescheduling scan request: %d.%06d sec",
sec, usec);
}
wpa_supplicant_scan -> wpa_supplicant_trigger_scan
-> radio_add_work(wpa_s, 0, "scan", 0, wpas_trigger_scan_cb, ctx)
wpas_trigger_scan_cb -> wpa_drv_scan
static inline int wpa_drv_scan(struct wpa_supplicant *wpa_s,
struct wpa_driver_scan_params *params)
{
if (wpa_s->driver->scan2) // callback
return wpa_s->driver->scan2(wpa_s->drv_priv, params);
return -1;
}
grep scan2 callback
external/wpa_supplicant_8/wpa_supplicant$ grep -nr "scan2\s*=" .
./src/drivers/driver_wext.c:2401: .scan2 = wpa_driver_wext_scan,
./src/drivers/driver_privsep.c:726: .scan2 = wpa_driver_privsep_scan,
./src/drivers/driver_test.c:2677: .scan2 = wpa_driver_test_scan,
./src/drivers/driver_bsd.c:1618: .scan2 = wpa_driver_bsd_scan,
./src/drivers/driver_nl80211.c:12612: .scan2 = driver_nl80211_scan2,
./src/drivers/driver_ndis.c:3217: wpa_driver_ndis_ops.scan2 = wpa_driver_ndis_scan;
refer to ./src/drivers/driver_nl80211.c:12612
driver_nl80211_scan2 -> wpa_driver_nl80211_scan
msg = nl80211_scan_common(drv, NL80211_CMD_TRIGGER_SCAN, params,
bss->wdev_id_set ? &bss->wdev_id : NULL);
if (!msg)
return -1;
use NL80211_CMD_TRIGGER_SCAN talk with kernel(cfg80211.ko)
kernel
grep NL80211_CMD_TRIGGER_SCAN in kernel source:
kernel$ cgrep NL80211_CMD_TRIGGER_SCAN
./net/wireless/nl80211.c:9053: .cmd = NL80211_CMD_TRIGGER_SCAN,
./net/wireless/nl80211.c:9605: NL80211_CMD_TRIGGER_SCAN) < 0) {
./include/uapi/linux/nl80211.h:255: * option to specify additional IEs in NL80211_CMD_TRIGGER_SCAN,
./include/uapi/linux/nl80211.h:260: * @NL80211_CMD_TRIGGER_SCAN: trigger a new scan with the given parameters
./include/uapi/linux/nl80211.h:759: NL80211_CMD_TRIGGER_SCAN,
./include/uapi/linux/nl80211.h:1362: * This attribute is used with %NL80211_CMD_TRIGGER_SCAN and
./include/uapi/linux/nl80211.h:3863: * of NL80211_CMD_TRIGGER_SCAN and NL80211_CMD_START_SCHED_SCAN
refer to net/wireless/nl80211.c:9053
static struct genl_ops nl80211_ops[] = {
// ... ...
{
.cmd = NL80211_CMD_TRIGGER_SCAN,
.doit = nl80211_trigger_scan,
.policy = nl80211_policy,
.flags = GENL_ADMIN_PERM,
.internal_flags = NL80211_FLAG_NEED_WDEV_UP |
NL80211_FLAG_NEED_RTNL,
},
// ... ...
};
nl80211_trigger_scan -> rdev_scan(rdev, request);
static inline int rdev_scan(struct cfg80211_registered_device *rdev,
struct cfg80211_scan_request *request)
{
int ret;
trace_rdev_scan(&rdev->wiphy, request);
ret = rdev->ops->scan(&rdev->wiphy, request); // callback
trace_rdev_return_int(&rdev->wiphy, ret);
return ret;
}
边栏推荐
- After reciting the eight part essay, I won the hemp in June
- 3 years of testing experience. I don't even understand what I really need on my resume. I need 20K to open my mouth?
- How transformers Roberta adds tokens
- Getting started with unityshader - Surface Shader
- Migrate Oracle database from windows system to Linux Oracle RAC cluster environment (2) -- convert database to cluster mode
- File system - basic knowledge of disk and detailed introduction to FAT32 file system
- 使用ShaderGraph制作边缘融合粒子Shader的启示
- 3年测试经验,连简历上真正需要什么都没搞明白,张口就要20k?
- 请问polarDB数据库可以通过mysql进行数据源连接吗
- [analysis of STL source code] functions and applications of six STL components (directory)
猜你喜欢

leecode学习笔记-机器人走到终点的最短路径

Lizuofan, co-founder of nonconvex: Taking quantification as his lifelong career

計網 | 【四 網絡層】知識點及例題

Getting started with unityshader - Surface Shader

进入阿里做测试员遥不可及?这里或许有你想要的答案
![Planification du réseau | [quatre couches de réseau] points de connaissance et exemples](/img/c3/d7f382409e99eeee4dcf4f50f1a259.png)
Planification du réseau | [quatre couches de réseau] points de connaissance et exemples

AI服装生成,帮你完成服装设计的最后一步

14 bs对象.节点名称.name attrs string 获取节点名称 属性 内容

都2022年了,你还不了解什么是性能测试?

消息称一加将很快更新TWS耳塞、智能手表和手环产品线
随机推荐
高速缓存Cache详解(西电考研向)
Enlightenment of using shadergraph to make edge fusion particle shader
Summary of knowledge points of computer level III (database) test preparation topics
PE文件基础结构梳理
请问polarDB数据库可以通过mysql进行数据源连接吗
When they are in private, they have a sense of propriety
UnityShader入门精要——PBS基于物理的渲染
【直播回顾】战码先锋第七期:三方应用开发者如何为开源做贡献
Intranet learning notes (7)
Intranet learning notes (6)
电脑端微信用户图片DAT格式解码为图片(TK版)
入坑机器学习:一,绪论
Smartctl opens the device and encounters permission denied problem troubleshooting process record
Random list random generation of non repeating numbers
leecode学习笔记-机器人走到终点的最短路径
Are programmers from Huawei, Alibaba and other large manufacturers really easy to find?
Insurance can also be bought together? Four risks that individuals can pool enough people to buy Medical Insurance in groups
商城项目 pc----商品详情页
E - average and median
调用系统函数安全方案