Android HAL到Framework

news2024/10/6 22:23:55

一、为什么需要Framwork?

Framework实际上是⼀个应⽤程序的框架,提供了很多服务:

1、丰富⽽⼜可扩展的视图(Views),

可以⽤来构建应⽤程序,它包括列表(lists),⽹格(grids),⽂本框(text boxes),按钮(buttons),甚⾄可嵌⼊的web浏览器。

2、内容提供器(Content Providers)

使得应⽤程序可以访问另⼀个应⽤程序的数据(如联系⼈数据库),或者共享它们⾃⼰的数据

3、资源管理器(Resource Manager)

提供⾮代码资源的访问,如本地字符串,图形,和布局⽂件(layout files)。

4、通知管理器(Notification Manager)

使得应⽤程序可以在状态栏中显⽰⾃定义的提⽰信息。

5、活动管理器(Activity Manager)

⽤来管理应⽤程序⽣命周期并提供常⽤的导航回退功能。

二、应用层访问硬件,如何自定义系统Service?

1、应用层如何访问硬件

(1)Linux

        对于Linux来说的话,就比较简单,应用层的APP直接通过open一类的接口直接访问我们底层的驱动文件

(2)Android 

        对于Android来说的话,它就会有多种方式去访问,

1) APP ----- JNI ----- Kernel:

        这种就很直接明了,上层app访问JNI,再去访问kernel

2)APP ----- Service ----- JNI ----- Kernel:

        当我们要往系统里添加一个硬件的话,我们更希望把它封装为一个系统的服务,就可以以这种方式去访问到底层

3)APP ----- Service ----- JNI ----- HAL ----- Kernel:

        一些驱动厂商的一个源码呢他是不希望开放给我们的一个开发者是吧,但是他们又依赖着Android的开源框架,所以就有一种比较好的方法,既不需要公开源码,又可以实现同样的功能。就是把它封装成库,这样可以让厂家去提供一个现成的库,然后我们直接去使用,他就不用开放这一层的源码,这就是HAL层的存在意义。

 为什么需要JNI? 

        应⽤使⽤java编写,驱动⼀般使⽤c/cpp编写,提供⼀种Java访问c/cpp的⽅法。也就是Java代码可通过JNI接⼝调⽤C/C++⽅法。

 JNI开发流程的步骤: 

1)编写JNI⽅法表并注册
2)实现JNI的.c⽂件

2、自定义系统Service 

        Framework还有一个很重要的功能,就是系统server。所有的硬件呢都是通过我们的系统server去进行管理,那我们怎样为我们的硬件接口去添加一个自定义的系统serve呢?

(1)建立aidl通信接口;

(2)在system_server中注册service到servicemanager;

(3)实现service,对应aidl中的接口函数。

(4)client向servicemanager请求service,成功后,调用aidl接口函数,建立client进程和service进程的通信关系。

总结来说就是:

1)system_server完成注册功能;
2)servicemanager完成服务管理功能;
3)aidl完成通讯功能;

(1)建立aidl通信接口

在frameworks/base/core/java/android/os/路径下新建对应名称的一个aidl文件

下面我们以顾凯歌的一个蓝牙模块的服务为大家举例:

路径:frameworks/base/core/java/android/os/IGocsdkService.aidl:
(因为他是Interface的一个接口,所有在前面加个 "I")

+ package android.os;

+ interface IEmbededService {
+   interface IFmService {
+
+       //蓝牙状态回调注册去注销 
+       void registerCallback(IGocsdkCallback callback);
+       // 注销蓝牙状态
+       void unregisterCallback(IGocsdkCallback callback);
+       
+       //注释后面带的为操作后相应的回调回复
+       //蓝牙协议软复位  ---》 onInitSucceed()
+       void restBluetooth();
+       
+       //获取本地蓝牙名称  ---》onCurrentDeviceName()
+       void getLocalName();
+       
+       //设置本地蓝牙名称  ---》onCurrentDeviceName()
+       void setLocalName(String name);

+        ..................//等等一些,都为接口函数,会在下面实现

}

编译到系统

路径:frameworks/base/Android.mk 

diff --git a/android/frameworks/base/Android.bp b/android/frameworks/base/Android.bp
old mode 100644
new mode 100755
index d8a7f06..953759c
--- a/android/frameworks/base/Android.bp
+++ b/android/frameworks/base/Android.bp
@@ -265,6 +265,11 @@ java_defaults {
         "core/java/android/os/IRecoverySystemProgressListener.aidl",
         "core/java/android/os/IRemoteCallback.aidl",
         "core/java/android/os/ISchedulingPolicyService.aidl",
+        "core/java/android/os/IGocsdkService.aidl",
         ":statsd_aidl",
         "core/java/android/os/ISystemUpdateManager.aidl",
         "core/java/android/os/IThermalEventListener.aidl",

(2)在system_server中注册EmbededServicer到servicemanager

路径:frameworks/base/services/java/com/android/server/SystemServer.java

使用ServiceManager.addService添加我们自定义的server

@@ -1097,6 +1097,13 @@ public final class SystemServer {
}
    Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);

 +      try {
 +          Slog.i(TAG, "IGocsdkService");
 +           ServiceManager.addService("gocsdkService ", new GocsdkService());
 +   } catch (Throwable e) { 
 +       Slog.e(TAG, "Failure starting Gocsdk Service", e);
 +   }

(3)实现EmbededService,对应aidl中的接⼝函数

路径:frameworks/base/services/java/com/android/server/EmbededService.java

package com.android.server;
import android.content.Context;
import android.os.IGocsdkService;
import android.util.Slog;
public class GocsdkService extends IGocsdkService.Stub {
    private static final String TAG = "GocsdkService";
    GocsdkService(){
        Slog.i(TAG,"GocsdkService init");
    }
        public void registerCallback(IGocsdkCallback callback){
        return xxx;
    }
        public void unregisterCallback(IGocsdkCallback callback){
        return xxx;
    }
        public void getLocalName(){
        return xxx;
    }
        .......................
}

(4)在app中使⽤IEmbededService的大致流程如下

        很好理解吧,把我们对应的一个服务导入,然后去初始化一个类,然后通过ServiceManager去找到我们自定义的这个server,然后使用自定义服务的函数获取数据。

import android.os.IGocsdkService; //导入

private IGocsdkService mGocsdkService = null; //初始化类
mGocsdkService = IGocsdkService .Stub.asInterface(

        ServiceManager.getService("gocsdkService"));

int version= mEmbededService.getLocalName();
String text = String.value(localName);

(5)编译service,烧录

直接全sdk编译,防止有遗漏

(6)验证

使⽤service list查看是否有EmbededService

xxx:/ $ service list | grep gocsdkService
         gocsdkService: [android.os.IGocsdkService]

三、为什么需要Android HAL?

        Hardware Abstract Layer 硬件抽象层,由于Linux Kernel需要遵循GPL开源协议,硬件⼚商为了保护⾃⼰硬件⽅⾯的各项参数不被外泄,⽽⼀个设备的驱动程序包含了硬件的⼀些重要参数,所以驱动的开源势必会使硬件⼚商蒙受损失,Google为了保护硬件⼚商的利益,所以在Android系统中加⼊了HAL层,在HAL层中不必遵循GPL协议,所以代码可以封闭。
        所以如果硬件驱动开源的写在Kernel⾥,Framework直接调⽤,⽽不愿意开源的就写在HAL层⾥,实现闭源。也就是说,编写驱动分为两个部分,⼀个是HAL层的驱动代码,⼀个是Kernel层的驱动代码。


1、内核实现HAL驱动的⽅法有两种:

(1)采⽤直接调⽤so动态链接库⽅式

        采⽤共享库形式,在编译时会调⽤到。由于采⽤function call形式调⽤,因此可被多个进程使⽤,但会被mapping到多个进程空间中,造成浪费,同时需要考虑代码能否安全重⼊的问题。

(2)采⽤Stub代理⽅式调⽤

        采⽤HAL module和HAL stub结合形式,HAL stub不是⼀个share library,编译时
上层只拥有访问HAL stub的函数指针,并不需要HAL stub。上层通过HAL module提供的统⼀接⼝获取并操作HAL stub,so⽂件只会被mapping到⼀个进程,也不存在重复mapping和重⼊问题。

2、如何编写HAL层驱动

        我们现在一般都是采用第二种方式,基于HAL框架提供了三个结构体,分别为hw_device_t、hw_module_t、hw_module_methods_t,编写HAL层驱动则是依据这三个结构体作扩展,我们创建⾃⼰驱动的device_t,module_t代码,并且写hw_module_methods_t这个结构体中⽅法的实现代码,最后JNI层通过hw_get_module调⽤。

(1)在 android/hardware/libhardware/modules/xxx 路径下创建我们的HAL文件夹,例如LED:

mkdirhardware/libhardware/modules/led

path:hardware/libhardware/include/hardware/led_hal.h
path:hardware/libhardware/modules/embeded/led_hal.c

(1)led_hal.c:

#define LOG_TAG "dLed"
#include <hardware/hardware.h>
#include <hardware/led_hal.h>
#include <fcntl.h>
#include <errno.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
#define DEVICE_NAME "sys/led/embeded_blue_led"
#define MODULE_NAME "EmLed"

/*设备打开和关闭接⼝*/
static int embededled_device_open(const struct hw_module_t* module, const
char* name, struct hw_device_t** device);
static int embededled_device_close(struct hw_device_t* device);

/*设备访问接⼝*/
static int embededled_set_val(struct embededled_device_t* dev, int val);
static int embededled_get_val(struct embededled_device_t* dev, int* val);
static int embededled_device_open(const struct hw_module_t* module, const
char* name, struct hw_device_t** device) {
    struct embededled_device_t* dev;dev = (struct
    embededled_device_t*)malloc(sizeof(struct embededled_device_t));

    if(!dev) {
        ALOGI("embededled Stub: failed to alloc space");
        return -EFAULT;
    }
    memset(dev, 0, sizeof(struct embededled_device_t));
    //初始化设备相关信息,实现访问接⼝函数
    dev->common.tag = HARDWARE_DEVICE_TAG;
    dev->common.version = 0;
    dev->common.module = (hw_module_t*)module;
    dev->common.close = embededled_device_close;
    dev->set_val = embededled_set_val;
    dev->get_val = embededled_get_val;
    if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1) {
    ALOGI("embededled Stub: failed to open
    sys/embededled/embeded_blue_led -- %s.", strerror(errno));free(dev);
    return -EFAULT;
    }
    int status = 0;
    write(dev->fd, &status, sizeof(status));
    *device = &(dev->common);
    ALOGI("embededled Stub: open sys/embededled/embeded_blue_led
    successfully.");
    return 0;
}

static int embededled_device_close(struct hw_device_t* device) {
    struct embededled_device_t* embededled_device = (struct
    embededled_device_t*)device;
    if(embededled_device) {
        close(embededled_device->fd);
        free(embededled_device);
    }
        return 0;
}

static int embededled_set_val(struct embededled_device_t* dev, int val) {
    ALOGI("embededled Stub: set value %d to device.", val);
    write(dev->fd, &val, sizeof(val));
    return 0;
    }

static int embededled_get_val(struct embededled_device_t* dev, int* val) {
if(!val) {
    ALOGI("embededled Stub: error val pointer");

    return -EFAULT;
}
    read(dev->fd, val, sizeof(*val));
    ALOGI("embededled Stub: get value %d from device", *val);
    return 0;
}

/*模块⽅法表*/
static struct hw_module_methods_t embededled_module_methods = {
open: embededled_device_open
};

/*模块实例变量*/
struct embededled_module_t HAL_MODULE_INFO_SYM = {
common: {
    tag: HARDWARE_MODULE_TAG,
    version_major: 1,
    version_minor: 0,
    id: EMBEDEDLED_HARDWARE_MODULE_ID,
    name: MODULE_NAME,
    author: MODULE_AUTHOR,
    methods: &embededled_module_methods,
}
};

led_hal.h:
path:hardware/libhardware/include/hardware/led_hal.h

#ifndef ANDROID_LED_INTERFACE_H
#define ANDROID_LED_INTERFACE_H
#include <hardware/hardware.h>
__BEGIN_DECLS
/*定义模块ID*/
#define EMBEDEDLED_HARDWARE_MODULE_ID "led_hal"
/*硬件模块结构体*/
struct led_module_t {
        struct hw_module_t common;
};
/*硬件接⼝结构体*/
struct embededled_device_t {
        struct hw_device_t common;
        int fd;
        int (*set_val)(struct led_device_t* dev, int val);
        int (*get_val)(struct led_device_t* dev, int* val);
};
__END_DECLS
#endif

四、JNI层添加

JNI开发流程的步骤:

第1步:编写JNI⽅法表并注册
第2步:实现JNI的.c⽂件

里面呢就是我们要实现的三个函数,然后再把对应的方法注册到我们的server里面去

Android.mk
1 diff --git a/frameworks/base/services/core/jni/Android.mk
            b/frameworks/base/services/core/jni/Android.mk
2     index 0f0124bd46..305773298a 100644
3     --- a/frameworks/base/services/core/jni/Android.mk
4     +++ b/frameworks/base/services/core/jni/Android.mk
5     @@ -36,6 +36,7 @@ LOCAL_SRC_FILES += \
6     $(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \
7     $(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \
8     $(LOCAL_REL_DIR)/com_android_server_PersistentDataBlockService.cpp \
9     + $(LOCAL_REL_DIR)/com_android_server_EmbededLedService.cpp \
10     $(LOCAL_REL_DIR)/onload.cpp
11
12     LOCAL_SRC_FILES += \
把注册JNI⽅法函数添加到系统中
1 diff --git a/frameworks/base/services/core/jni/onload.cpp
    b/frameworks/base/services/core/jni/onload.cpp
2     index d5861f8c41..b52f7917fd 100644
3     --- a/frameworks/base/services/core/jni/onload.cpp
4     +++ b/frameworks/base/services/core/jni/onload.cpp
5     @@ -47,6 +47,7 @@ int
    register_android_server_PersistentDataBlockService(JNIEnv* env);
6     int register_android_server_Watchdog(JNIEnv* env);
7     int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
8     int register_com_android_server_rkdisplay_RkDisplayModes(JNIEnv* env);
9     +int register_android_server_EmbededLedService(JNIEnv* env);
10     };
11
12     using namespace android;
13     @@ -89,7 +90,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
14     register_android_server_Watchdog(env);
15     register_android_server_HardwarePropertiesManagerService(env);
16     register_com_android_server_rkdisplay_RkDisplayModes(env);
17     -
18     -
19     + register_android_server_EmbededLedService(env);
20     return JNI_VERSION_1_4;
21         }
然后按照同样的方法去建立AIDL&Service

1、AIDL:

1 package android.os;
2
3 interface IEmbededLedService {
4     void setVal(int val);
5     int getVal();
6 }
添加下⾯mk⽂件内容后,编译⽣成接⼝
1 diff --git a/frameworks/base/Android.mk b/frameworks/base/Android.mk
2 index b9692de0e1..c426a3cd99 100755
3 --- a/frameworks/base/Android.mk
4 +++ b/frameworks/base/Android.mk
5 @@ -240,6 +240,7 @@ LOCAL_SRC_FILES += \
6     core/java/android/os/IUpdateLock.aidl \
7     core/java/android/os/IUserManager.aidl \
8     core/java/android/os/IVibratorService.aidl \
9     + core/java/android/os/IEmbededLedService.aidl \
10     core/java/android/os/IDisplayDeviceManagementService.aidl \
11     core/java/android/os/IRkDisplayDeviceManagementService.aidl \
12     core/java/android/security/IKeystoreService.aidl \

2、Service

frameworks/base/services/java/com/android/server/EmbededLedService.java
1 package com.android.server;
2 import android.content.Context;
3 import android.os.IEmbededLedService;
4 import android.util.Slog;
5     public class EmbededLedService extends IEmbededLedService.Stub {
6         private static final String TAG = "EmbededLedService";
7         EmbededLedService() {
8
9     boolean status = init_native();
10     Slog.i(TAG,"EmbededLedService Stub init"+status);
11     }
12     public void setVal(int val) {
13         setVal_native(val);
14     }
15     public int getVal() {
16     return getVal_native();
17 }
18
19 //JNI⽅法
20 private static native boolean init_native();
21 private static native void setVal_native(int val);
22 private static native int getVal_native();
23 };

3、添加Service到System启动

1 diff --git
a/frameworks/base/services/java/com/android/server/SystemServer.java
b/frameworks/base/services/java/com/android/server/SystemServer.java
2 index cc6f1850e6..b22ecda734 100644
3 --- a/frameworks/base/services/java/com/android/server/SystemServer.java
4 +++ b/frameworks/base/services/java/com/android/server/SystemServer.java
5 @@ -1086,6 +1086,15 @@ public final class SystemServer {
6 } catch (Throwable e) {
7 reportWtf("starting DiskStats Service", e);
8 }
9 +
10 + try {
11 + Slog.i(TAG, "Embededled Service");
12 + ServiceManager.addService("embededled", new
EmbededLedService());
13 + } catch (Throwable e) {
14 + Slog.e(TAG, "Failure starting Embededled Service", e);
15 + }
16 +
17 +
18 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
19
20 if (!disableSamplingProfiler) {

4、编译&烧写

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1699501.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

指针(6)

1. sizeof和strlen的对比 1.1 sizeof 在学习操作符的时候&#xff0c;我们学习了 sizeof &#xff0c; sizeof 计算变量所占内存内存空间大小的&#xff0c;单位是字节&#xff0c;如果操作数是类型的话&#xff0c;计算的是使⽤类型创建的变量所占内存空间的大小。 sizeof 只…

精品丨快速申请免费https证书

https域名证书对提高网站排名有一定的好处&#xff0c;所以当今很多企业为了给网站一个好的安全防护&#xff0c;就会去申请该证书。如今很多企业虽然重视网站的安全防护&#xff0c;但是也重视成本&#xff0c;所以为了节约成本会考虑申请免费的https证书。 第一个好处 企业不…

力扣496. 下一个更大元素 I

Problem: 496. 下一个更大元素 I 文章目录 题目描述思路复杂度Code 题目描述 思路 因为题目说nums1是nums2的子集&#xff0c;那么我们先把nums2中每个元素的下一个更大元素算出来存到一个映射里&#xff0c;然后再让nums1中的元素去查表即可 复杂度 时间复杂度: O ( n 1 n 2…

吉林大学计科21级《软件工程》期末考试真题

文章目录 21级期末考试题一、单选题&#xff08;2分一个&#xff0c;十个题&#xff0c;一共20分&#xff09;二、问答题&#xff08;5分一个&#xff0c;六个题&#xff0c;一共30分&#xff09;三、分析题&#xff08;一个10分&#xff0c;一共2个&#xff0c;共20分&#xf…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-22讲 RTC 时钟设置

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

十四天学会Vue——Vue核心(理论+实战)(第一天)上篇

&#xff01;&#xff01;&#xff01;声明必看&#xff1a;由于本篇开始就写了Vue&#xff0c;内容过多&#xff0c;本篇部分内容还有待完善&#xff0c;小编先去将连续更新的js高阶第四天完成~本篇部分待完善内容明日更新 一、Vue核心&#xff08;上篇&#xff09; 热身top…

mysql - 索引原理

mysql索引原理 文中的查询, 以该表结构为例 CREATE TABLE user (id int NOT NULL COMMENT id,name varchar(255) COLLATE utf8mb4_bin NOT NULL COMMENT 姓名,age int NOT NULL COMMENT 年龄,sex tinyint(1) NOT NULL COMMENT 性别,phone varchar(255) CHARACTER SET utf8mb4…

06中间件RTOS/CP

Autosar CP 操作系统详解-CSDN博客 1. 什么是RTOS &#xff1f; RTOS&#xff0c;英文全称是 Real-time Operation System&#xff0c;中文就是 实时操作系统&#xff0c;又称及时操作系统。 实时操作系统&#xff0c;是指当外界事件或数据产生时&#xff0c;能够接受并以足…

GEC210编译环境搭建

一、下载编译工具链 下载&#xff1a;点击跳转 二、解压到 /usr/local/arm 目录 sudo mv gec210.zip /usr/local/arm cd /usr/local/arm sudo unzip gec210.zip 三、添加到环境变量 PATH/usr/local/arm/arm-cortex_a8-linux-gnueabi-4.7.3/bin:$PATH 四、测试验证 在终端…

微信小程序如何跳转微信公众号

1. 微信小程序如何跳转微信公众号 1.2. 微信公众号配置 登录微信公众号&#xff0c;点击【小程序管理】&#xff1a;   点击【添加】&#xff1a;   点击【关联小程序】&#xff1a;   输入小程序进行关联&#xff1a; 1.2. 微信小程序配置 登录微信小程序&#xf…

力扣刷题---LCS 02. 完成一半题目【简单】

题目描述 有 N 位扣友参加了微软与力扣举办了「以扣会友」线下活动。主办方提供了 2*N 道题目&#xff0c;整型数组 questions 中每个数字对应了每道题目所涉及的知识点类型。 若每位扣友选择不同的一题&#xff0c;请返回被选的 N 道题目至少包含多少种知识点类型。 示例 1&…

【NumPy】关于numpy.sum()函数,看这一篇文章就够了

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

JAVA实现图书管理系统(初阶)

一.抽象出对象: 1.要有书架&#xff0c;图书&#xff0c;用户&#xff08;包括普通用户&#xff0c;管理员用户&#xff09;。根据这些我们可以建立几个包&#xff0c;来把繁杂的代码分开&#xff0c;再通过一个类来把这些&#xff0c;对象整合起来实现系统。说到整合&#xf…

C++ List完全指南:使用方法与自定义实现

文章目录 list的使用几种构造函数 list的实现1.节点类的定义1.1节点类的构造函数 2.正向迭代器实现2.1operator*重载2.2operator->重载2.3operator重载2.4operator--2.5operator和operator&#xff01; 3.反向迭代器实现3.1operator*重载3.2operator->重载3.3operator重载…

SpringBoot使用Mock进行单元测试

需求说明&#xff1a;需要对一个service接口进行单元测试 1.在pom.xml中加入依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-ins…

计算机网络 1

两台主机想通信&#xff0c;其实本质就是两个文件的资源交换&#xff0c;但是长距离的通信&#xff0c;面临的是很多的问题。这个时候需要通过一些方式来保证可靠性 什么是协议 这样一个例子&#xff0c;我是住在农村&#xff0c;我读高中了我需要去县里面读书。这个时候呢&…

arm64虚拟化-CPU虚拟化

arm64虚拟化-CPU虚拟化 1 虚拟化1.1 CPU虚拟化1.2 内存虚拟化1.3 I/O虚拟化 2 异常状态2.1 AArch642.2 AArch32 3 启动到EL2异常等级4 CPU虚拟化4.1 进入VM4.2 退出VM 本篇博客是基于对苯叔《ARM64高级特性专题》的学习而总结的&#xff0c;大家如有需要可以去淘宝或者奔跑吧li…

【Nginx <末>】Nginx 基于 IP 地址的访问限制

目录 &#x1f44b;前言 &#x1f4eb;一、限制 IP 可以实现哪些功能 &#x1f440;二、 项目实现 2.1 访问控制实现 2.2 Nginx 配置中指定 IP 地址 &#x1f49e;️三、章末 &#x1f44b;前言 小伙伴们大家好&#xff0c;前面一段时间学习了 Nginx 的相关知识&#xff0c…

DAMA数据管理知识体系必背18张框图

近期对数据管理知识体系中比较重要的框图进行了梳理总结,总共有18张框图,供大家参考。主要涉及数据管理、数据治理阶段模式、数据安全需求、主数据管理关键步骤,主数据架构、DW架构、数据科学的7个阶段、数据仓库建设活动、信息收敛三角、大数据分析架构图、数据管理成熟度等…

Jenkins--从入门到入土

Jenkins–从入门到入土 文章目录 Jenkins--从入门到入土〇、概念提要--什么是CI/DI&#xff1f;1、CI&#xff08;Continuous Integration&#xff0c;持续集成&#xff09;2、DI&#xff08;DevOps Integration&#xff0c;DevOps 集成&#xff09;3、解决的问题 一、Jenkins安…