imx6ull学习记录(一)

这一块主要是了解linux系统驱动部分,编译镜像相关的知识,这里记录一下。

使用板子如下:
在这里插入图片描述
教程用的这一个版本:
在这里插入图片描述

1、基本环境搭建

这个比较简单,只是注意一下就是正点原子的教程用了一个NFS文件系统,简单来讲就是linux移植不是有三大块吗,uboot,linux内核和文件系统,正点原子教程里面大部分这个文件系统是放在虚拟机里面的,然后通过nfs的方式来访问的。

所以这里要关注一下
在这里插入图片描述
学习过程中我建了一个文件夹,然后nfs的文件也就一样放到里面去了
在这里插入图片描述
我的nfs路径为:
在这里插入图片描述
然后其他的部分参考教程吧,都是比较基础的部分,这里不再赘述。

2、linux系统移植

现在裸机开发的很少了,然后我本身也是做的应用层的开发,这块就接触的更少了,所以我觉得前面那些类似stm32一样去写linux驱动的这种可以直接略过不看,对后面的没有影响,直接跳到下面的章节来。
在这里插入图片描述

2.1 编译uboot并使用

这里先编译正点原子改好的uboot,就是把压缩包拿过来,然后解压,之后给了一个编译脚本进行编译
在这里插入图片描述
脚本内容如下

#!/bin/bash
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4

执行脚本就可以编译(记得给脚本提权限),编译结果如下
在这里插入图片描述
编译出来的目录里面的uboot.bin就是我们需要的uboot文件了
在这里插入图片描述
之后烧录到sd卡里面,然后通过sd卡来启动,这里也有个烧录脚本,烧录脚本直接去裸机例程那边拿过来用就行,路径如下所示:
在这里插入图片描述
查看sd卡路径
在这里插入图片描述
执行烧录命令
在这里插入图片描述
之后选择sd卡启动,启动方式看这里
在这里插入图片描述
执行如下所示,可以看到内核是最近编译的
在这里插入图片描述
教程后面还介绍了一些uboot使用相关的知识,这里暂时没有需求,先跳过,后面有空再看。

2.2 移植ubbot

这个过程就是改一些官方uboot的参数,使其适配正点原子的这个板子,这里也不赘述了,主要还是参考文档吧

这里重点关注这两个参数,这两个决定了uboot启动后会引导什么东西的问题

在这里插入图片描述
最后归结起来就是这两个

setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000;'
saveenv

可以测试一下,因为之前emmc里面已经刷过系统了
在这里插入图片描述
成功启动
在这里插入图片描述
上面的方式是引导emmc里面的镜像和文件系统,我们可以通过tftp的方式获取系统,命令如下(注意上面的命令是保存到了flash里面的,就是上电后会自动执行,所以如果要重新使用新的方式,就上电后按enter按键,重新进入uboot页面

先确认一下我们的tftp路径下有需要的东西
在这里插入图片描述
接下来先设置一下网络参数

setenv ipaddr 192.168.1.3
setenv ethaddr b8:ae:1d:01:00:00
setenv gatewayip 192.168.1.1
setenv netmask 255.255.255.0
setenv serverip 192.168.1.4

之后输入

setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000'
saveenv

正常启动,可以看到他是先用uboot拉了一下镜像在启动的
在这里插入图片描述
最后正常启动
在这里插入图片描述
上面的方式还可以进一步简化成nfs来挂载文件系统,这样开发的时候更方便,文件系统和我们的虚拟机里面的代码可以实时更新,使用的启动命令如下
在这里插入图片描述
如下所示

setenv bootcmd 'tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000'
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.4:/home/lx/IMX6ULL/nfs/rootfs,proto=tcp rw ip=192.168.1.3:192.168.1.4:192.168.1.1:255.255.255.0::eth0:off'
saveenv

他也是拉一个镜像过来,之后是这样的,我们可以尝试在虚拟机的文件系统里面做一下修改
在这里插入图片描述
进入串口,可以看到同步的修改来了
在这里插入图片描述

2.3 编译linux内核

上面已经介绍了uboot的编译和一些启动相关的内容,下面开始正式内核的移植,请直接跳到这一章来看
在这里插入图片描述
这里大部分工作也是修改imx官方的内核工程,适配我们现在这块板子(具体过程这里就不赘述了),之后也是用一个板子进行编译,移植的过程我觉得这个总结的还是可以的
在这里插入图片描述

编译命令如下

#!/bin/sh
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_alientek_emmc_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j4

编译结果如下所示
在这里插入图片描述
在下面的两个文件夹中拿到镜像和设备树(之后烧录就是前面说的内容了)
在这里插入图片描述

2.4 编译busybox

这里根文件系统这块正点原子用的是busybox来实现的,这部分请直接跳到这个章节来看
在这里插入图片描述
按照教程里面的进行修改,之后输入make进行编译
在这里插入图片描述
之后输入下面的命令安装到我们的nfs目录下面去

make install CONFIG_PREFIX=/home/lx/IMX6ULL/nfs/rootfs

后面的步骤根据教程添加需要的库进去
在这里插入图片描述
最后是这样的
在这里插入图片描述
之后是按照教程创建了这个文件来测试
在这里插入图片描述
编译
在这里插入图片描述
执行程序
在这里插入图片描述
至此文件系统就也OK了

3、系统烧写

进入这个路径,找到这个
在这里插入图片描述
前面我们编译好的linux系统几个大件都能找到下面需要的材料
在这里插入图片描述
但是我实测应该是改成这个样子(可以根据自己实际需求来改吧)
在这里插入图片描述

接下来就是用我们的文件替换掉 NXP 官方的文件,先将图中的 zImage、 u-bootimx6ull14x14evk_emmc.imx 和 zImage-imx6ull-14x14-evk-emmc.dtb 这三个文件拷贝到 mfgtoolswith-rootfs/mfgtools/Profiles/Linux/OS Firmware/firmware 目录中,替换掉原来的文件。然后将图中的所有 4 个文件都拷贝到 mfgtools-with-rootfs/mfgtools/Profiles/Linux/OS Firmware/files目录中,这两个操作完成以后我们就可以进行烧写了。

之后进入USB模式,然后就点击开始
在这里插入图片描述
烧录过程串口会同步信息,如下所示:
在这里插入图片描述
之后选择emmc启动,至此,烧录到emmc完成(别忘了设置成emmc启动)

3、linux驱动开发

根据教程,linux驱动开发有三大类,分别是字符设备驱动,块设备驱动和网络设备驱动,字符设备驱动是占用篇幅最大的一类驱动,因为字符设备最多,从最简单的点灯到 I2C、 SPI、音频等都属于字符设备驱动的类型。这部分一般可以自己来实现,块设备驱动和网络设备驱动一般比字符设备驱动复杂,但是这部分一般是供应商那边实现,然后这边调用一下就行。块设备驱动一般值储存设备的驱动,例如EMMC,NAND,SD卡和U盘这一类。网络设备驱动就是网络驱动,不管是有线网络还是无线网络,都属于网络设备驱动。一个设备可以同时属于不同驱动,比如USB网卡,使用了USB接口,那么是字符设备驱动,但是又有wifi功能,也属于网络设备驱动。

3.1 字符设备开发基本知识

linux的应用程序驱动程序的流程如下所示,从下面的图中也可以更深刻的了解linux下一切皆文件的思想,驱动加载成功后会在/dev目录下面生成一个相应的文件,通过对这个文件进行操作即可实现对硬件的操作。
在这里插入图片描述
这里还需要注意就是我们的应用程序是运行在用户空间的,但是linux驱动是运行在内核空间的,属于内核的一部分,我们在用户空间想要实现对内核的操作,就需要实现open函数来打开这个设备,这个就是系统调用,open、 close、 write 和 read 等这些函数是由 C 库提供的,在 Linux 系统中,系统调用作为 C 库的一部分,可以直达内核,他的具体流程如下所示:
在这里插入图片描述
linux驱动一般可以有两种方式,一种是直接将驱动编进内核,一种是编成内核模块,我们调试的时候编成内核模块即可(内核模块就是.ko文件)下面是一个简单的内核模块示例

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

static int __init hello_init(void)
{
    printk(KERN_INFO "Hello, world!\n");
    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "Goodbye, world!\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("LX2035");
MODULE_DESCRIPTION("A simple kernel module");

编写一下makefile,注意一下下面的路径换成自己的即可

KERNELDIR := /home/lx/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)
obj-m := my_module.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

可以看到编译后生成ko文件
在这里插入图片描述
将内核模块放到我们的板子里面加载然后卸载,效果如下所示,说明内核模块成功弄好了
在这里插入图片描述

3.2 字符设备开发示例

前面已经介绍了字符设备开发的基本示例,下面看一下如何使用那些文件操作函数来实现字符设备的开发(其实就是点灯!)

下面先直接贴代码

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>

#define CHRDEVBASE_MAJOR 200         /*主设备号*/
#define CHRDEVBASE_NAME "chrdevbase" /*设备名*/

static char readbuf[100];
static char writebuf[100];
static char kerneldata[] = {"kernel data!"};

/*打开设备*/
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
    printk("chrdevbase open!\r\n");
    return 0;
}

/*从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;

    memcpy(readbuf, kerneldata, sizeof(kerneldata));
    /*copy_to_user 内核空间的数据到用户空间的复制*/
    retvalue = copy_to_user(buf, readbuf, cnt);
    if (retvalue == 0)
    {
        printk("kernel senddata ok!\r\n");
    }
    else
    {
        printk("kernel senddata failed!\r\n");
    }

    return 0;
}

/*向设备写入数据
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t chrdevbase_write(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;

    /*copy_from_user 接收用户空间传递给内核的数据*/
    retvalue = copy_from_user(writebuf, buf, cnt);
    if (retvalue == 0)
    {
        printk("kernel recevdata:%s\r\n", writebuf);
    }
    else
    {
        printk("kernel recevdata failed!\r\n");
    }

    return 0;
}

static int chrdevbase_release(struct inode *inode, struct file *filp)
{

    return 0;
}

static struct file_operations chrdevbase_fops = {
    .owner = THIS_MODULE,
    .open = chrdevbase_open,
    .read = chrdevbase_read,
    .write = chrdevbase_write,
    .release = chrdevbase_release,
};

static int __init chrdevbase_init(void)
{
    int retvalue = 0;

    retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);

    if (retvalue < 0)
    {
        printk("chrdevbase driver register failed\r\n");
    }
    printk("chrdevbase_init()\r\n");
    return 0;
}

static void __exit chrdevbase_exit(void)
{
    unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
    printk("chrdevbase_exit()\r\n");
}

module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("liuxing");

上述代码中关注一下初始化函数,就做了这些事,除了打开,然后读写函数之外还有一个设备号和设备名称的概念

retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);

关于设备号:为了方便管理, Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。

我们可以查看当前系统中已经使用了的设备号,输入下面命令
在这里插入图片描述
设备号可以静态分配,也可以动态分配,比如上面我用的就是静态分配的方式实现的,如果要使用动态分配就是(静态分配存在的问题是如果没有看系统已有的设备号,自己随便写的一个设备号可能和已有的设备号冲突

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

但是注意就是取消的时候也要注销这个设备号

void unregister_chrdev_region(dev_t from, unsigned count)

另外还可以关注两个函数copy_to_usercopy_from_user,前面一个函数是完成内核空间的数据到用户空间的复制,后面一个函数是完成用户空间的数据到内核空间的复制。这两个函数在用户空间和内核空间的数据传递有很重要的作用。
在这里插入图片描述
下面编写一个内核的测试程序

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

static char usrdata[] = {"user data!"};

int main(int argc, char *argv[])
{
    int fd, retvalue;
    char *filename;
    char readbuf[100], writenuf[100];

    if (argc != 3)
    {
        printf("error usage\r\n");
        return -1;
    }

    filename = argv[1];

    fd = open(filename, O_RDWR);
    if (fd < 0)
    {
        printf("Can't open file %s\r\n", filename);
        return -1;
    }

    if (atoi(argv[2]) == 1)
    {
        retvalue = read(fd, readbuf, 50);
        if (retvalue < 0)
        {
            printf("read file %s failed!\r\n", filename);
        }
        else
        {
            printf("read data:%s\r\n", readbuf);
        }
    }

    if (atoi(argv[2]) == 2) /*写文件*/
    {
        memcpy(writenuf, usrdata, sizeof(usrdata));
        retvalue = write(fd, writenuf, 50);
        if(retvalue < 0)
        {
            printf("write file %s failed!\r\n", filename);
        }
    }

    retvalue = close(fd);
    if(retvalue < 0)
    {
        printf("Can't close file %s\r\n", filename);
        return -1;
    }

    return 0;
}

编译程序还是用之前的那个命令,用交叉编译工具来编译

arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp

下面开始实验
在这里插入图片描述
创建一个设备节点
在这里插入图片描述
在应用程序给他发消息过去
在这里插入图片描述
如果不用了就卸载
在这里插入图片描述

3.3 字符设备led开发示例

对外设的驱动,到最后都是归结到寄存器的配置上,也就是配置到相应的硬件寄存器,对于I.MX6U-ALPHA 开发板,板子上的 LED 连接到 I.MX6ULL 的 GPIO1_IO03 这个引脚上。

真实情况下我们要写寄存器只要写那个地址就行了,但是linux还不一样,linux有MMU,这个会整出来一个虚拟内存的东西,作用是让内存变的大一些,这个在linux中是很普遍的,导致我们在linux中的写的地址不是真的地址,因此需要先了解一下MMU这个东西,MMU完成的功能如下所示:

  • 完成虚拟空间到物理空间的映射。
  • 内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。

内存映射可以从下面的表中看出,对于32位的处理器来说,虚拟地址的范围是 2^32=4GB,开发板上有 512MB 的 DDR3,这 512MB 的内存就是物理内存,经过 MMU 可以将其映射到整个 4GB 的虚拟空间,这也就意味着会有多个虚拟地址映射到同一个物理地址的情况,这个情况是由处理器来处理的,这个问题我们暂且不关注,我们目前只需要关注真实的硬件地址对应的是虚拟内存的哪个位置就行了。
在这里插入图片描述
Linux 内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU 访问的都是虚拟地址。比 如 I.MX6ULL 的 GPIO1_IO03 引 脚 的 复 用 寄 存 器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03的地址为 0X020E0068。如果没有开启 MMU 的话直接向 0X020E0068 这个寄存器地址写入数据就可以配置 GPIO1_IO03 的复用功能。现在开启了 MMU,并且设置了内存映射,因此就不能直接向 0X020E0068 这个地址写入数据了。我们必须得到 0X020E0068 这个物理地址在 Linux 系统里面对应的虚拟地址,这里就涉及到了物理内存和虚拟内存之间的转换,需要用到两个函数:ioremap和iounmap。

这两个函数的作用也比较清晰:

  • ioremap:获取指定物理地址空间对应的虚拟地址空间
  • iounmap:卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射

接下来看一下IO内存访问函数,这里的IO是输入输出的意思,涉及到IO端口和IO内存,当外部寄存器或者内存映射到IO空间的时候,称为IO端口,当外部寄存器或者内存映射到内存空间的时候,称为IO内存。对于ARM体系来说目前只有IO内存这个概念,使用 ioremap 函数将寄存器的物理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址,但是 Linux 内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作

主要是下面的几个函数,分别是对8,16,32bit的读操作和写操作

  • u8 readb(const volatile void __iomem *addr)
  • u16 readw(const volatile void __iomem *addr)
  • u32 readl(const volatile void __iomem *addr)
  • void writeb(u8 value, volatile void __iomem *addr)
  • void writew(u16 value, volatile void __iomem *addr)
  • void writel(u32 value, volatile void __iomem *addr)

下面就可以正式开始写代码了,直接贴代码出来看看

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define LED_MAJOR 200  /*主设备号*/
#define LED_NAME "led" /*设备名*/

#define LEDOFF 0
#define LEDON 1

#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

void led_switch(u8 sta)
{
    u32 val = 0;
    if (sta == LEDON)
    {
        val = readl(GPIO1_DR);
        val &= ~(1 << 3);
        writel(val, GPIO1_DR);
    }
    else if (sta == LEDOFF)
    {
        val = readl(GPIO1_DR);
        val |= (1 << 3);
        writel(val, GPIO1_DR);
    }
}

static int led_open(struct inode *inode, struct file *filp)
{
    return 0;
}

static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

static ssize_t led_write(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;

    retvalue = copy_from_user(databuf, buf, cnt);
    if (retvalue < 0)
    {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    ledstat = databuf[0]; /*获取状态值*/

    if (ledstat == LEDON)
    {
        led_switch(LEDON);
    }
    else if (ledstat == LEDOFF)
    {
        led_switch(LEDOFF);
    }

    return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release = led_release,
};

static int __init led_init(void)
{
    int retvalue = 0;
    u32 val = 0;

    IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
    SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
    SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
    GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
    GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);

    /*使能时钟*/
    val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3 << 26); /* 清除以前的设置 */
    val |= (3 << 26);  /* 设置新值 */
    writel(val, IMX6U_CCM_CCGR1);

    /*设置复用*/
    writel(5, SW_MUX_GPIO1_IO03);

    /*设置属性*/
    writel(0x10B0, SW_PAD_GPIO1_IO03);

    /*设置输出功能*/
    val = readl(GPIO1_GDIR);
    val &= ~(1 << 3); /* 清除以前的设置 */
    val |= (1 << 3);  /* 设置为输出 */
    writel(val, GPIO1_GDIR);

    /*默认关闭led*/
    val = readl(GPIO1_DR);
    val |= (1 << 3);
    writel(val, GPIO1_DR);

    /* 6、注册字符设备驱动 */
    retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
    if (retvalue < 0)
    {
        printk("register chrdev failed!\r\n");
        return -EIO;
    }

    return 0;
}

static void __exit led_exit(void)
{
    /* 取消映射 */
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);

    unregister_chrdev(LED_MAJOR, LED_NAME);
    printk("led_exit()\r\n");
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("liuxing");

其实基本也就是上面一个字符设备驱动修改了一点实现的,加了内存映射的部分,实现这里还是写寄存器,就是裸机开发那一套
在这里插入图片描述
下面看一下测试app,测试app这里我们只要写0和1即可,下面是完整代码

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h" 
#include "stdlib.h"
#include "string.h"


#define LEDOFF 0
#define LEDON 1

int main(int argc, char *argv[])
{
    int fd, retvalue;
    char *filename;
    unsigned char databuf[1];

    if (argc != 3)
    {
        printf("error usage\r\n");
        return -1;
    }
    filename = argv[1];

    fd = open(filename, O_RDWR);
    if (fd < 0)
    {
        printf("Can't open file %s\r\n", filename);
        return -1;
    }

    databuf[0] = atoi(argv[2]);
    retvalue = write(fd, databuf, sizeof(databuf));
    if(databuf < 0)
    {
        printf("led control failed \r\n");
        close(fd);
        return -1;
    }

    retvalue = close(fd);
    if(retvalue < 0)
    {
        printf("file close error \r\n");
        return -1;
    }

    return 0;
}

加载到板子里面看看,执行下面的命令可以看到板子的led亮灭变化
在这里插入图片描述
最后不要忘了卸载驱动

3.4 新字符设备开发示例

register_chrdevunregister_chrdev 这两个函数是老版本驱动使用的函数,现在新的字符设备驱动已经不再使用这两个函数,而是使用Linux内核推荐的新字符设备驱动API函数。

先说一下之前用的那个字符设备驱动的问题

使用 register_chrdev函数注册字符设备的时候只需要给定一个主设备号即可,但是这样会带来两个问题:

  • 需要我们事先确定好哪些主设备号没有使用。
  • 会将一个主设备号下的所有次设备号都使用掉,比如现在设置 LED 这个主设备号为200,那么 0~1048575(2^20-1)这个区间的次设备号就全部都被 LED 一个设备分走了。这样太浪费次设备号了!一个 LED 设备肯定只能有一个主设备号,一个次设备号。

解决这个问题就是采用前面提到过的使用动态设备号分配的方案alloc_chrdev_region,子设备号一般直接为0,另外字符设备也有新的注册方式,如下所示:
在这里插入图片描述
这里面有两个重要的成员变量:opsdev,这两个就是字符设备文件操作函数集合file_operations以及设备号dev_t

之后使用专门的初始话函数初始话和添加节点,注意还在最后使用class_create来实现自动创建设备节点,之前都是通过mknod /dev/led c 200 0来创建的。
在这里插入图片描述
关于自动创建设备节点,使用的是mdev的机制,mdev是busybox创建的一个udev的简化版本,udev是可以检测系统中的硬件设备状态,从而来创建或者删除设备文件,比如使用modprobe 命令成功加载驱动模块以后就自动在/dev 目录下创建对应的设备节点文件,使用rmmod 命令卸载驱动模块以后就删除掉/dev 目录下的设备节点文件。

要实现这个,只需要在rcs下面最底部加入这个命令即可:
在这里插入图片描述
具体反馈到应用上来看就是使用上面的说的class_create函数创建类,之后用device_create函数创建设备就行,下面直接看源代码:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define NEWCHRLED_CNT 1            /* 设备号个数 */
#define NEWCHRLED_NAME "newchrled" /* 名字 */
#define LEDOFF 0                   /* 关灯 */
#define LEDON 1                    /* 开灯 */

/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

struct newchrled_dev
{
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
};

struct newchrled_dev newchrled;

void led_switch(u8 sta)
{
    u32 val = 0;
    if (sta == LEDON)
    {
        val = readl(GPIO1_DR);
        val &= ~(1 << 3);
        writel(val, GPIO1_DR);
    }
    else if (sta == LEDOFF)
    {
        val = readl(GPIO1_DR);
        val |= (1 << 3);
        writel(val, GPIO1_DR);
    }
}

static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &newchrled;
    return 0;
}

static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

static ssize_t led_write(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;

    retvalue = copy_from_user(databuf, buf, cnt);
    if (retvalue < 0)
    {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    ledstat = databuf[0]; /*获取状态值*/

    if (ledstat == LEDON)
    {
        led_switch(LEDON);
    }
    else if (ledstat == LEDOFF)
    {
        led_switch(LEDOFF);
    }

    return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static struct file_operations newchrled_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release = led_release,
};

static int __init led_init(void)
{
    int retvalue = 0;
    u32 val = 0;

    IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
    SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
    SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
    GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
    GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);

    /*使能时钟*/
    val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3 << 26); /* 清除以前的设置 */
    val |= (3 << 26);  /* 设置新值 */
    writel(val, IMX6U_CCM_CCGR1);

    /*设置复用*/
    writel(5, SW_MUX_GPIO1_IO03);

    /*设置属性*/
    writel(0x10B0, SW_PAD_GPIO1_IO03);

    /*设置输出功能*/
    val = readl(GPIO1_GDIR);
    val &= ~(1 << 3); /* 清除以前的设置 */
    val |= (1 << 3);  /* 设置为输出 */
    writel(val, GPIO1_GDIR);

    /*默认关闭led*/
    val = readl(GPIO1_DR);
    val |= (1 << 3);
    writel(val, GPIO1_DR);

    /* 6、注册字符设备驱动 */
    if (newchrled.major)
    {
        newchrled.devid = MKDEV(newchrled.major, 0);
        register_chrdev_region(newchrled.devid,
                               NEWCHRLED_CNT,
                               NEWCHRLED_NAME);
    }
    else
    {
        alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);
        newchrled.major = MAJOR(newchrled.devid);
        newchrled.minor = MAJOR(newchrled.devid);
    }
    printk("newcheled major=%d,minor=%d\r\n", newchrled.major, newchrled.minor);

    /*初始化cdev*/
    newchrled.cdev.owner = THIS_MODULE;
    cdev_init(&newchrled.cdev, &newchrled_fops);

    /*添加一个cdev*/
    cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);

    /*创建类*/
    newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
    if (IS_ERR(newchrled.class))
    {
        return PTR_ERR(newchrled.class);
    }

    /*创建设备*/
    newchrled.device = device_create(newchrled.class,
                                     NULL,
                                     newchrled.devid,
                                     NULL,
                                     NEWCHRLED_NAME);

    if (IS_ERR(newchrled.device))
    {
        return PTR_ERR(newchrled.device);
    }

    return 0;
}

static void __exit led_exit(void)
{
    /* 取消映射 */
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);

    cdev_del(&newchrled.cdev);
    unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);
    device_destroy(newchrled.class, newchrled.devid);
    class_destroy(newchrled.class);

    printk("led_exit()\r\n");
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("liuxing");

编译后加载到开发板,可以看到自动运行起来了
在这里插入图片描述
可以正常实现开关灯控制
在这里插入图片描述

3.5 设备树开发示例

在新版本的linux中,arm相关的驱动都采用了设备树,很多新的CPU也都是基于设备树,因此学习了解设备树还是很有必要的。下面部分来了解一下设备数相关的知识

设备树起源

描述设备树的文件叫DTS,这个DTS采用树的结构来描述板级设备,也就是开发板上的信息,例如CPU 数量、 内存基地址、 IIC 接口上接了哪些设备、 SPI 接口上接了哪些设备这些内容,如下图所示:
在这里插入图片描述
树的主干就是系统总线,IIC 控制器、GPIO 控制器、SPI 控制器等都是接到系统主线上的分支。IIC 控制器有分为 IIC1 和 IIC2 两种,其中 IIC1 上接了 FT5206 和 AT24C02这两个 IIC 设备, IIC2 上只接了 MPU6050 这个设备。 DTS 文件的主要功能就是按照图所示的结构来描述板子上的设备信息, DTS 文件描述设备信息是有相应的语法规则要求的,也就是说只要掌握了这个规则,就能清楚怎么编写设备树。

设备树前后写驱动区别

这个部分直接参考原教程吧,愿教程举了个例子,如下所示
在这里插入图片描述
就是说每个板子为了支持这些芯片都得写这样一个文件到里面去,然后linux有需要很好的兼容性,这些东西都会被加入到linux内核中,导致linux内核越来越大,之后就引入了设备树这个概念
在这里插入图片描述

DTS、DTB 和 DTC

这里也直接看教程就行
在这里插入图片描述
因此我们如果仅仅编译设备树的话,只要在源码目录下输入即可,编译完整的zImage才使用make all

make dtbs

具体到我们这个板子上来,是那个设备树可以看这个
在这里插入图片描述
这里面这个就是我们需要的了
在这里插入图片描述

DTS语法

设备树很大,一般不会从头到尾重写一个dts文件,大多数时候是直接在SOC厂商的dts下面修改,教程中以imx6ull-alientek-emmc.dts为例学习一下dts语法,这个文件在这个路径下面
在这里插入图片描述
可以看到他开头就引用了原厂的dts文件
在这里插入图片描述
打开原厂的dts文件,就可以看到他描述的一些外设信息
在这里插入图片描述
这里我们可以进入到can的描述里面看一下
在这里插入图片描述
原教程根据这个得出了一个设备树的基本模版:

/ {
	aliases {
		can0 = &flexcan1;
    };

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;

        cpu0: cpu@0 {
            compatible = "arm,cortex-a7";
            device_type = "cpu";
            reg = <0>;
        };
    };

    intc: interrupt-controller@00a01000 {
        compatible = "arm,cortex-a7-gic";
        #interrupt-cells = <3>;
        interrupt-controller;
        reg = <0x00a01000 0x1000>,
              <0x00a02000 0x100>;
    };
}

具体说明如下图,另外还有很多信息,比较多,还是参考原教程吧,这里不过多赘述
在这里插入图片描述
设备树在板子中的体现
在这里插入图片描述

3.5 设备树驱动led测试

先修改设备树,设备树我们现在用的是这个
在这里插入图片描述
在跟节点加入下面内容:
在这里插入图片描述
源代码如下:

	alphaled {
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "atkalpha-led";
		status = "okay";
		reg = <	0X020C406C 0X04 	/* CCM_CCGR1_BASE */
				0X020E0068 0X04 	/* SW_MUX_GPIO1_IO03_BASE */
				0X020E02F4 0X04 	/* SW_PAD_GPIO1_IO03_BASE */
				0X0209C000 0X04 	/* GPIO1_DR_BASE */
				0X0209C004 0X04 >; 	/* GPIO1_GDIR_BASE */
	};

之后编写基于设备树的驱动代码,可以和上一章的代码进行对比,基本就是寄存器的信息在这个程序中看不到了,变成了从设备树中读取代码。

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define dtsled_CNT 1         /* 设备号个数 */
#define dtsled_NAME "dtsled" /* 名字 */
#define LEDOFF 0             /* 关灯 */
#define LEDON 1              /* 开灯 */

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

struct dtsled_dev
{
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node *nd; /*设备节点*/
};

struct dtsled_dev dtsled;

void led_switch(u8 sta)
{
    u32 val = 0;
    if (sta == LEDON)
    {
        val = readl(GPIO1_DR);
        val &= ~(1 << 3);
        writel(val, GPIO1_DR);
    }
    else if (sta == LEDOFF)
    {
        val = readl(GPIO1_DR);
        val |= (1 << 3);
        writel(val, GPIO1_DR);
    }
}

static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &dtsled;
    return 0;
}

static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

static ssize_t led_write(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;

    retvalue = copy_from_user(databuf, buf, cnt);
    if (retvalue < 0)
    {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    ledstat = databuf[0]; /*获取状态值*/

    if (ledstat == LEDON)
    {
        led_switch(LEDON);
    }
    else if (ledstat == LEDOFF)
    {
        led_switch(LEDOFF);
    }

    return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static struct file_operations dtsled_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release = led_release,
};

static int __init led_init(void)
{
    u32 val = 0;
    int ret = 0;
    u32 regdata[14];
    const char *str;
    struct property *proper;

    /*获取设备树中的属性数据*/
    dtsled.nd = of_find_node_by_path("/alphaled");
    if (dtsled.nd == NULL)
    {
        printk("alphaled node can not found!\r\n");
        return -EINVAL;
    }
    else
    {
        printk("alphaled node has been found!\r\n");
    }

    proper = of_find_property(dtsled.nd, "compatible", NULL);
    if (proper == NULL)
    {
        printk("compatible property find failed\r\n");
    }
    else
    {
        printk("compatible = %s\r\n", (char *)proper->value);
    }

    ret = of_property_read_string(dtsled.nd, "status", &str);
    if (ret < 0)
    {
        printk("status read failed!\r\n");
    }
    else
    {
        printk("status = %s\r\n", str);
    }
    ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
    if (ret < 0)
    {
        printk("reg property read failed!\r\n");
    }
    else
    {
        u8 i = 0;
        printk("reg data:\r\n");
        for (i = 0; i < 10; i++)
            printk("%#X ", regdata[i]);
        printk("\r\n");
    }

#if 0 /*下面两种都是可以的*/
    /* 1、寄存器地址映射 */
    IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);
    SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
    SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
    GPIO1_DR = ioremap(regdata[6], regdata[7]);
    GPIO1_GDIR = ioremap(regdata[8], regdata[9]);
#else
    IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0);
    SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);
    SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);
    GPIO1_DR = of_iomap(dtsled.nd, 3);
    GPIO1_GDIR = of_iomap(dtsled.nd, 4);
#endif

    /*使能时钟*/
    val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3 << 26); /* 清除以前的设置 */
    val |= (3 << 26);  /* 设置新值 */
    writel(val, IMX6U_CCM_CCGR1);

    /*设置复用*/
    writel(5, SW_MUX_GPIO1_IO03);

    /*设置属性*/
    writel(0x10B0, SW_PAD_GPIO1_IO03);

    /*设置输出功能*/
    val = readl(GPIO1_GDIR);
    val &= ~(1 << 3); /* 清除以前的设置 */
    val |= (1 << 3);  /* 设置为输出 */
    writel(val, GPIO1_GDIR);

    /*默认关闭led*/
    val = readl(GPIO1_DR);
    val |= (1 << 3);
    writel(val, GPIO1_DR);

    /* 6、注册字符设备驱动 */
    if (dtsled.major)
    {
        dtsled.devid = MKDEV(dtsled.major, 0);
        register_chrdev_region(dtsled.devid,
                               dtsled_CNT,
                               dtsled_NAME);
    }
    else
    {
        alloc_chrdev_region(&dtsled.devid, 0, dtsled_CNT, dtsled_NAME);
        dtsled.major = MAJOR(dtsled.devid);
        dtsled.minor = MAJOR(dtsled.devid);
    }
    printk("newcheled major=%d,minor=%d\r\n", dtsled.major, dtsled.minor);

    /*初始化cdev*/
    dtsled.cdev.owner = THIS_MODULE;
    cdev_init(&dtsled.cdev, &dtsled_fops);

    /*添加一个cdev*/
    cdev_add(&dtsled.cdev, dtsled.devid, dtsled_CNT);

    /*创建类*/
    dtsled.class = class_create(THIS_MODULE, dtsled_NAME);
    if (IS_ERR(dtsled.class))
    {
        return PTR_ERR(dtsled.class);
    }

    /*创建设备*/
    dtsled.device = device_create(dtsled.class,
                                  NULL,
                                  dtsled.devid,
                                  NULL,
                                  dtsled_NAME);

    if (IS_ERR(dtsled.device))
    {
        return PTR_ERR(dtsled.device);
    }

    return 0;
}

static void __exit led_exit(void)
{
    /* 取消映射 */
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);

    cdev_del(&dtsled.cdev);
    unregister_chrdev_region(dtsled.devid, dtsled_CNT);
    device_destroy(dtsled.class, dtsled.devid);
    class_destroy(dtsled.class);

    printk("led_exit()\r\n");
}

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("liuxing");

测试程序可以用之前的ledApp,直接拿过来用
在这里插入图片描述
放到文件里面,最后运行如下所示:
在这里插入图片描述

4、pinctrl 和 gpio 子系统

前面提到的那些驱动来实现功能都需要直接对寄存器来操作,前面的都还只是针对某个GPIO,要是一些复杂的外设,寄存器多的数不胜数,肯定是很麻烦,linux肯定也考虑到了这一点,因此linux内核提供了pinctrl 和 gpio 子系统用于gpio驱动。

先看一下前面的驱动led的实现:

  • 修改设备树, 添加相应的节点,节点里面重点是设置 reg 属性, reg 属性包括了 GPIO
    相关寄存器。
  • 获 取 reg 属 性 中 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 和IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 这两个寄存器地址,并且初始化这两个寄存器,这两个寄存器用于设置 GPIO1_IO03 这个 PIN 的复用功能、上下拉、速度等。
  • 设置 GPIO1_IO03来完成GPIO的输出

这个流程和stm32的那种逻辑驱动是很类似的,因此对于这种通用的行为,就可以做成一个系统来用。

pinctrl 子系统主要工作内容如下

  • 1、获取设备树中 pin 信息。
  • 2、根据获取到的 pin 信息来设置 pin 的复用功能
  • 3、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等

5、并发和竞争

5、1原子操作

5、2自旋锁

5、3自旋锁的衍生

读写自旋锁
顺序锁

5、4信号量

5、6互斥体

6、按键输入

7、内核定时器

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/356595.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MongoDB介绍及安装

文章目录 MongoDB介绍什么是MongoDBMongoDB技术优势MongoDB应用场景 MongoDB快速开始linux安装MongoDB启动MongoDB Server关闭MongoDB服务 Mongo shell使用mongo shell常用命令数据库操作集合操作 安全认证创建管理员账号常用权限创建应用数据库用户 Docker安装MongoDB工具官方…

物流平台如何与电商平台进行自动化流程管理

为什么要实现物流与电商平台进行自动化管理 实现物流平台与电商平台的自动化流程管理对企业和消费者都有着重要的意义&#xff0c;比如以下几点&#xff1a; 提高效率&#xff1a;自动化流程管理可以减少人为操作的错误和延误&#xff0c;提高订单处理和物流配送的效率。通过定…

What is Rust? Why Rust?

why Rust&#xff1f; 目前&#xff0c;Rust 变得越来越流行。然而&#xff0c;仍然有很多人&#xff08;和公司&#xff01;&#xff09;误解了 Rust 的主张价值是什么&#xff0c;甚至误解了它是什么。在本文中&#xff0c;我们将讨论 Rust 是什么以及为什么它是一种可以增强…

Pytest单元测试框架

第一章、pytest概述 Pytest is a framework that makes building simple and scalable tests easy. Tests are expressive and readable—no boilerplate code required. Get started in minutes with a small unit test or complex functional test for your application or l…

Linux提权:Docker组挂载 Rsync未授权 Sudo-CVE Polkit-CVE

目录 Rsync未授权访问 docker组挂载 Sudo-CVE漏洞 Polkit-CVE漏洞 这里的提权手法是需要有一个普通用户的权限&#xff0c;一般情况下取得的webshell权限可能不够 Rsync未授权访问 Rsync是linux下一款数据备份工具&#xff0c;默认开启873端口 https://vulhub.org/#/envir…

第九节HarmonyOS 常用基础组件17-ScrollBar

1、描述 滚动条组件ScrollBar&#xff0c;用于配合可滚动组件使用&#xff0c;如List、Grid、Scroll。 2、接口 可包含子组件 ScrollBar(value:{scroller:Scroller, direction?: ScrollBarDirection, state?: BarState}) 3、参数 参数名 参数类型 必填 描述 scrolle…

148基于matlab的带有gui的轮轨接触几何计算程序

基于matlab的带有gui的轮轨接触几何计算程序,根据不同的踏面和轨头&#xff0c;计算不同横移量下面的接触点位置。程序已调通&#xff0c;可直接运行。 148 matlab 轮轨接触 横移量 (xiaohongshu.com)

Android App开发基础(2)—— App的工程结构

本专栏文章 上一篇 Android开发修炼之路——&#xff08;一&#xff09;Android App开发基础-1 2 App的工程结构 本节介绍App工程的基本结构及其常用配置&#xff0c;首先描述项目和模块的区别&#xff0c;以及工程内部各目录与配置文件的用途说明&#xff1b;其次阐述两种级别…

【qt】switchBtn

方法1 在qtdesigner中设置按钮图标的三个属性&#xff0c;normal off 、normal on和checkabletrue。 from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5 import uic from switchBtn import Ui_Dialogclass Test(QDialog, Ui_…

如何使用Docker部署火狐浏览器并实现无公网ip远程访问

文章目录 1. 部署Firefox2. 本地访问Firefox3. Linux安装Cpolar4. 配置Firefox公网地址5. 远程访问Firefox6. 固定Firefox公网地址7. 固定地址访问Firefox Firefox是一款免费开源的网页浏览器&#xff0c;由Mozilla基金会开发和维护。它是第一个成功挑战微软Internet Explorer浏…

fastapi报错

初始化报错&#xff0c;非常低级错&#xff0c;扇自己10八张 app FastApi()

Java 集合 02 综合练习+基本数据类型对应的包装类

练习1、 自己写的代码&#xff1a; import java.util.ArrayList; public class practice {public static void main(String[] args) {//定义一个集合ArrayList<String> list new ArrayList<>();list.add("aaa");list.add("bbb");list.add(…

蓝桥杯备战——8.DS1302时钟芯片

1.分析原理图 由上图可以看到&#xff0c;芯片的时钟引脚SCK接到了P17,数据输出输入引脚IO接到P23,复位引脚RST接到P13。 2.查阅DS1302芯片手册 具体细节还需自行翻阅手册&#xff0c;我只截出重点部分 总结&#xff1a;数据在上升沿写出&#xff0c;下降沿读入&#xff0c;…

C# IP v4转地址·地名 高德

需求: IPv4地址转地址 如&#xff1a;输入14.197.150.014&#xff0c;输出河北省石家庄市 SDK: 目前使用SDK为高德地图WebAPI 高德地图开放平台https://lbs.amap.com/ 可个人开发者使用&#xff0c;不过有配额限制。 WebAPI 免费配额调整公告https://lbs.amap.com/news/…

C语言——O / 动态内存管理

一、为什么要有动态内存分配 我们已经掌握的内存开辟⽅式有&#xff1a; int val 20;//在栈空间上开辟四个字节 char arr[10] {0};//在栈空间上开辟10个字节的连续空间 但是上述的开辟空间的⽅式有两个特点&#xff1a; • 空间开辟⼤⼩是固定的。 • 数组在申明的时候&am…

Java - JDBC

Java - JDBC 文章目录 Java - JDBC引言JDBC1 什么是JDBC2 MySQL数据库驱动3 JDBC开发步骤4 具体介绍 引言 思考: 当下我们如何操作数据库&#xff1f; 使用客户端工具访问数据库&#xff0c;手工建立连接&#xff0c;输入用户名和密码登录。编写SQL语句&#xff0c;点击执行…

每日OJ题_算法_前缀和②_牛客DP35 【模板】二维前缀和

目录 二维前缀和原理 ②牛客DP35 【模板】二维前缀和 解析代码 二维前缀和原理 在一维数组前缀和算法的基础上&#xff0c;想到&#xff1a;计算二维数组前缀和&#xff0c;不就和计算一维数组前缀和一样&#xff0c;即计算每一个位置的前缀和就相当于&#xff1a; 此位置的…

微信小程序开发学习笔记《13》WXS脚本

微信小程序开发学习笔记《13》WXS脚本 博主正在学习微信小程序开发&#xff0c;希望记录自己学习过程同时与广大网友共同学习讨论。建议仔细阅读对应官方文档 一、WXS介绍 WXS ( WeiXin Script)是小程序独有的一套脚本语言&#xff0c;结合WXML&#xff0c;可以构建出页面的…

【Java与网络2】:HTTP核心知识与Curl工具

HTTP是当前应用最为广泛的通信协议&#xff0c;我们上网、玩游戏、刷视频、查美食都离不开HTTP协议。当我们做开发的时候&#xff0c; 需要经常和H5、Android、IOS、PC前端等不同团队的同学打交道&#xff0c;大家讨论的核心问题之一就是交互的时候协议怎么定&#xff0c;而这个…

###C语言程序设计-----C语言学习(6)#

前言&#xff1a;感谢老铁的浏览&#xff0c;希望老铁可以一键三连加个关注&#xff0c;您的支持和鼓励是我前进的动力&#xff0c;后续会分享更多学习编程的内容。 一. 主干知识的学习 1. while语句 除了for语句以外&#xff0c;while语句也用于实现循环&#xff0c;而且它…