嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)驱动程序基石(下)

简介: 嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十五)驱动程序基石

1.7.1.3 其他函数

1670925522111.jpg

1.7.2 编程、上机


1.7.3 内部机制


初学者知道work_struct中的函数是运行于内核线程的上下文,这就足够了。

在2.xx版本的Linux内核中,创建workqueue时就会同时创建内核线程;

在4.xx版本的Linux内核中,内核线程和workqueue是分开创建的,比较复杂。


1.7.3.1 Linux 2.x的工作队列创建过程


代码在kernel\workqueue.c中:

init_workqueues
keventd_wq = create_workqueue("events");
    __create_workqueue((name), 0, 0)
        for_each_possible_cpu(cpu) {
            err = create_workqueue_thread(cwq, cpu);
                    p = kthread_create(worker_thread, cwq, fmt, wq->name, cpu);


对于每一个CPU,都创建一个名为“events/X”的内核线程,X从0开始。

在创建workqueue的同时创建内核线程。

1670925551103.jpg


1.7.3.2 Linux 4.x的工作队列创建过程


Linux4.x中,内核线程和工作队列是分开创建的。

先创建内核线程,代码在kernel\workqueue.c中:

init_workqueues
/* initialize CPU pools */
for_each_possible_cpu(cpu) {
    for_each_cpu_worker_pool(pool, cpu) {
         /* 对每一个CPU都创建2个worker_pool结构体,它是含有ID的 */
         /*  一个worker_pool对应普通优先级的work,第2个对应高优先级的work */
}
/* create the initial worker */
for_each_online_cpu(cpu) {
    for_each_cpu_worker_pool(pool, cpu) {
        /* 对每一个CPU的每一个worker_pool,创建一个worker */ 
/* 每一个worker对应一个内核线程 */
        BUG_ON(!create_worker(pool));
    }
}


create_worker函数代码如下:

1670925577008.jpg

创建好内核线程后,再创建workqueue,代码在kernel\workqueue.c中:

init_workqueues
system_wq = alloc_workqueue("events", 0, 0);
    __alloc_workqueue_key
        wq = kzalloc(sizeof(*wq) + tbl_size, GFP_KERNEL);  // 分配workqueue_struct
        alloc_and_link_pwqs(wq) // 跟worker_poll建立联系

1670925592590.jpg

一开始时,每一个worker_poll下只有一个线程,但是系统会根据任务繁重程度动态创建、销毁内核线程。所以你可以在work中打印线程ID,发现它可能是变化的。


参考文章:

https://zhuanlan.zhihu.com/p/91106844

https://www.cnblogs.com/vedic/p/11069249.html

https://www.cnblogs.com/zxc2man/p/4678075.html


1.8 中断的线程化处理


使用GIT命令载后,本节源码位于这个目录下:

01_all_series_quickstart\
05_嵌入式Linux驱动开发基础知识\source\
06_gpio_irq\
    10_read_key_irq_poll_fasync_block_timer_tasklet_workqueue_threadedirq


请先回顾《18.2.7 新技术:threaded irq》。

复杂、耗时的事情,尽量使用内核线程来处理。上节视频介绍的工作队列用起来挺简单,但是它有一个缺点:工作队列中有多个work,前一个work没处理完会影响后面的work。解决方法有很多种,比如干脆自己创建一个内核线程,不跟别的work凑在一块了。在Linux系统中,对于存储设备比如SD/TF卡,它的驱动程序就是这样做的,它有自己的内核线程。

对于中断处理,还有另一种方法:threaded irq,线程化的中断处理。中断的处理仍然可以认为分为上半部、下半部。上半部用来处理紧急的事情,下半部用一个内核线程来处理,这个内核线程专用于这个中断。

内核提供了这个函数:

1670925614274.jpg

你可以只提供thread_fn,系统会为这个函数创建一个内核线程。发生中断时,系统会立刻调用handler函数,然后唤醒某个内核线程,内核线程再来执行thread_fn函数。


1.8.1 内核机制


1.8.1.1 调用request_threaded_irq后内核的数据结构

1670925629414.jpg


1.8.1.2 request_threaded_irq


request_threaded_irq函数,肯定会创建一个内核线程。

源码在内核文件kernel\irq\manage.c中,

int request_threaded_irq(unsigned int irq, irq_handler_t handler,
    irq_handler_t thread_fn, unsigned long irqflags,
    const char *devname, void *dev_id)
{
    // 分配、设置一个irqaction结构体
  action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
  if (!action)
  return -ENOMEM;
  action->handler = handler;
  action->thread_fn = thread_fn;
  action->flags = irqflags;
  action->name = devname;
  action->dev_id = dev_id;
    retval = __setup_irq(irq, desc, action);  // 进一步处理
}


__setup_irq函数代码如下(只摘取重要部分):

if (new->thread_fn && !nested) {
ret = setup_irq_thread(new, irq, false);
setup_irq_thread函数代码如下(只摘取重要部分):
  if (!secondary) {
  t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
       new->name);
  } else {
  t = kthread_create(irq_thread, new, "irq/%d-s-%s", irq,
       new->name);
  param.sched_priority -= 1;
  }
new->thread = t;


1.8.1.3 中断的执行过程


对于GPIO中断,我使用QEMU的调试功能找出了所涉及的函数调用,其他板子可能稍有不同。

调用关系如下,反过来看:

Breakpoint 1, gpio_keys_gpio_isr (irq=200, dev_id=0x863e6930) at drivers/input/keyboard/gpio_keys.c:393
393 {
(gdb) bt
#0  gpio_keys_gpio_isr (irq=200, dev_id=0x863e6930) at drivers/input/keyboard/gpio_keys.c:393
#1  0x80270528 in __handle_irq_event_percpu (desc=0x8616e300, flags=0x86517edc) at kernel/irq/handle.c:145
#2  0x802705cc in handle_irq_event_percpu (desc=0x8616e300) at kernel/irq/handle.c:185
#3  0x80270640 in handle_irq_event (desc=0x8616e300) at kernel/irq/handle.c:202
#4  0x802738e8 in handle_level_irq (desc=0x8616e300) at kernel/irq/chip.c:518
#5  0x8026f7f8 in generic_handle_irq_desc (desc=<optimized out>) at ./include/linux/irqdesc.h:150
#6  generic_handle_irq (irq=<optimized out>) at kernel/irq/irqdesc.c:590
#7  0x805005e0 in mxc_gpio_irq_handler (port=0xc8, irq_stat=2252237104) at drivers/gpio/gpio-mxc.c:274
#8  0x805006fc in mx3_gpio_irq_handler (desc=<optimized out>) at drivers/gpio/gpio-mxc.c:291
#9  0x8026f7f8 in generic_handle_irq_desc (desc=<optimized out>) at ./include/linux/irqdesc.h:150
#10 generic_handle_irq (irq=<optimized out>) at kernel/irq/irqdesc.c:590
#11 0x8026fd0c in __handle_domain_irq (domain=0x86006000, hwirq=32, lookup=true, regs=0x86517fb0) at kernel/irq/irqdesc.c:627
#12 0x80201484 in handle_domain_irq (regs=<optimized out>, hwirq=<optimized out>, domain=<optimized out>) at ./include/linux/irqdesc.h:168
#13 gic_handle_irq (regs=0xc8) at drivers/irqchip/irq-gic.c:364
#14 0x8020b704 in __irq_usr () at arch/arm/kernel/entry-armv.S:464


我们只需要分析__handle_irq_event_percpu函数,它在kernel\irq\handle.c中:

1670925723310.jpg


线程的处理函数为irq_thread,代码在kernel\irq\handle.c中:

1670925731483.jpg


1.8.2 编程、上机


调用request_threaded_irq函数注册中断,调用free_irq卸载中断。

从前面可知,我们可以提供上半部函数,也可以不提供:

① 如果不提供

内核会提供默认的上半部处理函数:irq_default_primary_handler,它是直接返回IRQ_WAKE_THREAD。

② 如果提供的话

返回值必须是:IRQ_WAKE_THREAD。


在thread_fn中,如果中断被正确处理了,应该返回IRQ_HANDLED。


1.9 mmap


应用程序和驱动程序之间传递数据时,可以通过read、write函数进行。这涉及在用户态buffer和内核态buffer之间传数据,如下图所示:

1670925743089.jpg

应用程序不能直接读写驱动程序中的buffer,需要在用户态buffer和内核态buffer之间进行一次数据拷贝。这种方式在数据量比较小时没什么问题;但是数据量比较大时效率就太低了。比如更新LCD显示时,如果每次都让APP传递一帧数据给内核,假设LCD采用102460032bpp的格式,一帧数据就有102460032/8=2.3MB左右,这无法忍受。

改进的方法就是让程序可以直接读写驱动程序中的buffer,这可以通过mmap实现(memory map),把内核的buffer映射到用户态,让APP在用户态直接读写。


1.9.1 内存映射现象与数据结构

假设有这样的程序,名为test.c:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int a;
int main(int argc, char **argv)
{
  if (argc != 2)
  {
  printf("Usage: %s <number>\n", argv[0]);
  return -1;
  }
  a = strtol(argv[1], NULL, 0);
  printf("a's address = 0x%lx, a's value = %d\n", &a, a);
  while (1)
  {
  sleep(10);
  }
  return 0;
}


在PC上如下编译(必须静态编译):

gcc  -o  test  test.c  -staitc


分别执行test程序2次,最后执行ps,可以看到这2个程序同时存在,这2个程序里a变量的地址相同,但是值不同。如下图:

观察到这些现象:

① 2个程序同时运行,它们的变量a的地址都是一样的:0x6bc3a0;

② 2个程序同时运行,它们的变量a的值是不一样的,一个是12,另一个是123。


疑问来了:

① 这2个程序同时在内存中运行,它们的值不一样,所以变量a的地址肯定不同;

② 但是打印出来的变量a的地址却是一样的。

怎么回事?

这里要引入虚拟地址的概念:CPU发出的地址是虚拟地址,它经过MMU(Memory Manage Unit,内存管理单元)映射到物理地址上,对于不同进程的同一个虚拟地址,MMU会把它们映射到不同的物理地址。如下图:

1670925783513.jpg

当前运行的是app1时,MMU会把CPU发出的虚拟地址addr映射为物理地址paddr1,用paddr1去访问内存。

当前运行的是app2时,MMU会把CPU发出的虚拟地址addr映射为物理地址paddr2,用paddr2去访问内存。

MMU负责把虚拟地址映射为物理地址,虚拟地址映射到哪个物理地址去?

可以执行ps命令查看进程ID,然后执行“cat /proc/325/maps”得到映射关系。

每一个APP在内核里都有一个tast_struct,这个结构体中保存有内存信息:mm_struct。而虚拟地址、物理地址的映射关系保存在页目录表中,如下图所示:

1670925794495.jpg

解析如下:

① 每个APP在内核中都有一个task_struct结构体,它用来描述一个进程;

② 每个APP都要占据内存,在task_struct中用mm_struct来管理进程占用的内存;

内存有虚拟地址、物理地址,mm_struct中用mmap来描述虚拟地址,用pgd来描述对应的物理地址。

注意:pgd,Page Global Directory,页目录。

③ 每个APP都有一系列的VMA:virtual memory

比如APP含有代码段、数据段、BSS段、栈等等,还有共享库。这些单元会保存在内存里,它们的地址空间不同,权限不同(代码段是只读的可运行的、数据段可读可写),内核用一系列的vm_area_struct来描述它们。

vm_area_struct中的vm_start、vm_end是虚拟地址。

④ vm_area_struct中虚拟地址如何映射到物理地址去?

每一个APP的虚拟地址可能相同,物理地址不相同,这些对应关系保存在pgd中。


1.9.2 ARM架构内存映射简介


ARM架构支持一级页表映射,也就是说MMU根据CPU发来的虚拟地址可以找到第1个页表,从第1个页表里就可以知道这个虚拟地址对应的物理地址。一级页表里地址映射的最小单位是1M。

ARM架构还支持二级页表映射,也就是说MMU根据CPU发来的虚拟地址先找到第1个页表,从第1个页表里就可以知道第2级页表在哪里;再取出第2级页表,从第2个页表里才能确定这个虚拟地址对应的物理地址。二级页表地址映射的最小单位有4K、1K,Linux使用4K。

一级页表项里的内容,决定了它是指向一块物理内存,还是指问二级页表,如下图:

1670925810012.jpg


1.9.2.1 一级页表映射过程


一线页表中每一个表项用来设置1M的空间,对于32位的系统,虚拟地址空间有4G,4G/1M=4096。所以一级页表要映射整个4G空间的话,需要4096个页表项。

第0个页表项用来表示虚拟地址第0个1M(虚拟地址为0~0xFFFFF)对应哪一块物理内存,并且有一些权限设置;

第1个页表项用来表示虚拟地址第1个1M(虚拟地址为0x100000~0x1FFFFF)对应哪一块物理内存,并且有一些权限设置;

依次类推。

使用一级页表时,先在内存里设置好各个页表项,然后把页表基地址告诉MMU,就可以启动MMU了。

以下图为例介绍地址映射过程:

① CPU发出虚拟地址vaddr,假设为0x12345678

② MMU根据vaddr[31:20]找到一级页表项:

虚拟地址0x12345678是虚拟地址空间里第0x123个1M,所以找到页表里第0x123项,根据此项内容知道它是一个段页表项。

段内偏移是0x45678。

③ 从这个表项里取出物理基地址:Section Base Address,假设是0x81000000

④ 物理基地址加上段内偏移得到:0x81045678

所以CPU要访问虚拟地址0x12345678时,实际上访问的是0x81045678的物理地址

1670925820741.jpg


1.9.2.2 二级页表映射过程


首先设置好一级页表、二级页表,并且把一级页表的首地址告诉MMU。


以下图为例介绍地址映射过程:

① CPU发出虚拟地址vaddr,假设为0x12345678

② MMU根据vaddr[31:20]找到一级页表项:

虚拟地址0x12345678是虚拟地址空间里第0x123个1M,所以找到页表里第0x123项。根据此项内容知道它是一个二级页表项。

③ 从这个表项里取出地址,假设是address,这表示的是二级页表项的物理地址;

④ vaddr[19:12]表示的是二级页表项中的索引index即0x45,在二级页表项中找到第0x45项;

⑤ 二级页表项格式如下:

1670925829413.jpg

里面含有这4K或1K物理空间的基地址page base addr,假设是0x81889000:

它跟vaddr[11:0]组合得到物理地址:0x81889000 + 0x678 = 0x81889678。

所以CPU要访问虚拟地址0x12345678时,实际上访问的是0x81889678的物理地址

1670925840893.jpg


1.9.3 怎么给APP新建一块内存映射


1.9.3.1 mmap调用过程


从上面内存映射的过程可以知道,要给APP新开劈一块虚拟内存,并且让它指向某块内核buffer,我们要做这些事:

① 得到一个vm_area_struct,它表示APP的一块虚拟内存空间;

很幸运,APP调用mmap系统函数时,内核就帮我们构造了一个vm_area_stuct结构体。里面含有虚拟地址的地址范围、权限。

② 确定物理地址:

你想映射某个内核buffer,你需要得到它的物理地址,这得由你提供。

③ 给vm_area_struct和物理地址建立映射关系:

也很幸运,内核提供有相关函数。

APP里调用mmap时,导致的内核相关函数调用过程如下:

1670925858401.jpg


1.9.3.2 cache和buffer


本小节参考:

ARM的cache和写缓冲器(write buffer)

请点击


使用mmap时,需要有cache、buffer的知识。下图是CPU和内存之间的关系,有cache、buffer(写缓冲器)。Cache是一块高速内存;写缓冲器相当于一个FIFO,可以把多个写操作集合起来一次写入内存。

1670925877132.jpg

程序运行时有“局部性原理”,这又分为时间局部性、空间局部性。

① 时间局部性:

在某个时间点访问了存储器的特定位置,很可能在一小段时间里,会反复地访问这个位置。

② 空间局部性:

访问了存储器的特定位置,很可能在不久的将来访问它附近的位置。


而CPU的速度非常快,内存的速度相对来说很慢。CPU要读写比较慢的内存时,怎样可以加快速度?根据“局部性原理”,可以引入cache


① 读取内存addr处的数据时:

先看看cache中有没有addr的数据,如果有就直接从cache里返回数据:这被称为cache命中。

如果cache中没有addr的数据,则从内存里把数据读入,注意:它不是仅仅读入一个数据,而是读入一行数据(cache line)。

而CPU很可能会再次用到这个addr的数据,或是会用到它附近的数据,这时就可以快速地从cache中获得数据。


② 写数据:

CPU要写数据时,可以直接写内存,这很慢;也可以先把数据写入cache,这很快。

但是cache中的数据终究是要写入内存的啊,这有2种写策略:

a. 写通(write through):

数据要同时写入cache和内存,所以cache和内存中的数据保持一致,但是它的效率很低。能改进吗?可以!使用“写缓冲器”:cache大哥,你把数据给我就可以了,我来慢慢写,保证帮你写完。

有些写缓冲器有“写合并”的功能,比如CPU执行了4条写指令:写第0、1、2、3个字节,每次写1字节;写缓冲器会把这4个写操作合并成一个写操作:写word。对于内存来说,这没什么差别,但是对于硬件寄存器,这就有可能导致问题。

所以对于寄存器操作,不会启动buffer功能;对于内存操作,比如LCD的显存,可以启用buffer功能。


b. 写回(write back):

新数据只是写入cache,不会立刻写入内存,cache和内存中的数据并不一致。

新数据写入cache时,这一行cache被标为“脏”(dirty);当cache不够用时,才需要把脏的数据写入内存。

使用写回功能,可以大幅提高效率。但是要注意cache和内存中的数据很可能不一致。这在很多时间要小心处理:比如CPU产生了新数据,DMA把数据从内存搬到网卡,这时候就要CPU执行命令先把新数据从cache刷到内存。反过来也是一样的,DMA从网卡得过了新数据存在内存里,CPU读数据之前先把cache中的数据丢弃。


是否使用cache、是否使用buffer,就有4种组合(Linux内核文件arch\arm\include\asm\pgtable-2level.h):

1670925889938.jpg

上面4种组合对应下表中的各项,一一对应(下表来自s3c2410芯片手册,高架构的cache、buffer更复杂,但是这些基础知识没变):

1670925899932.jpg

1670925906230.jpg

第1种是不使用cache也不使用buffer,读写时都直达硬件,这适合寄存器的读写。

第2种是不使用cache但是使用buffer,写数据时会用buffer进行优化,可能会有“写合并”,这适合显存的操作。因为对显存很少有读操作,基本都是写操作,而写操作即使被“合并”也没有关系。

第3种是使用cache不使用buffer,就是“write through”,适用于只读设备:在读数据时用cache加速,基本不需要写。

第4种是既使用cache又使用buffer,适合一般的内存读写。


1.9.3.3 驱动程序要做的事


驱动程序要做的事情有3点:

① 确定物理地址

② 确定属性:是否使用cache、buffer

③ 建立映射关系


参考Linux源文件,示例代码如下:

1670925939599.jpg

还有一个更简单的函数:

1670925947475.jpg


1.9.4 编程


使用GIT命令载后,本节源码位于这个目录下:

01_all_series_quickstart\
05_嵌入式Linux驱动开发基础知识\source\
07_mmap


目的:我们在驱动程序中申请一个8K的buffer,让APP通过mmap能直接访问。


1.9.4.1 APP编程


APP怎么写?open驱动、buf=mmap(……)映射内存,直接读写buf就可以了,代码如下:

22      /* 1. 打开文件 */
23      fd = open("/dev/hello", O_RDWR);
24      if (fd == -1)
25      {
26              printf("can not open file /dev/hello\n");
27              return -1;
28      }
29
30      /* 2. mmap
31       * MAP_SHARED  : 多个APP都调用mmap映射同一块内存时, 对内存的修改大家都可以看到。
32       *               就是说多个APP、驱动程序实际上访问的都是同一块内存
33       * MAP_PRIVATE : 创建一个copy on write的私有映射。
34       *               当APP对该内存进行修改时,其他程序是看不到这些修改的。
35       *               就是当APP写内存时, 内核会先创建一个拷贝给这个APP,
36       *               这个拷贝是这个APP私有的, 其他APP、驱动无法访问。
37       */
38      buf =  mmap(NULL, 1024*8, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
39      if (buf == MAP_FAILED)
40      {
41              printf("can not mmap file /dev/hello\n");
42              return -1;
43      }


最难理解的是mmap函数MAP_SHARED、MAP_PRIVATE参数。使用MAP_PRIVATE映射时,在没有发生写操作时,APP、驱动访问的都是同一块内存;当APP发起写操作时,就会触发“copy on write”,即内核会先创建该内存块的拷贝,APP的写操作在这个新内存块上进行,这个新内存块是APP私有的,别的APP、驱动看不到。

仅用MAP_SHARED参数时,多个APP、驱动读、写时,操作的都是同一个内存块,“共享”。

MAP_PRIVATE映射是很有用的,Linux中多个APP都会使用同一个动态库,在没有写操作之前大家都使用内存中唯一一份代码。当APP1发起写操作时,内核会为它复制一份代码,再执行写操作,APP1就有了专享的、私有的动态库,在里面做的修改只会影响到APP1。其他程序仍然共享原先的、未修改的代码。

有了这些知识后,下面的代码就容易理解了,请看代码中的注释:

44
45      printf("mmap address = 0x%x\n", buf);
46      printf("buf origin data = %s\n", buf); /* old */
47
48      /* 3. write */
49      strcpy(buf, "new");
50
51      /* 4. read & compare */
52      /* 对于MAP_SHARED映射:  str = "new"
53       * 对于MAP_PRIVATE映射: str = "old"
54       */
55      read(fd, str, 1024);
56      if (strcmp(buf, str) == 0)
57      {
58              /* 对于MAP_SHARED映射,APP写的数据驱动可见
59               * APP和驱动访问的是同一个内存块
60               */
61              printf("compare ok!\n");
62      }
63      else
64      {
65              /* 对于MAP_PRIVATE映射,APP写数据时, 是写入另一个内存块(是原内存块的"拷贝")
66               */
67              printf("compare err!\n");
68              printf("str = %s!\n", str);  /* old */
69              printf("buf = %s!\n", buf);  /* new */
70      }


执行测试程序后,查看到它的进程号PID,执行这样的命令查看这个程序的内存使用情况:


cat  /proc/PIC/maps


1.9.4.2 驱动编程


驱动程序要做什么?

① 分配一块8K的内存

使用哪一个函数分配内存?

1670926027814.jpg

我们应该使用kmalloc或kzalloc,这样得到的内存物理地址是连续的,在mmap时后APP才可以使用同一个基地址去访问这块内存。(如果物理地址不连续,就要执行多次mmap了)。


② 提供mmap函数

关键在于mmap函数,代码如下:

1670926039026.jpg

要注意的是,remap_pfn_range中,pfn的意思是“Page Frame Number”。在Linux中,整个物理地址空间可以分为第0页、第1页、第2页,诸如此类,这就是pfn。假设每页大小是4K,那么给定物理地址phy,它的pfn = phy / 4096 = phy >> 12。内核的page一般是4K,但是也可以配置内核修改page的大小。所以为了通用,pfn = phy >> PAGE_SHIFT。


APP调用mmap后,会导致驱动程序的mmap函数被调用,最终APP的虚拟地址和驱动程序中的物理地址就建立了映射关系。APP可以直接访问驱动程序的buffer。


1.9.4.3 上机测试


在Ubuntu中编译好驱动、测试程序,放到开发板。

在开发板上安装驱动、执行测试程序。

相关文章
|
5月前
|
网络协议 算法 Linux
【嵌入式软件工程师面经】Linux网络编程Socket
【嵌入式软件工程师面经】Linux网络编程Socket
149 1
|
3月前
|
NoSQL Linux C语言
嵌入式GDB调试Linux C程序或交叉编译(开发板)
【8月更文挑战第24天】本文档介绍了如何在嵌入式环境下使用GDB调试Linux C程序及进行交叉编译。调试步骤包括:编译程序时加入`-g`选项以生成调试信息;启动GDB并加载程序;设置断点;运行程序至断点;单步执行代码;查看变量值;继续执行或退出GDB。对于交叉编译,需安装对应架构的交叉编译工具链,配置编译环境,使用工具链编译程序,并将程序传输到开发板进行调试。过程中可能遇到工具链不匹配等问题,需针对性解决。
|
3月前
|
传感器 人工智能 网络协议
:嵌入式 Linux 及其用途
【8月更文挑战第24天】
153 0
|
4月前
|
Ubuntu 算法 Linux
嵌入式Linux的学习误区
**嵌入式Linux学习误区摘要** 1. **过度聚焦桌面Linux** - 许多学习者误将大量时间用于精通桌面Linux系统(如RedHat、Fedora、Ubuntu),认为这是嵌入式Linux开发的基石。 - 实际上,桌面Linux仅作为开发工具和环境,目标不应是成为Linux服务器专家,而应专注于嵌入式开发工具和流程。 2. **盲目阅读Linux内核源码** - 初学者在不了解Linux基本知识时试图直接研读内核源码,这往往导致困惑和挫败感。 - 在具备一定嵌入式Linux开发经验后再有针对性地阅读源码,才能有效提升技能。
41 4
|
5月前
|
Linux
【GEC6818开发板】Linux驱动中printk无法在终端输出显示
【GEC6818开发板】Linux驱动中printk无法在终端输出显示
|
5月前
|
消息中间件 安全 Java
【嵌入式软件工程师面经】Linux多进程与多线程
【嵌入式软件工程师面经】Linux多进程与多线程
62 1
|
8天前
|
运维 安全 Linux
Linux中传输文件文件夹的10个scp命令
【10月更文挑战第18天】本文详细介绍了10种利用scp命令在Linux系统中进行文件传输的方法,涵盖基础文件传输、使用密钥认证、复制整个目录、从远程主机复制文件、同时传输多个文件和目录、保持文件权限、跨多台远程主机传输、指定端口及显示传输进度等场景,旨在帮助用户在不同情况下高效安全地完成文件传输任务。
83 5
|
8天前
|
Linux
Linux系统之expr命令的基本使用
【10月更文挑战第18天】Linux系统之expr命令的基本使用
35 4
|
5天前
|
运维 监控 网络协议
|
6天前
|
监控 Linux Shell