0%

Ubuntu配置VNC远程桌面连接Windows电脑

  • 参考指南
  • 考虑安装xrdp,通过sudo apt-get install xrdp安装
  • 安装好之后再安装deconf-editor,也就是sudo apt-get install dconf-editor安装
  • deconf-editor中关闭org-gnome-desktop-remote-access下面的requlre-encryption即可
  • 安装好之后配置vnc服务的开机自启动
  • 参考这篇博客
  • https://blog.csdn.net/qq_40723748/article/details/120013808
  • 主要就是要在/etc/systemd/user目录下创建一个systemd服务文件, 命名为user-defined.service
  • 这个文件具有固定的格式,如下
[Unit]
After=network.service

[Service]
ExecStart=/home/<username>/<usershell>.sh
ExecStop=echo "anbox-session-manager is running"

[Install]
WantedBy=default.target
  • 其中将usernameusershell替换为自己的用户名和新建的脚本
  • 自己新建的脚本的第一行一定要注意带有#!/bin/bash
  • 这个脚本启动过程中出错的话是不会报错的,比较难debug
  • 然后使用sudo chmod赋予自己新建的.sh文件和user-defined.service权限
sudo chmod 744 ~/test.sh
sudo chmod 664 /etc/systemd/user/user-defined.service
  • 然后调用以下两行刷新系统服务
sudo systemctl daemon-reload
systemctl --user enable user-defined.service
  • 如果上述方法不行的话采用这篇博客的内容博客
  • 将service文件创建到/etc/systemd/system/user-defined.service
  • 然后开启服务直接sudo systemctl enable user-defined.service即可,避免报错
  • 该服务不可以简单的用系统的Service跟随Network启动,因为此时桌面还没有启动,会导致VNC找不到用户界面而启动失败

    使用/etc/init/<文件名>.conf

  • 参考

然后使用windows自带的远程桌面连接,在系统没有登陆的前提下进行远程桌面,否则会黑屏

  • image-20220303180321664

  • 此处保持设置不变,输入linux的用户名和密码

  • 注意,假如卡顿的话可以修改设置为

  • 点击最下的显示选项,点击体验,修改为LAN,会变流畅一些

  • image-20220303180359964

  • sudo reboot之后远程主机不需要登陆直接链接即可

  • image-20220303181038496

  • image-20220303181046174

如果Ubuntu没有自带VNC远程桌面

Ubuntu端

  • 参考
  • 安装x11vnc sudo apt-get install x11vnc
  • 然后设置密码 x11vnc -storepasswd
  • 将其保存到~/.vnc/passwd之后即可
  • 给予文件~/.vnc/passwd全部权限(777)即可不需要sudo也可以使用x11vnc的密码
    • x11vnc -forever -shared -rfbauth ~/.vnc/passwd -rfbport 5901
    • 上述语句中指定了端口为5901,也可以自己修改为合适的端口

      Nvidia Jetson设备

  • 参考

    Windows端

  • VNCViewer官网下载软件 此处
  • 找到对应的端口链接即可
  • 解决部分Windows防火墙导致的904问题参考
  • picture 1

    sudo不需要密码

  • 参考

    Linux执行一次性任务和循环任务

  • 一次性任务参考at
  • 循环任务使用cron
  • 参考
  • cron参考
  • at参考
  • 查看Linux的任务(service):
    • systemctl -l

      实现Linux开机延时启动

  • at非交互式添加任务参考
  • echo "需要执行的shell命令" | at 时间
  • 一个可能的思路:
    • user-defined.service中执行一个shell脚本(注意第一行必须是#!/bin/bash) ,这个脚本执行一个非交互式向at添加命令的语句(带有一定的延迟),但是这种方法可能并不成功,因为at的时间有一些问题
    • 之所以不直接把这句话放在service里执行是可能会影响echo的解析导致不能正确执行
    • 总之不建议在service里直接执行脚本
  • 但是x11vnc似乎并不支持开机自带启动,否则会导致权限问题(即使密码文件已经给了777权限
  • 修改参考
  • 验证是否启动的方法
    • sudo netstat -an | grep <端口号>
    • ps -aux|grep vnc

      Linux的target

  • 参考
  • 查看依赖关系systemctl list-dependencies multi-user.target

在固态和机械盘上安装Ubuntu

注意事项

  • 挂载系统不同分区的时候,注意/和/home的空间大小是按照/来计算的,也就是说假如/home是另外挂载在容量更大的机械硬盘上的话,是无法使用的,同样会因为空间超出而导致无法继续使用

系统映像烧写

  • 使用UltraISO点击此处打开.iso文件
  • image-20220302114757775
  • 打开之后选择启动–写入硬盘映像
  • image-20220302114830381
  • 然后选择插入的U盘
  • image-20220302114901025
  • 开始写入即可

磁盘空间划分

  • 在C盘划分大约40G的空闲空间,在机械盘划分150G的空闲空间,然后开始安装

进入BIOS并且使用U盘启动

  • 我的电脑是微星主板,按下开机键之后连续按下delete直到进入BIOS界面

  • 无论是简易模式还是高级模式都能找到这个拖动条

  • image-20220302115447406

  • 用鼠标将U盘拖动到第一个即可

  • 点击右上角x保存并退出

  • 选择normal installation

  • 点击continue,等待一段时间

  • 此处选择something else

  • image-20220302115923889

  • 首先选中free space ,划分/boot分区512M即可

  • image-20220302120523616

  • 此时划分交换空间8G

  • image-20220302120719950

  • C盘的剩余空间划分给系统/目录

  • image-20220302121047750

  • 机械盘上的空间划分给/home

  • image-20220302121132920

  • 注意,device for boot loader installation选择/boot所在的分区,否则有可能安装好后系统无法启动

  • image-20220302122927538


  • 有时候,安装好Ubuntu之后,微星主板启动的时候还是会直接进入Windows,无法进入Ubuntu,这是因为硬盘启动项的原因

  • image-20220302153254474

    • 顺序为,先点击顶部的倒梯形切换到高级模式,然后点击设置-启动-底部的UEFI硬盘BBS优先权

    • image-20220302153411158

    • 将第一个选择为Ubuntu即可

  • 退出BIOS设置,重新启动,可见已经可以进入ubuntu提供的系统选择的界面了

Linux根文件系统

什么是文件系统

  • 根文件系统一般也叫做 rootfs,那么什么叫根文件系统?看到“文件系统”这四个字,很多 人,包括我第一反应就是 FATFS、FAT、EXT4、YAFFS 和 NTFS 等这样的文件系统。在这里, 根文件系统并不是 FATFS 这样的文件系统代码,EXT4 这样的文件系统代码属于 Linux 内核的 一部分。Linux 中的根文件系统更像是一个文件夹或者叫做目录(在我看来就是一个文件夹,只 不过是特殊的文件夹),在这个目录里面会有很多的子目录。根目录下和子目录中会有很多的文件,这些文件是 Linux 运行所必须的,比如库、常用的软件和命令、设备文件、配置文件等等。

  • 根文件系统首先是内核启动时所 mount(挂载)的第一个文件系统,内核代码映像文件保存在 根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和 服务等加载到内存中去运行。

  • 嵌入式 Linux 并没有将内 核代码镜像保存在根文件系统中,而是保存到了其他地方。比如 NAND Flash 的指定存储地址、 EMMC 专用分区中。根文件系统是 Linux 内核启动以后挂载(mount)的第一个文件系统,然后从 根文件系统中读取初始化脚本,比如 rcS,inittab 等。根文件系统和 Linux 内核是分开的,单独 的 Linux 内核是没法正常工作的,必须要搭配根文件系统。如果不提供根文件系统,Linux 内核 在启动的时候就会提示内核崩溃(Kernel panic)的提示

  • 根文件系统是其他文件系统的根,没有这个“根”,其他的文件系统或者软件就别想工作。比如我们常用的 ls、mv、ifconfig 等命令 其实就是一个个小软件,只是这些软件没有图形界面,而且需要输入命令来运行。这些小软件就保存在根文件系统中

  • 以 Ubuntu 为例,根文件系统的目录名字为‘/’,没看错就是一个斜杠,所以输入如下命令就可以进入根目录中

cd / //进入根目录

/bin

  • bin 文件就是可执行文件。所以此目录下存放着系统 需要的可执行文件,一般都是一些命令,比如 ls、mv 等命令。此目录下的命令所有的客户都可 以使用。

/dev 目录

  • dev 是 device 的缩写,所以此目录下的文件都是和设备有关的,此目录下的文件都是设备文件。在 Linux 下一切皆文件,即使是硬件设备,也是以文件的形式存在的,比如 /dev/ttymxc0(I.MX6ULL 根目录会有此文件)就表示 I.MX6ULL 的串口 0,我们**要想通过串口 0 发送或者接收数据就要操作文件/dev/ttymxc0,通过对文件/dev/ttymxc0 的读写操作来实现串口 0 的数据收发**。

/etc 目录

  • 此目录下存放着各种配置文件,大家可以进入 Ubuntu 的 etc 目录看一下,里面的配置文件 非常多!但是在嵌入式 Linux 下此目录会很简洁。

/lib 目录

  • lib 是 library 的简称,也就是库的意思,因此此目录下存放着 Linux 所必须的库文件。这些 库文件是共享库,命令和用户编写的应用程序要使用这些库文件。

/mnt 目录

  • 临时挂载目录,一般是空目录,可以在此目录下创建空的子目录,比如/mnt/sd、/mnt/usb, 这样就可以将 SD 卡或者 U 盘挂载到/mnt/sd 或者/mnt/usb 目录中。

/proc 目录

  • 此目录一般是空的,当 Linux 系统启动以后会将此目录作为 proc 文件系统的挂载点,proc 是个虚拟文件系统,没有实际的存储设备。proc 里面的文件都是临时存在的,一般用来存储系 统运行信息文件。

/usr 目录

  • usr 不是 user 的缩写,而是 Unix Software Resource 的缩写,也就是 Unix 操作系统 软件资源目录。这里有个小知识点,那就是 Linux 一般被称为类 Unix 操作系统,苹果的 MacOS 也是类 Unix 操作系统。关于 Linux 和 Unix 操作系统的渊源大家可以直接在网上找 Linux 的发 展历史来看。既然是软件资源目录,因此**/usr 目录下也存放着很多软件**,一般系统安装完成以 后此目录占用的空间最多。

/var 目录

  • 此目录存放一些可以改变的数据

/sbin 目录

  • 此目录页用户存放一些可执行文件,但是此目录下的文件或者说命令只有管理员才能使用, 主要用户系统管理。

/sys 目录

  • 系统启动以后此目录作为 sysfs 文件系统的挂载点,sysfs 是一个类似于 proc 文件系统的特 殊文件系统,sysfs 也是基于 ram 的文件系统,也就是说它也没有实际的存储设备。此目录是系 统设备管理的重要目录,此目录通过一定的组织结构向用户提供详细的内核数据结构信息。

/opt目录

  • 可选的文件、软件存放区,由用户选择将哪些文件或软件放到此目录中。

Linux内核启动分析

start_kernel函数

  • start_kernel 通过调用众多的子函数来完成 Linux 启动之前的一些初始化工作,由于 start_kernel 函数里面调用的子函数太多,而这些子函数又很复杂,因此我们简单的来看一下一 些重要的子函数。

  • 精简后的内容大致如下

    • image-20220227110529062
    • image-20220227110555519
    • image-20220227110617809
    • image-20220227110631266
    • image-20220227110653054

启动过程中调用的函数分析

  • lockdep_init();,死锁检测,初始化两个hash表,尽可能早的执行

  • set_task_stack_end_magic(&init_task);,设置任务栈结束魔术数,用于栈溢出检测

  • smp_setup_processor_id();跟 SMP 有关(多核处理器),设置处理器 ID。

  • debug_objects_early_init();,做一些和 debug 有关的初始化

  • boot_init_stack_canary();栈溢出检测初始化

  • cgroup_init_early(), cgroup 初始化,cgroup 用于控制 Linux 系统资源

  • local_irq_disable()关闭当前 CPU 中断

  • boot_cpu_init();跟 CPU 有关的初始化

  • page_address_init(); 页地址相关的初始化

  • pr_notice("%s", linux_banner);打印 Linux 版本号、编译时间等信息

  • setup_arch(&command_line)架构相关的初始化,此函数会解析传递进来的ATAGS 或者设备树(DTB)文件。会根据设备树里面的 model 和 compatible 这两个属性值来查找Linux 是否支持这个单板。此函数也会获取设备树 中 chosen 节点下的 bootargs 属性值来得到命令 行参数,也就是 uboot 中的 bootargs 环境变量的值,获取到的命令行参数会保存到command_line 中。

  • mm_init_cpumask(&init_mm);看名字,应该是和内存有关的初始化

  • setup_command_line(command_line); 好像是存储命令行参数

  • setup_nr_cpu_ids();如果只是 SMP(多核 CPU)的话,此函数用于获取CPU 核心数量,CPU 数量保存在变量 nr_cpu_ids 中。

  • setup_per_cpu_areas(); 在 SMP 系统中有用,设置每个 CPU 的 per-cpu 数据

  • build_all_zonelists(NULL, NULL); 建立系统内存页区(zone)链表

  • page_alloc_init(); 处理用于热插拔 CPU 的页

  • pr_notice("Kernel command line: %s\n", boot_command_line); 打印命令行信息

  • parse_early_param(); 解析命令行中的 console 参数

  • setup_log_buf(0);设置 log 使用的缓冲区

  • pidhash_init(); 构建 PID 哈希表,Linux 中每个进程都有一个 ID,这个 ID 叫做 PID。通过构建哈希表可以快速搜索进程信息结构体。

  • vfs_caches_init_early();预先初始化 vfs(虚拟文件系统)的目录项和索引节点缓存

  • sort_main_extable();定义内核异常列表

  • trap_init(); 完成对系统保留中断向量的初始化

  • mm_init();内存管理初始化

  • sched_init();初始化调度器,主要是初始化一些结构体

  • preempt_disable();关闭优先级抢占

if (WARN(!irqs_disabled(), /* 检查中断是否关闭,如果没有的话就关闭中断 */
"Interrupts were enabled *very* early, fixing it\n"))
local_irq_disable();
  • idr_init_cache(); IDR 初始化,IDR 是 Linux 内核的整数管理机 制,也就是将一个整数 ID 与一个指针关联起来。
  • rcu_init();初始化 RCU,RCU 全称为 Read Copy Update(读-拷贝修改)
  • trace_init();跟踪调试相关初始化
  • radix_tree_init();基数树相关数据结构初始化
  • early_irq_init(); 初始中断相关初始化,主要是注册 irq_desc 结构体变量,因为 Linux 内核使用 irq_desc 来描述一个中断。
  • init_IRQ();中断初始化
  • tick_init(); tick 初始化
  • init_timers();初始化定时器
  • hrtimers_init();初始化高精度定时器
  • softirq_init(); 软中断初始化
  • time_init();初始化系统时间
  • local_irq_enable(); 使能中断
  • kmem_cache_init_late(); slab 初始化,slab 是 Linux 内存分配器
  • console_init(); 初始化控制台,之前 print 打印的信息都存放 缓冲区中,并没有打印出来。只有调用此函数初始化控制台以后才能在控制台上打印信息
  • lockdep_info();如果定义了宏 CONFIG_LOCKDEP,那么此函数打印一些信息
  • locking_selftest()锁自测
  • kmemleak_init();kmemleak 初始化,kmemleak 用于检查内存泄漏
  • calibrate_delay(); 测定 BogoMIPS 值,可以通过 BogoMIPS 来判断 CPU 的性能.BogoMIPS 设置越大,说明 CPU 性能越好。
  • pidmap_init(); PID 位图初始化
  • anon_vma_init(); 生成 anon_vma slab 缓存
  • cred_init();为对象的每个用于赋予资格(凭证)
  • fork_init(); 初始化一些结构体以使用 fork 函数
  • proc_caches_init();给各种资源管理结构分配缓存
  • buffer_init();初始化缓冲缓存
  • key_init();初始化密钥
  • security_init(); 安全相关初始化
  • vfs_caches_init(totalram_pages); 为 VFS 创建缓存
  • signals_init();初始化信号
  • page_writeback_init(); 页回写初始化
  • proc_root_init(); 注册并挂载 proc 文件系统
  • cpuset_init();初始化 cpuset,cpuset 是将 CPU 和内存资源以逻辑性和层次性集成的一种机制,是 cgroup 使用的子系统之一
  • cgroup_init(); 初始化 cgroup
  • taskstats_init_early();进程状态初始化
  • check_bugs();检查写缓冲一致性

rest_init 函数

  • image-20220227114640698

  • image-20220227114923703

  • 执行内容

    • 第 387 行,调用函数 rcu_scheduler_starting,启动 RCU 锁调度器

    • 第 394 行,调用函数 kernel_thread 创建 kernel_init 进程,也就是大名鼎鼎的 init 内核进程init 进程的 PID 为 1。init 进程一开始是内核进程(也就是运行在内核态),后面 init 进程会在根 文件系统中查找名为“init”这个程序,这个“init”程序处于用户态,通过运行这个“init”程 序,init 进程就会实现从内核态到用户态的转变。

    • 第 396 行,调用函数 kernel_thread 创建 kthreadd 内核进程,此内核进程的 PID 为 2kthreadd 进程负责所有内核进程的调度和管理

    • 第 409 行,最后调用函数 cpu_startup_entry 来进入 idle 进程,cpu_startup_entry 会调用 cpu_idle_loop,cpu_idle_loop 是个 while 循环,也就是 idle 进程代码。idle 进程的 PID 为 0,idle 进程叫做空闲进程,如果学过 FreeRTOS 或者 UCOS 的话应该听说过空闲任务。idle 空闲进程 就和空闲任务一样,当 CPU 没有事情做的时候就在 idle 空闲进程里面“瞎逛游”,反正就是给 CPU 找点事做。当其他进程要工作的时候就会抢占 idle 进程,从而夺取 CPU 使用权。其实大 家应该可以看到 idle 进程并没有使用 kernel_thread 或者 fork 函数来创建,因为它是有主进程演 变而来的。 在 Linux 终端中输入“ps -A”就可以打印出当前系统中的所有进程,其中就能看到 init 进 程和 kthreadd 进程,如图

    • image-20220227120610800

    • 没有显示 PID 为 0 的 idle 进程,那是因为 idle 进程是内核进程。

init进程

  • kernel_init 函数就是 init 进程具体做的工作,定义在文件 init/main.c 中,函数内容如下:

  • image-20220227122017680

  • image-20220227122029865

    • 第 932 行,kernel_init_freeable 函数用于完成 init 进程的一些其他初始化工作,稍后再来具 体看一下此函数。
    • 第 940 行,ramdisk_execute_command 是一个全局的 char 指针变量,此变量值为“/init”, 也就是根目录下的 init 程序。ramdisk_execute_command 也可以通过 uboot 传递,在 bootargs 中 使用“rdinit=xxx”即可,xxx 为具体的 init 程序名字。
    • 第 943 行,如果存在“/init”程序的话就通过函数 run_init_process 来运行此程序。
    • 第 956 行,如果 ramdisk_execute_command 为空的话就看 execute_command 是否为空,反 正不管如何一定要在根文件系统中找到一个可运行的 init 程序。execute_command 的值是通过 uboot 传递,在 bootargs 中使用“init=xxxx”就可以了,比如“init=/linuxrc”表示根文件系统中 的 linuxrc 就是要执行的用户空间 init 程序。
    • 第 963~966 行,如果 ramdisk_execute_command 和 execute_command 都为空,那么就依次 查找“/sbin/init”、“/etc/init”、“/bin/init”和“/bin/sh”,这四个相当于备用 init 程序,如果这四 个也不存在,那么 Linux 启动失败!
    • 第 969 行,如果以上步骤都没有找到用户空间的 init 程序,那么就提示错误发生!

kernel_init_freeable 函数

  • kernel_init 会调用此函数来做一些 init 进程初始化工作。kernel_init_freeable 定义在文件 init/main.c 中
  • 缩减后的函数内容如下
  • image-20220227122233775
  • image-20220227122256401
    • 第 1002 行,do_basic_setup 函数用于完成 Linux 下设备驱动初始化工作!非常重要。 do_basic_setup 会调用 driver_init 函数完成 Linux 下驱动模型子系统的初始化。
    • 第 1005 行,打开设备“/dev/console”,在 Linux 中一切皆为文件!因此“/dev/console”也 是一个文件,此文件为控制台设备。每个文件都有一个文件描述符,此处打开的“/dev/console” 文件描述符为 0,作为标准输入(0)
    • 第 1008 和 1009 行,sys_dup 函数将标准输入(0)的文件描述符复制了 2 次,一个作为标准 输出(1),一个作为标准错误(2)。这样标准输入、输出、错误都是/dev/console 了。console 通过 uboot 的 bootargs 环境变量设置,“console=ttymxc0,115200”表示将/dev/ttymxc0 设置为 console, 也就是 I.MX6U 的串口 1。当然,也可以设置其他的设备为 console,比如虚拟控制台 tty1,设 置 tty1 为 console 就可以在 LCD 屏幕上看到系统的提示信息。
    • 第 1020 行,调用函数 prepare_namespace 来挂载根文件系统。根文件系统也是由命令行参 数指定的,就是 uboot 的 bootargs 环境变量。比如“root=/dev/mmcblk1p2 rootwait rw”就表示根 文件系统在/dev/mmcblk1p2 中,也就是 EMMC 的分区 2 中。

U_Boot入门

U_Boot简介

  • Linux 系统要启动就必须需要一个 bootloader 程序,也就说芯片上电以后先运行一段 bootloader程序。这段bootloader程序会先初始化DDR等外设,然后将Linux内核从flash(NAND, NOR FLASH,SD,MMC 等)拷贝到 DDR 中,最后启动 Linux 内核。当然了,bootloader 的实 际工作要复杂的多,但是它最主要的工作就是启动 Linux 内核,bootloader 和 Linux 内核的关系 就跟 PC 上的 BIOS 和 Windows 的关系一样,bootloader 就相当于 BIOS。所以我们要先搞定 bootloader,很庆幸,有很多现成的 bootloader 软件可以使用,比如 U-Boot、vivi、RedBoot 等 等,其中以 U-Boot 使用最为广泛,为了方便书写,本书会将 U-Boot 写为 uboot。
  • uboot 的全称是 Universal Boot Loader,uboot 是一个遵循 GPL 协议的开源软件,uboot 是一 个裸机代码,可以看作是一个裸机综合例程。现在的 uboot 已经支持液晶屏、网络、USB 等高 级功能。
  • uboot 官网为 http://www.denx.de/wiki/U-Boot/

UBoot顶层makefile

  • 编译需要的shell脚本

    • image-20220225210752704
    • 第 1 行是 shell 脚本要求的,必须是“#!/bin/bash”或者“#!/bin/sh”
    • 第 2 行使用了 make 命令,用于清理工程,也就是每次在编译 uboot 之前都清理一下工程。 这里的 make 命令带有三个参数,第一个是 ARCH,也就是指定架构,这里肯定是 arm;第二个 参数 CROSS_COMPILE 用于指定编译器,只需要指明编译器前缀就行了,比如 arm-linux-gnueabihf-gcc 编译器的前缀就是“arm-linux-gnueabihf-”;最后一个参数 distclean 就是清除工程。
    • 第 3 行也使用了 make 命令,用于配置 uboot。同样有三个参数,不同的是,最后一个参数是 mx6ull_14x14_ddr512_emmc_defconfig。前面说了 uboot 是 bootloader 的一种,可以用来引导 Linux,但是 uboot 除了引导 Linux 以外还可以引导其它的系统而且 uboot 还支持其它的架构 和外设,比如 USB、网络、SD 卡等。这些都是可以配置的,需要什么功能就使能什么功能。所 以在编译 uboot 之前,一定要根据自己的需求配置 uboot。mx6ull_14x14_ddr512_emmc_defconfig 就是正点原子针对 I.MX6U-ALPHA 的 EMMC 核心板编写的配置文件,这个配置文件在 uboot 源码的 configs 目录中。在 uboot 中,通过“make xxx_defconfig”来配置 uboot,xxx_defconfig 就是不同板子的配置文件,这些配置文件都在 uboot/configs 目录中
    • 第 4 行有 4 个参数,用于编译 uboot,通过第 3 行配置好 uboot 以后就可以直接“make”编 译 uboot 了。其中 V=1 用于设置编译过程的信息输出级别;-j 用于设置主机使用多少线程编译 uboot,最好设置成我们虚拟机所设置的核心数,如果在 VMware 里面给虚拟就分配了 4 个核, 那么使用-j4 是最合适的,这样 4 个核都会一起编译
  • 查看UBoot之前需要先进行一次编译

  • 编译后的文件结构

  • image-20220225210424844

  • image-20220225210443748

  • makefile文件是可以嵌套的

  • 也就是顶层 Makefile 可以调用子目录 中的 Makefile 文件。Makefile 嵌套在大项目中很常见,一般大项目里面所有的源代码都不会放 到同一个目录中,各个功能模块的源代码都是分开的,各自存放在各自的目录中。每个功能模 块目录下都有一个 Makefile,这个 Makefile 只处理本模块的编译链接工作,这样所有的编译链接工作就不用全部放到一个 Makefile 中,可以使得 Makefile 变得简洁明了。

Makefile分析

版本号

  • image-20220225232512333

  • 顶层 Makefile 一开始是版本号,内容如下(为了方便分析,顶层 Makefile 代码段前段行号 采用 Makefile 中的行号,因为 uboot 会更新

MAKEFLAGS变量

  • make 是支持递归调用的,也就是在 Makefile 中使用“make”命令来执行其他的 Makefile 文件,一般都是子目录中的 Makefile 文件。假如在当前目录下存在一个“subdir”子目录,这个 子目录中又有其对应的 Makefile 文件,那么这个工程在编译的时候其主目录中的 Makefile 就可 以调用子目录中的 Makefile,以此来完成所有子目录的编译。主目录的 Makefile 可以使用如下 代码来编译这个子目录:
$(MAKE) -C subdir
  • $(MAKE)就是调用“make”命令,-C 指定子目录。有时候我们需要向子 make 传递变量, 这个时候使用“export”来导出要传递给子 make 的变量即可,如果不希望哪个变量传递给子 make 的话就使用“unexport”来声明不导出:
export VARIABLE …… //导出变量给子 make 。
unexport VARIABLE…… //不导出变量给子 make。
  • 有两个特殊的变量:“SHELL”和“MAKEFLAGS”,这两个变量除非使用“unexport”声明, 否则的话在整个make的执行过程中,它们的值始终自动的传递给子make。在uboot的主Makefile 中有如下代码:
MAKEFLAGS += -rR --include-dir=$(CURDIR)
  • 上述代码使用“+=”来给变量 MAKEFLAGS 追加了一些值,“-rR”表示禁止使用内置的隐 含规则和变量定义,“–include-dir”指明搜索路径,”$(CURDIR)”表示当前目录。

命令输出

  • uboot 默认编译是不会在终端中显示完整的命令,都是短命令

  • image-20220225235732726

  • 上述代码中先使用 ifeq 来判断”$(origin V)”和”command line”是否相等。这里用到了 Makefile 中的函数 origin,origin 和其他的函数不一样,它不操作变量的值,origin 用于告诉你变量是哪 来的

$(origin <variable>)
  • variable 是变量名,origin 函数的返回值就是变量来源,因此$(origin V)就是变量 V 的来源。 如果变量 V 是在命令行定义的那么它的来源就是”command line”,这样”$(origin V)”和”command line”就相等了。当这两个相等的时候变量 KBUILD_VERBOSE 就等于 V 的值,比如在命令行中 输入“ V=1 “ 的 话 那 么 KBUILD_VERBOSE=1 。如果没有在命令行输入 V 的 话 KBUILD_VERBOSE=0

  • 第 80 行判断 KBUILD_VERBOSE 是否为 1,如果 KBUILD_VERBOSE 为 1 的话变量 quiet

  • 和 Q 都为空,如果 KBUILD_VERBOSE=0 的话变量 quiet 为“quiet_“,变量 Q 为“@”

    • V=1 的话

    • image-20220226000158806

    • V=0 或者命令行不定义 V 的话

    • image-20220226000426810

  • Makefile 中会用到变量 quiet 和 Q 来控制编译的时候是否在终端输出完整的命令,在顶层 Makefile 中有很多如下所示的命令

    • $(Q)$(MAKE) $(build)=tools
  • 如果 V=0 的话上述命令展开就是“@ make $(build)=tools”,make 在执行的时候默认会在终 端输出命令,但是在命令前面加上“@”就不会在终端输出命令了。当 V=1 的时候 Q 就为空, 上述命令就是“make $(build)=tools”,因此在 make 执行的过程,命令会被完整的输出在终端上

  • 有些命令会有两个版本

    • image-20220226000920939
  • sym 命令分为“quiet_cmd_sym”和“cmd_sym”两个版本,这两个命令的功能都是一样的, 区别在于 make 执行的时候输出的命令不同。quiet_cmd_xxx 命令输出信息少,也就是短命令, 而 cmd_xxx 命令输出信息多,也就是完整的命令。

    • 如果变量 quiet 为空的话,整个命令都会输出。
    • 如果变量 quiet 为“quiet_”的话,仅输出短版本。 _
    • _如果变量 quiet 为“silent_”的话,整个命令都不会输出。

静默输出

  • 上一小节讲了,设置 V=0 或者在命令行中不定义 V 的话,编译 uboot 的时候终端中显示的 短命令,但是还是会有命令输出,有时候我们在编译 uboot 的时候不需要输出命令,这个时候 就可以使用 uboot 的静默输出功能。编译的时候使用“make -s”即可实现静默输出

    • image-20220226002019735
    • image-20220226002036927
  • 判断当前正在使用的编译器版本号是否为 4.x,判断$(filter 4.%,$(MAKE_VERSION)) 和“ ”(空)是否相等,如果不相等的话就成立,执行里面的语句。也就是说 $(filter 4.%,$(MAKE_VERSION))不为空的话条件就成立,这里用到了 Makefile 中的 filter 函数,这是 个过滤函数,函数格式如下

    • image-20220226002617614
    • filter 函数表示以 pattern 模式过滤 text 字符串中的单词,仅保留符合模式 pattern 的单词, 可以有多个模式。函数返回值就是符合 pattern 的字符串。因此$(filter 4.%,$(MAKE_VERSION)) 的含义就是在字符串“MAKE_VERSION”中找出符合“4.%”的字符(%为通配符), MAKE_VERSION 是make工具的版本号,ubuntu16.04里面默认自带的make工具版本号为4.1, 大家可以输入“make -v”查看。因此$(filter 4.%,$(MAKE_VERSION))不为空,条件成立
  • ,如果$(filter %s ,$(firstword x$(MAKEFLAGS)))不为空的话条件 成立,变量 quiet 等于“silent_”。这里也用到了函数 filter,在$(firstword x$(MAKEFLAGS)))中 过滤出符合“%s”的单词。到了函数 firstword,函数 firstword 是获取首单词,函数格式如下

    • $(firstword <text>)

    • firstword 函数用于取出 text 字符串中的第一个单词,函数的返回值就是获取到的单词。当 使用“make -s”编译的时候,“-s”会作为 MAKEFLAGS 变量的一部分传递给 Makefile。

    • 添加如下内容

      • image-20220226105110225
    • 可见增加-s之后,firstword变成了

      • image-20220226110058265
      • 可见也不是直接-s,但是filter的返回值肯定不为空

设置编译结果的输出目录

  • uboot 可以将编译出来的目标文件输出到单独的目录中,在 make 的时候使用“O”来指定 输出目录,比如“make O=out”就是设置目标文件输出到 out 目录中。这么做是为了将源文件 和编译产生的文件分开,当然也可以不指定 O 参数,不指定的话源文件和编译产生的文件都在 同一个目录内,一般我们不指定 O 参数。

  • image-20220226110625560

  • image-20220226110641751

  • 第 124 行判断“O”是否来自于命令行,如果来自命令行的话条件成立,KBUILD_OUTPUT 就为$(O),因此变量 KBUILD_OUTPUT 就是输出目录。

  • 第 135 行判断 KBUILD_OUTPUT 是否为空。

  • 第 139 行调用 mkdir 命令,创建 KBUILD_OUTPUT 目录,并且将创建成功以后的绝对路 径赋值给 KBUILD_OUTPUT。至此,通过 O 指定的输出目录就存在了。

DSP28377d开启–fp_mode提高浮点数除法性能

在程序中执行浮点数除法比如

Speed=(float)1000000/(float)count*speedTemp*15;

的时候会看到如下的提示:

image-20220219183335144

  • 此时需要在项目的properties中选择

  • image-20220219183408708

  • 选择optimizations中的--fp_moderelaxed即可(如图)

  • image-20220219183528275

C语言常见内存操作函数

memcpy()

<string.h>

void *memcpy(void *str1, const void *str2, size_t n)
  • str1 – 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。

  • str2 – 指向要复制的数据源,类型强制转换为 void* 指针。

  • n – 要被复制的字节数。

  • 该函数返回一个指向目标存储区 str1 的指针。

memchr()

void *memchr(const void *str, int c, size_t n)
  • str – 指向要执行搜索的内存块。

  • c – 以 int 形式传递的值,但是函数在每次字节搜索时是使用该值的无符号字符形式。

  • n – 要被分析的字节数。

  • 该函数返回一个指向匹配字节的指针,如果在给定的内存区域未出现字符,则返回 NULL。

memcmp()

int memcmp(const void *str1, const void *str2, size_t n)
  • str1 – 指向内存块的指针。
  • str2 – 指向内存块的指针。
  • n – 要被比较的字节数。

返回值

  • 如果返回值 < 0,则表示 str1 小于 str2。
  • 如果返回值 > 0,则表示 str1 大于 str2。
  • 如果返回值 = 0,则表示 str1 等于 str2。

memmove()

  • C 库函数 void *memmove(void *str1, const void *str2, size_t n)str2 复制 n 个字符到 str1,但是在重叠内存块这方面,memmove() 是比 memcpy() 更安全的方法。如果目标区域和源区域有重叠的话,memmove() 能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,复制后源区域的内容会被更改。如果目标区域与源区域没有重叠,则和 memcpy() 函数功能相同
void *memmove(void *str1, const void *str2, size_t n)
  • str1 – 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
  • str2 – 指向要复制的数据源,类型强制转换为 void* 指针。
  • n – 要被复制的字节数。
  • 该函数返回一个指向目标存储区 str1 的指针。

memset()

  • C 库函数 void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。
void *memset(void *str, int c, size_t n)
  • str – 指向要填充的内存块。
  • c – 要被设置的值。该值以 int 形式传递,但是函数在填充内存块时是使用该值的无符号字符形式。
  • n – 要被设置为该值的字符数。
  • 该值返回一个指向存储区 str 的指针。

strcat()

  • C 库函数 char *strcat(char *dest, const char *src)src 所指向的字符串追加到 dest 所指向的字符串的结尾。
char *strcat(char *dest, const char *src)
  • dest – 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串。

  • src – 指向要追加的字符串,该字符串不会覆盖目标字符串。

  • 该函数返回一个指向最终的目标字符串 dest 的指针。

strcmp()

  • C 库函数 int strcmp(const char *str1, const char *str2)str1 所指向的字符串和 str2 所指向的字符串进行比较。
int strcmp(const char *str1, const char *str2)
  • str1 – 要进行比较的第一个字符串。
  • str2 – 要进行比较的第二个字符串。

返回值

  • 如果返回值小于 0,则表示 str1 小于 str2。
  • 如果返回值大于 0,则表示 str1 大于 str2。
  • 如果返回值等于 0,则表示 str1 等于 str2。

malloc()

void *malloc(size_t size)
  • size – 内存块的大小,以字节为单位。
  • 该函数返回一个指针 ,指向已分配大小的内存。如果请求失败,则返回 NULL。

calloc()

C 库函数 void *calloc(size_t nitems, size_t size) 分配所需的内存空间,并返回一个指向它的指针。malloccalloc 之间的不同点是,malloc 不会设置内存为零,而 calloc 会设置分配的内存为零。

void *calloc(size_t nitems, size_t size)
  • nitems – 要被分配的元素个数。
  • size – 元素的大小。

realloc()

C 库函数 void *realloc(void *ptr, size_t size) 尝试重新调整之前调用 malloccalloc 所分配的 ptr 所指向的内存块的大小。

void *realloc(void *ptr, size_t size)
  • ptr – 指针指向一个要重新分配内存的内存块,该内存块之前是通过调用 malloc、calloc 或 realloc 进行分配内存的。如果为空指针,则会分配一个新的内存块,且函数返回一个指向它的指针。
  • size – 内存块的新的大小,以字节为单位。如果大小为 0,且 ptr 指向一个已存在的内存块,则 ptr 所指向的内存块会被释放,并返回一个空指针。
  • 该函数返回一个指针 ,指向重新分配大小的内存。如果请求失败,则返回 NULL。

free()

C 库函数 void free(void *ptr) 释放之前调用 calloc、malloc 或 realloc 所分配的内存空间。

void free(void *ptr)

system()

C 库函数 int system(const char *command)command 指定的命令名称或程序名称传给要被命令处理器执行的主机环境,并在命令完成后返回。(实际上就是在程序中执行Linux命令行的指令)

int system(const char *command)
  • command – 包含被请求变量名称的 C 字符串。
  • 如果发生错误,则返回值为 -1,否则返回命令的状态。

abs()

C 库函数 int abs(int x) 返回 x 的绝对值。

int abs(int x)
  • x – 完整的值。
  • 该函数返回 x 的绝对值。

exit()

C 库函数 void exit(int status) 立即终止调用进程。任何属于该进程的打开的文件描述符都会被关闭,该进程的子进程由进程 1 继承,初始化,且会向父进程发送一个 SIGCHLD 信号。

void exit(int status)
  • status – 返回给父进程的状态值。
  • 该函数不返回值。

abort()

C 库函数 void abort(void) 中止程序执行,直接从调用的地方跳出。

void abort(void)

atof()

C 库函数 double atof(const char *str) 把参数 str 所指向的字符串转换为一个浮点数(类型为 double 型),根据字面意思,不是根据具体的字节进行转换

double atof(const char *str)

atoi()

C 库函数 int atoi(const char *str) 把参数 str 所指向的字符串转换为一个整数(类型为 int 型)。

int atoi(const char *str)

atol()

C 库函数 long int atol(const char *str) 把参数 str 所指向的字符串转换为一个长整数(类型为 long int 型)。

long int atol(const char *str)

strtod()

C 库函数 *double strtod(const char *str, char *endptr) 把参数 str 所指向的字符串转换为一个浮点数(类型为 double 型)。如果 endptr 不为空,则指向转换中最后一个字符后的字符的指针会存储在 endptr 引用的位置。

double strtod(const char *str, char **endptr)
  • str – 要转换为双精度浮点数的字符串。
  • endptr – 对类型为 char* 的对象的引用,其值由函数设置为 str数值后的下一个字符。

strtol()

C 库函数 *long int strtol(const char *str, char *endptr, int base) 把参数 str 所指向的字符串根据给定的 base 转换为一个长整数(类型为 long int 型),base 必须介于 2 和 36(包含)之间,或者是特殊值 0。

long int strtol(const char *str, char **endptr, int base)
  • str – 要转换为长整数的字符串。
  • endptr – 对类型为 char* 的对象的引用,其值由函数设置为 str 中数值后的下一个字符。
  • base – 基数,必须介于 2 和 36(包含)之间,或者是特殊值 0。

strtoul()

C 库函数 *unsigned long int strtoul(const char *str, char *endptr, int base) 把参数 str 所指向的字符串根据给定的 base 转换为一个无符号长整数(类型为 unsigned long int 型),base 必须介于 2 和 36(包含)之间,或者是特殊值 0。

unsigned long int strtoul(const char *str, char **endptr, int base)
  • str – 要转换为无符号长整数的字符串。
  • endptr – 对类型为 char* 的对象的引用,其值由函数设置为 str 中数值后的下一个字符。
  • base – 基数,必须介于 2 和 36(包含)之间,或者是特殊值 0。

DSP28377相关知识

BLDC(PWM)相关

  • 驱动的PWM中135为一组,246为一组

串口相关

  • 中断:TXINT和RXINT

中断

开启中断的步骤

#include “DSP2833x_Device.h”

#include “DSP2833x_Examples.h”

interrupt void cpu_TImer0_isr(void);//声明中断服务函数

void main()

{

//step1:初始化系统控制、PLL、看门狗、允许外设时钟

InitSysCtrl();

//step2:初始化GPIO

InitGpio();

//step3:清除所有中断,初始化PIE向量表

DINT;

InitPieCtrl(); //初始化PIE控制器

IER = 0x0000; //禁止CPU中断

IFR = 0x0000; //清除所有CPU中断标志

InitPieVectTable(); //初始化PIE中断向量表

EALLOW;

PieVectTable.TINT0 = &cpu_TImer0_isr; //重映射中断向量,指向中断服务程序

EDIS;

//step4:初始化外设模块

InitCpuTImers();

ConfigCpuTimer(&CpuTimer0,1501000000); //150MHz,周期1秒

CpuTimer0Regs.TCR.all = 0x4001; //允许定时器中断,且设置TSS为0启动定时器工作

//step5:

IER |= M_INT1; //允许CPU的INT1中断,该中断连接至TINT0

PieCtrlRegs.PIEIER1.bit.INTx7 = 1; //在PIE中断组1中允许TINT0中断

EINT; //清除全局屏蔽

ERTM; //允许全局实时中断

//step6:循环

while1);

}

interrupt void cpu_timer0_isr(void

{

CpuTimer0.InterruptCount++;

PieCtrlRegs.PIEACK.all = PIEACK_GROUP1; //清除PIE中断组1的应答位,以便CPU再次响应

}

DSP28377d与无刷直流电机的接线方法

霍尔传感器:

  • 程序中指定的CAP霍尔传感器输入引脚为
InitECap1Gpio(20);
GPIO_SetupPinOptions(20, GPIO_INPUT, GPIO_ASYNC);
InitECap2Gpio(21);
GPIO_SetupPinOptions(21, GPIO_INPUT, GPIO_ASYNC);
InitECap3Gpio(23);
GPIO_SetupPinOptions(23, GPIO_INPUT, GPIO_ASYNC);
  • 找到芯片的20,21和23脚,位置在

  • image-20220216115100394

对应的是开发板上的外扩引脚

  • image-20220216115209306

  • 所以电机驱动板上的中间霍尔传感器输出口的2,3,4pin分别连接开发板上J5的25,26,27引脚,同时利用J5的30和29脚分别是5V和GND给电机控制板提供5V电源,以上的排针倒数6pin中除了CAP4之外,都接到电机驱动板上的对应的引脚上了。

电机驱动 PWM

  • PWM的连接比较简单,主要就是将开发板上的J5的前6个输出脚

  • image-20220216115439328

  • 按照1-6的顺序对应连接到电机驱动板上的长排针PWM1-6

  • image-20220216115526376

  • 如上,再加上供电即可

  • 注意,假如传感器接线存在问题,电机会停在某一个相位不动(有力矩阻碍转动但是自身不转动)而且会产生大量的热,同时电源会发出一个电流声(猜测是电流太大引起的)。

接线图

  • image-20220216120140369

  • image-20220216120155122

C语言多个线程与客户端通信的服务器

  • 代码结构:将与客户端收发信息的部分单独取出放入新的线程中
  • 捕获服务器收到的Ctrl+c信号(也就是SIGINT),将此信号绑定一个自定义的信号处理函数(为了防止程序被中断但是没有关闭socket描述符)
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <net/if.h>
#include <sys/ioctl.h>

#include <pthread.h>
#include <signal.h>


#define SERVERPORT 8080

int sockfd;

/*linux上支持(Android上也支持), 此函数不仅能获取IP,还可以获取MAC地址、掩码和广播地址等*/
int get_local_ip_using_ifconf(char *str_ip)
{
int sock_fd, intrface;
struct ifreq buf[INET_ADDRSTRLEN];
struct ifconf ifc;
char *local_ip = NULL;
int status = -1;

if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) >= 0)
{
ifc.ifc_len = sizeof(buf);
ifc.ifc_buf = (caddr_t)buf;
if (!ioctl(sock_fd, SIOCGIFCONF, (char *)&ifc))
{
intrface = ifc.ifc_len/sizeof(struct ifreq);
while (intrface-- > 0)
{
if (!(ioctl(sock_fd, SIOCGIFADDR, (char *)&buf[intrface])))
{
local_ip = NULL;
local_ip = inet_ntoa(((struct sockaddr_in*)(&buf[intrface].ifr_addr))->sin_addr);
if(local_ip)
{
strcpy(str_ip, local_ip);
status = 0;
if(strcmp("127.0.0.1", str_ip))
{
break;
}
}

}
}
}
close(sock_fd);
}
return status;
}

void* clientRoutine(void * clientFd)
{
int* clientFdt = (int*)clientFd;
char recvbuf[512];
int ret;
for (;;)
{
// 接收缓冲区清零
memset(recvbuf, 0x0, sizeof(recvbuf));
// 读数据
ret = recv(*clientFdt, recvbuf, sizeof(recvbuf), 0);
if (0 >= ret)
{
puts("client disconnected");
close(*clientFdt);
pthread_exit(NULL);
}
// 将读取到的数据以字符串形式打印出来
printf("from client: %s\n", recvbuf);

if (0 == strncmp("exit", recvbuf, 4))
{
printf("server exit...\n");
close(*clientFdt);
pthread_exit(NULL);
}
}
}

void SIGINTHandler(int sig)
{
close(sockfd);
printf("\nServer stopping, already closed socket %d. \n", sockfd);
exit(EXIT_SUCCESS);
}

int main()
{
struct sockaddr_in server_addr = {0};
struct sockaddr_in client_addr = {0};
char ip_str[20] = {0};

int addrlen = sizeof(client_addr);
int ret;
char localIPBuf[20];

signal(SIGINT, (sig_t)SIGINTHandler);

/* 打开套接字,得到套接字描述符 */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (0 > sockfd)
{
perror("socket error");
exit(EXIT_FAILURE);
}

/* 将套接字与指定端口号进行绑定 */
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(SERVERPORT);

char buf[20];
get_local_ip_using_ifconf(buf);
puts(buf);


inet_ntop(AF_INET, &(server_addr.sin_addr), localIPBuf, sizeof(localIPBuf));
puts(localIPBuf);

ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (0 > ret)
{
perror("bind error");
close(sockfd);
exit(EXIT_FAILURE);
}
/* 使服务器进入监听状态 */
ret = listen(sockfd, 50);
if (0 > ret)
{
perror("listen error");
close(sockfd);
exit(EXIT_FAILURE);
}
/* 阻塞等待客户端连接 */
while(1)
{
int clientTemp;
clientTemp = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen);
if (0 > clientTemp)
{
perror("accept error");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("有客户端接入...\n");
inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip_str, sizeof(ip_str));
printf("客户端主机的 IP 地址: %s\n", ip_str);
printf("客户端进程的端口号: %d\n", client_addr.sin_port);
pthread_t thTemp;
pthread_create(&thTemp, NULL, clientRoutine, &clientTemp);
/* 接收客户端发送过来的数据 */

}

/* 关闭套接字 */
close(sockfd);
exit(EXIT_SUCCESS);
}

  • 效果
  • image-20220206222037573