1. 打补丁,patch -p1 < ../linux-2.6.22.6.patch 。 -p1 这个命令参数的意思是忽略补丁文件中目录的第一个 / 之前的内容。后面参数的意思是把指定目录中的 patch 文件打到当前目录。

  2. 配置。可以使用 find -name "*.defconfig" 查找一下内核当前支持的一些 board 的配置。找到 s3c2410_defconfig 比较相近,就执行 make s3c2410_defconfig 进行配置,这时候 s3c2410_defconfig 里面的配置就写入到 .config 文件中了。然后使用 make menuconfig 在 s3c2410_defconfig 的基础上进行修改。一般内核先配置 make s3c2410_defconfig,然后编译 make menuconfig。有时候厂家会提供 config_厂家,这时候只需 cp config_厂家 .config,然后 make menuconfig 就可以了。

  3. 编译内核直接用 make,但是给 uboot 用的内核需要用 make uImage 来编译,这个和正常的内核相比,多了一个头部内容。适合给 uboot 引导使用。

  4. uboot 中烧录内核使用 k 命令,这个命令具体可以在 cmd_menu.c 中找到 k 的具体内容, usbslave 1 0x30000000, nand erase kernel, nand write.jffs2 0x30000000 kernel (filesize)。 先接受内核到 0x30000000 内存中,然后擦除 nand 中 kernel 分区,然后从 0x30000000 内存地址处读取数据,写入 nand 中的 kernel 分区,写入(filesize) 大小。

  5. 如果删除了 root 分区,那么 内核启动到一定程度的时候,就会卡在那边。

  6. 内核编译的时候,根据 .config 生成 include/linux/autoconf.h 这个里面是对各个配置生成的详细的头文件,里面的 CONFIG_ 给各个 C语言的源码使用。.config 中设置为 y 或者 m,在 autoconfig.h 中宏定义就都是 1. 而 y 和 m 的区别体现在 子目录的 makefile 里面,比如说 drivers/net/makefile。在内核子目录中的 makefile 里面,配置为 y 和 m 是不同的, obj_y+= 这个会编译到内核里面去, obj_m+= 这个会编译为模块 ko 给内核加载。 配置还会出现在 include/config/auto.conf 中。配置的 y 和 m 先在 auto.conf 中自动生成好。

make uImage 时候,首先根据 .config 生成 autoconfig.h 给源代码使用,生成 auto.conf 给 顶层 makefile 包含,给子目录的 makefile 使用。

  1. linux-版本号 / Doc / kbuild 这里面的 makefile.txt 里面有内核 makefile 的详细讲解。 如果要编译为一个模块,类似的写法是:

    obj_m += ab.o
    ab_objs := a.o b.o

    这样 a.c, b.c 两个文件就会被先编译为 a.o, b.o,然后被链接为 ab.ko 这个模块。

  2. 当使用 make uImage 的时候,会使用顶层的 makefile,顶层 makefile 里面有 include (srctree)/arch/(ARCH)/Makefile , 这样就包含了 arm 这样架构目录下面的 makefile。顶层 makefile 里面还有 include /config/auto.conf,这样就包含了配置。 uImage 的依赖是 vmlinux, vmlinux 是真正的内核文件,加上了头部之后,才是 uImage。如果直接就是 make 的话,那么就是 all,all 依赖的也是 vmlinux。 make uImage V=1 这个命令是让编译时候打印出来的信息更加详细。打印信息中的 ld -T 后面的 lds 文件是具体的链接脚本。第一个编译的文件是 arch/arm/kernel/head.o ,这个是 head.S 汇编文件,链接脚本是 arch/arm/kernel/vmlinux.lds

tq2440 里面需要编译的是 zImage,然后使用配套的 uboot,可以直接下载 zImage 内核并启动。 tq2440 make zImage 的时候,会提示错误 Can't use 'defined(@array)' (Maybe you should just omit the defined()?) at kernel/timeconst.pl line 373. /opt/ARM/mini6410/linux/linux-2.6.38/kernel/Makefile:140: recipe for target 'kernel/timeconst.h' failed make[1]: [kernel/timeconst.h] Error 255 Makefile:916: recipe for target 'kernel' failed make: [kernel] Error 2

报错信息提示我们文件kernelkernel/timeconst.pl的第373不能使用'defined(@array)',将kernel/timeconst.pl中第373行的defined()去掉只留下@val就可以了. 考虑去掉defined(),改为: 372         @val = @{canned_values{hz}}; 373         if (!@val) { 374                 @val = compute_values($hz); 375         }

关于 zImage 和 uImage 的差别,可以参考 https://www.cnblogs.com/linhaostudy/p/6735697.html

  1. compress 目录下面的 head.S 是为了压缩内核的。如果内核过大,可以压缩内核,然后在内核前面加上自解压代码,这样组合起来变成一个小一点的内核。运行的时候,先自解压,之后再执行内核。

  2. lookup_machine_type 中的
    
    3:      .long .
         .long arch_info_begin
         .long arch_info_end

adr r3, 3b @ 让 r3 等于 标号3所在地方的物理地址。 ldmia r3, {r4, r5, r6} @ 让 r4 等于 r3 的虚拟地址,也就是 标号3 的虚拟地址, 让 r5 等于 arch_info_begin, r6 等于 arch_info_end. sub r3, r3, r4 @ r3 等于虚拟地址和物理地址之间的偏移 add r5, r5, r3 @ r5 是对应的 物理地址 add r6, r6, r3 @ r6 是对应的 物理地址。


11. head.S 中内核启动时,首先判断是否支持 cpu,然后判断是否支持 单板(这个通过 u-boot 中 执行内核的 kernel 函数调用时传入的 machine_id),然后建立页表, 使能 mmu,然后跳转 start_kernel 来处理 u-boot 传入的启动参数。

start_kernel setup_arch //解析 u-boot 传入的启动参数 setup_command_line //解析 u-boot 传入的启动参数 parse_early_param do_early_param 从 setup_start 到 setup_end,调用 early 函数。 unknown_bootoption obsolute_checksetup 从 setup_start 到 setup_end,调用非 early 函数。 rest_init kernel_init prepare_namespace mount_root 这样就能挂载根文件系统了。 init_post 这个函数里面 打开 console,然后执行 init 等应用程序


挂载根文件系统按照 u-boot 中的 boot_args 这个参数来做。 uboot 中的 boot_args 里面的 root=/dev/mtdblock3 是和 linux 中的 arch/arm/plat-s3c24xx 中的 common-smdk.c 定义的 smdk_default_nand_part[] 这个分区结构体数组对应的。

使用 patch 命令打补丁, -p1 标识忽略第一级目录。 在串口上使用 q 命令,推出菜单界面,然后 print 可以打印出环境变量。想要返回菜单,只要输入 menu 即可。输入 ? 打印出可选命令。输入多条命令,可以使用分号隔开。输入 help 是各个命令的 usage 短帮助信息,输入 help print 这样指定命令,就会出现 help 长帮助信息。使用 set 命令修改环境变量,使用 save 保存环境变量,使用 reset 复位。boot 命令可以启动内核。 make 最后打印出来的信息是可以看到 ld 的具体命令,里面包含了链接地址,这个就是 sdram 中的位置。 start.s 汇编文件里面包含了启动汇编。 增加 uboot 命令,只需要在源文件中增加 类似于 do_bootm 这样的函数 和 U_BOOT_CMD 这样的宏,然后修改相应文件夹下的 makefile,增加对应的 obj 即可。然后 make,实在不行就 make disclean, make xxx_config, make。 linux 分区: boot, env, kernel, root ,具体分区在源码里面写死。MTDPARTS_DEFAULT 这个地方定义的,包含在单板头文件里面。使用命令 mtd 可以看到当前分区情况。do_bootm 是用来启动内核的, jffs2 这种格式不要求对齐,其他格式可能需要各种对齐。

flash 上存在的内核是 uImage,这个是由头部和真正的内核两部分组成。in_load 加载地址, in_ep 入口地址。加载地址是 u-boot 需要把真正的内核移动到那个地址。普通 bootm 的时候,可能把 uImage 放到 0x30007FC0,然后前面64字节是头部,真正的内核在 0x30008000 这个位置,如果 in_load 也是这个地址,那么 u-boot 就可以省略移动内核这个步骤,否则还需要把内核移动到 in_load 的地址。真正的启动命令 do_bootm_linux, 里面的 theKernel 就等于 in_ep 入口地址,然后从这个地址开始执行内核。

u-boot 需要把一些参数给 内核,这些参数放在一些约定好的地址,然后从这些地址按照指定的格式读取,可能是 0x30000100 地址。通过 setup_start_tag(bd) 这样的方式设定,最后一个是 setup_end_tag(bd)。bd->bi_arch_number 机器地址。

  1. ram 初始化: 在 start.S 中, bl cpu_init_crit 这句,在 tq2440 中是直接调用,在韦东山里面是通过和 TEXT_BASE 进行比较,如果从 RAM 中运行就不进行 初始化。

  2. uboot 第一阶段初始化主要在 start.S 里面,主要是芯片本身初始化。第二阶段初始化是从 lib_arm/board.c 里面的 start_armboot 开始的,里面有一个 for 循环,通过函数指针调用函数,存储这些指针的结构体 gd 在 SDRAM 中的 128 字节的 CPU_GBL_DATA_SIZE 里面,相应的函数实现在 board/EmbedSky/EmbedSky.c 里面。

  3. 在 start_armboot 函数中,韦东山代码增加了一个判断,从调试器加载时,相应的处理。if (PreLoadedONRAM)

  4. command.c command.h 中有 u_boot_cmd 这个段相关的变量,最后通过 链接文件,链接到一起。然后函数执行的时候,遍历段中所有的结构体的命令,相等的就执行相应的函数。

  5. 增加 uboot 命令,可以仿照 dobootm 命令来制作自己的命令,需要提供 函数 do 和 通过宏定义来实现的结构体变量 U_BOOT_CMD。还有别忘了相应的头文件。

  6. uboot 的 清除编译过程文件是 make distclean,配置命令 make EmbedSky_config,编译是 make。

  7. uboot 读出内核,通过 nand read.jffs2 0x30007FC0 kernel 来从 nand 中的 kernel 分区里面读出内核。nand 中的 kernel 分区是在 include/configs/EmbedSky.h 中定义的,通过宏定义 MTDPARTS_DEFAULT 来定义具体的各个分区。也可以通过 uboot 的命令行模式中输入 mtd 命令来查看当前 uboot 的 分区信息。 分区的名字只是个标识不重要,主要的是分区的开始偏移地址和分区大小。

  8. 命令 nand read.jffs2 不需要页对齐,可以长度是任意的,其他的 read 命令需要对齐。

  9. uboot 的内核镜像是 uImage,由头部和真正的内核组成,头部中的 ih_load 是加载地址,ih_ep 是入口地址,uboot 通过读取头部数据,把内核放到加载地址,然后跳转到入口地址执行。

  10. 编译时链接地址 0x30007FC0,这个地址加上 64 字节的 uImage 头部内容,结果就是 0x30008000,这个就是真正的内核加载地址。这样做的好处是,因为地址能够匹配上,那么 cmd_bootm.c 中 ntohl(hdr->ih_load) == addr 成立,就不会移动内核,否则通过 memmove 函数在调用把内核移动到加载地址处,这样能够加快启动速度。

  11. do_bootm_linux 来启动内核。

  12. uboot 通过设置启动参数的方法告诉内核一些必要的参数,然后跳转到入口地址,真正的开始启动内核。

  13. ih_ep 中包含函数指针,把这个函数指针交给 theKernel,然后调用 theKernel 来启动内核。

  14. uboot 在 0x30001000 按照一定的格式存放内核参数,然后由内核来读取这些参数。 在 cmd_bootm.c 中使用 setup_start_tag(bd),setup_memory_tags(bd), setup_end_tag(bd) 这种格式来存放 tag 参数。

  15. uboot 中 armlinux.c 里面 memory_tags 里面关于 sdram 开始地址和大小,是在 board 文件中的 dram_init()中 通过 gd->bd->bi_dram[0].start, .size 来赋值的。

  16. commandline_tas() 中的 参数 commandline 来源于 getenv 中的 “bootargs",这个可以在 uboot 运行起来会直接 print 查看,也可以查看源码。root= 是根文件系统位置,init= 这个是第一个应用程序,console= 这个是信息打印的窗口。

  17. theKernel(0, bd->bi_arch_number, bd->bi_boot_parms), 第三个参数是从 0x30001000 开始的 start_tag 开始的一连串的 parms。bi_arch_number 这个是机器ID,在 board 文件里面通过 gd->bd->bi_arch_number 来定义,arch_number 不同的电路板是不一样的数值。uboot 把 arch_number 交给内核,内核检查是否支持这个 arch_number 的PCBA。如果支持,系统控制权就交给内核,后面就没有 uboot 的事情了。

  18. 在 uboot 命令行下,输入 boot,就可以通过 bootcmd 和 bootarg 两个参数来启动内核。

使用 stm32f4 调试uart 接收, 使用 空闲中断,dma 双缓冲模式,有以下几点需要注意的。

  1. 调试的时候断点不要打在 if (USART_GetITStatus(USART6, USART_IT_IDLE) != RESET) 这种语句上面,要打在 if 的代码块里面。mdk 调试的时候,会出现一些 bug,当在 if (USART_GetITStatus(USART6, USART_IT_IDLE) != RESET) 断点的时候,能够看到 idle 信号出现,但是只要向下执行,不管是单步还是怎样,下一步的时候 idle 标志位就会消失。导致进不了空闲中断处理语句。但是如果断点打在代码块里面,就能够正常进入了。

  2. 使用双缓冲的时候, DMA_DoubleBufferModeConfig, DMA_DoubleBufferModeCmd 需要放在 DMA_Cmd 前面,还有别忘了 开启 circular 模式。

  3. 接收的时候,已经接收的字节数,等于 buffersize - ndtr 的结果。

  4. 开启双缓冲的时候,可以不用 disable dma,只需要在 空闲中断里面,识别出 ndtr 和 ct,然后经过计算得出本次传送的数据个数,并把本次开始的地址和数据个数记录下来,就可以由非中断部分程序来读取这一帧接收的数据。

位置无关码

bl 是位置无关码,指令中带的数值是,编译的时候,编译器计算好的,需要跳转的位置减去 bl 指令所在位置的结果。这样当程序最开始在 4k sram 中运行的时候,跳转的位置是在 0 + offset 的位置,当后期sdram 初始化好了之后,程序移动到 sdram 中运行的时候,跳转的位置是 0x30000000 + offset 的位置。

ldr 是位置相关码,指令中的位置是编译好的绝对位置,如果 sdram 中还没有初始化好,跳转的位置是 0x30000000地址以上的值,那就出错了。

如果是 C 语言,想要某些位置无关,那么就不能用全局变量和静态变量。

长距离跳转

b 和 bl 只能前后跳转 32M的范围,想要长距离跳转需要用 ldr pc, =main 这种方式来做。一般从sram 跳转到 sdram 中执行的时候,可以用 ldr 的指令来跳转。 bl 默认lr 是下一条指令,可以直接作为子函数的调用。ldr 只是单纯的跳转,用于调用子函数的时候,必须前面先用一条 ldr lr, =int_return 之类的语句作为返回用的 lr 位置。

链接脚本地址

编译器根据链接脚本中的地址来确定程序中各个部分的地址。链接脚本允许设定一部分放到 0地址开始的地方,一部分放到 0x30000000开始的地方,也可以都放到 0地址开始的地方,或者都放到 0x30000000 开始的地方。这里有好几个需要注意的地方:

  1. 从0地址开始的地方不能超过 4k sram 的大小。
  2. 正常使用 0x30000000 部分内容之前,必须初始化 sdram,才能正常使用。不然一些函数的跳转,全局变量等等都会引发出错。
  3. 如果是使用 0x30000000 部分的代码,并且还没来得及初始化 sdram,就需要跳转或者子函数,那么一定要使用位置无关码进行跳转,否则也会出错。

位置无关配置寄存器

本来可以把配置放到数组里面,然后用 for 循环给寄存器复制。但是如果想用位置无关代码的话,那么最好一个一个直接对寄存器赋值数值。这两种汇编出来后反编译是不一样的。直接赋值,汇编里面是一句句不太一样的数值进行加加减减,最后出来想要的数据并赋值。而使用数组,想要的数值是写死在函数中一小块位置上,数值比较清晰。

issues/679

create new UUID

cat /proc/sys/kernel/random/uuid

example config : multi port , multi user

vim /etc/v2???/config.json

{
  "inbounds": [{
    "port": 1234,
    "protocol": "vmess",
    "settings": {
      "clients": [
        {
          "id": "UUID",
          "level": 1,
          "alterId": 64
        },
        {
          "id": "UUID",
          "level": 1,
          "alterId": 64
        }
      ]
    }
  },
 {
    "port": 1235,
    "protocol": "vmess",
    "settings": {
      "clients": [
        {
          "id": "UUID",
          "level": 1,
          "alterId": 64
        },
        {
          "id": "UUID",
          "level": 1,
          "alterId": 64
        }
      ]
    }
  }],
  "outbounds": [{
    "protocol": "freedom",
    "settings": {}
  },{
    "protocol": "blackhole",
    "settings": {},
    "tag": "blocked"
  }],
  "routing": {
    "rules": [
      {
        "type": "field",
        "ip": ["geoip:private"],
        "outboundTag": "blocked"
      }
    ]
  }
}

最后

v2??? restart 

重启 v2 的服务即可。

按照韦东山的视频中 sdram的裸机代码,写了一份,通过 minitools 下载到 0x30000000,然后烧录到 nand中,接过不能正常运行。 尝试过多种方法后,只有一种解决方法,就是不要用 0x30000000这个地址。 可以把 makefile 中的地址改为0x30008000,minitools 中下载的地址也改为 0x30008000,这样就可以正常的运行了。 可能 0x30000000 前面的一些内存被使用了?

后来换了一个 TQ2440的开发板,不使用 minitools,而是用 DNW配合bootload下载程序到nand,程序链接地址选择 0x30000000 是可以运行的。使用 DNW 驱动使用 韦东山的 zadig程序,安装 libusb-win32 驱动,然后使用 dnw_100ask.exe 来下载程序。

所以初步怀疑,这个现象可能和 minitools 软件相关。

汇编指令中 bne label 这条指令有以下两种特别的写法:bne 1b, bne 1f.

bne 1b 指的是 backward,倒退寻找标号为 1 的地方并跳转。

同样也有 bne 1f,值得是 forward,向前寻找标号为1的地方并跳转。

最近使用 mdk526,编辑设置使用 utf-8,编辑窗口中文正常,但是编译的时候提示 warning: #870-D: invalid multibyte character sequence,解决的方法很简单,在编辑选项 misc 里面填写 --no_multibyte_chars 或者 locale=english,都可以顺利编译。

但是编译完成之后还有一个烦心的事情,市面上很多串口工具的中文都是 gbk 编码的,不能识别 utf-8 的串口中文,需要专门找支持的串口工具。

找来找去,找到一个 teraterm-4.97.exe,这个是支持中文的,只需要如下修改即可。

Setup->Terminal
locale: chinese
CodePage: 936

关闭迅雷极速版升级提示 Step 1: 退出迅雷极速版,进入迅雷安装目录下的 Data 目录,删除 ThunderPush 文件夹。 Step 2: 在 Data 目录下新建一个文本文件,并命名为 ThunderPush,删除后缀名 .txt。(需要勾选显示 “文件的扩展名”)

Step 3: 右键 ThunderPush 文件 -> 属性,勾选 “只读”;安全 -> 编辑 -> 完全控制勾选 “拒绝”,应用。

完成!再打开迅雷极速版应该就不会弹出升级提示弹窗了。o( ̄▽ ̄)d 关闭迅雷自启服务 如果查看任务管理器的话,我们会发现,开机后无论迅雷是否运行,都会自动启动 ThunderPlatform.exe 进程和 XLServicePlatform 服务。

如果在使用迅雷的话,运行两个程序没啥问题。但你说咱们没打开迅雷,它默默地在后台跑这两个程序占用资源干什么呢…… 打开记事本输入下面代码,保存为 killxltask.bat,然后放入 [C:\Users\用户名\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup] 开机自启文件夹中。 net stop XLServicePlatform taskkill /F /im ThunderPlatform.exe 开机后会自动运行该脚本,清除这两个程序。

或者把文件夹里有个“OnlineInstall.exe”的文件删除掉,简历一个假的并禁止访问应该也可以。

今天在 写了个简单的 led 的汇编程序,下载到 mini2440 的 nand flash 里面可以正常运行,但是下载到 sdram 里面不能运行。

后来发现有几个注意点,

  1. 要在 sdram 中运行,链接脚本的地址不能像 nand 里面一样是0,必须是 sdram 里面的地址,比如说 0x30000000.
  2. 下载到 ram 中的地址必须和 链接脚本的地址一致。
  3. 0x30000000 这个地址不行,0x30008000 这个地址肯定没有问题,0x30000020 超过这个地址,也都可以运行,0x3000001c 这个地址不能运行,但是 minitools 会断掉链接后自动链接上,其他小于 0x30000020 的地址,都是没有运行反应的。

具体是什么原因,还要后续进一步的调试了。

但是也不是每个程序都能在 sdram 中运行,具体情况暂时还不清楚,尽量还是烧录到 nand 中在运行,不容易出问题。