查看: 12762|回复: 9

[分享] 好文分享-linux经验之谈:内核数据结构之kfifo

[复制链接]
  • TA的每日心情
    开心
    2024-3-26 15:16
  • 签到天数: 266 天

    [LV.8]以坛为家I

    3298

    主题

    6545

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    32004
    最后登录
    2024-4-9
    发表于 2022-1-12 09:16:35 | 显示全部楼层 |阅读模式




    1、前言
      最近项目中用到一个环形缓冲区(ring buffer),代码是由linux内核的kfifo改过来的。缓冲区在文件系统中经常用到,通过缓冲区缓解cpu读写内存和读写磁盘的速度。例如一个进程A产生数据发给另外一个进程B,进程B需要对进程A传的数据进行处理并写入文件,如果B没有处理完,则A要延迟发送。为了保证进程A减少等待时间,可以在A和B之间采用一个缓冲区,A每次将数据存放在缓冲区中,B每次冲缓冲区中取。这是典型的生产者和消费者模型,缓冲区中数据满足FIFO特性,因此可以采用队列进行实现。Linux内核的kfifo正好是一个环形队列,可以用来当作环形缓冲区。生产者与消费者使用缓冲区如下图所示:
    13.png
      环形缓冲区的详细介绍及实现方法可以参考http://en.wikipedia.org/wiki/Circular_buffer,介绍的非常详细,列举了实现环形队列的几种方法。环形队列的不便之处在于如何判断队列是空还是满。维基百科上给三种实现方法。


    2、linux 内核kfifo
      kfifo设计的非常巧妙,代码很精简,对于入队和出对处理的出人意料。首先看一下kfifo的数据结构:
    1. struct kfifo {
    2.     unsigned char *buffer;     /* the buffer holding the data */
    3.     unsigned int size;         /* the size of the allocated buffer */
    4.     unsigned int in;           /* data is added at offset (in % size) */
    5.     unsigned int out;          /* data is extracted from off. (out % size) */
    6.     spinlock_t *lock;          /* protects concurrent modifications */
    7. };
    复制代码
    kfifo提供的方法有:
    1. 1 //根据给定buffer创建一个kfifo
    2. 2 struct kfifo *kfifo_init(unsigned char *buffer, unsigned int size,
    3. 3                 gfp_t gfp_mask, spinlock_t *lock);
    4. 4 //给定size分配buffer和kfifo
    5. 5 struct kfifo *kfifo_alloc(unsigned int size, gfp_t gfp_mask,
    6. 6                  spinlock_t *lock);
    7. 7 //释放kfifo空间
    8. 8 void kfifo_free(struct kfifo *fifo)
    9. 9 //向kfifo中添加数据
    10. 10 unsigned int kfifo_put(struct kfifo *fifo,
    11. 11                 const unsigned char *buffer, unsigned int len)
    12. 12 //从kfifo中取数据
    13. 13 unsigned int kfifo_put(struct kfifo *fifo,
    14. 14                 const unsigned char *buffer, unsigned int len)
    15. 15 //获取kfifo中有数据的buffer大小
    16. 16 unsigned int kfifo_len(struct kfifo *fifo)
    复制代码
    定义自旋锁的目的为了防止多进程/线程并发使用kfifo。因为in和out在每次get和out时,发生改变。初始化和创建kfifo的源代码如下:
    1. 1 struct kfifo *kfifo_init(unsigned char *buffer, unsigned int size,
    2. 2              gfp_t gfp_mask, spinlock_t *lock)
    3. 3 {
    4. 4     struct kfifo *fifo;
    5. 6     /* size must be a power of 2 */
    6. 7     BUG_ON(!is_power_of_2(size));
    7. 9     fifo = kmalloc(sizeof(struct kfifo), gfp_mask);
    8. 10     if (!fifo)
    9. 11         return ERR_PTR(-ENOMEM);
    10. 13     fifo->buffer = buffer;
    11. 14     fifo->size = size;
    12. 15     fifo->in = fifo->out = 0;
    13. 16     fifo->lock = lock;
    14. 17
    15. 18     return fifo;
    16. 19 }
    17. 20 struct kfifo *kfifo_alloc(unsigned int size, gfp_t gfp_mask, spinlock_t *lock)
    18. 21 {
    19. 22     unsigned char *buffer;
    20. 23     struct kfifo *ret;
    21. 29     if (!is_power_of_2(size)) {
    22. 30         BUG_ON(size > 0x80000000);
    23. 31         size = roundup_pow_of_two(size);
    24. 32     }
    25. 34     buffer = kmalloc(size, gfp_mask);
    26. 35     if (!buffer)
    27. 36         return ERR_PTR(-ENOMEM);
    28. 38     ret = kfifo_init(buffer, size, gfp_mask, lock);
    29. 39
    30. 40     if (IS_ERR(ret))
    31. 41         kfree(buffer);
    32. 43     return ret;
    33. 44 }
    复制代码
    在kfifo_init和kfifo_calloc中,kfifo->size的值总是在调用者传进来的size参数的基础上向2的幂扩展,这是内核一贯的做法。这样的好处不言而喻--对kfifo->size取模运算可以转化为与运算,如:kfifo->in % kfifo->size 可以转化为 kfifo->in & (kfifo->size – 1)


    kfifo的巧妙之处在于in和out定义为无符号类型,在put和get时,in和out都是增加,当达到最大值时,产生溢出,使得从0开始,进行循环使用。put和get代码如下所示:
    1. 1 static inline unsigned int kfifo_put(struct kfifo *fifo,
    2. 2                 const unsigned char *buffer, unsigned int len)
    3. 3 {
    4. 4     unsigned long flags;
    5. 5     unsigned int ret;
    6. 6     spin_lock_irqsave(fifo->lock, flags);
    7. 7     ret = __kfifo_put(fifo, buffer, len);
    8. 8     spin_unlock_irqrestore(fifo->lock, flags);
    9. 9     return ret;
    10. 10 }
    11. 11
    12. 12 static inline unsigned int kfifo_get(struct kfifo *fifo,
    13. 13                      unsigned char *buffer, unsigned int len)
    14. 14 {
    15. 15     unsigned long flags;
    16. 16     unsigned int ret;
    17. 17     spin_lock_irqsave(fifo->lock, flags);
    18. 18     ret = __kfifo_get(fifo, buffer, len);
    19. 19         //当fifo->in == fifo->out时,buufer为空
    20. 20     if (fifo->in == fifo->out)
    21. 21         fifo->in = fifo->out = 0;
    22. 22     spin_unlock_irqrestore(fifo->lock, flags);
    23. 23     return ret;
    24. 24 }
    25. 25
    26. 26
    27. 27 unsigned int __kfifo_put(struct kfifo *fifo,
    28. 28             const unsigned char *buffer, unsigned int len)
    29. 29 {
    30. 30     unsigned int l;
    31. 31        //buffer中空的长度
    32. 32     len = min(len, fifo->size - fifo->in + fifo->out);
    33. 34     /*
    34. 35      * Ensure that we sample the fifo->out index -before- we
    35. 36      * start putting bytes into the kfifo.
    36. 37      */
    37. 39     smp_mb();
    38. 41     /* first put the data starting from fifo->in to buffer end */
    39. 42     l = min(len, fifo->size - (fifo->in & (fifo->size - 1)));
    40. 43     memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), buffer, l);
    41. 45     /* then put the rest (if any) at the beginning of the buffer */
    42. 46     memcpy(fifo->buffer, buffer + l, len - l);
    43. 47
    44. 48     /*
    45. 49      * Ensure that we add the bytes to the kfifo -before-
    46. 50      * we update the fifo->in index.
    47. 51      */
    48. 53     smp_wmb();
    49. 55     fifo->in += len;  //每次累加,到达最大值后溢出,自动转为0
    50. 57     return len;
    51. 58 }
    52. 59
    53. 60 unsigned int __kfifo_get(struct kfifo *fifo,
    54. 61              unsigned char *buffer, unsigned int len)
    55. 62 {
    56. 63     unsigned int l;
    57. 64         //有数据的缓冲区的长度
    58. 65     len = min(len, fifo->in - fifo->out);
    59. 67     /*
    60. 68      * Ensure that we sample the fifo->in index -before- we
    61. 69      * start removing bytes from the kfifo.
    62. 70      */
    63. 72     smp_rmb();
    64. 74     /* first get the data from fifo->out until the end of the buffer */
    65. 75     l = min(len, fifo->size - (fifo->out & (fifo->size - 1)));
    66. 76     memcpy(buffer, fifo->buffer + (fifo->out & (fifo->size - 1)), l);
    67. 78     /* then get the rest (if any) from the beginning of the buffer */
    68. 79     memcpy(buffer + l, fifo->buffer, len - l);
    69. 81     /*
    70. 82      * Ensure that we remove the bytes from the kfifo -before-
    71. 83      * we update the fifo->out index.
    72. 84      */
    73. 86     smp_mb();
    74. 88     fifo->out += len; //每次累加,到达最大值后溢出,自动转为0
    75. 90     return len;
    76. 91 }
    复制代码
    put和get在调用__put和__get过程都进行加锁,防止并发。从代码中可以看出put和get都调用两次memcpy,这针对的是边界条件。例如下图:蓝色表示空闲,红色表示占用。

    (1)空的kfifo,
    14.png
    (2)put一个buffer后
    15.png
    (3)get一个buffer后
    16.png
    (4)当此时put的buffer长度超出in到末尾长度时,则将剩下的移到头部去
    17.png
    3、测试程序
    仿照kfifo编写一个ring_buffer,现有线程互斥量进行并发控制。设计的ring_buffer如下所示:
    1. 1 /**@brief 仿照linux kfifo写的ring buffer
    2.   2  *@atuher Anker  date:2013-12-18
    3.   3 * ring_buffer.h
    4.   4  * */
    5.   5
    6.   6 #ifndef KFIFO_HEADER_H
    7.   7 #define KFIFO_HEADER_H
    8.   8
    9.   9 #include <inttypes.h>
    10. 10 #include <string.h>
    11. 11 #include <stdlib.h>
    12. 12 #include <stdio.h>
    13. 13 #include <errno.h>
    14. 14 #include <assert.h>
    15. 15
    16. 16 //判断x是否是2的次方
    17. 17 #define is_power_of_2(x) ((x) != 0 && (((x) & ((x) - 1)) == 0))
    18. 18 //取a和b中最小值
    19. 19 #define min(a, b) (((a) < (b)) ? (a) : (b))
    20. 20
    21. 21 struct ring_buffer
    22. 22 {
    23. 23     void         *buffer;     //缓冲区
    24. 24     uint32_t     size;       //大小
    25. 25     uint32_t     in;         //入口位置
    26. 26     uint32_t       out;        //出口位置
    27. 27     pthread_mutex_t *f_lock;    //互斥锁
    28. 28 };
    29. 29 //初始化缓冲区
    30. 30 struct ring_buffer* ring_buffer_init(void *buffer, uint32_t size, pthread_mutex_t *f_lock)
    31. 31 {
    32. 32     assert(buffer);
    33. 33     struct ring_buffer *ring_buf = NULL;
    34. 34     if (!is_power_of_2(size))
    35. 35     {
    36. 36     fprintf(stderr,"size must be power of 2.\n");
    37. 37         return ring_buf;
    38. 38     }
    39. 39     ring_buf = (struct ring_buffer *)malloc(sizeof(struct ring_buffer));
    40. 40     if (!ring_buf)
    41. 41     {
    42. 42         fprintf(stderr,"Failed to malloc memory,errno:%u,reason:%s",
    43. 43             errno, strerror(errno));
    44. 44         return ring_buf;
    45. 45     }
    46. 46     memset(ring_buf, 0, sizeof(struct ring_buffer));
    47. 47     ring_buf->buffer = buffer;
    48. 48     ring_buf->size = size;
    49. 49     ring_buf->in = 0;
    50. 50     ring_buf->out = 0;
    51. 51         ring_buf->f_lock = f_lock;
    52. 52     return ring_buf;
    53. 53 }
    54. 54 //释放缓冲区
    55. 55 void ring_buffer_free(struct ring_buffer *ring_buf)
    56. 56 {
    57. 57     if (ring_buf)
    58. 58     {
    59. 59     if (ring_buf->buffer)
    60. 60     {
    61. 61         free(ring_buf->buffer);
    62. 62         ring_buf->buffer = NULL;
    63. 63     }
    64. 64     free(ring_buf);
    65. 65     ring_buf = NULL;
    66. 66     }
    67. 67 }
    68. 68
    69. 69 //缓冲区的长度
    70. 70 uint32_t __ring_buffer_len(const struct ring_buffer *ring_buf)
    71. 71 {
    72. 72     return (ring_buf->in - ring_buf->out);
    73. 73 }
    74. 74
    75. 75 //从缓冲区中取数据
    76. 76 uint32_t __ring_buffer_get(struct ring_buffer *ring_buf, void * buffer, uint32_t size)
    77. 77 {
    78. 78     assert(ring_buf || buffer);
    79. 79     uint32_t len = 0;
    80. 80     size  = min(size, ring_buf->in - ring_buf->out);        
    81. 81     /* first get the data from fifo->out until the end of the buffer */
    82. 82     len = min(size, ring_buf->size - (ring_buf->out & (ring_buf->size - 1)));
    83. 83     memcpy(buffer, ring_buf->buffer + (ring_buf->out & (ring_buf->size - 1)), len);
    84. 84     /* then get the rest (if any) from the beginning of the buffer */
    85. 85     memcpy(buffer + len, ring_buf->buffer, size - len);
    86. 86     ring_buf->out += size;
    87. 87     return size;
    88. 88 }
    89. 89 //向缓冲区中存放数据
    90. 90 uint32_t __ring_buffer_put(struct ring_buffer *ring_buf, void *buffer, uint32_t size)
    91. 91 {
    92. 92     assert(ring_buf || buffer);
    93. 93     uint32_t len = 0;
    94. 94     size = min(size, ring_buf->size - ring_buf->in + ring_buf->out);
    95. 95     /* first put the data starting from fifo->in to buffer end */
    96. 96     len  = min(size, ring_buf->size - (ring_buf->in & (ring_buf->size - 1)));
    97. 97     memcpy(ring_buf->buffer + (ring_buf->in & (ring_buf->size - 1)), buffer, len);
    98. 98     /* then put the rest (if any) at the beginning of the buffer */
    99. 99     memcpy(ring_buf->buffer, buffer + len, size - len);
    100. 100     ring_buf->in += size;
    101. 101     return size;
    102. 102 }
    103. 103
    104. 104 uint32_t ring_buffer_len(const struct ring_buffer *ring_buf)
    105. 105 {
    106. 106     uint32_t len = 0;
    107. 107     pthread_mutex_lock(ring_buf->f_lock);
    108. 108     len = __ring_buffer_len(ring_buf);
    109. 109     pthread_mutex_unlock(ring_buf->f_lock);
    110. 110     return len;
    111. 111 }
    112. 112
    113. 113 uint32_t ring_buffer_get(struct ring_buffer *ring_buf, void *buffer, uint32_t size)
    114. 114 {
    115. 115     uint32_t ret;
    116. 116     pthread_mutex_lock(ring_buf->f_lock);
    117. 117     ret = __ring_buffer_get(ring_buf, buffer, size);
    118. 118     //buffer中没有数据
    119. 119     if (ring_buf->in == ring_buf->out)
    120. 120     ring_buf->in = ring_buf->out = 0;
    121. 121     pthread_mutex_unlock(ring_buf->f_lock);
    122. 122     return ret;
    123. 123 }
    124. 124
    125. 125 uint32_t ring_buffer_put(struct ring_buffer *ring_buf, void *buffer, uint32_t size)
    126. 126 {
    127. 127     uint32_t ret;
    128. 128     pthread_mutex_lock(ring_buf->f_lock);
    129. 129     ret = __ring_buffer_put(ring_buf, buffer, size);
    130. 130     pthread_mutex_unlock(ring_buf->f_lock);
    131. 131     return ret;
    132. 132 }
    133. 133 #endif
    复制代码
    采用多线程模拟生产者和消费者编写测试程序,如下所示:
    1. 1 /**@brief ring buffer测试程序,创建两个线程,一个生产者,一个消费者。
    2.   2  * 生产者每隔1秒向buffer中投入数据,消费者每隔2秒去取数据。
    3.   3  *@atuher Anker  date:2013-12-18
    4.   4  * */
    5.   5 #include "ring_buffer.h"
    6.   6 #include <pthread.h>
    7.   7 #include <time.h>
    8.   8
    9.   9 #define BUFFER_SIZE  1024 * 1024
    10. 10
    11. 11 typedef struct student_info
    12. 12 {
    13. 13     uint64_t stu_id;
    14. 14     uint32_t age;
    15. 15     uint32_t score;
    16. 16 }student_info;
    17. 17
    18. 18
    19. 19 void print_student_info(const student_info *stu_info)
    20. 20 {
    21. 21     assert(stu_info);
    22. 22     printf("id:%lu\t",stu_info->stu_id);
    23. 23     printf("age:%u\t",stu_info->age);
    24. 24     printf("score:%u\n",stu_info->score);
    25. 25 }
    26. 26
    27. 27 student_info * get_student_info(time_t timer)
    28. 28 {
    29. 29     student_info *stu_info = (student_info *)malloc(sizeof(student_info));
    30. 30     if (!stu_info)
    31. 31     {
    32. 32     fprintf(stderr, "Failed to malloc memory.\n");
    33. 33     return NULL;
    34. 34     }
    35. 35     srand(timer);
    36. 36     stu_info->stu_id = 10000 + rand() % 9999;
    37. 37     stu_info->age = rand() % 30;
    38. 38     stu_info->score = rand() % 101;
    39. 39     print_student_info(stu_info);
    40. 40     return stu_info;
    41. 41 }
    42. 42
    43. 43 void * consumer_proc(void *arg)
    44. 44 {
    45. 45     struct ring_buffer *ring_buf = (struct ring_buffer *)arg;
    46. 46     student_info stu_info;
    47. 47     while(1)
    48. 48     {
    49. 49     sleep(2);
    50. 50     printf("------------------------------------------\n");
    51. 51     printf("get a student info from ring buffer.\n");
    52. 52     ring_buffer_get(ring_buf, (void *)&stu_info, sizeof(student_info));
    53. 53     printf("ring buffer length: %u\n", ring_buffer_len(ring_buf));
    54. 54     print_student_info(&stu_info);
    55. 55     printf("------------------------------------------\n");
    56. 56     }
    57. 57     return (void *)ring_buf;
    58. 58 }
    59. 59
    60. 60 void * producer_proc(void *arg)
    61. 61 {
    62. 62     time_t cur_time;
    63. 63     struct ring_buffer *ring_buf = (struct ring_buffer *)arg;
    64. 64     while(1)
    65. 65     {
    66. 66     time(&cur_time);
    67. 67     srand(cur_time);
    68. 68     int seed = rand() % 11111;
    69. 69     printf("******************************************\n");
    70. 70     student_info *stu_info = get_student_info(cur_time + seed);
    71. 71     printf("put a student info to ring buffer.\n");
    72. 72     ring_buffer_put(ring_buf, (void *)stu_info, sizeof(student_info));
    73. 73     printf("ring buffer length: %u\n", ring_buffer_len(ring_buf));
    74. 74     printf("******************************************\n");
    75. 75     sleep(1);
    76. 76     }
    77. 77     return (void *)ring_buf;
    78. 78 }
    79. 79
    80. 80 int consumer_thread(void *arg)
    81. 81 {
    82. 82     int err;
    83. 83     pthread_t tid;
    84. 84     err = pthread_create(&tid, NULL, consumer_proc, arg);
    85. 85     if (err != 0)
    86. 86     {
    87. 87     fprintf(stderr, "Failed to create consumer thread.errno:%u, reason:%s\n",
    88. 88         errno, strerror(errno));
    89. 89     return -1;
    90. 90     }
    91. 91     return tid;
    92. 92 }
    93. 93 int producer_thread(void *arg)
    94. 94 {
    95. 95     int err;
    96. 96     pthread_t tid;
    97. 97     err = pthread_create(&tid, NULL, producer_proc, arg);
    98. 98     if (err != 0)
    99. 99     {
    100. 100     fprintf(stderr, "Failed to create consumer thread.errno:%u, reason:%s\n",
    101. 101         errno, strerror(errno));
    102. 102     return -1;
    103. 103     }
    104. 104     return tid;
    105. 105 }
    106. 106
    107. 107
    108. 108 int main()
    109. 109 {
    110. 110     void * buffer = NULL;
    111. 111     uint32_t size = 0;
    112. 112     struct ring_buffer *ring_buf = NULL;
    113. 113     pthread_t consume_pid, produce_pid;
    114. 114
    115. 115     pthread_mutex_t *f_lock = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
    116. 116     if (pthread_mutex_init(f_lock, NULL) != 0)
    117. 117     {
    118. 118     fprintf(stderr, "Failed init mutex,errno:%u,reason:%s\n",
    119. 119         errno, strerror(errno));
    120. 120     return -1;
    121. 121     }
    122. 122     buffer = (void *)malloc(BUFFER_SIZE);
    123. 123     if (!buffer)
    124. 124     {
    125. 125     fprintf(stderr, "Failed to malloc memory.\n");
    126. 126     return -1;
    127. 127     }
    128. 128     size = BUFFER_SIZE;
    129. 129     ring_buf = ring_buffer_init(buffer, size, f_lock);
    130. 130     if (!ring_buf)
    131. 131     {
    132. 132     fprintf(stderr, "Failed to init ring buffer.\n");
    133. 133     return -1;
    134. 134     }
    135. 135 #if 0
    136. 136     student_info *stu_info = get_student_info(638946124);
    137. 137     ring_buffer_put(ring_buf, (void *)stu_info, sizeof(student_info));
    138. 138     stu_info = get_student_info(976686464);
    139. 139     ring_buffer_put(ring_buf, (void *)stu_info, sizeof(student_info));
    140. 140     ring_buffer_get(ring_buf, (void *)stu_info, sizeof(student_info));
    141. 141     print_student_info(stu_info);
    142. 142 #endif
    143. 143     printf("multi thread test.......\n");
    144. 144     produce_pid  = producer_thread((void*)ring_buf);
    145. 145     consume_pid  = consumer_thread((void*)ring_buf);
    146. 146     pthread_join(produce_pid, NULL);
    147. 147     pthread_join(consume_pid, NULL);
    148. 148     ring_buffer_free(ring_buf);
    149. 149     free(f_lock);
    150. 150     return 0;
    151. 151 }
    复制代码
    测试结果如下所示:
    18.png





    签到签到
    回复

    使用道具 举报

  • TA的每日心情
    开心
    2024-4-10 22:38
  • 签到天数: 1335 天

    [LV.10]以坛为家III

    88

    主题

    4292

    帖子

    12

    版主

    Rank: 7Rank: 7Rank: 7

    积分
    9049
    最后登录
    2024-4-13
    发表于 2022-1-12 09:38:38 | 显示全部楼层
    我在我的项目里面也是使用的内核的这个FIFO架构。
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2024-3-26 15:16
  • 签到天数: 266 天

    [LV.8]以坛为家I

    3298

    主题

    6545

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    32004
    最后登录
    2024-4-9
     楼主| 发表于 2022-1-12 09:44:47 | 显示全部楼层
    jobszheng5 发表于 2022-1-12 09:38
    我在我的项目里面也是使用的内核的这个FIFO架构。

    看来我分享的很到位
    签到签到
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2024-4-10 22:38
  • 签到天数: 1335 天

    [LV.10]以坛为家III

    88

    主题

    4292

    帖子

    12

    版主

    Rank: 7Rank: 7Rank: 7

    积分
    9049
    最后登录
    2024-4-13
    发表于 2022-1-12 10:30:50 | 显示全部楼层
    我不知道你是否看得懂这些分享的帖子内容。
    但你的分享帖子质量确实非常高,并且覆盖了高、中、低不同的技术水平的读者。
    赞一下
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2024-3-26 15:16
  • 签到天数: 266 天

    [LV.8]以坛为家I

    3298

    主题

    6545

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    32004
    最后登录
    2024-4-9
     楼主| 发表于 2022-1-12 10:38:34 | 显示全部楼层
    jobszheng5 发表于 2022-1-12 10:30
    我不知道你是否看得懂这些分享的帖子内容。
    但你的分享帖子质量确实非常高,并且覆盖了高、中、低不同的技 ...

    最近在学OpenVINO,会看看一些文章,平常看到好的文章会分享给大家
    签到签到
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    慵懒
    2024-4-9 17:01
  • 签到天数: 1478 天

    [LV.10]以坛为家III

    203

    主题

    2万

    帖子

    64

    超级版主

    Rank: 8Rank: 8

    积分
    92609
    最后登录
    2024-4-9
    发表于 2022-1-12 10:47:41 | 显示全部楼层
    NXP管管 发表于 2022-1-12 10:38
    最近在学OpenVINO,会看看一些文章,平常看到好的文章会分享给大家

    大佬厉害啊~~
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    慵懒
    2024-4-9 17:01
  • 签到天数: 1478 天

    [LV.10]以坛为家III

    203

    主题

    2万

    帖子

    64

    超级版主

    Rank: 8Rank: 8

    积分
    92609
    最后登录
    2024-4-9
    发表于 2022-1-12 10:48:17 | 显示全部楼层
    我记得好像LPC54608的CAN报文接收模式中,好像也可以配置成使用环形FIFO
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2024-4-10 22:38
  • 签到天数: 1335 天

    [LV.10]以坛为家III

    88

    主题

    4292

    帖子

    12

    版主

    Rank: 7Rank: 7Rank: 7

    积分
    9049
    最后登录
    2024-4-13
    发表于 2022-1-12 14:05:58 | 显示全部楼层
    学习OpenVINO?!
    管管这是啥身份啊!?
    要不要来点DPDK啊~~
    该会员没有填写今日想说内容.
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2020-12-18 10:54
  • 签到天数: 8 天

    [LV.3]偶尔看看II

    40

    主题

    262

    帖子

    0

    高级会员

    Rank: 4

    积分
    977
    最后登录
    2024-3-10
    发表于 2022-1-14 13:17:46 | 显示全部楼层
    不错,谢谢分享~~~
    哎...今天够累的,签到来了~
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2024-3-26 15:16
  • 签到天数: 266 天

    [LV.8]以坛为家I

    3298

    主题

    6545

    帖子

    0

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    32004
    最后登录
    2024-4-9
     楼主| 发表于 2022-1-14 13:26:50 | 显示全部楼层
    jobszheng5 发表于 2022-1-12 14:05
    学习OpenVINO?!
    管管这是啥身份啊!?
    要不要来点DPDK啊~~

    想跟着大家一起进步
    签到签到
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /4 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2024-4-20 20:31 , Processed in 0.148205 second(s), 28 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表