1. 程式人生 > >解析 | openshift原始碼簡析之pod網路配置(下)

解析 | openshift原始碼簡析之pod網路配置(下)

【編者按】openshift底層是通過kubelet來管理pod,kubelet通過CNI外掛來配置pod網路.openshift node節點在啟動的時會在一個goroutine中啟動kubelet, 由kubelet來負責pod的管理工作。

本文主要從原始碼的角度入手,簡單分析在openshift環境下kubelet是如何通過呼叫openshift sdn外掛來配置pod網路。

上一節分析了openshift-sdn外掛是如何配置Pod網路的,本節分析openshift-sdn外掛獲取Pod IP時cniServer的處理流程。

CNIServer流程

在上面的分析中我們知道,openshift-sdn外掛是通過方法doCNIServerAdd向cniserver來請求IP的,那cniserver是如何處理請求的呢?我們先來看cniServer的邏輯。
cniServer的定義位於openshit程式碼庫的pkg/network/node/cniserver/cniserver.go檔案,定義如下:

1type CNIServer struct {
2    http.Server
3    requestFunc cniRequestFunc
4    rundir      string
5    config      *Config
6}

它包括了一個http server,以及一個處理請求的handler cniRequestFunc, 還有一些配置相關的欄位。
cniSever的構造器方法位於pkg/network/node/cniserver/cniserver.go#L120, 內容如下:

 1// Create and return a new CNIServer object which will listen on a socket in the given path
 2func NewCNIServer(rundir string, config *Config) *CNIServer {
 3    router := mux.NewRouter()
 4
 5    s := &CNIServer{
 6        Server: http.Server{
 7            Handler: router,
 8        },
 9        rundir: rundir,
10        config: config,
11    }
12    router.NotFoundHandler = http.HandlerFunc(http.NotFound)
13    router.HandleFunc("/", s.handleCNIRequest).Methods("POST")
14    return s
15}

從上面第13行的程式碼可以看出,該server只處理一條POST方法的路由,處理請求的handler是handleCNIRequest這個方法,該方法的定義位於                        pkg/network/node/cniserver/cniserver.go#L277,內容如下:

 1// Dispatch a pod request to the request handler and return the result to the
 2// CNI server client
 3func (s *CNIServer) handleCNIRequest(w http.ResponseWriter, r *http.Request) {
 4    req, err := cniRequestToPodRequest(r)
 5    if err != nil {
 6        http.Error(w, fmt.Sprintf("%v", err), http.StatusBadRequest)
 7        return
 8    }
 9
10    glog.V(5).Infof("Waiting for %s result for pod %s/%s", req.Command, req.PodNamespace, req.PodName)
11    result, err := s.requestFunc(req)
12    if err != nil {
13        http.Error(w, fmt.Sprintf("%v", err), http.StatusBadRequest)
14    } else {
15        // Empty response JSON means success with no body
16        w.Header().Set("Content-Type", "application/json")
17        if _, err := w.Write(result); err != nil {
18            glog.Warningf("Error writing %s HTTP response: %v", req.Command, err)
19        }
20    }
21}

從第11行可以看出,該方法又是呼叫requestFunc這個方法來處理請求,請求結束後通過w.Write或者是http.Error返回呼叫者的response。requestFunc是在cniserver的Start的方法中傳入的,傳入的實際上是podManager的handleCNIRequest方法,該方法位於檔案pkg/network/node/pod.go#L25,內容如下:

1// Enqueue incoming pod requests from the CNI server, wait on the result,
2// and return that result to the CNI client
3func (m *podManager) handleCNIRequest(request *cniserver.PodRequest) ([]byte, error) {
4    glog.V(5).Infof("Dispatching pod network request %v", request)
5    m.addRequest(request)
6    result := m.waitRequest(request)
7    glog.V(5).Infof("Returning pod network request %v, result %s err %v", request, string(result.Response), result.Err)
8    return result.Response, result.Err
9}

在第5行該方法先通過addRequest方法把請求放到一個佇列裡面,然後呼叫第6行的waitRequest等待請求執行完成。
addRequest定義位於pkg/network/node/pod.go#L240, 內容如下:

1// Add a request to the podManager CNI request queue
2func (m *podManager) addRequest(request *cniserver.PodRequest) {
3    m.requests <- request
4}

可以看出請求被放到了m.requests這個channel裡面,也就是在這裡用channel做的佇列。
waitRequest是從一個channel裡取出結果,定義位於pkg/network/node/pod.go#L245,內容如下:

1// Wait for and return the result of a pod request
2func (m *podManager) waitRequest(request *cniserver.PodRequest) *cniserver.PodResult {
3    return <-request.Result
4}

剛才說了addRequest會把請求放到m.requests這個佇列裡面,那佇列裡的請求是如何被執行的呢?答案就是podManager在啟動時會在一個gorotine裡呼叫processCNIRequests這個方法,該方法會迴圈的從m.requests這個channel裡面取出請求執行。processCNIRequests定義位於pkg/network/node/pod.go#L286,內容如下:

 1// Process all CNI requests from the request queue serially.  Our OVS interaction
 2// and scripts currently cannot run in parallel, and doing so greatly complicates
 3// setup/teardown logic
 4func (m *podManager) processCNIRequests() {
 5    for request := range m.requests {
 6        glog.V(5).Infof("Processing pod network request %v", request)
 7        result := m.processRequest(request)
 8        glog.V(5).Infof("Processed pod network request %v, result %s err %v", request, string(result.Response), result.Err)
 9        request.Result <- result
10    }
11    panic("stopped processing CNI pod requests!")
12}

可以看出該方法通過一個for迴圈不斷的從m.requests裡面取出請求,然後呼叫processRequest方法來處理請求,最後把處理的結果在放到request.Result裡面由上面的waitRequest來獲取。
我們來分析processRequest方法的執行邏輯,該方法定義位於pkg/network/node/pod.go#L296,內容如下:

 1func (m *podManager) processRequest(request *cniserver.PodRequest) *cniserver.PodResult {
 2    m.runningPodsLock.Lock()
 3    defer m.runningPodsLock.Unlock()
 4
 5    pk := getPodKey(request)
 6    result := &cniserver.PodResult{}
 7    switch request.Command {
 8    case cniserver.CNI_ADD:
 9        ipamResult, runningPod, err := m.podHandler.setup(request)
10        if ipamResult != nil {
11            result.Response, err = json.Marshal(ipamResult)
12            if err == nil {
13                m.runningPods[pk] = runningPod
14                if m.ovs != nil {
15                    m.updateLocalMulticastRulesWithLock(runningPod.vnid)
16                }
17            }
18        }
19        if err != nil {
20            PodOperationsErrors.WithLabelValues(PodOperationSetup).Inc()
21            result.Err = err
22        }
23    case cniserver.CNI_UPDATE:
24        vnid, err := m.podHandler.update(request)
25        if err == nil {
26            if runningPod, exists := m.runningPods[pk]; exists {
27                runningPod.vnid = vnid
28            }
29        }
30        result.Err = err
31    case cniserver.CNI_DEL:
32        if runningPod, exists := m.runningPods[pk]; exists {
33            delete(m.runningPods, pk)
34            if m.ovs != nil {
35                m.updateLocalMulticastRulesWithLock(runningPod.vnid)
36            }
37        }
38        result.Err = m.podHandler.teardown(request)
39        if result.Err != nil {
40            PodOperationsErrors.WithLabelValues(PodOperationTeardown).Inc()
41        }
42    default:
43        result.Err = fmt.Errorf("unhandled CNI request %v", request.Command)
44    }
45    return result
46}

可以看出該方法針對request.Command的三種不同取值有三部分邏輯來分別處理,我們重點分析Command等於cniserver.CNI_ADD時的邏輯,也就是前面呼叫openshift-sdn時傳遞ADD引數的處理邏輯。在Command等於cniserver.CNI_ADD部分的程式碼主要是呼叫第9行的podHandler的setup方法,該方法的定義位於pkg/network/node/pod.go#L497,內容如下:

 1// Set up all networking (host/container veth, OVS flows, IPAM, loopback, etc)
 2func (m *podManager) setup(req *cniserver.PodRequest) (cnitypes.Result, *runningPod, error) {
 3    defer PodOperationsLatency.WithLabelValues(PodOperationSetup).Observe(sinceInMicroseconds(time.Now()))
 4
 5    pod, err := m.kClient.Core().Pods(req.PodNamespace).Get(req.PodName, metav1.GetOptions{})
 6    if err != nil {
 7        return nil, nil, err
 8    }
 9
10    ipamResult, podIP, err := m.ipamAdd(req.Netns, req.SandboxID)
11    if err != nil {
12        return nil, nil, fmt.Errorf("failed to run IPAM for %v: %v", req.SandboxID, err)
13    }
14
15    // Release any IPAM allocations and hostports if the setup failed
16    var success bool
17    defer func() {
18        if !success {
19            m.ipamDel(req.SandboxID)
20            if mappings := m.shouldSyncHostports(nil); mappings != nil {
21                if err := m.hostportSyncer.SyncHostports(Tun0, mappings); err != nil {
22                    glog.Warningf("failed syncing hostports: %v", err)
23                }
24            }
25        }
26    }()
27
28    // Open any hostports the pod wants
29    var v1Pod v1.Pod
30    if err := kapiv1.Convert_core_Pod_To_v1_Pod(pod, &v1Pod, nil); err != nil {
31        return nil, nil, err
32    }
33    podPortMapping := kubehostport.ConstructPodPortMapping(&v1Pod, podIP)
34    if mappings := m.shouldSyncHostports(podPortMapping); mappings != nil {
35        if err := m.hostportSyncer.OpenPodHostportsAndSync(podPortMapping, Tun0, mappings); err != nil {
36            return nil, nil, err
37        }
38    }
39
40    vnid, err := m.policy.GetVNID(req.PodNamespace)
41    if err != nil {
42        return nil, nil, err
43    }
44
45    if err := maybeAddMacvlan(pod, req.Netns); err != nil {
46        return nil, nil, err
47    }
48
49    ofport, err := m.ovs.SetUpPod(req.SandboxID, req.HostVeth, podIP, vnid)
50    if err != nil {
51        return nil, nil, err
52    }
53    if err := setupPodBandwidth(m.ovs, pod, req.HostVeth, req.SandboxID); err != nil {
54        return nil, nil, err
55    }
56
57    m.policy.EnsureVNIDRules(vnid)
58    success = true
59    return ipamResult, &runningPod{podPortMapping: podPortMapping, vnid: vnid, ofport: ofport}, nil
60}

該方法的主要邏輯有兩個,一是第10行呼叫m.ipamAdd獲取IP,這裡涉及到IPAM,後面單獨分析;另一個是第49行呼叫ovs.SetUpPod設定OVS規則,後面也會單獨分析。

至此,openshfit-sdn請求IP時cniServer的處理流程分析結束,下節我們分析cniServer如何呼叫IPAM外掛來管理IP。

上面分析了openshfit-sdn請求IP時cniServer的處理流程,這一節我們分析cniServer呼叫IPAM外掛來管理IP的邏輯。

IPAM

cniServer是呼叫IPAM外掛host-local來做IP管理的,該外掛位於/opt/cni/bin目錄,是一個預編譯的二進位制可執行程式。本節將從IP的分配和釋放兩方面來分析cniServer跟host-local的互動流程。

IP分配

前面章節說了cniServer是呼叫了podManager的ipamAdd方法來獲取IP的,那它又是如何同host-local外掛互動的呢,我們來展開分析。
ipamAdd方法的定義位於pkg/network/node/pod.go#L422, 內容如下:

 1// Run CNI IPAM allocation for the container and return the allocated IP address
 2func (m *podManager) ipamAdd(netnsPath string, id string) (*cni020.Result, net.IP, error) {
 3    if netnsPath == "" {
 4        return nil, nil, fmt.Errorf("netns required for CNI_ADD")
 5    }
 6
 7    args := createIPAMArgs(netnsPath, m.cniBinPath, cniserver.CNI_ADD, id)
 8    r, err := invoke.ExecPluginWithResult(m.cniBinPath+"/host-local", m.ipamConfig, args)
 9    if err != nil {
10        return nil, nil, fmt.Errorf("failed to run CNI IPAM ADD: %v", err)
11    }
12
13    // We gave the IPAM plugin 0.2.0 config, so the plugin must return a 0.2.0 result
14    result, err := cni020.GetResult(r)
15    if err != nil {
16        return nil, nil, fmt.Errorf("failed to parse CNI IPAM ADD result: %v", err)
17    }
18    if result.IP4 == nil {
19        return nil, nil, fmt.Errorf("failed to obtain IP address from CNI IPAM")
20    }
21
22    return result, result.IP4.IP.IP, nil
23}

上面程式碼第7行先通過createIPAMArgs方法構建一個引數變數args,變數定義如下:

1struct {
2    Command string
3    ContainerID string
4    NetNS string
5    PluginArgs [][2]string
6    PluginArgsStr string
7    IfName string
8    Path string
9}

構建後的變數的Command的值是“ADD”,這樣在呼叫host-local時就會執行ADD相關的操作。
第8行通過invoke.ExecPluginWithResult來呼叫執行host-local外掛,傳入了上面建立的引數變數args,同時傳入了一個變數ipamConfig,ipamConfig裡面包含了pod所在node的子網相關配置以及一些host-local外掛的配置,內容類似如下:

 1{
 2    "cniVersion":"0.3.1",
 3    "name":"examplenet",
 4    "ipam":{
 5        "type":"host-local",
 6        "ranges":[
 7            [
 8                {
 9                    "subnet":"203.0.113.0/24"
10                }
11            ]
12        ],
13        "dataDir":"/tmp/cni-example"
14    }
15}

呼叫host-local類似如下命令:

1echo '{ "cniVersion": "0.3.1", "name": "examplenet", "ipam": { "type": "host-local", "ranges": [ [{"subnet": "203.0.113.0/24"}]], "dataDir": "/tmp/cni-example"  } }' | CNI_COMMAND=ADD CNI_CONTAINERID=example CNI_NETNS=/proc/48776/ns/net CNI_IFNAME=eth0 CNI_PATH=/opt/cni/bin /opt/cni/bin/host-local

呼叫返回的resut的值類似:

1{
2    "ips":[
3        {
4            "version":"4",
5            "address":"203.0.113.2/24",
6            "gateway":"203.0.113.1"
7        }
8    ]
9}

獲取的IP資訊以及閘道器資訊在上面程式碼的第22行返回給呼叫者,也就是第三節中分析的podManager的setup方法的第10行。

IP釋放

當cniServer接收到釋放IP的請求時,會呼叫podManager的ipamDel方法,定義位於pkg/network/node/pod.go#L445,內容如下:

1// Run CNI IPAM release for the container
2func (m *podManager) ipamDel(id string) error {
3    args := createIPAMArgs("", m.cniBinPath, cniserver.CNI_DEL, id)
4    err := invoke.ExecPluginWithoutResult(m.cniBinPath+"/host-local", m.ipamConfig, args)
5    if err != nil {
6        return fmt.Errorf("failed to run CNI IPAM DEL: %v", err)
7    }
8    return nil
9}

該方法的邏輯跟ipamAdd一樣,都是通過呼叫host-local外掛來完成相應的操作,不同的是該方法在呼叫時傳入了一個Command等於CNI_DEL的args,這樣在呼叫host-local時就會執行IP釋放的相關操作。

host-local會把所有已經分配過的IP記錄到本地,也就是ipamConfig配置的dataDir目錄下,在openshit環境下是記錄到/var/lib/cni/networks/openshift-sdn目錄下。目錄下的內容類似如下:

1[[email protected] ~]# ls /var/lib/cni/networks/openshift-sdn
210.128.0.114  10.128.0.116  last_reserved_ip.0
3[[email protected] ~]#

上面列出的每一個以ip命名的檔案都代表一個已經分配的IP,它的內容是該IP所在的pod的ID. 內容類似如下:

1[[email protected] ~]# cat /var/lib/cni/networks/openshift-sdn/10.128.0.114
27a1c2e242c2a2d750382837b81283952ad9878ae496195560f9854935d7e4d31[[email protected] ~]#

當分配IP時,host-local會在該目錄下新增一條記錄,釋放IP時會刪除相應的記錄。

關於host-local的邏輯不再作分析,後面會有單獨的章節來分析,有興趣的可以看看原始碼,位於https://github.com/containernetworking/plugins/tree/master/plugins/ipam/host-local程式碼庫下。

至此,IPAM的邏輯分析結束,下一節我們分析cniServer是如何呼叫ovs controller來設定Pod ovs規則。

上面我們分析了cniServer是如何通過IPAM外掛來管理IP,本節主要分析cniServer是如何通過ovs controller設定pod相關的ovs規則。

OVS規則設定

openshift底層的網路用的是ovs, 那麼在配置好pod IP之後,又是如何設定跟pod相關的ovs規則的呢?下面作一分析。
openshift node在啟動時會建立一個ovs controller,由它來完成ovs網路配置的各種操作。在第三節我們分析過,cniServer是通過呼叫ovs controller的SetUpPod方法來設定pod ovs規則,呼叫的程式碼位於: pkg/network/node/pod.go#L544, 內容如下:

1ofport, err := m.ovs.SetUpPod(req.SandboxID, req.HostVeth, podIP, vnid)

SetUpPod的定義位於pkg/network/node/ovscontroller.go#L267,內容如下:

1func (oc *ovsController) SetUpPod(sandboxID, hostVeth string, podIP net.IP, vnid uint32) (int, error) {
2    ofport, err := oc.ensureOvsPort(hostVeth, sandboxID, podIP.String())
3    if err != nil {
4        return -1, err
5    }
6    return ofport, oc.setupPodFlows(ofport, podIP, vnid)
7}

在上面程式碼的第2行,SetUpPod又呼叫了ensureOvsPort這個方法,該方法的定義位於pkg/network/node/ovscontroller.go#L227,內容如下:

1func (oc *ovsController) ensureOvsPort(hostVeth, sandboxID, podIP string) (int, error) {
2    return oc.ovs.AddPort(hostVeth, -1,
3        fmt.Sprintf(`external-ids=sandbox="%s",ip="%s"`, sandboxID, podIP),
4    )
5}

如程式碼所示,該方法又呼叫了ovs的AddPort方法,我們再來分析AddPort方法。該方法的定義位於pkg/util/ovs/ovs.go#L31,內容如下:

 1func (ovsif *ovsExec) AddPort(port string, ofportRequest int, properties ...string) (int, error) {
 2    args := []string{"--may-exist", "add-port", ovsif.bridge, port}
 3    if ofportRequest > 0 || len(properties) > 0 {
 4        args = append(args, "--", "set", "Interface", port)
 5        if ofportRequest > 0 {
 6            args = append(args, fmt.Sprintf("ofport_request=%d", ofportRequest))
 7        }
 8        if len(properties) > 0 {
 9            args = append(args, properties...)
10        }
11    }
12    _, err := ovsif.exec(OVS_VSCTL, args...)
13    if err != nil {
14        return -1, err
15    }
16    ofport, err := ovsif.GetOFPort(port)
17    if err != nil {
18        return -1, err
19    }
20    if ofportRequest > 0 && ofportRequest != ofport {
21        return -1, fmt.Errorf("allocated ofport (%d) did not match request (%d)", ofport, ofportRequest)
22    }
23    return ofport, nil
24}

分析上面的程式碼你會發現,AddPort實際上是呼叫了底層的ovs-vsctl命令將pod的host端的虛擬網絡卡加入到了ovs網橋br0上,這樣br0上的流量就可以通過該網絡卡進入pod了。該方法的呼叫類似於下面的命令列,假設pod host端的網絡卡是veth3258a5e2:

1ovs-vsctl --may-exist add-port br0 veth3258a5e2

接著回到SetUpPod方法,在第6行中呼叫了setupPodFlows來設定pod IP的ovs規則,該方法的定義位於pkg/network/node/ovscontroller.go#L233,內容如下:

 1func (oc *ovsController) setupPodFlows(ofport int, podIP net.IP, vnid uint32) error {
 2    otx := oc.ovs.NewTransaction()
 3
 4    ipstr := podIP.String()
 5    podIP = podIP.To4()
 6    ipmac := fmt.Sprintf("00:00:x:x:x:x/00:00:ff:ff:ff:ff", podIP[0], podIP[1], podIP[2], podIP[3])
 7
 8    // ARP/IP traffic from container
 9    otx.AddFlow("table=20, priority=100, in_port=%d, arp, nw_src=%s, arp_sha=%s, actions=load:%d->NXM_NX_REG0[], goto_table:21", ofport, ipstr, ipmac, vnid)
10    otx.AddFlow("table=20, priority=100, in_port=%d, ip, nw_src=%s, actions=load:%d->NXM_NX_REG0[], goto_table:21", ofport, ipstr, vnid)
11    if oc.useConnTrack {
12        otx.AddFlow("table=25, priority=100, ip, nw_src=%s, actions=load:%d->NXM_NX_REG0[], goto_table:30", ipstr, vnid)
13    }
14
15    // ARP request/response to container (not isolated)
16    otx.AddFlow("table=40, priority=100, arp, nw_dst=%s, actions=output:%d", ipstr, ofport)
17
18    // IP traffic to container
19    otx.AddFlow("table=70, priority=100, ip, nw_dst=%s, actions=load:%d->NXM_NX_REG1[], load:%d->NXM_NX_REG2[], goto_table:80", ipstr, vnid, ofport)
20
21    return otx.Commit()
22}

在上面程式碼的第9行到第19行,分別呼叫了AddFlow來設定各種ovs規則,第9行到第10行設定了從pod出去的ARP/IP流量的規則,第16行設定了進入POD的ARP流量規則,第19行設定了進入POD的IP流量規則。 AddFlow實際上是呼叫了命令列工具ovs-ofctl來設定各種ovs規則。關於這些規則的詳細內容不再作分析,感興趣的同學可以自行研究。

至此,ovs規則的設定流程分析完畢,openshit pod網路配置的流程也全部分析完畢。

相關推薦

解析 | openshift原始碼pod網路配置(

【編者按】openshift底層是通過kubelet來管理pod,kubelet通過CNI外掛來配置pod網路.openshift node節點在啟動的時會在一個goroutine中啟動kubelet, 由kubelet來負責pod的管理工作。 本文主要從原始碼的角度

Hadoopjob提交流程原始碼

1. 進入Job提交方法 public boolean waitForCompletion(boolean verbose               

JAVA8的IO流原始碼

讓我們來分析一下java8裡面的IO原始碼。 一般來說分兩類,即位元組流和字元流,通過下面的思維導向圖總結下: 關於流有幾點是要注意的: 第一,讀寫流要及時的關閉,使用close方法。 第二,深入理解read和readline的區別。具體請看下面的原始碼: 需要注意

ffmpeg原始碼(十三ffmpeg API變更 2009-03-01—— 2017-05-09變更

The last version increases were: libavcodec: 2015-08-28 libavdevice: 2015-08-28 libavfilter: 2015-08-28 libavformat: 2015-08-28 libavresample: 201

ffmpeg原始碼(一結構總覽

未畢業通過校招進入了某做機的公司從事camera方面的工作。比較悲劇的是做了將近一年的Camera之後,正要研究Camera上下層打通任督二脈的時候,公司架構調整加上OS版本大變動,被調到了多媒體組(不過也好,我對編碼解碼這塊也是嚮往已久)。以前大學的時候用vi

ffmpeg原始碼(二av_register_all(),avcodec_register_all()

av_register_all() 該函式在所有基於ffmpeg的應用程式中幾乎都是第一個被呼叫的。只有呼叫了該函式,才能使用複用器,編碼器等。 av_register_all()呼叫了avcodec_register_all()。avcodec_regis

SparseArray詳解及原始碼

一、前言 SparseArray 是 Android 在 Android SdK 為我們提供的一個基礎的資料結構,其功能類似於 HashMap。與 HashMap 不同的是它的 Key 只能是 int 值,不能是其他的型別。 二、程式碼分析 1. demo 及其簡析 首先也還是先通過 demo 來看一

二、View Animation動畫原始碼——動畫的啟動執行

不知道大夥有沒有想過,當我們呼叫了 View.startAnimation(animation) 之後,動畫是不是馬上就開始執行了? ——我們先來看看 View.startAnimation(animation) 方法裡都做那那些事情。 public voi

MJPG-streamer原始碼

  MJPG-streamer主體上是由main函式和輸入外掛、輸出外掛組成。   軟體執行的流程是先對攝像頭進行初始化然後設定基本的輸入輸出引數,接著從攝像頭中獲取資料放到全域性記憶體中,然後通知輸出函式來取出,接著輸出。   攝像頭的初始化由結構體vdIn來進行

ffmpeg原始碼(九av_log(),AVClass,AVOption

1.av_log() av_log()是FFmpeg中輸出日誌的函式。隨便開啟一個FFmpeg的原始碼檔案,就會發現其中遍佈著av_log()函式。一般情況下FFmpeg類庫的原始碼中是不允許使用printf()這種的函式的,所有的輸出一律使用av_log()

網路架構方面迴圈神經網路RNN

  一、前言 1.1 誕生原因   在普通的前饋神經網路(如多層感知機MLP,卷積神經網路CNN)中,每次的輸入都是獨立的,即網路的輸出依賴且僅依賴於當前輸入,與過去一段時間內網路的輸出無關。但是在現實生活中,許多系統的輸出不僅依賴於當前輸入,還與過去一段時間內系統的輸出有關,即需要網路保留一

ElementUI 原始碼——原始碼結構篇

ElementUI 作為當前運用的最廣的 Vue PC 端元件庫,很多 Vue 元件庫的架構都是參照 ElementUI 做的。作為一個有夢想的前端(鹹魚),當然需要好好學習一番這套比較成熟的架構。 目錄結構解析 首先,我們先來看看 ElementUI 的目錄結構,總體來說,ElementUI 的目錄結構與

vuex原始碼

前言 基於 vuex 3.1.2 按如下流程進行分析: Vue.use(Vuex); const store = new Vuex.Store({ actions, getters, state, mutations, modules // ... });

併發系列(二——FutureTask類原始碼

背景   本文基於JDK 11,主要介紹FutureTask類中的run()、get()和cancel() 方法,沒有過多解析相應interface中的註釋,但閱讀原始碼時建議先閱讀註釋,明白方法的主要的功能,再去看原始碼會更快。   文中若有不正確的地方歡迎大夥留言指出,謝謝了! 1、FutureTask類

Linux 基礎 高階網路配置 (網路橋接

一、網路橋接 網路橋接用網路橋實現共享上網主機和客戶機除了利用軟體外,還可以用XP自帶的網路橋建立連線用雙網絡卡的機器做主機。 橋接: 一般的交換機,網橋就有橋接作用。就交換機來說,本身有一個埠與mac的對映表,通過這些,隔離了衝突域(collision)。 簡單的說就是通過網橋可以把

Linux基礎 高階網路配置 (鏈路聚合

一、鏈路聚合 簡介: 鏈路聚合(Link Aggregation)是一個計算機網路術語,指將多個物理埠匯聚在一起,形成一個邏輯埠,以實現出/入流量吞吐量在各成員埠的負荷分擔,交換機根據使用者配置的埠負荷分擔策略決定網路封包從 哪個成員埠傳送到對端的交換機。當交換機檢測到其中一個成員埠的鏈路發

雲客Drupal8原始碼分析外掛系統(

以下內容僅是一個預覽,完整內容請見文尾: 至此本系列對外掛的介紹全部完成,涵蓋了系統外掛的所有知識 全文目錄(全文10476字): 例項化外掛 外掛對映Plugin mapping 外掛上下文  

[RK3288][Android6.0] WiFi無線網路配置的關閉過程

Platform: Rockchip OS: Android 6.0 Kernel: 3.10.92 是wifi enabled的逆過程,不過呼叫的也是setWifiEnable()介面. onSwitchChanged -> WifiEnabler.java   mWifiManager.setWif

系統學習機器學習神經網路(四 --SOM

轉自:http://blog.csdn.net/xbinworld/article/details/50818803,其實內容更多的是百度文庫裡叫《SOM自組織特徵對映神經網路》這篇文章上的,博主增加了一些理解。 本文詳細介紹一下自組織神經網路概念和原理,並重點介紹一下自組

Python實戰神經網路(1

 python有專門的神經網路庫,但為了加深印象,我自己在numpy庫的基礎上,自己編寫了一個簡單的神經網路程式,是基於Rosenblatt感知器的,這個感知器建立在一個線性神經元之上,神經元模型的求和節點計算作用於突觸輸入的線性組合,同時結合外部作用的偏置,對若干個