主頁 > 後端開發 > PCIE_DMA實體五:基于XILINX XDMA的PCIE高速采集卡

PCIE_DMA實體五:基于XILINX XDMA的PCIE高速采集卡

2020-10-01 14:19:13 後端開發

PCIE_DMA實體五:基于XILINX XDMA的PCIE高速采集卡

一:前言

這一年關于PCIE高速采集卡的業務量激增,究其原因,發現百度“xilinx pcie dma”,出來的都是本人的博客,前期的博文主要以教程為主,教大家如何理解PCIE協議以及如何正確使用PCIE相關的IP核,因為涉及到商業道德,本人不能將公司自研的IP核以及相關工程應用放到網上,但為了滿足大家對PCIE高速采集卡這塊的業務需求,博主特地利用業余時間,使用XDMA這個xilinx官方IP,配合xilinx提供的linux驅動,在KC705開發板上實作了一套高速采集系統,該系統可對前端ADC產生的不大于2GB/s的連續或非連續資料進行實時采集,同時該采集卡具備資料發送功能,可以將用戶檔案或者記憶體中的資料寫到FPGA的發送FIFO中,速率約為2GB/s,該采集卡具備上位機讀寫FPGA用戶暫存器的功能,讀寫介面為local bus介面,方便易用,當然,如果您的高速采集卡需要大于2GB/s的采集速率,那博主只能拿出壓箱底的另一套QDMA采集系統了,該系統在VC709上具備6.1GB/s的連續采集能力,要知道VC709的PCIE理論帶寬都只有6.4GB/s,高達95%的傳輸效率真的很恐怖了,當然這套QDMA采集系統能有如此威力,主要拜FPGA大神馬克杰所賜,馬哥寫的驅動充分發揮了系統的最大性能,吊打Xilinx的官方驅動,

二:前期準備

1、XILINX KC705開發板

2、pg195-pcie-dma.pdf

3、Vivado2018.2套件

4、X86主機一臺,安裝64位centos7.4 1708作業系統

5、XDMA linux驅動2018版本,GitHub上有下載,

三:系統框圖

采集卡系統框圖

從左到右從上到下依次介紹模塊以及相應功能

1.data_gen

此模塊模擬ADC產生的流資料,在本系統中,采樣時鐘250M,模擬AD資料位寬64位,故AD實時采樣速率為2GB/s,可通過Send_En隨時中止或繼續資料產生,

2.axis data width converter

此模塊將流資料64位位寬轉換成128位位寬,時鐘250M,

3.axis data fifo

此模塊為流資料緩沖FIFO,深度不大,128足矣,真正的快取要靠ddr完成, 

4.YDMA

此模塊為博主自己寫的采集卡DMA控制器,該控制器的功能主要分四塊:一,將收到的ST資料(axis介面)轉換成MM資料(axi介面)寫入DDR3;二,將需要發送的MM資料(axi介面)從DDR3中取出后轉換成ST資料(axis介面)供用戶使用;三,將XDMA輸出的BYPASS介面轉換成local_bus介面供用戶讀寫暫存器使用;四,中斷控制器,將寫DDR和讀DDR產生的中斷送給XDMA,用戶可設定包大小,中斷包個數,中斷超時時間,

5.user_reg_define

此模塊為用戶暫存器讀寫模塊,讀寫介面為local bus介面,此用例中我們用它來配置Send_En,

6.axis_data_check

此模塊用來校驗上位機發下來的資料,

7.XDMA

此模塊由上位機驅動控制,通過PCIE以SG_DMA的方式讀寫DDR3中的資料,

8.memory interface generator

此模塊為DDR3控制器,使用AXI介面,

綜上,整個采集卡包含兩個方向的資料流向:FPGA>>PC:

data_gen->data_fifo->YDMA->DDR3->XDMA    

PC>>FPGA:XDMA->DDR3->YDMA->data_check

當然FPGA邏輯部分最大的難點就在YDMA上,為了滿足對任意包長、任意間隔的連續或非連續資料進行實時采集,需要產生大量的中斷以及與之相對應的ddr快取地址和快取長度等中斷資訊,但XDMA驅動最大的bug恰恰出在中斷上,為了規避XDMA的中斷bug,又要提升整體的采集性能,需要對中斷控制做精細設計,同時,對于那些突發的狀況,比如采集資料突然中斷的情況、急停急起的情況,都需要通過邏輯和軟體的相互配合,才能跑出令人滿意的采集效果,至于PC往FPGA發資料這個功能對于采集卡來說是錦上添花而已,因為有客戶提出,需要將采集到的資料做處理,處理完后通過FPGA再發到另一個設備上,故我在YDMA上做了一個發資料的功能,用戶介面也是大家最熟悉易用的axis(fifo)介面,因為YDMA包含一定技術含量,故該采集系統不能免費提供給大家,需要的用戶可以聯系我談價格,

四:軟體設計

XDMA的驅動是官方提供的,這里不做詳細解讀,總之XDMA驅動就是把PCIE DMA包成了多種字符設備:xdma_h2c,xdma_c2h,xdma_user,xdma_control,xdma_bypass,xdma_events

經過本人測驗使用,我只推薦使用xdma_h2c,xdma_c2h,xdma_bypass,xdma_events這四個字符設備,xdma_h2c用來把資料從記憶體寫到FPGA的DDR,xdma_c2h用來把資料從FPGA的DDR讀到記憶體,xdma_bypass用來配置FPGA的用戶暫存器,xdma_events用來讀取用戶中斷,

下面我們來看看采集卡的測驗程式我們是怎么寫的,里面給出了詳細的注釋:

#define _BSD_SOURCE
#define _XOPEN_SOURCE 500
#include <assert.h>
#include <time.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <memory.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <sys/mman.h>
#include <errno.h>


//#include "dma_utils.c"

#define FATAL do { fprintf(stderr, "Error at line %d, file %s \n", __LINE__, __FILE__); exit(1); } while(0)
 

#define DEVICE_NAME_H2C "/dev/xdma0_h2c_0"
#define DEVICE_NAME_C2H "/dev/xdma0_c2h_0"
#define DEVICE_NAME_REG "/dev/xdma0_bypass"

#define MAP_SIZE (1*1024*1024)
#define MAP_MASK (MAP_SIZE - 1)

#define     RCV_EN_CMD      0 
#define     RX_DM_RST    1
struct timezone tz_time;
struct timeval tv_time3;
struct timeval tv_time4;
pthread_t rcv_tid ;
pthread_t print_sta_id;
pthread_t event_thread;
int work = 0 ;
int lxcj =0;
int int_rc;
unsigned int lastData = https://www.cnblogs.com/yuzeren48/archive/2020/10/01/0;
unsigned int rcvPktNum = 0;
unsigned long rcvBytes = 0;
unsigned long rcvBytes_l = 0;
unsigned int errnum = 0 ;
int c2h_fd ;
int h2c_fd ;
int control_fd;
int interrupt_fd;
void *control_base;
static sem_t int_sem_rx;
static sem_t int_sem_tx;
char *device_c2h = DEVICE_NAME_C2H;
char *device_h2c = DEVICE_NAME_H2C;
char *device_reg = DEVICE_NAME_REG;

static void write_control(void *base_addr,int offset,uint32_t val);//寫用戶暫存器
static uint32_t read_control(void *base_addr,int offset);//讀用戶暫存器
/*開中斷*/
int open_event(char *devicename)
{
int fd;
fd=open(devicename,O_RDWR|O_SYNC );
if(fd==-1)
{printf("open event error\n");
 return -1;} 
return fd;
}
/*獲取用戶中斷*/
int read_event(int fd)
{
int val;
read(fd,&val,4);
return val;
}
/*打開字符設備*/
static int open_control(char *filename)
{
    int fd;
    fd = open(filename, O_RDWR | O_SYNC);
    if(fd == -1)
    {
        printf("open control error\n");
        return -1;
    }
    return fd;
}
/*獲取設備對應的記憶體映射地址*/
static void *mmap_control(int fd,long mapsize)
{
    void *vir_addr;
    vir_addr = mmap(0, mapsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    return vir_addr;
}
/*寫用戶暫存器*/
static void write_control(void *base_addr,int offset,uint32_t val)
{
    //uint32_t writeval = htoll(val);
    *((uint32_t *)(base_addr+offset)) = val;
}
/*讀用戶暫存器*/
static uint32_t read_control(void *base_addr,int offset)
{
    uint32_t read_result = *((uint32_t *)(base_addr+offset));
    //read_result = ltohl(read_result);
    return read_result;
}


/*列印行程,5秒列印一次統計資訊,包含收到的包個數,錯誤包個數,以及當前的采集速率*/
void *printStatus()
{
    unsigned int lostNum = 0 ;
    unsigned int allNum  = 0 ;
    while( work ==1) 
        {
            sleep(5);
            printf("rcvPkt[%8x], err[%8x] , rate[%d]MBps\n",   rcvPktNum, errnum , (rcvBytes-rcvBytes_l)/5/1000000 );
            rcvBytes_l = rcvBytes ;
            
        }
}

/*資料校驗,因為模擬ADC資料是累加數,故收到的當前包的第一個數應該是上一次包的第一個數加上上次包的長度*/
void checkData(unsigned int *add, unsigned int len)
{


    if(lastData != add[0]  & lastData!=0 ) 
        {
            errnum ++;
            if(errnum<20 )printf("l[%8x], n[%8x][%8x], [%8x], p[%x] , len[%d]\n",    lastData ,   add[0], add[1],     add[0] -  lastData ,  (add[0] -  lastData)/len  , len) ;

        }


    rcvBytes = rcvBytes + len ;
    lastData = add[0] + len/8 ;


}

/*ADC連續資料采集處理行程*/
void *procPkt( )
{
    int i;
    int rxint_rc;//接收中斷資訊暫存器回傳值
    unsigned int * rxBuf;//接收資料存放的地址
    int  rxlen; //接收資料的長度
    int  c2h0_inbuf =0;
    c2h_fd= open(device_c2h, O_RDWR | O_NONBLOCK);//打開xdma_c2h字符設備

    posix_memalign((void **)&rxBuf, 1024, 8*1024*1024);//開一個8M的記憶體空間用于暫存接收資料
        
    printf("procAD up. \n" );
       while(       work ==1  )
        {
            if(lxcj==1)            
            { 
                //assert(c2h_fd >= 0);
                 sem_wait(&int_sem_rx); //等待用戶接收中斷        
                rxint_rc=read_control(control_base,0x10020);//從接收中斷暫存器中獲取接收中斷相關的中斷資訊
                int icnt;
                
                if((rxint_rc&0x80000000)>>31)  icnt= rxint_rc &0x00ffffff;//接收中斷暫存器bit31表示是否有接收中斷,bit23-bit0表示有幾個中斷包
                else continue;
                
                write_control(control_base,0x10020,icnt);//清中斷暫存器,寫入的內容為即將要處理的中斷包個數
           
                for(i=0;i<icnt;i++) //處理中斷包
                {   int count = read_control(control_base,0x10018);//讀接收中斷狀態FIFO,獲取當前中斷包的實際長度
                    off_t off = lseek(c2h_fd, c2h0_inbuf, SEEK_SET);//和FPGA協商好從DDR3的0地址開始存放接收資料,故軟體從0地址開始取資料
                    rxlen = read(c2h_fd, rxBuf, count);//從DDR中取出資料放入rxbuffer 
                    write_control(control_base,0x10018,1); //清接收中斷FIFO  
                    c2h0_inbuf = c2h0_inbuf + 0x400000 ;//本測驗用例中設定的中斷包最大長度為4M
                    if(c2h0_inbuf==0x40000000)  c2h0_inbuf = 0;//當DDR3偏移達到1G的時候重新歸零
                    if(rxlen > 0) rcvPktNum ++ ;//統計接收包個數    
                       checkData(rxBuf ,  rxlen) ;//校驗接受到的包是否為連續數
                }    
            }


        }
        pthread_exit(0);  

}
/*寫資料行程,此用例中為發送任意大小檔案*/
void h2c_process(char *filename)
{   
    h2c_fd= open(device_h2c, O_RDWR | O_NONBLOCK);//打開xdma_h2c字符設備
    assert(h2c_fd>0);
    uint32_t send_len;//單次資料包發送長度,本測驗用例中以4M為單位
    uint32_t send_addr=0x0; 
    int rc;
    int file_fd;
    uint64_t size;//發送檔案的實際大小
    uint64_t snd_cnt;
    struct stat fileStat;
      file_fd = open(filename, O_RDONLY);//打開發送檔案
    assert(file_fd >= 0);
     rc= stat(filename, &fileStat );
      size = fileStat.st_size ; //獲取發送檔案的大小
      snd_cnt =size;
      char *sendbuff = NULL;
      posix_memalign((void **)&sendbuff, 1024/*alignment*/, 8*1024*1024);//開一塊8M的記憶體空間

    gettimeofday(&tv_time3, &tz_time);
    while(size!=0)
    {
        sem_wait(&int_sem_tx);//等待發送中斷信號量,該信號量初始值為9
        if(size>0x400000)  send_len = 0x400000;
        else send_len = size;
        off_t off_file = lseek(file_fd, send_addr, SEEK_SET);
        rc = read(file_fd, sendbuff, send_len);//將資料從檔案中讀到sendbuffer
           off_t off_h2c = lseek(h2c_fd, send_addr, SEEK_SET);  
        //printf("send_len=%d\n,sendbuff=%x\n",send_len,sendbuff);   
        rc = write(h2c_fd, sendbuff, send_len);//將sendbuffer中的資料發送到DDR3
        write_control(control_base,0x11000,send_addr);//將DDR3資料快取地址寫入FPGA端的發送地址暫存器
        write_control(control_base,0x11010,send_len); //將DDR3資料快取長度寫入FPGA端的發送長度暫存器 
        //printf("rc=%d\n",rc);
        assert(rc == send_len);
        size = size - send_len;
        send_addr = send_addr + send_len;
        if(send_addr==0x40000000)  send_addr = 0;//發送資料的DDR3快取偏移地址為1G時歸零
 
    }
gettimeofday(&tv_time4, &tz_time);  

printf("write done\n");
printf(" 時間 %ld useconds\n", (tv_time4.tv_sec - tv_time3.tv_sec) * 1000000 + tv_time4.tv_usec - tv_time3.tv_usec);
printf(" 資料量 %ld 位元組\n", snd_cnt);
printf(" 帶寬 %ld MB/s\n", snd_cnt / ((tv_time4.tv_sec - tv_time3.tv_sec) * 1000000 + tv_time4.tv_usec - tv_time3.tv_usec));

  if (file_fd >= 0)  close(file_fd);
  free(sendbuff);
}

/*中斷處理行程*/
void *event_process()
{
    int i;
    int txint_rc;
    interrupt_fd = open_event("/dev/xdma0_events_0");    //打開用戶中斷
    while(work==1)
    {             
      read_event(interrupt_fd);  //獲取用戶中斷
      int_rc=read_control(control_base,0x00000); //讀總中斷暫存器
      switch(int_rc)
      {      
          case 1: //接收中斷
              sem_post(&int_sem_rx);  
            break;
          case 2: //發送中斷
            txint_rc=read_control(control_base,0x11020); //從發送中斷暫存器中獲取發送中斷相關的中斷資訊
            int txicnt;                
            if((txint_rc&0x80000000)>>31)  txicnt= txint_rc &0x00ffffff;//發送中斷暫存器bit31表示是否有發送中斷,bit23-bit0表示發出了幾個中斷包
            else break;
            write_control(control_base,0x11020,txicnt);//清中斷暫存器,寫入的內容為即將要處理的中斷包個數
            for(i=0;i<txicnt;i++)  sem_post(&int_sem_tx); //為每個發出的中斷包釋放一個信號量
            break;
          default: break;
      }                                                    
    }
    pthread_exit(0);  
}

int main(int argc, char *argv[])
{
  

    ssize_t rc;
    char  inp ;
    unsigned int * rxBuf;
    posix_memalign((void **)&rxBuf, 1024, 1024*1024*1024);

    control_fd = open_control("/dev/xdma0_bypass");//打開bypass字符設備
    control_base = mmap_control(control_fd,MAP_SIZE);//獲取bypass映射的記憶體地址
    //c2h_fd= open(device_c2h, O_RDWR | O_NONBLOCK);
    //h2c_fd= open(device_h2c, O_RDWR | O_NONBLOCK);
    sem_init(&int_sem_rx, 0, 0);
    sem_init(&int_sem_tx, 0, 9);
    work =1 ;
    pthread_create(&rcv_tid , NULL,  procPkt,  NULL);
    pthread_create(&print_sta_id, NULL, printStatus,  NULL );
    pthread_create(&event_thread, NULL, event_process, NULL);

    write_control(control_base,0x10028,0xFFFFFF08);//寫接收中斷控制暫存器,bit31-bit8為中斷超時時間,bit7-bit0為多少個包產生一次中斷
    write_control(control_base,0x11028,0xFFFFFF00);//寫發送中斷控制暫存器,bit31-bit8為中斷超時時間,bit7-bit0為多少個包產生一次中斷
    char *file_write  = "/run/media/root/software/CentOS-7-x86_64-Everything-1708/CentOS-7-x86_64-Everything-1708.iso";
    while(inp!='o')
    {
      inp = getchar();
            switch(inp)
            {
            case 'w':
                h2c_process(file_write);
                break;        
            case 'r':   
                write_control(control_base,0x10030,4);//復位接收DMA
                rc=read( c2h_fd,  rxBuf, 1*1024);
                printf("rc=%x\n",rc);
                break;
            case 's':
                write_control(control_base,0x10030,4);//復位接收DMA
                lxcj=1;
                write_control(control_base,0x10038,1);//使能接收
                break;    
            case 'e':
                write_control(control_base,0x10038,0);//停止接收
                sleep(2);
                lxcj=0;
                break;
            case 't':   
                write_control(control_base,0x30008,1);//使能模擬ADC資料發送
                break;
            case 'p':   
                write_control(control_base,0x30008,0);//暫停模擬ADC資料發送
                break;
            case 'o':   
                write_control(control_base,0x10030,1);//復位接收DMA
                write_control(control_base,0x11030,1);//復位發送DMA
                break;
            default: break;
            }

    }
    
    
    work =0 ;
out:
    close(c2h_fd);
    close(h2c_fd);
    return rc;
}

 

五:測驗結果

本采集系統測驗環境為X86主機,CPU為Intel 酷睿i7 8700K,FPGA選用xilinx公司的KC705開發板,作業系統為centos7.4 1708,內核版本3.10.0-693,博主最近會在windows上做一版測驗程式,到時候分享給需要的朋友,

六:結束語

本博文展示的PCIE高速采集系統主要面向有這方面工程應用需求的朋友,不建議初學者作為學習使用,本人從事高速總線介面已七年有余,積累了大量總線相關的FPGA設計經驗,主要涉及FC、rapidio、千兆、萬兆以太網、lvds、mlvds、can、422、1553B,同時也可承接演算法加速或者視頻影像處理等相關專案,最后放上一段基于QDMA(非xilinx的官方IP)的PCIe高速采集卡在VC709上的測驗結果,致敬前輩馬哥!

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/145388.html

標籤:其他

上一篇:Python裝飾器實作函式動態型別檢查

下一篇:合肥工業大學 機器人技術 作業和實驗

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more