2024年8月

OpenVino快速落地部署教程

Openvino
是由
Intel
开发的专门用于优化和部署人工智能推理的半开源的工具包,主要用于对深度
推理做优化
。本教程适用于
Yolov5-7.0
,直接跑Yolov5为6FPS,使用OpenVino后为30FPS,未来将会出一系列其他模型(Paddle等)的OpenVino部署教程,测试平台——Intel Nuc 11代i5处理器

一、安装OpenVino

进入OpenVino官网

https://docs.openvino.ai/2024/get-started/install-openvino.html

选择自己喜欢的下载方式,本教程采用OpenVino-2022.3.1版本

二、模型转换

  1. 通过Yolov5自带的export.py文件将.pt转为.onnx格式

    python3 export.py --weights xxxx/xxxxx.pt --include onnx --batch_size 1 --opset 10
    
    PS:如果出现转换失败的提示,如:opset 10不支持或是onnx版本问题请重新搭建yolov5环境,按照requirements.txt里库的最低版本进行安装
    
  2. 使用OpenVino工具链将.onnx转为xml、bin模型

    mo --input_model xxx/xxx.onnx
    
    PS:如果openvino环境安装成功将可以在yolov5的环境中直接使用mo命令
    

PS:转换完成后请一定用模型可视化工具查看转换是否正确

三、采用以下代码快速部署

import openvino.runtime as ov
import cv2
import numpy as np
import openvino.preprocess as op

class ObjectDetector:
    def __init__(self, model_xml, model_bin, labels, device="CPU"):
        self.core = ov.Core()
        self.model = self.core.read_model(model_xml, model_bin)
        self.labels = labels
        self.preprocess_model()
        self.compiled_model = self.core.compile_model(self.model, device)
        self.infer_request = self.compiled_model.create_infer_request()

    def preprocess_model(self):
        premodel = op.PrePostProcessor(self.model)
        premodel.input().tensor().set_element_type(ov.Type.u8).set_layout(ov.Layout("NHWC")).set_color_format(op.ColorFormat.BGR)
        premodel.input().preprocess().convert_element_type(ov.Type.f32).convert_color(op.ColorFormat.RGB).scale([255., 255., 255.])
        premodel.input().model().set_layout(ov.Layout("NCHW"))
        premodel.output(0).tensor().set_element_type(ov.Type.f32)
        self.model = premodel.build()

    def infer(self, img):
        detections = []
        img_re, dw, dh = self.resizeimg(img, (640, 640))
        input_tensor = np.expand_dims(img_re, 0)
        self.infer_request.infer({0: input_tensor})
        output = self.infer_request.get_output_tensor(0)
        detections = self.process_output(output.data[0])
        return detections

    def process_output(self, detections):
        boxes = []
        class_ids = []
        confidences = []
        for prediction in detections:
            confidence = prediction[4].item()
            if confidence >= 0.6:
                classes_scores = prediction[5:]
                _, _, _, max_indx = cv2.minMaxLoc(classes_scores)
                class_id = max_indx[1]
                if (classes_scores[class_id] > .25):
                    confidences.append(confidence)
                    class_ids.append(class_id)
                    x, y, w, h = prediction[0].item(), prediction[1].item(), prediction[2].item(), prediction[3].item()
                    xmin = x - (w / 2)
                    ymin = y - (h / 2)
                    box = np.array([xmin, ymin, w, h])
                    boxes.append(box)
        indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.5)
        detections = []
        for i in indexes:
            j = i.item()
            detections.append({"class_index": class_ids[j], "confidence": confidences[j], "box": boxes[j]})
        return detections

    def resizeimg(self, image, new_shape):
        old_size = image.shape[:2]
        ratio = float(new_shape[-1] / max(old_size))
        new_size = tuple([int(x * ratio) for x in old_size])
        image = cv2.resize(image, (new_size[1], new_size[0]))
        delta_w = new_shape[1] - new_size[1]
        delta_h = new_shape[0] - new_size[0]
        color = [100, 100, 100]
        new_im = cv2.copyMakeBorder(image, 0, delta_h, 0, delta_w, cv2.BORDER_CONSTANT, value=color)
        return new_im, delta_w, delta_h


if __name__ == "__main__":
    # Example usage:
    labels = [
        "right",
        "warning",
        "left",
        "people",
        "10",
        "pullover",
        "10off",
        "green",
        "red"
    ]
    detector = ObjectDetector("/home/nuc/MyCar/yolov5-7.0/best.xml", "/home/nuc/MyCar/yolov5-7.0/best.bin", labels, "CPU")
    cap = cv2.VideoCapture(0)
    while True:
        ret, frame = cap.read()
        detections = detector.infer(frame)
        for detection in detections:
            classId = detection["class_index"]
            confidence = detection["confidence"]
            label = labels[classId]
            box = detection["box"]
            area = box[2] * box[3]
            print(f"Detected object: {label}, Confidence: {confidence}, Area: {area}")
    cap.release()

一、需求

Kubernetes 的命名空间主要用于组织和隔离资源,但默认情况下,不同命名空间中的 Pod 之间是可以相互通信的。为了实现更严格的网络隔离,同一套k8s需要根据不同的命名空间进行网络环境隔离,例如开发(dev01)测试(test01)环境。Network Policies 是 Kubernetes 提供的一种机制,用于控制 Pod 间的网络流量。你可以为每个命名空间定义 Network Policies 来限制 Pod 之间的通信。

二、相关解释

  • spec.PodSelector

它是pod选择器,基于标签选择与Network Policy处于同一namespace下的pod,如果pod被选中,则对其应用Network Policy中定义的规则。此为可选字段,当没有此字段时,表示选中所有pod。

  • spec.PolicyTypes

Network Policy定义的规则可以分成两种,一种是入pod的Ingress规则,一种是出pod的Egress规则。本字段可以看作是一个开关,如果其中包含Ingress,则Ingress部分定义的规则生效,如果是Egress则Egress部分定义的规则生效,如果都包含则全部生效。当然此字段也可选,如果没有指定的话,则默认Ingress生效,如果Egress部分有定义的话,Egress才生效。怎么理解这句话,下文会提到,没有明确定义Ingress、Egress部分,它也是一种规则,默认规则而非没有规则。

  • spec.ingress与spec.egress

spec.ingress

spec.egress
字段分别用于定义允许进入 Pod 的流量规则和允许离开 Pod 的流量规则。下面详细解释这两个字段的结构和包含的子项。

spec.ingress
结构

spec:
  ingress:
  - from:
    - podSelector:
        matchLabels: {} # 选择具有特定标签的 Pod
    - namespaceSelector:
        matchLabels: {} # 选择具有特定标签的命名空间
    - ipBlock:
        cidr: 0.0.0.0/0 # CIDR 地址范围
        except:
        - 10.0.0.0/8 # 排除的 CIDR 地址范围
  - ports:
    - protocol: TCP # 协议类型
      port: 80 # 具体端口
      endPort: 8080 # 如果是端口范围,则需要指定结束端口

spec.egress
结构

spec:
  egress:
  - to:
    - podSelector:
        matchLabels: {} # 选择具有特定标签的 Pod
    - namespaceSelector:
        matchLabels: {} # 选择具有特定标签的命名空间
    - ipBlock:
        cidr: 0.0.0.0/0 # CIDR 地址范围
        except:
        - 10.0.0.0/8 # 排除的 CIDR 地址范围
  - ports:
    - protocol: TCP # 协议类型
      port: 80 # 具体端口
      endPort: 8080 # 如果是端口范围,则需要指定结束端口

示例

假设你有一个 NetworkPolicy,它允许 Pod 接收来自 CIDR 地址范围
192.168.1.0/24
的流量,并允许 Pod 发送流量到 CIDR 地址范围
10.0.0.0/8
,同时只允许通过 TCP 协议的端口 80 和 443 进行通信。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-traffic
  namespace: my-namespace
spec:
  podSelector: {}
  ingress:
  - from:
    - ipBlock:
        cidr: 192.168.1.0/24
  - ports:
    - protocol: TCP
      port: 80
    - protocol: TCP
      port: 443
  egress:
  - to:
    - ipBlock:
        cidr: 10.0.0.0/8
  - ports:
    - protocol: TCP
      port: 80
    - protocol: TCP
      port: 443
  policyTypes:
  - Ingress
  - Egress

注意事项

  • 适用性: 该 NetworkPolicy 适用于
    my-namespace
    命名空间中的所有 Pod。
  • 流量控制: 控制了 Ingress 和 Egress 流量。
  • CIDR 地址范围:
    192.168.1.0/24

    10.0.0.0/8
    通常用于私有网络。
  • 端口控制: 只允许通过 TCP 协议的端口 80 和 443 进行通信。

三、测试规划

1、创建命名空间

2、创建pod和nodeport

3、应用策略之前测试

4、创建网络策略1-pod隔离

5、创建网络策略2-命名空间隔离

6、创建网络策略3-业务命名空间隔离

四、具体实施

1、创建命名空间

创建测试使用的3个命名空间sub1,sub2,sub3。

如果后面的网络策略使用到
namespaceSelector,在创建命名空间时需要带label,也可在需要时手动添加label。

kubectl create ns sub1 --labels ns=sub1
kubectl create ns sub2 --labels ns=sub2
kubectl create ns sub3 --labels ns=sub2

如果使用的是私有仓库,注意ns要创建docker-secret。

kubectl create secret docker-registry my-registry-secret --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD -n namespace

2、创建pod和nodeport

2.1、sub1创建sub1-pod1和sub1-pod1-nodeport,sub1-pod2;
2.2、sub2创建sub2-pod1和sub2-pod1-nodeport,sub2-pod2;
2.3、sub3创建sub3-pod1;

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sub1-pod1
  namespace: sub1
spec:
  selector:
    matchLabels:
      app: sub1pod1
  replicas: 1
  template:
    metadata:
      labels:
        app: sub1pod1
    spec:
      containers:
      - name: my-test01-01
        image: swr.cn-east-3.myhuaweicloud.com/k8s-imgs/account-platform-admin:dev01-084e7d9
        imagePullPolicy: IfNotPresent
      imagePullSecrets:
      - name: default-secret
      imagePullSecrets:
        - name: swr-secret

---

apiVersion: v1
kind: Service
metadata:
  name: sub1-pod1-nodeport
  namespace: sub1
spec:
  type: NodePort
  selector:
    app: sub1pod1
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8103
      nodePort: 32700

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sub1-pod2
  namespace: sub1
spec:
  selector:
    matchLabels:
      app: sub1pod2
  replicas: 1
  template:
    metadata:
      labels:
        app: sub1pod2
    spec:
      containers:
      - name: my-test01-02
        image: swr.cn-east-3.myhuaweicloud.com/k8s-imgs/account-platform-admin:dev01-084e7d9
        imagePullPolicy: IfNotPresent
      imagePullSecrets:
      - name: default-secret
      imagePullSecrets:
        - name: swr-secret
	
---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sub2-pod1
  namespace: sub2
spec:
  selector:
    matchLabels:
      app: sub2pod1
  replicas: 1
  template:
    metadata:
      labels:
        app: sub2pod1
    spec:
      containers:
      - name: my-test02-01
        image: swr.cn-east-3.myhuaweicloud.com/k8s-imgs/account-platform-admin:dev01-084e7d9
        imagePullPolicy: IfNotPresent
      imagePullSecrets:
      - name: default-secret
      imagePullSecrets:
        - name: swr-secret
---

apiVersion: v1
kind: Service
metadata:
  name: sub2-pod1-nodeport
  namespace: sub2
spec:
  type: NodePort
  selector:
    app: sub2pod1
  ports:
    - protocol: TCP
      port: 80
      targetPort: 8103
      nodePort: 32701

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sub2-pod2
  namespace: sub2
spec:
  selector:
    matchLabels:
      app: sub2pod2
  replicas: 1
  template:
    metadata:
      labels:
        app: sub2pod2
    spec:
      containers:
      - name: my-test02-02
        image: swr.cn-east-3.myhuaweicloud.com/k8s-imgs/account-platform-admin:dev01-084e7d9
        imagePullPolicy: IfNotPresent
      imagePullSecrets:
      - name: default-secret
      imagePullSecrets:
        - name: swr-secret

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sub3-pod1
  namespace: sub3
spec:
  selector:
    matchLabels:
      app: sub3pod1
  replicas: 1
  template:
    metadata:
      labels:
        app: sub3pod1
    spec:
      containers:
      - name: my-test03-01
        image: swr.cn-east-3.myhuaweicloud.com/k8s-imgs/account-platform-admin:dev01-084e7d9
        imagePullPolicy: IfNotPresent
      imagePullSecrets:
      - name: default-secret
      imagePullSecrets:
        - name: swr-secret  

查看创建的资源

3、
应用策略之前测试

在应用网络策略之前测试各pod之间、pod和外网之间是否可以通信。

sub1-pod1 ping sub1-pod2、sub2-pod1、sub3-pod1、qq.com

外部节点ping sub1-pod1-nodeport、sub2-pod1-nodeport

取k8s集群任一节点ip  10.34.106.14

总结:通过以上测试证明未加网络策略之前pod之间,pod外网之间都是互通的。

4、创建网络策略1-pod隔离

策略描述:在sub1中创建策略,使sub1中pod之间无法通信且和其他命名空间pod也无法通信,只能进出外网。
测试流程:
4.1、sub1-pod1、sub1-pod2、sub2-pod1三者之间互ping不通;
4.2、sub1-pod1、sub1-pod2可以ping通外网(包含域名),外网也能和sub1-pod1-nodeport通信;

本k8s集群pod cidr为:10.243.0.0/16

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: pod-policy
  namespace: sub1
spec:
  podSelector: {}
  ingress:
    - from:
      - ipBlock:
          cidr: 0.0.0.0/0
          except:
          - 10.243.0.0/16
  egress:
    - to:
      - ipBlock:
          cidr: 0.0.0.0/0
          except:
          - 10.243.0.0/16
  policyTypes:
  - Egress
  - Ingress

解释:

  1. podSelector
    :
    {}
    表示选择命名空间中的所有 Pod。
  2. ingress
    : 定义了进入命名空间的流量控制规则。
    • from
      : 表示允许来自哪些来源的流量。
    • ipBlock
      : 表示允许来自除了
      10.243.0.0/16
      CIDR 地址范围之外的所有 IP 地址的流量。
  3. egress
    : 定义了离开命名空间的流量控制规则。
    • to
      : 表示允许流向哪些目的地的流量。
    • ipBlock
      : 表示允许流向除了
      10.243.0.0/16
      CIDR 地址范围之外的所有 IP 地址的流量。
  4. policyTypes
    : 指定 NetworkPolicy 控制的流量类型,这里包括
    Ingress

    Egress

效果:

  • Ingress: 允许所有来源的流量,除了 CIDR 地址范围
    10.243.0.0/16
  • Egress: 允许流向所有目的地的流量,除了 CIDR 地址范围
    10.243.0.0/16

测试:

创建策略

sub1-pod1  ping  sub1-pod2和sub2-pod1 (不通)

sub2-pod1  ping  sub1-pod1和sub1-pod2(不通)

sub1-pod1  ping  外网ip(通)

sub1-pod1  ping  域名(不通)-- 因为域名解析服务dns在kube-system空间,而sub1屏蔽了所有空间,下面示例可解决。

外部访问sub1-pod1-nodeport(通)

取k8s集群任一节点ip  10.34.106.14

5、创建网络策略2-命名空间隔离

策略描述:在sub2中创建策略,使sub2中pod之间可以通信但和其他命名空间pod无法通信,也能进出外网。

测试流程:
5.1、sub2-pod1和sub2-pod2之间互ping可通;
5.2、sub2-pod1、sub2-pod2和sub3-pod1之间互ping不通;
5.3、sub2-pod1、sub2-pod2可以ping通外网(包含域名),外网也能和sub2-pod1-nodeport通信;

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: sub2
  namespace: sub2
spec:
  podSelector: {}
  egress:
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0
        except:
        - 10.243.0.0/16
    - namespaceSelector:    # 如果不想给命名空间加label,可以换成   - podSelector: {} # 允许来自同一命名空间中的所有 Pod 的流量
        matchLabels:
          ns: sub2
  ingress:
  - from:
    - ipBlock:
        cidr: 0.0.0.0/0
        except:
        - 10.243.0.0/16
    - namespaceSelector:    # 如果不想给命名空间加label,可以换成   - podSelector: {} # 允许来自同一命名空间中的所有 Pod 的流量
        matchLabels:
          ns: sub2
  policyTypes:
  - Egress
  - Ingress

解释

  1. podSelector
    :
    {}
    表示选择命名空间中的所有 Pod。
  2. ingress
    : 定义了进入命名空间的流量控制规则。
    • from
      : 表示允许来自哪些来源的流量。
    • ipBlock
      : 表示允许来自除了
      10.243.0.0/16
      CIDR 地址范围之外的所有 IP 地址的流量。
    • namespaceSelector
      : 表示允许来自具有标签
      ns=sub2
      的命名空间中的 Pod 的流量。
    • from
      字段中的
      podSelector: {}
      表示允许来自同一命名空间中的所有 Pod 的流量。
  3. egress
    : 定义了离开命名空间的流量控制规则。
    • to
      : 表示允许流向哪些目的地的流量。
    • ipBlock
      : 表示允许流向除了
      10.243.0.0/16
      CIDR 地址范围之外的所有 IP 地址的流量。
    • namespaceSelector
      : 表示允许流向具有标签
      ns=sub2
      的命名空间中的 Pod 的流量。
    • to
      字段中的
      podSelector: {}
      表示允许流向同一命名空间中的所有 Pod 的流量。
  4. policyTypes
    : 指定 NetworkPolicy 控制的流量类型,这里包括
    Ingress

    Egress

效果

  • Ingress: 允许所有来源的流量,除了 CIDR 地址范围
    10.243.0.0/16
    和来自其他命名空间的流量,如果使用namespaceSelector则允许进入命名空间具有标签
    ns=sub2中的pod,如果使用 podSelector: {} 表示允许来自同一命名空间中的所有 Pod 的流量
  • Egress: 允许流向所有目的地的流量,除了 CIDR 地址范围
    10.243.0.0/16
    和流向其他命名空间的流量,如果使用namespaceSelector则允许流向命名空间具有标签
    ns=sub2中的pod, 如果使用 podSelector: {} 表示允许流向同一命名空间中的所有 Pod 的流量

测试

创建策略

sub2-pod1  ping  sub2-pod2(通)

sub2-pod1  ping  sub3-pod1(不通)

sub3-pod1  ping  sub2-pod1、sub2-pod2(不通)

sub2-pod1  ping  外网ip(通)

sub2-pod1  ping  域名(不通)-- 因为域名解析服务dns在kube-system空间,而sub1屏蔽了所有空间,下面示例可解决。

外部 访问sub2-pod1-nodeport(通)

取k8s集群任一节点ip  10.34.106.14

6、创建网络策略3-业务命名空间隔离

实际应用中某些命名空间中的pod可能和其他命名空间中的pod有调用关系,还可能用到一些系统命名空间中的服务(如DNS服务),所以不能将某个命名空间完全和其他所有命名空间隔离,只需要将确定没有业务调用的命名空间隔离。

策略描述:更新第5步sub2中创建的策略,使sub2中的pod除了不能和sub3中的pod通信外,和其他所有地址都可通信。

测试流程:

6.1、sub2-pod1和sub2-pod2互ping可通;

6.2、sub2-pod1和kube-system命名空间中的coredns-pod之间互ping可通;

6.3、sub2-pod1和sub3-pod1互ping不通;

6.4、sub2-pod1可以ping通外网(域名),外网也能和sub2-pod1-nodeport通信;

由于没有直接阻止具有某个label的命名空间配置,所有只能间接通过放行具有某些labels的命名空间以达到阻止的目的。

label设置比较灵活,可根据实际情况配置,有一下几种方式可供参考:

1、可以将放行的所有命名空间配置一个统一的label,这样只需要在配置中放行具有这个label的命名空间即可;

2、也可给每个命名空间配置一个label,这样需要在配置中放行各个不同label的命名空间;

3、可以将通用的系统命名空间设置一个统一的label,给业务命名空间配置不同的label;

本例采用第2种,给所有需要放行的命名空间配置不同的label;

命名空间加label

kubectl label ns kube-system ns=kube-system
kubectl label ns kuboard ns=kuboard
kubectl label ns kube-public ns=kube-public

网络隔离配置

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: ns-policy-2
  namespace: sub2
spec:
  podSelector: {}
  ingress:
  - from:
    - podSelector: {}
    - ipBlock:
        cidr: 0.0.0.0/0
        except:
        - 10.243.0.0/16
  - from:
    - namespaceSelector:
        matchLabels:
          ns: kube-system
  - from:
    - namespaceSelector:
        matchLabels:
          ns: kuboard
  - from:
    - namespaceSelector:
        matchLabels:
          ns: kube-public
  egress:
  - to:
    - podSelector: {}
    - ipBlock:
        cidr: 0.0.0.0/0
        except:
        - 10.243.0.0/16
  - to:
    - namespaceSelector:
        matchLabels:
          ns: kube-system
  - to:
    - namespaceSelector:
        matchLabels:
          ns: kuboard
  - to:
    - namespaceSelector:
        matchLabels:
          ns: kube-public
  policyTypes:
  - Egress
  - Ingress

解释

  • podSelector
    :
    {}
    表示选择命名空间中的所有 Pod。
  • policyTypes
    :
    Ingress

    Egress
    表示同时控制进入和离开命名空间的流量。
  • ingress
    :
    from
    字段中的
    podSelector: {}
    表示允许来自同一命名空间中的所有 Pod 的流量;
    ipBlock
    表示允许来自除了 Kubernetes 内部网络 CIDR 地址范围10.243.0.0/16
    之外的所有 IP 地址的流量。
  • egress
    :
    to
    字段中的
    podSelector: {}
    表示允许流向同一命名空间中的所有 Pod 的流量;
    ipBlock
    表示允许流向除了 Kubernetes 内部网络 CIDR 地址范围10.243.0.0/16
    之外的所有 IP 地址的流量。
  • from

    to
    : 使用
    namespaceSelector
    允许放行具有标签
    ns=kube-system、ns=kuboardns、ns=kube-public
    的命名空间中的 Pod 的流量,间接阻止了其他命名空间的流量。

测试

创建策略

sub2-pod1  ping  sub2-pod2(通)

sub2-pod1  ping  kube-system命名空间中的coredns-pod(通)

sub2-pod1  ping  sub3-pod1(不通)

sub2-pod1可以ping通外网、域名(通)

外部 访问sub2-pod1-nodeport(通)

取k8s集群任一节点ip  10.34.106.14

树莓派镜像替换内核

1. 为什么要替换内核

  • 树莓派官方提供的镜像中,自带的内核版本为
    6.6.31

  • 然而
    github
    上提供的内核源码为
    6.6.40
    ,有些微差别

  • 此外,后续很有可能进行内核裁剪定制,替换内核是一个无法绕开的工作

2. 获取内核源码

  • github
    地址:


    https://github.com/raspberrypi/linux

  • 选择使用
    6.6.y
    版本的内核

  • 将其拷贝到
    ubuntu

  • 解压

    $ unzip linux-rpi-6.6.y.zip
    
  • 进入内核源码目录

    $ cd linux-rpi-6.6.y
    

3. 获取交叉编译工具链

4. 内核编译

  • 修改顶层
    Makefile
    ,指定目标平台
    ARCH
    和交叉编译工具链
    CROSS_COMPILE

  • 树莓派CM4使用的时博通BCM2711的方案,因此使用
    bcm2711_defconfig
    生成默认配置文件

    linux-rpi-6.6.y$ make bcm2711_defconfig
    
  • menuconfig
    配置

    linux-rpi-6.6.y$ make menuconfig
    
  • 在这里我需要将
    LAN78XX
    驱动直接编译进内核,不作为外部模块

  • 开始编译,内核镜像/驱动模块/设备树都需要编译,使用
    -j$(nproc)
    全核编译,加快编译速度

    linux-rpi-6.6.y$ make Image modules dtbs -j$(nproc)
    

5. 挂载树莓派镜像到Ubuntu

  • 解压,得到
    *.img
    的镜像文件

  • 挂载镜像到Ubuntu

    # .img镜像
    jun@ubuntu:$ ls
    2024-07-04-raspios-bookworm-arm64-lite.img
    
    # 查看第一个未使用的回环设备
    jun@ubuntu:$ losetup -f
    /dev/loop0
    
    # 将.img镜像关联到回环设备
    jun@ubuntu:$ sudo losetup /dev/loop0 2024-07-04-raspios-bookworm-arm64-lite.img 
    [sudo] password for jun:
    
    # 查看分区,检测到两个区,其中较小的是系统分区,较大的是根文件系统
    jun@ubuntu:$ sudo kpartx -av /dev/loop0
    add map loop0p1 (253:0): 0 1048576 linear 7:0 8192
    add map loop0p2 (253:1): 0 4481024 linear 7:0 1056768
    
    # 创建系统分区挂载目录
    jun@ubuntu:$ mkdir boot
    
    # 创建根文件系统挂载目录
    jun@ubuntu:$ mkdir rootfs
    
    # 挂载系统分区
    jun@ubuntu:$ sudo mount /dev/mapper/loop0p1 ./boot/
    
    # 挂载根文件系统
    jun@ubuntu:$ sudo mount /dev/mapper/loop0p2 ./rootfs/
    
    # 查看系统分区
    jun@ubuntu:$ ls ./boot/
    bcm2710-rpi-2-b.dtb       bcm2710-rpi-zero-2-w.dtb  bcm2711-rpi-cm4s.dtb       bootcode.bin  fixup4db.dat  fixup_x.dat      kernel8.img       start4.elf    start_x.elf
    bcm2710-rpi-3-b.dtb       bcm2711-rpi-400.dtb       bcm2712d0-rpi-5-b.dtb      cmdline.txt   fixup4x.dat   initramfs_2712   LICENCE.broadcom  start4x.elf
    bcm2710-rpi-3-b-plus.dtb  bcm2711-rpi-4-b.dtb       bcm2712-rpi-5-b.dtb        config.txt    fixup_cd.dat  initramfs8       overlays          start_cd.elf
    bcm2710-rpi-cm3.dtb       bcm2711-rpi-cm4.dtb       bcm2712-rpi-cm5-cm4io.dtb  fixup4cd.dat  fixup.dat     issue.txt        start4cd.elf      start_db.elf
    bcm2710-rpi-zero-2.dtb    bcm2711-rpi-cm4-io.dtb    bcm2712-rpi-cm5-cm5io.dtb  fixup4.dat    fixup_db.dat  kernel_2712.img  start4db.elf      start.elf
    
    # 查看根文件系统
    jun@ubuntu:$ ls ./rootfs/
    bin  boot  dev  etc  home  lib  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
    

6. 安装新内核

  • 进入内核源码目录

  • 安装内核模块到根文件系统

    linux-rpi-6.6.y$ sudo env PATH=$PATH make INSTALL_MOD_PATH=../rootfs modules_install
    

  • 安装头文件到根文件系统的
    usr
    目录

    linux-rpi-6.6.y$ sudo make headers_install INSTALL_HDR_PATH=../rootfs/usr/
    
  • 安装
    Image
    到挂载的
    boot
    分区

    linux-rpi-6.6.y$ sudo cp arch/arm64/boot/Image ../boot/kernel8.img
    
  • 安装设备树文件

    linux-rpi-6.6.y$ sudo cp arch/arm64/boot/dts/broadcom/*.dtb ../boot/
    linux-rpi-6.6.y$ sudo cp arch/arm64/boot/dts/overlays/*.dtb* ../boot/overlays/
    
  • 替换
    version.h

    linux-rpi-6.6.y$ sudo cp include/generated/uapi/linux/version.h ../rootfs/usr/include/linux/version.h
    

7. 解决内核模块无法加载的问题

  • 进入
    rootfs
    根文件系统中的驱动存放目录

    linux-rpi-6.6.y$ cd ./rootfs/lib/modules/6.6.40-v8/
    
  • 查找.ko文件,发现没有任何内核模块

    6.6.40-v8$ find . -name *ko
    
  • 检查发现,该目录下有很多
    .ko.xz
    文件,原因是
    make module_install
    命令执行时,自动将.ko文件进行了压缩,但是这样会导致内核启动时无法加载模块,需要将压缩的.ko文件进行解压

  • 编写脚本
    modules_install.sh
    用来在系统第一次开机时生成
    modules.dep
    文件,将该脚本存放在
    rootfs/lib/modules/6.6.40-v8/
    目录下,脚本内容如下

    #!/bin/bash
    
    # modules path
    MODULES_PATH=/lib/modules/$(uname -r)
    
    # 解压*.ko.xz文件
    module_decompress()
    {
            MODULES_XZ_FILES=`find ${MODULES_PATH} -name *ko.xz`
    
            for MODULE in ${MODULES_XZ_FILES}
            do
                    set -x
                    xz -dk ${MODULE}
                    set +x
            done
            
            return 0
    }
    
    RET=$(cat ${MODULES_PATH}/modules.dep)
    if [ -z "${RET}" ]; then
            
        # modules decompress
        module_decompress
        
        # generate modules.dep
        depmod
            
        # make sure this script executed only once
        SCRIPT_NAME=$(basename $0)
        sed -i "/$SCRIPT_NAME/d" /etc/rc.local
    		
        # reboot
        reboot
    else
            echo "modules already installed!"
    fi
    
    exit 0
    
  • 给脚本以可执行权限

    6.6.40-v8$ sudo chmod 777 modules_install.sh
    
  • 在根文件系统下的
    etc/rc.local
    中添加如下内容,使
    module_install.sh
    脚本开机自启动

    # modules_install
    /bin/bash /lib/modules/$(uname -r)/modules_install.sh &
    

8. 选择内核

  • 修改
    boot
    分区下的
    config.txt
    ,选择使用新的内核
    $ sudo vi ./boot/config.txt
    

  • config.txt
    末尾添加如下内容
    kernel=kernel8.img
    

9. 取消镜像挂载

  • 取消
    5. 挂载树莓派镜像到Ubuntu
    的镜像挂载
    jun@ubuntu:$ ls
    2024-07-04-raspios-bookworm-arm64-lite.img  boot  rootfs
    jun@ubuntu:$ sudo umount ./boot 
    jun@ubuntu:$ sudo umount ./rootfs
    jun@ubuntu:$ sudo losetup -d /dev/loop0
    

10. 镜像烧录

11. 查看内核是否替换成功

  • 开机,首次开机时系统会自动重启两次,属正常现象
  • 进入系统后查看内核版本,内核版本
    6.6.40
    ,替换成功
  # uname -a
  Linux IG-210 6.6.40-v8 #2 SMP PREEMPT Tue Aug 27 14:04:24 CST 2024 aarch64 GNU/Linux

背景

公司政策满3年可以换新电脑,前段时间申请了下,到手后发现是Win11系统,配置翻倍,欣然接受,把一些常用的软件都安装上,但是,用了一段时间后,发现右键刷新要点击2次,开始菜单找东西也完全靠搜索,任务栏不可定义了,和以前常用的右下角日历小工具不兼容,如果要和这些用惯好多年的操作say goodbye,是一个难受的事情,于是想办法折腾一下,把这些恢复“原Win10”的效果。

本文解决的痛点

1、开始菜单变化,找内容靠搜索

2、任务栏小工具无法展示

3、任务栏不可设置位置(顶部、底部、左侧、右侧)

4、鼠标右键菜单没有刷新

用到的工具

StartAllBack 3.6.14

下载地址:
https://www.startallback.com/

ps:这里特意说明一下这个版本号很重要,测试了几个版本,发现这个版本对任务栏小工具的支持最好

设置步骤和效果

1、启用经典开始菜单

效果图

2、启用增强型经典任务栏

效果图

3、资源窗口

效果图

总结

StartAllBack 是一款可以让开始菜单/任务栏/右键菜单重回经典模式的工具,如果有一天你也受不了Win11的新模式,可以用这个软件,重回“经典”也不失为一个不错的选择。

通讯对象

PDO

我的观点:一个 CANopen 设备可以拥有最多 512 个 RPDO 和 512 个 TPDO,总共最多 1024 个 PDO。(得到GPT4o的肯定)

CiA协议栈观点:一个只有一个逻辑设备的 CANopen 设备最多有 512 个 PDO。

PDO的两种用法:

  • TPDO:生产者PDO
  • RPDO:消费者PDO
特点

小而快

传输模式
  • 同步传输:通过(SYNC对象)来获取同步信号

    ​ 同步TPDO的传输倍率:n表示经过n个同步信号后发送下一个消息。(为0时,收到触发信号后的第一个同步信号后进行发送)

  • 事件驱动传输

触发模式

消息的触发模式

触发方式 解释 应用场景
事件驱动 特定的事件进行驱动 超限报警
定时器驱动 固定的时间间隔进行发送 周期性更新数据
远程请求 其它节点通过发送 RTR 帧来请求该设备发送相应的 PDO 允许节点灵活的请求数据
同步触发 由一定数量的Sync和内部事件触发 协调多个设备的同步操作
PDO读写
  • 写协议(推模式):生产者向0 ~ n个消费者写PDO数据(参考spi写数据)
  • 读协议(拉模式):消费者发送RTR请求向生产者请求数据(参考spi读数据)

PDO写

参数 请求/执行 响应/确认
PDO编号 强制
数据 强制

image

PDO读

参数 请求/执行 响应/确认
PDO编号 强制
数据 强制

image

对象描述
  • 通讯参数:定义了PDO的基本特性,如COB-ID、传输类型、禁止时间和事件定时器等,它决定了PDO是以何种方式发送和接收数据。


    • COB-ID:通过COB-ID可以唯一识别一个PDO消息


      • TPDO和RPDO的COB-ID是不同的
      • COB-ID的最高位设置为1时,表示该PDO被禁用
    • 传输类型(Transmission Type):PDO消息的传输方式


      • 同步传输
      • 异步传输

image

  • 抑制时间(Inhibit Time):两个PDO之间的最小时间间隔(以ms为单位)

  • 事件计时器(Event Timer):定了事件触发的周期时间,PDO在到期时发送(表示没有事件发生)

  • 同步计数器(Sync Counter):在同步传输类型下使用,规定了在接收多少次同步信号后发送

  • 映射参数:定义了哪些对象字典条目(变量)被包含在 PDO 消息中,以及这些变量在消息中的位置。


    • 映射条目数:定义了一个PDO在对象字典中的条目数量,每个条目代表一个数据段
    • 映射对象:每个映射对象由32位组成
      • 索引(Index,16位):对象字典中的索引
      • 子索引(Sub-Index, 8位):对象字典项的子索引
      • 位长度(Bit Length,8位):表示映射对象的数据位数(常为字节的整数倍)
PDO 传输实例

点我查看

Multiplex PDO(不懂且没看完,因资料少)

有两种MPDO的使用方法。第一是
目的地址模式(DAM)PDO
,第二是
源地址模式(SAM)PDO

支持接收MPDO的ANope的设备为
MPDO消费者
,支持发送MPDO的C为
MPDO生产者

发送MPDO由事件驱动,不支持定时、远程请求和同步触发模式

寻址模式
  • 目的地址模式(DAM):一个DAM-MPDO可以被所有该MPDO的消费者同时接收(
    类似于广播
    ),且无 应答。如果对象不存在,则生成EMCY帧。
  • 源地址模式(SAM):
MPDO读写

MPDO写

参数 请求/执行 响应/确认
PDO编号 强制
地址类型 强制
Node-ID 强制
Multiplexer 强制
数据 强制

image

SDO

特点

大而慢

传输模式
传输模式 概述 应答模式
快速传输 因数据小于等于4个字节,一帧足够发送完,无需分段 一帧一答
段传输 把数据分割成若干个段,每一段通过单独的帧发送 每帧每答
块传输 把多个段组合成一个子块,每一子块发送完才接受应答信号,所有子块组成块 每子块每答

快速传输模式:
只需要一次数据的发送和应答就可以完成数据传输,效率高,但是传输的数据量少。

段传输:
数据被分成多个段(segment)来传输,每个段最多可以包含7个字节的数据。这种模式的实现较为简单,适合中等长度的数据传输。

块传输:
相比段传输模式,块传输模式能够处理更大的数据量并且提高了传输效率。数据被分为多个块(block),每个块可以包含多个段。块传输还具有校验机制,能够确保数据的完整性和可靠性。

协议详解见
Or CiA301中文手册43页

SYNC

  • 同步生产者定期广播
    同步对象
    (SYNC)。
  • SYNC提供基本的网络同步机制,遵循生产消费模型,该服务无应答。
  • 传输1个字节的计数器(可选)

TIME

  • TIME生产者定期广播
    时间戳对象
    (TIME)。
  • TIME提供了简单的网络时钟。CANopen设备通过时间戳对象来校准本地时间。
  • 传输6个字节的时间戳

EMCY

  • 当设备内部出现致命错误将触发应急(EMCY)报文
  • 由应用设备以最高优先级发送到其他设备。任何具备紧急事件监控与处理能力的从站会接收并处理紧急报文。