• [技术干货] Linux驱动开发-编写按键驱动
    1. 杂项设备注册函数这篇文章介绍,如何使用杂项设备框架编写一个简单的按键驱动,完成编写、编译、安装、测试等流程,了解一个杂项字符设备驱动的开发流程。下面是杂项字符设备的接口:struct miscdevice { int minor;   /*次设备号 10 20 */ const char *name; /*设备节点的名称*/ const struct file_operations *fops; /*文件操作集合*/ struct list_head list; //链表 struct device *parent; struct device *this_device; const char *nodename; umode_t mode; }; ​ //注册杂项字符设备 extern int misc_register(struct miscdevice * misc); //注销杂项字符设备 extern int misc_deregister(struct miscdevice *misc);按键需要将值传递给应用层,需要使用到copy_to_user函数,这个函数还有一个配对的copy_from_user,下面介绍这两个函数的详细功能和参数:#include <asm/uaccess.h> ​ unsigned long copy_to_user(void __user *to, const void *from, unsigned long n) 函数功能: 将驱动层数据拷贝到应用层。 函数参数: void __user *to  用户空间的地址--到哪里去 const void *from  驱动层的地址--从哪里来 unsigned long n  拷贝的大小 返回值:  0表示成功。 >0表示未拷贝成功的数量。 ​ unsigned long copy_from_user(void *to, const void __user *from, unsigned long n) 函数功能: 将应用层的数据拷贝到驱动层。 函数参数: void  *to  驱动空间的地址--拷贝到哪里去 const void __user *from  用户空间的地址--从哪里来 unsigned long n  拷贝的大小 返回值:  0表示成功。 >0表示未拷贝成功的数量。2. 编写按键驱动使用杂项设备注册按键驱动,应用层使用read接口读取按键值。编写驱动之前需要先找到按键的原理图,找到按键接到CPU那个IO上的。然后再查阅数据手册,找到这个GPIO口的寄存器地址,寄存器的配置页面,方便初始化配置GPIO口为输入模式。2.1 按键驱动源代码#include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/miscdevice.h> #include <asm/io.h> #include <asm/uaccess.h> ​ /* 按键的寄存器*/ static unsigned int *GPX3CON; static unsigned int *GPX3DAT; ​ static int tiny4412_open(struct inode *inode, struct file *file) { printk("tiny4412_open-->ok\n"); return 0; } ​ /*应用层的函数: int key_val; read(fd,&key_val,4) ssize_t read(int fd, void *buf, size_t count); */ static ssize_t tiny4412_read(struct file *file, char __user *buf, size_t size, loff_t *seek) { int key_val=0; if(!(*GPX3DAT&1<<2)) //判断按键是否按下 { key_val=0x1; } else if(!(*GPX3DAT&1<<3)) //判断按键是否按下 { key_val=0x2; } else if(!(*GPX3DAT&1<<4)) //判断按键是否按下 { key_val=0x3; } else if(!(*GPX3DAT&1<<5)) //判断按键是否按下 { key_val=0x4; } ​ /*数据拷贝函数: 给应用层空间赋值--将驱动层的数据拷贝给应用层*/ /*copy_to_user(void __user *to, const void *from, unsigned long n)*/    int error; error=copy_to_user(buf,&key_val,4); if(error>0) { printk("数据拷贝失败.\n"); } return 0; } static ssize_t tiny4412_write(struct file *file, const char __user *buf, size_t size, loff_t *seek) { return 0; } static int tiny4412_release(struct inode *inode, struct file *file) { printk("tiny4412_release-->ok\n"); return 0; } ​ static struct file_operations fops= { .open=tiny4412_open, .read=tiny4412_read, .write=tiny4412_write, .release=tiny4412_release }; ​ /* Linux内核管理驱动---设备号 设备号是一个unsigned int 的变量--32位。 设备号=主设备号+次设备号 */ static struct miscdevice misc= { .minor = MISC_DYNAMIC_MINOR,  /*次设备号填255表示自动分配     主设备号固定为10*/ .name = "tiny4412_key",  /*/dev目录下文件名称*/ .fops = &fops, /*文件操作接口*/ }; ​ static int __init tiny4412_key_init(void) {    /*转换物理地址*/ GPX3CON=ioremap(0x11000C60,4); GPX3DAT=ioremap(0x11000C64,4); ​ /*配置GPIO口模式--配置按键*/ *GPX3CON&=0xFF0000FF; /*1. 杂项设备的注册函数*/ misc_register(&misc);    printk("按键: 驱动安装成功\n");    return 0; } ​ static void __exit tiny4412_key_exit(void) { /*2. 杂项设备的注销函数*/ misc_deregister(&misc); ​ /*取消转换*/ iounmap(GPX3CON); iounmap(GPX3DAT);    printk("按键: 驱动卸载成功\n"); } ​ module_init(tiny4412_key_init); /*驱动入口--安装驱动的时候执行*/ module_exit(tiny4412_key_exit); /*驱动出口--卸载驱动的时候执行*/ ​ MODULE_LICENSE("GPL");  /*设置模块的许可证--GPL*/2.2 makefile文件编译驱动的makefile代码。KER_DRI=/home/wbyq/work/linux-3.5/linux-3.5 all: make -C $(KER_DRI) M=`pwd` modules cp *.ko /home/wbyq/work/rootfs/code -f make -C $(KER_DRI) M=`pwd` modules clean arm-linux-gcc app.c -o app cp app /home/wbyq/work/rootfs/code -f rm app -f obj-m += miscdev_key_drv.o2.3 应用层驱动测试代码编译完运行时,传入按键的设备节点文件.#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> ​ int main(int argc,char **argv) {    if(argc!=2)   {        printf("./app <设备节点文件>\n");        return 0;   }    /*1. 打开设备文件*/    int fd=open(argv[1],O_RDWR);    if(fd<0)   {        printf("%s 设备驱动打开失败.\n",argv[1]);        return 0;   }    /*2.读写数据*/    int key_val;    while(1)   {        read(fd,&key_val,4);//读取按键值 if(key_val) { printf("%#x\n",key_val); }   }    /*3. 关闭文件*/    close(fd);    return 0; }2.4 驱动安装流程[root@wbyq code]# ls tiny4412_key_drv.ko [root@wbyq code]# [root@wbyq code]# insmod tiny4412_key_drv.ko [  173.340000] 驱动测试: 驱动安装成功 [root@wbyq code]# lsmod hello_drv 616 0 - Live 0xbf000000 (O) [root@wbyq code]# modinfo tiny4412_key_drv.ko filename:       tiny4412_key_drv.ko license:       GPL depends:         vermagic:       3.5.0-FriendlyARM SMP preempt mod_unload ARMv7 p2v8 [root@wbyq code]# rmmod tiny4412_key_drv.ko [  391.075000] 驱动测试: 驱动卸载成功 [root@wbyq code]#
  • [专题汇总] 嵌入式Linux开发技术-驱动篇
    一、前言Linux是一种开源操作系统, Linux 是基于 Unix 操作系统的,它提供了与 Unix 类似的文件系统、进程管理、网络连接等功能。 Linux 的核心是内核,它负责管理系统资源,如内存、处理器、磁盘空间等。内核是由内核程序员编写的,并且内核的源代码是公开的,可以被任何人修改和重新发布。 Linux 操作系统是跨平台的,它可以运行在各种不同的硬件平台上,如个人电脑、服务器、移动设备等。它也可以运行在各种不同的架构上,如 x86、ARM、PowerPC 等。 Linux 操作系统的优点非常多,其中最重要的是它是免费的,可以自由使用和修改。它还具有很高的稳定性和安全性,适用于各种类型的应用场景,包括个人电脑、服务器、移动设备等。 这篇文章整理了Linux下驱动开发的系列教程;在Linux下进行驱动开发,完全将驱动程序与应用程序隔开,中间通过C库函数以及系统调用完成驱动层和应用层的数据交换。在Linux下进行驱动开发,完全将驱动程序与应用程序隔开,中间通过C库函数以及系统调用完成驱动层和应用层的数据交换。由于Linux 是一个开源操作系统,可以移植到任意硬件平台,目前有很多物联网操作系统都基于Linux进行开发,从广义角度来看,Linux 是物联网生态系统的核心,从最小的物联网设备到边缘网关和云。最近一项由 Eclipse IoT 工作组、AGILE IoT、IEEE 和开放移动联盟赞助的在线调查发现,在物联网开发人员中,大约 72% 的受访者将 Linux 用于他们的物联网设备。其开源操作系统、可扩展性、安全特性和广泛的发行版等因素使 Linux 成为物联网开发的热门选择。二、本期系列文章 【1】嵌入式开发_(exynos4412)Tiny4412裸机开发-按键检测 https://bbs.huaweicloud.com/forum/thread-0231108714396506016-1-1.html  【2】嵌入式开发_(exynos4412)Tiny4412裸机开发-点亮LED灯 https://bbs.huaweicloud.com/forum/thread-0259108714885997023-1-1.html  【3】嵌入式Linux开发-busybox根文件系统制作 https://bbs.huaweicloud.com/forum/thread-02102108715054447017-1-1.html  【4】嵌入式Linux开发-uboot常用命令介绍(上篇) https://bbs.huaweicloud.com/forum/thread-0231108869191526022-1-1.html  【5】嵌入式Linux开发-uboot常用命令介绍(下篇) https://bbs.huaweicloud.com/forum/thread-0224108869504571029-1-1.html  【6】基于STM32+华为云IoT设计的智慧路灯(从0开始上云搭建系统框架) https://bbs.huaweicloud.com/forum/thread-0224108959170598032-1-1.html  【7】嵌入式Linux开发-编写自定义uboot命令 https://bbs.huaweicloud.com/forum/thread-0297110343079945016-1-1.html  【8】嵌入式Linux开发-根文件系统NFS网络挂载 https://bbs.huaweicloud.com/forum/thread-02101110343252006013-1-1.html  【9】嵌入式Linux开发-根文件系统本地挂载 https://bbs.huaweicloud.com/forum/thread-0279110343379535017-1-1.html  【10】嵌入式Linux下完成LCD屏文字显示(帧缓冲框架) https://bbs.huaweicloud.com/forum/thread-0282110343888980019-1-1.html  【11】Linux驱动框架与杂项字符设备框架介绍 https://bbs.huaweicloud.com/forum/thread-0219110344084488024-1-1.html  【12】Linux驱动开发-外部中断的注册使用(按键为例) https://bbs.huaweicloud.com/forum/thread-02109110344183191027-1-1.html  【13】Linux驱动开发-内核共享工作队列 https://bbs.huaweicloud.com/forum/thread-0279110344337151018-1-1.html  【14】Linux驱动开发-内核定时器 https://bbs.huaweicloud.com/forum/thread-0282110344392897020-1-1.html  【15】Linux驱动开发-编写超声波测距模块的驱动 https://bbs.huaweicloud.com/forum/thread-0297110344563810018-1-1.html 
  • [技术干货] Linux驱动开发-外部中断的注册使用(按键为例)
    1. 外部中断介绍前面有篇文章使用杂项设备完成了按键驱动的编写,实现了按键轮询检测,通过read函数向应用层传递按键值,这篇文章使用按键为例,介绍Linux内核里中断的注册方法,使用中断的方式检测按键是否按下,中断在单片机、设备驱动开发里使用的都非常多,可以更加实时的检测到按键触发的情况。Linux内核提供了中断的注册接口:(1)注册中断头文件     include\linux\interrupt.h ​ 定义文件 include\linux\interrupt.h ​ 函数原型 int request_irq(unsigned int irq,/*做实参传递给中断服务函数第1个参数*/ irq_handler_t handler, /*中断服务函数指针*/           unsigned long flags,           const char *name,           void *dev_id); /*做实参传递给中断服务函数第2个参数*/ ​ 函数功能 向内核注册一个中断服务函数; 当发生中断号为 irq 的中断时候,会执行 handler 指针函数。 ​ 函数参数 irq:中断编号(每个中断源有惟一的编号)。 ​ handler:中断服务函数指针。          原型 typedef irqreturn_t (*irq_handler_t)(int, void *)。 flag:中断的标志,用来描述本中断的基本特征的。      有固定的值,由中断源的特征决定;      比如外中断有:上升沿,下降沿触发中断这类标志。 name:中断名字, 注册后会出现cat /proc/interrupts dev_id: 这个参数是传递给中断服务函数。         对共享中断来说, 这个参数一定有要;         当注销共享中断中的其中一个时, 用这个来标识要注销哪一个。         对于有惟一入口的中断,可以传递 NULL;         但是一般来说都会传递一个有意义指针,在中断程序中使用, 以方便编程。 ​     返回值 0 表示成功 -EINVAL (无效参数22)表示中断号无效。 -EBUSY (设备或者资源忙16)表示中断已经被占用。(2)注销中断void free_irq(unsigned int irq,void * dev_id) irq: 要注销的中断号 dev_id:其实就是注册时候使用的dev参数,在共享中断必不可少,不能传递NULL。 注意:为了防止在注销时同时发生中断,调用时候,先禁止中断。(3)中断开启与关闭禁止中断 void disable_irq_nosync(unsigned int irq); void disable_irq(unsigned int irq); 参数:irq,要禁止的中断对应的编号。 注意:在中断服务程序中不能使用 disable_irq 这个函数,否则内核崩溃,可以使用 disable_irq_nosync。 disable_irq:函数调用后,函数不会马上返回,而等待中断程序执行完成才返回,在中断调用会导致死锁。 disable_irq_nosync:调用后,函数马上返回。 ​ 使能中断 void enable_irq(unsigned int irq); 参数:irq,要使能的中断对应的编号 (4)获取irq中断号int gpio_to_irq(unsigned gpio);2. 外部中断驱动编写2.1 按键原理图2.2 驱动示例代码insmod 安装驱动之后就直接注册按键中断,没有注册字符设备框架,当按键按下之后,直接在驱动层通过printk打印数据提示到终端。#include <linux/kernel.h> #include <linux/module.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> ​ /*存放按键的信息*/ struct m_key_info { int gpio; char name[50]; int val; int irq; }; ​ struct m_key_info key_info[]= { {EXYNOS4_GPX3(2),"key_irq_1",0x01}, {EXYNOS4_GPX3(3),"key_irq_2",0x02}, {EXYNOS4_GPX3(4),"key_irq_3",0x03}, {EXYNOS4_GPX3(5),"key_irq_4",0x04}, }; ​ ​ /* 中断服务函数 */ static irqreturn_t key_irq_handler(int irq, void *dev) {    struct m_key_info *p=(struct m_key_info*)dev; if(gpio_get_value(p->gpio)==0) //判断按键是否按下 { printk("按键值:%#x\n",p->val); } else { printk("按键值:%#x\n",p->val|0x80); } return IRQ_HANDLED; } ​ static int __init tiny4412_interrupt_drv_init(void) { int i; for(i=0;i<sizeof(key_info)/sizeof(key_info[0]);i++) { /*1. 获取中断号*/ key_info[i].irq=gpio_to_irq(key_info[i].gpio); ​ /*2. 注册中断*/ if(request_irq(key_info[i].irq,key_irq_handler,IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,key_info[i].name,&key_info[i])) { printk("中断号%d注册失败:%s\n",key_info[i].irq,key_info[i].name); } }    printk("按键中断 驱动注册-安装成功.\n");    return 0; } ​ static void __exit tiny4412_interrupt_drv_exit(void) { /*注销中断*/ int i=0; for(i=0;i<sizeof(key_info)/sizeof(key_info[0]);i++) { free_irq(key_info[i].irq,&key_info[i]); }    printk("按键中断 驱动注销成功.\n"); } ​ /*驱动入口*/ module_init(tiny4412_interrupt_drv_init); /*驱动出口*/ module_exit(tiny4412_interrupt_drv_exit); /*许可证*/ MODULE_LICENSE("GPL");2.3 makefile代码KER_DRI=/home/wbyq/work/linux-3.5/linux-3.5 all: make -C $(KER_DRI) M=`pwd` modules cp *.ko /home/wbyq/work/rootfs/code -f make -C $(KER_DRI) M=`pwd` modules clean obj-m += interrupt_key.o
  • [技术干货] 嵌入式Linux开发-uboot常用命令介绍(下篇)
    1. 前言这篇文章是UBOOT命令介绍的下篇,接着上篇文章介绍剩下的几个命令用法。主要是涉及的命令是:磁盘分区、磁盘文件加载、内核引导、二进制文件加载、跳转命令、磁盘文件系统格式等等。2. UBOOT命令2.1 fatls –列出指定目录下的文件查看帮助: TINY4412 # ? fatls  fatls - list files in a directory (default /)           列出一个目录文件  ​  Usage:  fatls <interface> <dev[:part]> [directory]  ​     - list files from 'dev' on 'interface' in a 'directory'参数说明: <interface>: mmc 或 usb;  dev: 设备编号;  part: 设备分区号;  [directory]: 目录, 是可选, 可以不写,不写默认 / 目录查看SD卡中的文件列表(查看之前SD需要有完好的分区才行,可以通过fdisk进行分区,从U-BOOT和内核地址之后开始分区,防止将U-BOOT和内核清除) TINY4412 # fatls mmc 0 /  Partition1: Start Address(0x71c53a), Size(0x2025c6)             system volume information/              12345/  ​  0 file(s), 2 dir(s)   共用两个目录,0个文件----进过确认正确的查看子目录下的文件: TINY4412 # fatls mmc 0 /12345  Partition1: Start Address(0x71c53a), Size(0x2025c6)             ./             ../              5567/  ​  0 file(s), 3 dir(s)2.2 从一个MMC文件系统(fat)中加载一个二进制文件到DDR查看帮助: TINY4412 # help fatload  fatload - fatload - load binary file from a dos filesystem  ​  ​  Usage:  fatload <interface> <dev[:part]> <addr> <filename> [bytes]      - load binary file 'filename' from 'dev' on 'interface'       to address 'addr' from dos filesystem参数说明: <interface>: mmc 或 usb;  dev:       设备编号(可以通过启动时查看或者列出存储器);  part:       设备分区号;  <addr>:     DDR 内存地址  <filename>: 要加载二进制文件( 包含完整路径)  [bytes]:要加载数据大小,字节为单位。可选的,可以不写, 不写时候默认等于文件大小。  加载文件需要SD或者EMMC有完好的文件系统。先将SD卡从开发板取出(开发板不要断电),通过读卡器插入 PC,复制一些文件到卡里,然后再重新插入开发板中 (SD卡拔出来时开发板不要断电,目的想测试一下 mmc rescan 命令作用)。SD卡拔掉之后,UBOOT一样可以运行,因为程序已经拷贝到DDR中运行了,只要不断电U-BOOT就可以正常运行。文件拷贝完将SD卡再放回开发板,先不要重新扫描 mmc 设备,直接输入 fatls 就会出错: TINY4412 # fatls mmc 0   /* 打印错误信息,因为开发板没有断电,设备0是SD卡*/  count: 1  # Tx: Inverter delay / Rx: Inverter delay  ​  count: 2  ## Tx: Basic delay / Rx: Inverter delay  ​  count: 3  ## Tx: Inverter delay / Rx: Basic delay  ​  count: 4  ### Tx: Basic delay / Rx: Basic delay  ​  count: 5  # Tx: Disable / Rx: Basic delay  ​  count: 6  ## Tx: Disable / Rx: Inverter delay  ​  count: 7  ### Tx: Basic delay / Rx: Disable  ​  count: 8  ### Tx: Inverter delay / Rx: Disable  mmc read failed ERROR: -19  data.dest: 0xc3cfbbdc  data.blocks: 1  data.blocksize: 512  MMC_DATA_READ  ** Can't read from device 0 **  ​  ** Unable to use mmc 0:1 for fatls **  TINY4412 # 扫描设备0,再读出信息: TINY4412 # mmc rescan 0 扫描设备  TINY4412 # fatls mmc 0   列出设备的文件目录  ​  /* 成功列出了SD卡文件目录信息*/  Partition1: Start Address(0xa203d2), Size(0x2037b2)             system volume information/    4783928   zimage     277108   u-boot.bin     127245   纇/u-boot.pdf       5268   2015-12-30txt     731729   shell,                     - a.pdf  ​  5 file(s), 1 dir(s)开始测试 fatload 命令: TINY4412 # fatload mmc 0 48000000 zimage     将 zimage文件加载到DDR的48000000地址处    Partition1: Start Address(0xa203d2), Size(0x2037b2)  reading zimage  ​  4783928 bytes read 成功加载文件的大小(字节单位)  TINY4412 # md.b 48000000     打印出DDR 48000000地址处的数据  48000000: 00 00 a0 e1 00 00 a0 e1 00 00 a0 e1 00 00 a0 e1   ................  48000010: 00 00 a0 e1 00 00 a0 e1 00 00 a0 e1 00 00 a0 e1   ................  48000020: 02 00 00 ea 18 28 6f 01 00 00 00 00 38 ff 48 00   .....(o.....8.H.  48000030: 01 70 a0 e1 02 80 a0 e1 00 20 0f e1 03 00 12 e3   .p....... ......2.3 cmp --比较内存数据是否相同 查看帮助  TINY4412 # ? cmp                      cmp - memory compare   内存比较  ​  Usage:  cmp [.b, .w, .l] addr1 addr2 count 格式         注意:count 是用十六进制表示  cmp .b :以1个字节方式  cmp .w :以2 个字节方式  cmp .l :以4 个字节方式  ​  格式:  Cmp.b 地址1 地址2 比较数据的数量  ​  ① 比较DRR两个地址数据是否相等  TINY4412 # cmp.b 48000000 49000000 10     比较两个地址数据---数量是10个  byte at 0x48000000 (0x00) != byte at 0x49000000 (0xff)  Total of 0 bytes were the same 共有0字节是相同的  TINY4412 #  ​  ​  ② 从MMC读取1个扇区的数据到DDR的两个地址  TINY4412 # mmc read 0 48000000 1 1  ​  MMC read: dev # 0, block # 1, count 1 ... 1 blocks read: OK  TINY4412 # mmc read 0 49000000 1 1  ​  MMC read: dev # 0, block # 1, count 1 ... 1 blocks read: OK    ③ 再次比较两个地址的数据  TINY4412 # cmp.b 48000000 49000000 10   ( 注意:这里的10是十六进制的10 ,转成十进制就是16)  Total of 16 bytes were the same 共有16个字节都是一样的。2.4 mm --地址以自动增加的方式修改内存数据 查看帮助:  TINY4412 # ? mm  mm - memory modify (auto-incrementing address) 修改内存(增加的地址)  ​  Usage:  mm [.b, .w, .l] address   格式: address要修改的地址  ​  ① 先将DDR某处数据打印出来,方便修改完比较  TINY4412 # md.b 48000000 10  48000000: a3 69 d3 18 e9 7d b9 66 d1 6b d5 6e d4 79 a6 79   .i...}.f.k.n.y.y  ​  ② 修改数据  TINY4412 # mm.b 48000000              48000000: a3 ? 5   //把a3 修改为5  48000001: 69 ? 6   //把69 修改为6  48000002: d3 ? 7  48000003: 18 ? 8  48000004: e9 ?       不想修改直接按下<回车键>跳过  48000005: 7d ? 9  48000006: b9 ? TINY4412 # <INTERRUPT>   修改完直接按ctrl+c 结束  ​  ③ 再次查看数据  TINY4412 # md.b 48000000 10  48000000: 05 06 07 08 e9 09 b9 66 d1 6b d5 6e d4 79 a6 79   .......f.k.n.y.y 修改之后的数据  ​  将修改之前的数据与修改之后的比较,发现已经修改成功!  其他类似命令:  mm.w:一次修改 2 字节  mm.l:一次修改 4 字节2.5 cp –内存拷贝 查看帮助:  TINY4412 # ? cp  cp - memory copy   内存复制  ​  Usage: 用法格式  cp [.b, .w, .l] source target count   注意这里的数量是用16进制表示的  格式:cp.b 源地址 目标地址 数量  ① 读出DDR两个地址的数据,方便后面比较  TINY4412 # md.b 45000000 10   显示数据  45000000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff     ................  TINY4412 # md.b 49000000 10   显示数据  49000000: a3 69 d3 18 e9 7d b9 66 d1 6b d5 6e d4 79 a6 79   .i...}.f.k.n.y.y  ​  ​  ② 将DDR的4900000地址前10个字节拷贝到45000000地址处  TINY4412 # cp 49000000 45000000 10  ​  ③ 将两处地址的数据再显示出来  TINY4412 # md.b 45000000 10          45000000: a3 69 d3 18 e9 7d b9 66 d1 6b d5 6e d4 79 a6 79   .i...}.f.k.n.y.y  TINY4412 # md.b 49000000 10  49000000: a3 69 d3 18 e9 7d b9 66 d1 6b d5 6e d4 79 a6 79   .i...}.f.k.n.y.y  拷贝之后,将两处地址数据再次比较,两边数据是一样的。2.6 loady - 使用串口下载二进制数据到内存中U-BOOT支持的串口传输模式: loadb   - load binary file over serial line (kermit mode)  loads   - load S-Record file over serial line  loady   - load binary file over serial line (ymodem mode)串口下载文件到DDR,上面是U-BOOT支持串口的3种传输模式。CRT串口终端支持的协议: 查看帮助:  TINY4412 # ? loady  Unknown command '' - try 'help' without arguments for list of all known commands  ​  loady - load binary file over serial line (ymodem mode)  用在串行线加载二进制文件(ymodem模式)  ​  Usage:  loady [ off ] [ baud ]      - load binary file over serial line with offset 'off' and baudrate 'baud'  参数说明:  [ off ]: DDR 内存地址, 可选。  [ baud ]:使用多快的波特率下载, 可选,不填就表示默认的115200波特率。  示例:  loady 0x40000000 115200测试loady命令:(1)下载文件到内存 TINY4412 # loady 40000000 下载文件到DDR 40000000地址  ## Ready for binary (ymodem) download to 0x40000000 at 0 bps...(2)对比数据内容(3)执行代码上面下载的bin文件是一个按键的裸机程序,可以用go命令跳转到指定地址去执行下载的代码。 TINY4412 # go 40000000  ## Starting application at 0x40000000 ...  跳转过去之后,按下按键测试! 测试结果正常,按键程序可以正常执行。2.7 go–CPU 跳转到指定的地址执行代码一旦 go 指令执行后, CPU 就会去执行指定地址处的代码。查看帮助: TINY4412 # ? go  go - start application at address 'addr'       在addr处启动应用程序  ​  Usage:  go addr [arg ...]      - start application at address 'addr'       passing 'arg' as arguments 作为参数传递的参数测试go命令 将SD卡第一个扇区数据读到DDR内存中等待执行。读8个扇区  TINY4412 # mmc read 0 45000000 1 8    ​  MMC read: dev # 0, block # 1, count 8 ... 8 blocks read: OK  ​  跳转到45000000地址去执行程序  TINY4412 # go 45000000  ## Starting application at 0x45000000 ...   开始执行地址处的代码,因为扇区1开始存放的是BL1代码,重新执行启动了UBOOT  OK  ​  U-Boot 2010.12 (Jan 01 2016 - 02:37:55) for TINY4412  ​  CPU:   S5PC220 [Samsung SOC on SMP Platform Base on ARM CortexA9]         APLL = 1400MHz, MPLL = 800MHz  ​  Board: TINY4412  DRAM:   1023 MiB  ...........................................................................................................2.8 打开关闭emmc设备引导分区 查看帮助:  TINY4412 # ? emmc  emmc - Open/Close eMMC boot Partition 打开/关闭emmc引导分区  ​  Usage:  emmc open <device num>  emmc close <device num>  ​  对设备读写操作,需要先打开,读写完毕,再关闭。  示例:  emmc close 1   打开设备1  emmc open 1   关闭设备1  ​  emmc close 0   打开设备0  emmc open 0   关闭设备02.9 movi 子系统----从MMC向DDR读写数据该指令在产品发布时需要用到,用来固化内核和UBOOT。查看帮助: TINY4412 # ? movi  movi - movi     - sd/mmc r/w sub system for SMDK board  ​  Usage:  movi init - Initialize moviNAND and show card info  movi read zero {fwbl1 | u-boot} {device_number} {addr} - Read data from sd/mmc 读取数据从sd / mmc  movi write zero {fwbl1 | u-boot} {device_number} {addr} - Read data from sd/mmc 读取数据从sd / mmc  movi read {u-boot | kernel} {device_number} {addr} - Read data from sd/mmc     读取数据从sd / mmc  movi write {fwbl1 | u-boot | kernel} {device_number} {addr} - Write data to sd/mmc 写入数据到sd / mmc  movi read rootfs {device_number} {addr} [bytes(hex)] - Read rootfs data from sd/mmc by size       从sd/mmc读取rootfs数据大小  movi write rootfs {device_number} {addr} [bytes(hex)] - Write rootfs data to sd/mmc by size       写rootfs sd/mmc的数据大小  movi read {sector#} {device_number} {bytes(hex)} {addr} - instead of this, you can use "mmc read"  movi write {sector#} {device_number} {bytes(hex)} {addr} - instead of this, you can use "mmc write"(1)把 sd 卡中 u-boot 的第一阶段的 bl1 数据复制到内存,然后再写入 emmc 对应位置 movi read fwbl1 0 40000000;       //从SD(设备编号为)拷贝bl1到DDR内存地址  emmc open 1;                   //打开EMMC设备  movi write zero fwbl1 1 40000000;   //将DDR地址处数据写入到EMMC对应位置  emmc close 1;                   //关闭EMMC设备用法示例: TINY4412 # movi read fwbl1 0 40000000;  reading FWBL1 ..device 0 Start 1, Count 16  MMC read: dev # 0, block # 1, count 16 ... 16 blocks read: OK 从SD卡第1个扇区开始读,连续读16个扇区  completed  TINY4412 # emmc open 1;  eMMC OPEN Success.!!                         !!!Notice!!!  !You must close eMMC boot Partition after all image writing!  !eMMC boot partition has continuity at image writing time.!  !So, Do not close boot partition, Before, all images is written.!  TINY4412 # movi write zero fwbl1 1 40000000;  writing FWBL1 ..device 1 Start 0, Count 16  MMC write: dev # 1, block # 0, count 16 ... 16 blocks written: OK 从EMMC第0个扇区写,连续写16个扇区  completed  TINY4412 # emmc close 1;  eMMC CLOSE Success.!!  ​  因为SD卡的特性,第0个扇区不能使用,数据只能从第1个扇区开始存放。  EMMC可以直接从第0个扇区存放数据。  所以-----EMMC的第0个扇区相当于SD卡的第1个扇区(2)把sd卡中u-boot的BL2 数据复制到内存,然后再写入 emmc 对应位置 movi read bl2 0 40000000;     //从SD(设备编号为0)拷贝bl2到DDR内存地址  emmc open 1;                 //打开EMMC设备  movi write zero bl2 1 40000000; //将DDR地址处数据写入到EMMC对应位置  emmc close 1;                 //关闭EMMC设备示例: TINY4412 # movi read bl2 0 40000000  reading BL2 ..device 0 Start 17, Count 32  MMC read: dev # 0, block # 17, count 32 ... 32 blocks read: OK //从SD卡的第17个扇区开始读,连续读32个扇区。                                                     //查看UBOOT烧写脚本可知,BL2是从SD卡第17扇区开始烧写的  completed  TINY4412 # emmc open 1            eMMC OPEN Success.!!                         !!!Notice!!!  !You must close eMMC boot Partition after all image writing!  !eMMC boot partition has continuity at image writing time.!  !So, Do not close boot partition, Before, all images is written.!  TINY4412 # movi write zero bl2 1 40000000      writing BL2 ..device 1 Start 16, Count 32  MMC write: dev # 1, block # 16, count 32 ... 32 blocks written: OK   //向EMMC的第17个扇区开始写,连续写32个扇区。  completed  TINY4412 # emmc close 1  eMMC CLOSE Success.!!(3)把 sd 卡中 u-boot 复制到内存,然后再写入 emmc 对应位置 movi read u-boot 0 40000000;     将SD卡的U-BOOT.Bin读到DDR内存空间  emmc open 1;                   打开EMMC设备  movi write zero u-boot 1 40000000; 将DDR的数据写入EMMC设备  emmc close 1;                   关闭EMMC示例: TINY4412 # movi read u-boot 0 40000000  reading bootloader..device 0 Start 49, Count 656  MMC read: dev # 0, block # 49, count 656 ... 656 blocks read: OK 从SD卡第49个扇区开始,读取656个扇区到DDR内存  completed  TINY4412 # emmc open 1  eMMC OPEN Success.!!                         !!!Notice!!!  !You must close eMMC boot Partition after all image writing!  !eMMC boot partition has continuity at image writing time.!  !So, Do not close boot partition, Before, all images is written.!  TINY4412 # movi write zero u-boot 1 40000000  writing bootloader..device 1 Start 48, Count 656        MMC write: dev # 1, block # 48, count 656 ... 656 blocks written: OK 向EMMC的第49个扇区,连续写入656个扇区到DDR内存  completed  TINY4412 # emmc close 1  eMMC CLOSE Success.!!(4)把 sd 卡中 u-boot 安全加密数据复制到内存,然后再写入 emmc 对应位置 movi read tzsw 0 40000000;     将安全加密数据拷贝到DDR  emmc open  1;               打开EMMC设备  movi write zero tzsw 1 40000000; 将DDR数据写入EMMC  emmc close 1;                 关闭EMMC设备示例: TINY4412 # movi read tzsw 0 40000000  reading 0 TrustZone S/W.. Start 705, Count 320  MMC read: dev # 0, block # 705, count 320 ... 320 blocks read: OK 从SD卡的第705个扇区开始,连续读取320个扇区到DDR  Completed                                             安全加密数据是从SD的705个扇区存放的  TINY4412 # emmc open 1  eMMC OPEN Success.!!                         !!!Notice!!!  !You must close eMMC boot Partition after all image writing!  !eMMC boot partition has continuity at image writing time.!  !So, Do not close boot partition, Before, all images is written.!  TINY4412 # movi write zero tzsw 1 40000000;  writing 1 TrustZone S/W.. Start 704, Count 320  MMC write: dev # 1, block # 704, count 320 ... 320 blocks written: OK 写入EMMC  completed  TINY4412 # emmc close 1  eMMC CLOSE Success.!!(5)把 sd 卡中内核数据复制到内存,然后再写入 emmc 对应位置 movi read kernel 0 40000000;     将SD卡的内核数据读到DDR内存中  movi write kernel 1 40000000;     将DDR的数据写入EMMC中示例: TINY4412 # movi read kernel 0 40000000  reading kernel..device 0 Start 1057, Count 12288  MMC read: dev # 0, block # 1057, count 12288 ... 12288 blocks read: OK 从SD卡1057扇区开始,连续读12288个扇区到DDR  completed  TINY4412 # movi write kernel 1 40000000  writing kernel..device 1 Start 1057, Count 12288  MMC write: dev # 1, block # 1057, count 12288 ... 12288 blocks written: OK将DDR的数据写入EMMC,从1057开始写,连续写12288个扇区  completed2.10 movi 精简命令-一键拷贝将UBOOT和内核数据固化到EMMC精简命令: movi r f 0 40008000;emmc open 1;movi w z f 1 40008000;emmc close 1;  movi r b 0 40008000;emmc open 1;movi w z b 1 40008000;emmc close 1;  movi r u 0 40008000;emmc open 1;movi w z u 1 40008000;emmc close 1;  movi r t 0 40008000;emmc open 1;movi w z t 1 40008000;emmc close 1;  movi r k 0 40008000;movi w k 1 40008000;2.11 bootcmd命令的使用bootcmd命令是设置U-BOOT启动成功后执行的命令代码。示例: set bootcmd 'mmc read 0 40000000 421 1;md.b 40000000'  ​  格式:setenv ‘ 需要执行的命令’       Save //保存设置2.12 执行二进制文件-->bootm命令bootm命令是用来引导经过U-Boot的工具mkimage打包后的kernel image的。查看帮助: TINY4412 # ? bootm  bootm - boot application image from memory //bootm从内存中启动应用程序  ​  Usage:  bootm [addr [arg ...]]      - boot application image stored in memory         passing arguments 'arg ...'; when booting a Linux kernel,          'arg' can be the address of an initrd image  //传递参数的参数…”;当引导Linux内核,“参数”可以是映像文件的地址  ​  Sub-commands to do part of the bootm sequence. The sub-commands must be  issued in the order below (it's ok to not issue all sub-commands):          start [addr [arg ...]]         loados  - load OS image 加载操作系统映像         cmdline - OS specific command line processing/setup 操作系统特定的命令行处理/设置         bdt     - OS specific bd_t processing 操作系统特定bd_t处理         prep    - OS specific prep before relocation or go         go      - start OS 启动操作系统示例:(1)直接引导内核 TINY4412 # mmc read 0 40007fc0 421 3000    将SD卡内核读到DDR内存空间----内核映像是从SD卡1057扇区开始存放的,连续占用了12288个扇区  (注意: 421是0x421  3000是0x3000)  MMC read: dev # 0, block # 1057, count 12288 ... 12288 blocks read: OK  ​  TINY4412 # bootm 40007fc0   执行DDR--40007fc0地址处的二进制文件  Boot with zImage  ​  Starting kernel ...  ​  Uncompressing Linux... done, booting the kernel.    (2)设置UBOOT启动成功自动引导内核 TINY4412 # setenv bootcmd 'mmc read 0 40007fc0 421 3000;bootm 40007fc0' U-BOOT启动成功之后自动执行  TINY4412 # save 保存设置  或者使用bootcmd=movi read kernel 0 40008000;movi read rootfs 0 41000000 100000;bootm 40008000 410000002.13 分区命令-fdisk查看帮助: TINY4412 # ? fdisk  fdisk - fdisk for sd/mmc. //硬盘分区工具  Usage:  fdisk -p <device_num>   - print partition information   //打印分区信息  fdisk -c <device_num> [<sys. part size(MB)> <user data part size> <cache part size>]          - create partition //创建分区(分区单位是M)(1)查看分区信息示例 TINY4412 # fdisk -p 0   //查看SD卡分区信息  ​  分区         大小       扇区开始地址       扇区数量(512字节一个扇区)         分区ID名称  partion #   size(MB)       block start #             block count                       partition_Id     1          1028          7456058              2106822                           0x06     4             0         28049408               441                             0x00 (2)给SD卡分区示例(分区时--会自行在SD卡开头大约66M后的空间开始分区,因为开头部分需要用来存放内核与U-BOOT) TINY4412 # fdisk -c 0 320 2057 520   //给SD卡分区, -c 表示分区  fdisk is completed     //提示分区完成  ​  分区         大小       扇区开始地址       扇区数量(512字节一个扇区)         分区ID名称  partion #   size(MB)     block start #               block count                     partition_Id     1          4416          6090216              9045762                       0x0C     2           320           134343              656788                         0x83     3          2062           791131              4224341                       0x83     4           524          5015472              1074744                       0x83 2.14 指定EMMC的分区文件系统格式U-BOOT支持格式化的文件系统格式: fatformat- fatformat - disk format by FAT32  ext3format- ext3format - disk format by ext3  ext2format- ext2format - disk format by ext2查看 fatformat命令使用帮助: TINY4412 # ? fatformat  fatformat - fatformat - disk format by FAT32             指定磁盘的格式位FAT32  ​  Usage:  fatformat <interface(only support mmc)> <dev:partition num> 用法格式          - format by FAT32 on 'interface'        ​  其他两个命令,用法一样!(1)指定分区命令-用法示例 fatformat mmc 0:1   //表示将第0个盘的第一个分区初始化为 fat  ext3format mmc 0:2   //表示将第0个盘的第二个分区初始化为 ext3  ext2format mmc 0:3       //表示将第0个盘的第三个分区初始化为 ext2  ext3format mmc 0:4   //表示将第0个盘的第四个分区初始化为 ext3(2)SD卡分区文件系统格式化完毕,将SD卡插入电脑,查看SD卡的分区信息(3)将SD卡挂载进虚拟机,查看设备节点。
  • [技术干货] 嵌入式Linux开发-uboot常用命令介绍(上篇)
    1. 前言U-boot是一种开源bootloader, 作用是用来引导操作,以及给开发人员提供测试调试工具。本身算是个精简的Linux系统,主要是负责硬件的初始化和引导,本身带有一些工具,作为引导程序,常作为嵌入式设备的引导。当真正的系统开始运行的时候U-boot就把管理权限交了出去。选择U-Boot的理由: ① 开放源码; ② 支持多种嵌入式操作系统内核,如Linux、NetBSD, VxWorks, QNX, RTEMS, ARTOS, LynxOS, android; ③ 支持多个处理器系列,如PowerPC、ARM、x86、MIPS; ④ 较高的可靠性和稳定性; ⑤ 高度灵活的功能设置,适合U-Boot调试、操作系统不同引导要求、产品发布等; ⑥ 丰富的设备驱动源码,如串口、以太网、SDRAM、FLASH、LCD、NVRAM、EEPROM、RTC、键盘等; ⑦ 较为丰富的开发调试文档与强大的网络技术支持;U-BOOT工作模式U-Boot的工作模式有启动加载模式和下载模式。启动加载模式是Bootloader的正常工作模式,嵌入式产品发布时,Bootloader必须工作在这种模式下,Bootloader将嵌入式操作系统从FLASH中加载到SDRAM中运行,整个过程是自动的。下载模式就是Bootloader通过某些通信手段将内核映像或根文件系统映像等从PC机中下载到目标板的FLASH中。用户可以利用Bootloader提供的一些命令接口来完成自己想要的操作。当前测试uboot命令的开发板是友善之臂tin441,使用三星的EXYNOS4412,本篇文章重点是介绍uboot命令行的命令功能,用法,使用的uboot版本是2010.12,是友善之臂官方提供的uboot。2. UBOOT命令介绍2.1 帮助命令--help查看当前的UBOOT支持那些命令。 TINY4412 # help  ?       - alias for 'help'  base    - 打印一组地址偏移量  bdinfo  - 开发板的信息结构  boot    - boot default, i.e., run 'bootcmd'  bootd   - boot default, i.e., run 'bootcmd'  bootelf - Boot from an ELF image in memory  bootm   - 从内存启动应用程序  bootp   - 通过使用BOOTP / TFTP协议的网络引导映像  bootvx  - Boot vxWorks from an ELF image  chpart  - 更改活动分区  cmp     - memory compare  coninfo - print console devices and information  cp      - 内存拷贝  crc32   - 检验和的计算  dcache  - 启用或禁用数据缓存  dnw     - dnw     - USB设备进行初始化并准备好接受Windows server(特定的)  ​  echo    - echo args to console  editenv - 修改环境变量  emmc    - 打开/关闭eMMC引导分区  env     - 环境处理命令  exit    - 退出脚本  ext2format- ext2 ext2format——磁盘格式  ​  ext2load- 从Ext2文件系统加载二进制文件  ext2ls  - 在一个目录列表文件(默认/)  ext3format- ext3 ext3format——磁盘格式  ​  false   - 什么也不做,但没有成功  fastboot- fastboot——使用USB fastboot协议  ​  fatformat- FAT32 fatformat——磁盘格式  ​  fatinfo - fatinfo——打印文件系统的信息  fatload - fatload——从dos加载二进制文件的文件系统  ​  fatls   - 一个目录列表文件(默认/)  fdisk   - fdisk for sd/mmc.  ​  go      - 在“addr”启动应用程序  help    - 打印命令描述/使用帮助  icache  - enable or disable instruction cache  iminfo  - print header information for application image  imxtract- extract a part of a multi-image  itest   - return true/false on integer compare  loadb   - load binary file over serial line (kermit mode)  loads   - load S-Record file over serial line  loady   - load binary file over serial line (ymodem mode)  loop    - infinite loop on address range  md      - memory display  mm      - memory modify (auto-incrementing address)  mmc     - MMC子系统  mmcinfo - mmcinfo <dev num>-- display MMC info  movi    - movi  - sd/mmc r/w sub system for SMDK board  mtdparts- define flash/nand partitions  mtest   - simple RAM read/write test  mw      - memory write (fill)  nfs     - boot image via network using NFS protocol  nm      - memory modify (constant address)  ping    - send ICMP ECHO_REQUEST to network host  printenv- print environment variables  reginfo - print register information  reset   - Perform RESET of the CPU  run     - run commands in an environment variable  saveenv - save environment variables to persistent storage  setenv  - set environment variables  showvar - print local hushshell variables  sleep   - delay execution for some time  source  - run script from memory  test    - minimal test like /bin/sh  tftpboot- boot image via network using TFTP protocol  true    - do nothing, successfully  usb     - USB sub-system  version - print monitor version2.2 查看具体命令的使用方法--help格式: help <你想要查的指令> 或者 ? <你想要查的指令> , 甚至 h <你想要查的指令缩写>。 TINY4412 # help sleep  sleep - 延迟执行一段时间  ​  Usage:  sleep N      - 延迟执行N秒(N是_decimal_ ! ! !)2.3 打印环境变量--printenv TINY4412 # printenv  baudrate=115200  bootargs=root=/dev/nfs nfsroot=192.168.18.3:/work/rootfs ip=192.168.18.123:192.168.18.3:192.168.18.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0,115200  bootcmd=movi read kernel 0 40008000;movi read rootfs 0 41000000 400000;bootm 40008000 41000000  bootdelay=3  ethaddr=00:40:5c:26:0a:5b  gatewayip=192.168.0.1  ipaddr=192.168.0.20  netmask=255.255.255.0  serverip=192.168.0.10  ​  Environment size: 416/16380 bytesbaudrate: 当前的波特率。 一般不修改。 bootcmd: 启动命令。 bootdelay:启动命令 bootcmd 延时执行的时间。 ethaddr: 网卡 MAC 地址。 gatewayip:网关 IP 地址。 ipaddr: 开发板 IP 地址。 netmask: 子网掩码。 serverip: 服务器 IP(一般是 PC 的 IP,给开发板提供各种网络服务的主机的 IP) bootargs: u-boot 传递给操作系统内核的启动参数。(很重要)使用示例 打印指定的环境变量格式: printenv 打印的环境变量名称 TINY4412 # printenv bootargs  bootargs=root=/dev/nfs nfsroot=192.168.18.3:/work/rootfs ip=192.168.18.123:192.168.18.3:192.168.18.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0,1152002.4 设置环境变量--setenv 查看帮助:  TINY4412 # help setenv  setenv - set environment variables -->作用是设置环境变量  ​  Usage: //命令使用方法  setenv name value ... //使用格式      - set environment variable 'name' to 'value ...'     //设置环境变量“名称”“数值……格式  setenv name      - delete environment variable 'name'  //删除环境变量使用示例 设置上电的延时时间:  TINY4412 # setenv bootdelay 10     //设置上电延时时间为10秒  TINY4412 # saveenv               //保存设置  Saving Environment to SMDK bootable device...  done  ​  设置波特率示例:  TINY4412 # setenv baudrate 115200   //设置波特率为115200  ## Switch baudrate to 115200 bps and press ENTER ... //设置完需要重启开发板,自动生效  ​  删除环境变量示例:  setenv baudrate   //删除baudrate环境变量引用环境变量示例: TINY4412 # setenv timer 10     //随便设置一个环境变量  TINY4412 # setenv bootdelay ${timer}   //引用环境变量  TINY4412 # save                   //保存环境变量  Saving Environment to SMDK bootable device...  done  TINY4412 # print  baudrate=115200  bootargs=root=/dev/nfs nfsroot=192.168.18.3:/work/rootfs ip=192.168.18.123:192.168.18.3:192.168.18.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0,115200  bootcmd=movi read kernel 0 40007fc0;bootm 40007fc0  bootdelay=10   //设置成功  ethaddr=00:40:5c:26:0a:5b  gatewayip=192.168.18.1  ipaddr=192.168.18.123  netmask=255.255.255.0  serverip=192.168.18.124  timer=10   //设置的新环境变量  ​  Environment size: 389/16380 bytes  TINY4412 # 2.5 设置bootargs参数bootargs是环境变量中的重中之重,甚至可以说整个环境变量都是围绕着bootargs来设置的。coherent_pool参数: 设置DMA的大小  示例: coherent_pool=2M本地挂载示例 set bootargs root=/dev/mmcblk0p2 rootfstype=ext3 init=/linuxrc console=ttySAC0,115200  ​  set bootargs root=/dev/mmcblk0p2 rw rootfstype=ext3 init=/linuxrc console=ttySAC0,115200NFS网络挂载示例: set bootargs root=/dev/nfs nfsroot=192.168.18.3:/work/nfs_root ip=192.168.18.123:192.168.18.3:192.168.18.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0,115200root参数用来指定根文件系统挂载的位置。nfsroot参数是NFS网络文件系统挂载才需要设置,后面跟着服务器的NFS地址,挂载目录ip参数是设置开发板的网卡IP地址,NFS网络挂载时必须设置。init 是指定挂载文件系统之后运行的脚本,用来做一些系统初始化。2.6 查看开发板的配置信息--bdinfo TINY4412 # bdinfo  arch_number = 0x00001200 ->开发板的机器码, 用来引导操作系统的内核  boot_params = 0x40000100 ->启动参数存储的内存位置  DRAM bank = 0x00000000 -> DRAM 编号,这里表示是第 0 个 DDR  -> start = 0x40000000    -->DRAM 的起始地址  -> size = 0x10000000     -->DRAM 的大小 ( 0x10000000 /1024 /1024 = 256M)  DRAM bank = 0x00000001 -> DRAM 编号,这里表示是第 1 个 DDR  -> start = 0x50000000    -->DRAM 的起始地址  -> size = 0x10000000     -->DRAM 的大小( 0x10000000 /1024 /1024 = 256M)  DRAM bank = 0x00000002-> DRAM 编号,这里表示是第 2 个 DDR  -> start = 0x60000000    -->DRAM 的起始地址  -> size = 0x10000000     ->DRAM 的大小( 0x10000000 /1024 /1024 = 256M)  DRAM bank = 0x00000003-> DRAM 编号,这里表示是第 3 个 DDR  -> start = 0x70000000    ->DRAM 的起始地址  -> size = 0x0FF00000     ->DRAM 的大小( 0x10000000 /1024 /1024 = 256M)  ethaddr = 00:40:5c:26:0a:5b  ->网卡 MAC 地址(DM9600)  ip_addr = 192.168.0.20      ->开发板的 IP  baudrate = 0           bps ->波特率,这里是代码有问题,应该 115200  TLB addr = 0x3FFF0000      ->MMU(CPU) 映射表存储位置  relocaddr = 0xC3E00000     ->代码重新定位的地址  reloc off = 0x00000000      ->重定位地址  irq_sp = 0xC3CFBF58       ->irq堆栈指针  sp start = 0xC3CFBF50     ->开始地址堆栈指针  FB base = 0x00000000      ->framebuffer基地址2.7 内存数据显示->md查看帮助: TINY4412 # ? md  md - memory display 内存数据显示---只能显示内存中的数据,就是说只能在DDR地址中操作  ​  Usage:  md [.b, .w, .l] address [# of objects]  Md.b : 以字节方式显示数据 Md.w : 以字(2 个字节) Md.l : 以双字(4 个字节) 以上表示以字节、字(2 个字节)、双字(4 个字节)为单位进行显示格式: Md.b <要显示的地址> [显示的数据个数] TINY4412 # md.b 1000000 10   //将起始地址1000000处的10个数据显示到终端  01000000: 06 00 00 ea fe ff ff ea fe ff ff ea fe ff ff ea   ................示例: TINY4412 # md.b 1000000 10               一个字节显示:  01000000: 06 00 00 ea fe ff ff ea fe ff ff ea fe ff ff ea   ................  TINY4412 # md.w 1000000 10               两个字节显示  01000000: 0006 ea00 fffe eaff fffe eaff fffe eaff   ................  01000010: fffe eaff fffe eaff 301a ea00 301b ea00   .........0...0..  TINY4412 # md.l 1000000 10               四个字节显示  01000000: ea000006 eafffffe eafffffe eafffffe   ................  01000010: eafffffe eafffffe ea00301a ea00301b   .........0...0..  01000020: e59f01a4 e3a01000 e5801000 e59f019c   ................  01000030: e5900000 e200003e e330003e 1a00000d   ....>...>.0.....2.8 复制内存命令 cp查看帮助: TINY4412 # help cp  cp - memory copy 内存拷贝 --只能在内存中拷贝,就是说只能在DDR地址中操作  ​  Usage:  cp [.b, .w, .l] source target count 源地址 目标地址 数量个数示例1: TINY4412 # cp 100000 4000000 10  从起始地址100000开始拷贝10个数据到4000000的地址处示例2: TINY4412 # md.b 46000000  46000000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff   ................  TINY4412 # md.b 10000000            10000000: 11 20 41 e4 08 00 05 08 05 00 00 00 10 00 00 00   . A.............  TINY4412 # cp 10000000 46000000 10   从起始地址10000000开始拷贝10个数据到46000000的地址处  TINY4412 # md.b 46000000            46000000: 11 20 41 e4 08 00 05 08 05 00 00 00 10 00 00 00   . A.............2.9 查看EMMC的信息->mmcinfo查看帮助: TINY4412 # help mmcinfo  mmcinfo - mmcinfo <dev num>-- display MMC info >输出指定编号 mmc 的信息, <dev num>是要指定的编号  ​  Usage:  mmcinfo 编号说明: mmc 的编号是会变化的, Tiny4412 板上有 EMMC,有SD卡。这两个都归类为 MMC。 编号是0,1。 但是谁是0,谁是 1,是不确定的, 和启动方式有关。 在哪个存储器启动,哪个就是编号就是0。查看SD卡信息: TINY4412 # mmcinfo 0  Device: S3C_HSMMC2         设备名称  Manufacturer ID: 2             制造商标识 ID  OEM: 544d                   设备制造商  Name: SE08G                 名称  Tran Speed: 0      Rd Block Len: 512             每一块的大小,字节为单位  SD version 2.0                 SD卡的版本  High Capacity: Yes             是否是大容量卡  Size: 7460MB (block: 15278080)   容量大小,(总共有多少个块)  Bus Width: 4-bit               总线宽度,SD卡接口是4条线  Boot Partition Size: 0 KB         引导分区大小查看EMMC的信息: TINY4412 # mmcinfo 1  Device: S5P_MSHC4           设备  Manufacturer ID: 15         制造商ID  OEM: 100                   原始设备制造商  Name: 4YMD3                 名称  Tran Speed: 0               Tran速度  Rd Block Len: 512           每一块的大小,字节为单位  MMC version 4.0             MMC版本  High Capacity: Yes         是否是大容量卡  Size: 3728MB (block: 7634944) 卡的容量和总共的块大小  Bus Width: 8-bit             总线宽度  Boot Partition Size: 4096 KB 引导分区大小2.10 mmc命令子系统mmc不是单独的命令,他是一个子系统,支持多个命令。查看mmc子系统的帮助信息 TINY4412 # help mmc  mmc - MMC sub system         MMC子系统  ​  Usage:  mmc read <device num> addr blk# cnt         --从 mmc 指定扇区读取数据到 ddr 中  mmc write <device num> addr blk# cnt       --写 ddr 中的数据到指定 mmc 扇区中  mmc rescan <device num>                     --重新扫描指定设备, 相当于重新初始化  mmc erase <boot | user> <device num> <start block> <block count> --擦除指定扇区  mmc list - lists available devices          --列出有效的 mmc 设备参数说明: <device num>: mmc 编号,编号原则同前面说的,就是对哪一个设备操作。  addr: DDR3 内存地址;  blk#: 要读/写的 mmc 扇区地址起始地址;  cnt: 要读/写的 mmc 扇区数量;  boot: 引用分区,一般是操作 bl1,bl2,u-boot 的 mmc 扇区范围。  user: 用户分区, 一般是操作内核,文件系统的 mmc 扇区范围。  <start block>:要擦除的 mmc 扇区起始地址;  <block count>:要擦除的 mmc 扇区数量;mmc 命令中的参数都是 16 进制表示,不是 10 进制表示(1)从MMC扇区读数据到DDR内存中->mmc read 格式:mmc read <device num> addr blk# cnt  blk#:要读/写的 mmc 扇区的起始地址 (十六进制表示)  Cnt :要读/写的 mmc 扇区数量(十六进制表示)  addr: DDR3 内存地址;  TINY4412 # mmc read 0 45000000 1 1  ​  MMC read: dev # 0, block # 1, count 1 ... 1 blocks read: OK  ​  这里是从SD卡的第1个扇区开始,读取一个扇区的数据到DDR的45000000地址处!  ​  示例:  TINY4412 # md.b 48000000 10  48000000: ff ff ff ff ff ff ff ff ff ff ff ff bf ff ff ff   ................  ​  //从SD卡第一个扇区开始,读取一个扇区的数据到DDR的48000000地址处  TINY4412 # mmc read 0 48000000 1 1  ​  MMC read: dev # 0, block # 1, count 1 ... 1 blocks read: OK  TINY4412 # md.b 48000000 10        48000000: a3 69 d3 18 e9 7d b9 66 d1 6b d5 6e d4 79 a6 79   .i...}.f.k.n.y.y(2)mmc write --写 ddr 中的数据到指定 mmc 扇区中 格式:mmc write <device num> addr blk# cnt --将 ddr 中的数据到写到指定mmc 扇区中  blk#:要读/写的 mmc 扇区的起始地址(十六进制表示)  Cnt :要读/写的 mmc 扇区数量(十六进制表示)  addr:DDR3 内存地址;  示例:  TINY4412 # mmc write 0 48000000 1 1 从DDR 48000000地址处,写1个扇区的数据到SD的第1个扇区  ​  MMC write: dev # 0, block # 1, count 1 ... 1 blocks written: OK(3)擦除指定扇区 格式:  mmc erase <boot | user> <device num> <start block> <block count> --擦除指定扇区  参数说明:  <start block>:要擦除的 mmc 扇区起始地址  <block count>:要擦除的 mmc 扇区数量  boot: 引用分区  User: 用户分区  为了方便比较,先将SD卡的第1个扇区内容读到DDR中。  ​  读出第10个扇区的数据  TINY4412 # mmc read 0 48000000 1 1  ​  MMC read: dev # 0, block # 1, count 1 ... 1 blocks read: OK  ​  显示第1个扇区的数据  TINY4412 # md.b 48000000 30        48000000: a3 69 d3 18 e9 7d b9 66 d1 6b d5 6e d4 79 a6 79   .i...}.f.k.n.y.y  48000010: 07 00 00 ea fe ff ff ea fe ff ff ea fe ff ff ea   ................  48000020: fe ff ff ea fe ff ff ea fe ff ff ea fe ff ff ea   ................    ​  擦除SD卡的第一个扇区  TINY4412 # mmc erase user 0 1 1    START: 1 BLOCK: 1       开始1扇区,擦除1扇区  high_capacity: 1         高容量  Capacity: 15278080       容量  ​  Erase                   擦除  ​   512 B erase Done        512字节  MMC erase Success.!! MMC擦除成功。! !    ​  再读再显示  TINY4412 # mmc read 0 40000000 1 1  ​  MMC read: dev # 0, block # 1, count 1 ... 1 blocks read: OK  TINY4412 # md.b 40000000 30        40000000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff   ................  40000010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff   ................  40000020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff   ................    ​  恢复数据,再读再显示  TINY4412 # mmc write 0 48000000 1 1   将DDR里的数据写入SD卡中  ​  MMC write: dev # 0, block # 1, count 1 ... 1 blocks written: OK  TINY4412 # mmc read 0 40000000 1 1   再次读出数据  ​  MMC read: dev # 0, block # 1, count 1 ... 1 blocks read: OK  TINY4412 # md.b 40000000 30         显示---数据已经恢复  40000000: a3 69 d3 18 e9 7d b9 66 d1 6b d5 6e d4 79 a6 79   .i...}.f.k.n.y.y  40000010: 07 00 00 ea fe ff ff ea fe ff ff ea fe ff ff ea   ................  40000020: fe ff ff ea fe ff ff ea fe ff ff ea fe ff ff ea   ................(4)列出MMC设备--mmc list TINY4412 # mmc list  S3C_HSMMC2: 0 --- 0 编号的 mmc 设备,这里接 SD 卡  S5P_MSHC4: 1  --- 1 编号的 mmc 设备,这里接 开发板板载的EMMC2.11 查看MMC分区信息--fatinfo TINY4412 # help fatinfo  fatinfo - fatinfo - print information about filesystem                     打印文件系统信息  ​  Usage:  fatinfo <interface> <dev[:part]>   格式说明      - print information about filesystem from 'dev' on 'interface'  ​  参数说明:  <interface>: mmc 或 usb;  dev: 设备编号;  part: 设备分区号  ​  ​  查看第0个设备信息---这里是SD卡:  TINY4412 # fatinfo mmc 0    -----Partition 1-----  Partition1: Start Address(0x2e2e2e2e), Size(0x2e2e2e2e)  分区1       起始地址                 大小  ------------------------  -----Partition 2-----  Partition1: Start Address(0x2e2e2e2e), Size(0x2e2e2e2e)  ------------------------  -----Partition 3-----  Partition1: Start Address(0x2e2e2e2e), Size(0x2e2e2e2e)  ------------------------  -----Partition 4-----  Partition1: Start Address(0x2e2e2e2e), Size(0x2e2e2e2e)  ------------------------  Interface: SD/MMC  接口             Device 0: Vendor: Man 02544d Snr c9226e33 Rev: 2.1 Prod: SE08G             Type: Removable Hard Disk               类型:可移动硬盘                           Capacity: 14.5 MB = 0.0 GB (29840 x 512)  Partition 1: Filesystem: FAT32 "NO NAME   "下一篇继续介绍。
  • [技术干货] AI提取图片里包含的文字信息-解决文字无法复制的痛点
    1. 前言平时工作中编写开发技术文档,或者学生在编写论文时,经常会上网搜索一些参考文献、文档。比如: 上网搜索相似的内容参考一下或者引用别人的一段文字,有时候看到一篇较好的内容想要保存等等。这个过程中会发现,很多网站的提供的页面都是不能复制粘贴的,或者直接是图片形式提供,为了方便能获取这些文字,当前就利用华为云提供的 通用文字识别接口,识别图片里的文本内容,方便复制文字。这个功能QQ上也集成了,使用很方便,这里利用华为云的接口实现一个与QQ类似的功能,截图之后识别图片里包含的文本内容。这个文字识别接口里不仅仅有通用文字识别功能,还支持很多其他功能:比如身份证、驾驶证、保险单、手写文本、火车票,行驶证.......等等功能。还支持用户自定义识别模板,指定需要识别的关键字段,实现用户特定格式图片的自动识别和结构化提取。2. 文本识别接口使用介绍2.1 开通服务地址: cid:link_1这个文字识别服务是按调用次数计费的,每个用户每月有1000次的免费调用次数,开通服务后就可以使用。2.2 接口地址官网帮助文档: cid:link_2POST https://{endpoint}/v2/{project_id}/ocr/general-text 示例: https://ocr.cn-north-4.myhuaweicloud.com/v2/0e5957be8a00f53c2fa7c0045e4d8fbf/ocr/general-text 请求头: { "X-Auth-Token": "******", "Content-Type": "application/json;charset=UTF-8" } 请求体: { "image": ----这是图片的bas64编码 } 响应结果: { "result": { "words_block_count": 13, "words_block_list": [ { "words": "撤,还是不撤?", "location": [ [ 43, 39 ], [ 161, 39 ], [ 161, 60 ], [ 43, 60 ] ] }, { "words": "让我更骄傲的是公司在大灾面前的表现。", "location": [ [ 72, 95 ], [ 332, 95 ], [ 332, 113 ], [ 72, 113 ] ] }, { "words": "2011年3月11日14时46分,日本东北部海域发生里氏9.0级", "location": [ [ 71, 122 ], [ 482, 122 ], [ 482, 142 ], [ 71, 142 ] ] }, { "words": "地震并引发海啸。那一刻,我们正在距离东京100公里的热海开会,", "location": [ [ 41, 149 ], [ 481, 149 ], [ 481, 171 ], [ 41, 171 ] ] }, { "words": "感觉“咚”", "location": [ [ 42, 180 ], [ 114, 180 ], [ 114, 199 ], [ 42, 199 ] ] }, { "words": "地被震了一下。面对地震,", "location": [ [ 115, 178 ], [ 296, 178 ], [ 296, 199 ], [ 115, 199 ] ] }, { "words": "大家都很镇定,", "location": [ [ 300, 179 ], [ 400, 179 ], [ 400, 197 ], [ 300, 197 ] ] }, { "words": "直到看到电", "location": [ [ 405, 179 ], [ 483, 179 ], [ 483, 196 ], [ 405, 196 ] ] }, { "words": "视上触目惊心的画面:15时 25 分,海啸到达陆前高田市海岸;15时", "location": [ [ 41, 206 ], [ 485, 206 ], [ 485, 228 ], [ 41, 228 ] ] }, { "words": "26分,海啸到达陆前高田市中心;15时43分,陆前高田市依稀只能", "location": [ [ 40, 234 ], [ 486, 234 ], [ 486, 258 ], [ 40, 258 ] ] }, { "words": "看到四层高的市府大楼的屋顶,一瞬间,城镇就变成了汪洋……对", "location": [ [ 40, 262 ], [ 487, 262 ], [ 487, 287 ], [ 40, 287 ] ] }, { "words": "我来说,地震跟家常便饭一样,可眼前的灾难比以往任何一次都要", "location": [ [ 40, 292 ], [ 487, 292 ], [ 487, 317 ], [ 40, 317 ] ] }, { "words": "惨烈,完全超出了我的预期。", "location": [ [ 41, 326 ], [ 231, 326 ], [ 231, 345 ], [ 41, 345 ] ] } ], "direction": -1 } }在请求参数里的X-Auth-Token参数比较重要,调用华为云的任何API接口都需要这个参数,获取方式可以看前面的文章。比如这篇文章: cid:link_32.3 在线调试接口地址: cid:link_0使用调试接口想体验识别效果,图片的数据支持base64编码、http网络图片地址传入,测试非常方便。关于获取图片base64编码的方式,在文档里也有介绍,直接通过浏览器获取。3. 实现代码代码采用QT编写的,请求API接口实现调用。其他语言方法是一样的。3.1 实现效果3.2 核心代码//解析反馈结果 void Widget::replyFinished(QNetworkReply *reply) { QString displayInfo=""; int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); //读取所有数据 QByteArray replyData = reply->readAll(); qDebug()<<"状态码:"<<statusCode; qDebug()<<"反馈的数据:"<<QString(replyData); //更新token if(function_select==3) { displayInfo="token 更新失败."; //读取HTTP响应头的数据 QList<QNetworkReply::RawHeaderPair> RawHeader=reply->rawHeaderPairs(); qDebug()<<"HTTP响应头数量:"<<RawHeader.size(); for(int i=0;i<RawHeader.size();i++) { QString first=RawHeader.at(i).first; QString second=RawHeader.at(i).second; if(first=="X-Subject-Token") { Token=second.toUtf8(); displayInfo="token 更新成功."; //保存到文件 SaveDataToFile(Token); break; } } QMessageBox::information(this,"提示",displayInfo,QMessageBox::Ok,QMessageBox::Ok); return; } //判断状态码 if(200 != statusCode) { //解析数据 QJsonParseError json_error; QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error); if(json_error.error == QJsonParseError::NoError) { //判断是否是对象,然后开始解析数据 if(document.isObject()) { QString error_str=""; QJsonObject obj = document.object(); QString error_code; //解析错误代码 if(obj.contains("error_code")) { error_code=obj.take("error_code").toString(); error_str+="错误代码:"; error_str+=error_code; error_str+="\n"; } if(obj.contains("error_msg")) { error_str+="错误消息:"; error_str+=obj.take("error_msg").toString(); error_str+="\n"; } //显示错误代码 QMessageBox::information(this,"提示",error_str,QMessageBox::Ok,QMessageBox::Ok); } } return; } //结果返回 if(function_select==1) { //解析数据 QJsonParseError json_error; QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error); if(json_error.error == QJsonParseError::NoError) { //判断是否是对象,然后开始解析数据 if(document.isObject()) { QJsonObject obj = document.object(); QString error_code; //解析 if(obj.contains("result")) { QJsonObject obj1=obj.take("result").toObject(); QString bank_name; QString card_number; QString type; QString text; if(obj1.contains("bank_name")) { bank_name=obj1.take("bank_name").toString(); } if(obj1.contains("card_number")) { card_number=obj1.take("card_number").toString(); } if(obj1.contains("type")) { type=obj1.take("type").toString(); } text="发卡行:"+bank_name+"\n"; text+="卡号:"+card_number+"\n"; text+="卡类型:"+type+"\n"; ui->plainTextEdit->setPlainText(text); } } } } //结果返回 if(function_select==2) { //解析数据 QJsonParseError json_error; QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error); if(json_error.error == QJsonParseError::NoError) { //判断是否是对象,然后开始解析数据 if(document.isObject()) { QJsonObject obj = document.object(); QString error_code; //解析 if(obj.contains("result")) { QJsonObject obj1=obj.take("result").toObject(); int words_block_count; QString text=""; if(obj1.contains("words_block_count")) { words_block_count=obj1.take("words_block_count").toInt(); // text=QString("识别到%1行文本.\n").arg(words_block_count); } if(obj1.contains("words_block_list")) { QJsonArray array=obj1.take("words_block_list").toArray(); for(int i=0;i<array.size();i++) { QJsonObject obj2=array.at(i).toObject(); if(obj2.contains("words")) { text+=obj2.take("words").toString(); text+="\n"; } } } ui->plainTextEdit->setPlainText(text); } } } } } /* 功能: 获取token */ void Widget::GetToken() { //表示获取token function_select=3; QString requestUrl; QNetworkRequest request; //设置请求地址 QUrl url; //获取token请求地址 requestUrl = QString("https://iam.%1.myhuaweicloud.com/v3/auth/tokens") .arg(SERVER_ID); //自己创建的TCP服务器,测试用 //requestUrl="http://10.0.0.6:8080"; //设置数据提交格式 request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json;charset=UTF-8")); //构造请求 url.setUrl(requestUrl); request.setUrl(url); QString text =QString("{\"auth\":{\"identity\":{\"methods\":[\"password\"],\"password\":" "{\"user\":{\"domain\": {" "\"name\":\"d73ef34-6aa9-4120-aebe-c9e03375af1b"},\"name\": \")d73ef34-6aa9-4120-aebe-c9e03375af1b",\"password\": \"\"}}}," "\"scope\":{\"project\":{\"name\":\"Id73ef34-6aa9-4120-aebe-c9e03375af1b"}}}}") .arg(MAIN_USER) .arg(IAM_USER) .arg(IAM_PASSWORD) .arg(SERVER_ID); //发送请求 manager->post(request, text.toUtf8()); } //粘贴图片 void Widget::on_pushButton_copy_clicked() { QClipboard *clipboard = QApplication::clipboard(); const QMimeData *mimeData = clipboard->mimeData(); if (mimeData->hasImage()) { //将图片数据转为QImage QImage img = qvariant_cast<QImage>(mimeData->imageData()); if(!img.isNull()) { ui->widget->SetImage(img); } } } //获取图片里的文字信息 void Widget::getTextInfo(QImage image) { function_select=2; QString requestUrl; QNetworkRequest request; //存放图片BASE64编码 QString imgData; //设置请求地址 QUrl url; //人脸搜索请求地址 requestUrl = QString("https://ocr.%1.myhuaweicloud.com/v2/%2/ocr/general-text") .arg(SERVER_ID) .arg(PROJECT_ID); //设置数据提交格式 request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json;charset=UTF-8")); //将图片进行Base64编码 imgData = QString(toBase64(image)); //编码后的图片大小不超过2M //设置token request.setRawHeader("X-Auth-Token",Token); //构造请求 url.setUrl(requestUrl); request.setUrl(url); QString post_param=QString ("{" "\"image\": \"d73ef34-6aa9-4120-aebe-c9e03375af1b"" "}").arg(imgData); //发送请求 manager->post(request, post_param.toUtf8()); }
  • [技术干货] Linux系统编程-进程间通信(消息队列)
    前面文章介绍了Linux下进程的创建,管理,陆续介绍了进程间通信的方式:管道、内存映射、共享内存等。这篇文章继续介绍Linux的进程间通信方式消息队列。1. 消息队列介绍消息队列通过名字字面意思理解就是队列排队-和平常超市买东西排队付款一样结构,消息队列与FIFO很相似,都是一个队列结构,都可以有多个进程往队列里面写信息,多个进程从队列中读取信息。但FIFO需要读、写的两端事先都打开,才能够开始信息传递工作。而消息队列可以事先往队列中写信息,需要时再打开读取信息。注意事项:消息队列属于顺序队列形式的结构,向队列里写的每一条消息,会追加到队列后面,读取一个就从队列里消除一个。写函数不会阻塞,除非队列里存放的消息数量已经满了,才会导致写阻塞。读函数,从队列里读取不到数据时,会阻塞。查看当前系统所有的消息队列:[root@wbyq 20181005]# ipcs -q ------ Message Queues -------- 键值 消息队列ID 使用的字节数量 队里现存的消息数量 key msqid owner perms used-bytes messages 0x000004d3 0 root 666 65532 192 0x00003044 32769 root 666 65424 564 0x0a120534 65538 root 0 2352 21 0x0a00000f 196611 root 0 65520 2730 0xffffffff 163844 root 0 0 0 查看消息队列的详细信息:[root@wbyq 20181005]# ipcs -l ------ Shared Memory Limits -------- max number of segments = 4096 max seg size (kbytes) = 4194303 max total shared memory (kbytes) = 1073741824 min seg size (bytes) = 1 ------ Semaphore Limits -------- max number of arrays = 128 max semaphores per array = 250 max semaphores system wide = 32000 max ops per semop call = 32 semaphore max value = 32767 ------ Messages: Limits -------- max queues system wide = 1736 【系统最多的消息队列数量(最多支持同时1736个消息队列)】 max size of message (bytes) = 65536 【单个消息的最大字节数】 default max size of queue (bytes) = 65536 【默认的单个队列的大小65536】 默认单个队列总字节大小为: 65535字节。 消息队列的最大字节数量和消息的最大条数加起来的总字节数不能超过65535字节。**System V IPC机制消息队列相关的函数接口: ** 这里先列出所有函数,下面会详细介绍每个函数的用法。#include <sys / types.h> #include <sys / ipc.h> #include <sys / msg.h> int msgget(key_t key, int msgflg); int msgsnd(int msqid, struct msgbuf * msgp, size_t msgsz, int msgflg); ssize_t msgrcv(int msqid, struct msgbuf * msgp, size_t msgsz, long msgtyp, int msgflg); int msgctl(int msqid, int cmd, struct msqid_ds * buf);2. 消息队列相关函数介绍2.1 msgget函数函数原型:#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key, int msgflg);功能 msgget用于创建和访问一个消息队列。 参数 (1) key:是唯一标识一个消息队列的关键字,如果为IPC_PRIVATE(值为0,用创建一个只有创建者进程才可以访问的消息队列),表示创建一个只由调用进程使用的消息队列,非0值的key(可以通过ftok函数获得)表示创建一个可以被多个进程共享的消息队列; (2) msgflg:指明队列的访问权限和创建标志,创建标志的可选值为IPC_CREAT和IPC_EXC,如果单独指定IPC_CREAT,msgget要么返回新创建的消息队列id,要么返回具有相同key值的消息队列id;如果IPC_EXCL和IPC_CREAT同时指明,则要么创建新的消息队列,要么当队列存在时,调用失败并返回-1。/*1. 创建消息队列*/ int msgid = msgget((key_t)1235,0666 | IPC_CREAT);返回值 成功执行时,返回消息队列标识值(0也是成功的)。失败返回-1,errno被设为以下的某个值。EACCES:指定的消息队列已存在,但调用进程没有权限访问它,而且不拥有CAP_IPC_OWNER权能 EEXIST:key指定的消息队列已存在,而msgflg中同时指定IPC_CREAT和IPC_EXCL标志 ENOENT:key指定的消息队列不存在同时msgflg中不指定IPC_CREAT标志 ENOMEM:需要建立消息队列,但内存不足 ENOSPC:需要建立消息队列,但已达到系统的最大消息队列容量2.2 msgsnd和msgrcy函数原型:#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);功能函数msgsnd和msgrcy用来将消息添加到消息队列中和从一个消息队列中获取信息。参数(1)msgid:指明消息队列的ID; 通常是msgget函数成功的返回值。(2)msgbuf:是消息结构体,它的长度必须小于系统规定的上限,必须以一个长整型成员变量开始,接收函数将用这个成员变量来确定消息的类型。必须重写这个结构体,其中第一个参数类型不能改,其他可以自定义。如下:struct msgbuf { long mtype; /* type of message */ char mtext[1]; /* message text */ };字段mtype是用户自己指定的消息类型(通常是1—5中的任意一个数值),该结构体第2个成员仅仅是一种说明性的结构,实际上用户可以使用任何类型的数据,就是消息内容;(3)msgsz是消息体的大小,每个消息体最大不要超过4K; 不含消息类型占用的4个字节,即mtext的长度(4)msgtyp有3种选项:msgtyp == 0 接收队列中的第1个消息,不区分消息类型 msgtyp > 0 接收对列中的第1个类型等于msgtyp的消息 msgtyp < 0 接收其类型小于或等于msgtyp绝对值的第1个最低类型消息(5)msgflg有3种选项:0:当消息队列满时,msgsnd将会阻塞,直到消息能写进消息队列 IPC_NOWAIT:当消息队列已满的时候,msgsnd函数不等待立即返回 IPC_NOERROR:若发送的消息大于size字节,则把该消息截断,截断部分将被丢弃,且不通知发送进程。2.3 msgctl函数原型:#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgctl(int msqid, int cmd, struct msqid_ds *buf);功能msgctl是消息队列的控制函数,常用来删除消息队列。参数(1)msqid:由msgget返回的消息队列标识符。(2)cmd:通常为IPC_RMID表示删除消息队列。(3)参数buf通常为NULL。通过命令查看系统消息信息(1)ipcs -q 命令查看系统的消息队列(2)ipcs -m查看系统的共享内存(3)ipcs -s 查看系统的信号量集。3. 案例代码: 消息队列示例1下面两个程序分别编译,依次运行,不分先后顺序,就可以看到效果了。3.1 发送消息#include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/stat.h> #define BUFFER 255 struct msgtype { //重新定义该结构体 long mtype; //第一个参数类型不变 char buf[BUFFER]; }; int main(int argc,char **argv) { if(argc!=3) { printf("./app <消息> <消息类型-整数>\n"); return 0; } /*1. 创建消息队列*/ int msgid = msgget((key_t)1235,0666 | IPC_CREAT); /*2. 向消息队列发送消息*/ struct msgtype msg; memset(&msg,0,sizeof(struct msgtype)); msg.mtype = atoi(argv[2]); //给结构体的成员赋值 strncpy(msg.buf,argv[1],BUFFER); msgsnd(msgid,&msg,sizeof(struct msgtype),0); return 0; }3.2 读取消息#include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/stat.h> #include <sys/msg.h> #define BUFFER 255 struct msgtype { long mtype; char buf[BUFFER]; }; int main(int argc,char **argv) { if(argc!=2) { printf("./app <消息类型-整数>\n"); return 0; } /*1. 创建消息队列-如果存在就打开消息队列*/ int msgid = msgget((key_t)1235, 0666 | IPC_CREAT); //获得消息队列 struct msgtype msg; memset(&msg,0,sizeof(struct msgtype)); while(1) { /*2. 从消息队列里读取指定消息类型*/ msgrcv(msgid,&msg,sizeof(struct msgtype),atoi(argv[1]),0); printf("读取的消息: %s\n", msg.buf); } return 0; }4. 案例代码: 消息队列基本使用下面两个例子,一个例子用于创建队列,并向队列里写数据,另一个例子从队列里读取数据。4.1 向队列写入消息程序运行需要传入两个额外的参数。一个是消息的类型,一个是具体的内容。./app 1 hello ./app 2 123456789示例代码:#include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <dirent.h> #include <stdlib.h> #include <sys/mman.h> #include <sys/wait.h> #include <sys/shm.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdlib.h> struct msgbuf { long mtype; /* message type, must be > 0 */ char mtext[1024]; /* message data */ }; int main(int argc,char **argv) { if(argc!=3) { printf("./app <消息类型> <消息内容>\n"); return 0; } /*1. 创建消息队列*/ int msqid=msgget(123456,0666|IPC_CREAT); /*2. 向消息队列里加入数据*/ struct msgbuf msg_buf; msg_buf.mtype=atoi(argv[1]); //消息类型 1、2、3、4、5 strcpy(msg_buf.mtext,argv[2]); //消息内容 msgsnd(msqid,&msg_buf,sizeof(struct msgbuf),0); printf("消息发送成功:%s,%s\n",argv[1],argv[2]); return 0; }4.2 从队列读取消息#include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <dirent.h> #include <stdlib.h> #include <sys/mman.h> #include <sys/wait.h> #include <sys/shm.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdlib.h> struct msgbuf { long mtype; /* message type, must be > 0 */ char mtext[1024]; /* message data */ }; int main(int argc,char **argv) { if(argc!=2) { printf("./app <消息类型>\n"); return 0; } /*1. 创建消息队列*/ int msqid=msgget(123456,0666|IPC_CREAT); /*2. 从消息队列里取出数据*/ struct msgbuf msg_buf; msgrcv(msqid,&msg_buf,sizeof(struct msgbuf),atoi(argv[1]),0); printf("消息读取成功:%d,%s\n",msg_buf.mtype,msg_buf.mtext); return 0; }
  • [技术干货] Linux系统编程-进程间通信(管道)
    1. 进程间通信方式介绍这篇文章介绍Linux下进程的间的通信方式,常用的方式如下:1. socket—网络通信 2. 管道---无名管道—命名管道---文件--FIFO 3. 消息队列 4. 共享内存 5. 信号量集 6. 信号—signal捕获信号---kill命令发送信号 int kill(pid_t pid, int sig);2. 标准流管道标准流管道像文件操作有标准io流一样,管道也支持文件流模式。用来创建连接到另一进程的管道popen和pclose。 函数原型:#include <stdio.h> FILE* popen(const char* command, const char* open_mode); int pclose(FILE* fp);popen用于启动进程,用法含义与fopen类似,第二个参数填权限,支持填"r"和"w"。pclose用于关闭进程,释放资源。popen启动进程之后可以直接与启动的进程间通信,比较方便。**示例代码: **从标准管道流中读取 打印/etc/profile的内容:#include <stdio.h> #include <stdlib.h> int main() { FILE *file=NULL; size_t len=0; char buff[1024+1]; file=popen("cat /etc/profile","r"); //执行cat /etc/profile命令,读方式。 if(file!=NULL) { len=fread(buff,1,1024,file); //读数据 buff[len]='\0'; printf("len=Þbd86b09-1ee3-4acf-8ecf-7bf7c1c73160n %s\n",len,buff); } pclose(file); //等待线程结束。 return 0; }3. 无名管道无名管道用于有亲戚关系的进程间通信。 比如: 兄弟进程、父子进程等。#include <unistd.h> int pipe(int fds[2]);pipe函数用于创建一个无名管道,如果成功,fds[0]就存放可读的文件描述符,fds[1]就存放可写文件描述符。返回值: 0表示成功,-1表示失败。无名管道的特点:只能在亲缘关系进程间通信(父子或兄弟)半双工(固定的读端和固定的写端)虚拟管道文件是一个存在内存的特殊文件,可以用read、write函数进行操作。这里说的管道,就像一条水管,有两个端口,一端进水,另一端出水。管道也有两个端口,分别是读端和写端,进水可看成数据从写端被写入,出水可看数据从读端被读出。在程序里分别就对应了read和write函数。示例代码:#include <stdio.h> #include <unistd.h> #include <string.h> int main(int argc,char **argv) { int fds[2]; /*1. 创建无名管道:得到管道读写文件描述符 fds[0]和读端相对应, fds[1]和写端相对应*/ pipe(fds); /*2. 创建子进程*/ pid_t pid; pid=fork(); if(pid==0) //子进程 { char buff[100+1]; int cnt; //从管道的读端读取数据 cnt=read(fds[0],buff,100); //带阻塞功能 buff[cnt]='\0'; printf("子进程收到的数据:%d,%s\n",cnt,buff); } else //父进程 { char buff[]="1234567890"; //向管道的写端写数据 write(fds[1],buff,strlen(buff)); //等待子进程结束 wait(NULL); printf("父进程正常结束.\n"); } return 0; }4. 命名管道无名管道只能在亲缘关系的进程间通信大大限制了管道的使用,有名管道突破了这个限制,通过指定路径名的形式实现不相关进程间的通信,因为命名管道通信使用的管道是一个实体文件,在磁盘上的存在的,而无名管道是存在内存中的虚拟文件,其他进程无法访问,导致没有关联的进程无法进行通信。4.1 在命令行如何创建管道文件?[wbyq@wbyq test]$ mkfifo test.fifo [wbyq@wbyq test]$ ls test.fifo [wbyq@wbyq test]$ ls -l 总用量 0 prw-rw-r--. 1 wbyq wbyq 0 10月 15 15:29 test.fifo4.2 在命令行演示两个相关的进程通过进行管道文件进行通信4.3 创建fifo文件的函数1. 创建FIFO文件 #include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode); 参数: pathname:创建的FIFO文件的全路径名; mode:文件访问权限,比如0666。 返回值:如果创建成功,则返回0,否则-1。 2. 删除FIFO文件 #include <unistd.h> int unlink(const char *pathname); 3. 用命令创建和删除FIFO文件 用命令mkfifo创建。 用命令unlink删除。4.4 创建写端: FIFO#include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(int argc,char **argv) { if(argc!=2) { printf("./a.out <fifo文件--写>\n"); return 0; } int fd=open(argv[1],O_WRONLY); if(fd<0) { printf("%s 文件打开失败.\n",argv[1]); return 0; } char buff[]="1234567890"; write(fd,buff,strlen(buff)); close(fd); return 0; } 示例代码: 进程B负责读数据 #include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(int argc,char **argv) { if(argc!=2) { printf("./a.out <fifo文件--读>\n"); return 0; } int fd=open(argv[1],O_RDONLY); if(fd<0) { printf("%s 文件打开失败.\n",argv[1]); return 0; } char buff[100+1]; int cnt; cnt=read(fd,buff,100); buff[cnt]='\0'; printf("buff=%s\n",buff); close(fd); return 0; }4.5 创建读端: FIFO在命令行演示两个相关的进程通过进行管道文件进行通信.代码创建读端,读取数据:#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> int main(void) { int fd=0; int len=0; char buff[100]; fd=open("myfifo",O_RDONLY); len=read(fd,buff,100); buff[len]='\0'; printf("read: %s\n",buff); return 0; }
  • [技术干货] C语言编程_常见的问题解决办法和案例
    介绍的C语言知识如下:宽字符定义使用、gtk图像框架安装与测试、动态数组定义、switch的...符号用法、windows下操作目录相关函数、可变形参的函数定义方式以及数据的提取办法,scanf函数在widows下vs里的使用问题、windows下system执行命令调用系统可执行文件运行。一、Linux下C语言代码定义宽字符问题用多个字节来代表的字符称之为宽字符。宽字符是双字节多语言字符代码。在当今的全球计算业内使用的大多数字符(包括技术符号和特殊的发布字符),都可以根据 Unicode 规范表示为宽字符形式。无法以 1 个宽字符表示的字符可以通过 Unicode 的代理项功能以 Unicode 对表示。由于每个宽字符总是以固定的 16 位大小表示,因此使用宽字符可以简化使用国际字符集进行的编程。#include <stdio.h>#include <string.h>#include <wchar.h>int main(void){ /*文本编辑器必须是在UTF-8编码下才可以正常编译*/ char *p="1234567890中国"; //16 char *p2="中国中国"; //12 UTF-8编码下一个中文3个字节 wchar_t *wp=L"1234567890中国"; //12 wchar_t *wp2=L"中国中国"; //4 printf("strlen p:Õ6be90d7-ec09-4ca3-867b-6a782bdc474cn",strlen(p)); printf("strlen p2:Õ6be90d7-ec09-4ca3-867b-6a782bdc474cn",strlen(p2)); printf("wcslen p:Õ6be90d7-ec09-4ca3-867b-6a782bdc474cn",wcslen(wp)); //返回值是12 printf("wcslen p2:Õ6be90d7-ec09-4ca3-867b-6a782bdc474cn",wcslen(wp2)); //返回值是4 return 0;}二、C语言动态数组定义示例(C99)C99支持一种名为变长数组的结构来方便程序员。C++也提供了一种长度可在程序运行时确定的数组类型:动态数组。声明格式为:(声明 int 类型的数组)#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <dirent.h> //ls /456/789/*int main(int argc,char **argv){ if(argc!=2) { printf("正确参数:./a.out <目录路径>\n"); return 0; } /*1. 打开目录*/ DIR *dir; struct dirent *dir_info; dir=opendir(argv[1]); if(dir==NULL) { printf("%s目录打开失败!\n",argv[1]); return 0; } /*2. 遍历目录*/ while(dir_info=readdir(dir)) { if(strcmp(dir_info->d_name,".")==0||strcmp(dir_info->d_name,"..")==0) { continue; } //1. 定义动态数组,存放完整路径 char str[strlen(dir_info->d_name)+strlen(argv[1])+1]; //2. 拷贝目录路径 strcpy(str,argv[1]); //3. 拼接文件名称 strcat(str,dir_info->d_name); //4. 执行正常程序 printf("%s\n",str); /*..视频播放器(str)、音乐播放器、数码相册.......*/ } /*3. 关闭目录*/ closedir(dir); return 0;}三、switch多分支结构新特性(C99)ANSI于1989年制定了C编程语言的官方标准,并于1990年成为国际标准,C语言规范在一段时间内保持相对静态,而C ++继续发展,主要是在其自身的标准化工作中。规范修正案1在1995年为C制定了一个新标准,但只是为了纠正1989年标准的一些细节,并为国际字符集增加了更广泛的支持。该标准在20世纪90年代后期进行了进一步修订,导致1999年出版了ISO / IEC 9899:1999,并于2000年5月作为ANSI标准采用。该标准版本定义的语言通常称为“ C99" 。#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <dirent.h> //ls /456/789/*int main(int argc,char **argv){ if(argc!=2) { printf("正确参数:./a.out <数字>\n"); return 0; } int num=atoi(argv[1]); switch(num) { case 1 ... 5: printf("你输入的是1到5的范围数据!\n"); break; case 10 ... 50: printf("你输入的是10到50的范围数据!\n"); break; case 60 ... 80: printf("你输入的是60到80的范围数据!\n"); break; case 'a' ... 'z': printf("你输入的是a到z的范围数据!\n"); break; default: printf("范围不满足!\n"); break; } return 0;}四、GTK库安装说明在linux下想使用C语言做一个图形化的界面可以选择GTK。1、安装gcc/g++/gdb/make 等基本编程工具sudo apt-get install build-essential2、安装 libgtk2.0-dev libglib2.0-dev 等开发相关的库文件sudo apt-get install gnome-core-devel3、用于在编译GTK程序时自动找出头文件及库文件位置sudo apt-get install pkg-config4、安装 devhelp GTK文档查看程序sudo apt-get install devhelp5、安装 gtk/glib 的API参考手册及其它帮助文档sudo apt-get install libglib2.0-doc libgtk2.0-doc6、安装基于GTK的界面GTK是开发Gnome窗口的c/c++语言图形库sudo apt-get install glade libglade2-dev7、安装gtk2.0 或者 将gtk+2.0所需的所有文件统通下载安装完毕sudo apt-get install libgtk2.0-dev安装完毕后就可以开始写第一个简单的程序了!五、windows下改变控制台#include "stdio.h"int main(){ //printf("\33[%dm",100); //printf("\33[x;yH"); //将输出坐标定位在第x行,第y列 //printf("\33[%d;%dH",10,10);//将输出坐标定位在第x行,第y列 //printf("\33[10;10H"); printf("hello world!\n");} /*一、设置控制台 1、更改屏幕的背景色,字体颜色 printf("\33[%dm", i); 30<= i <=37 设置字体颜色 30黑,31红,32绿,33黄,34蓝,35紫,36深绿,37白 40<= i <=47 设置背景颜色 40黑,41红,42绿,43黄,44蓝,45紫,46深绿,47白 2、任意指定屏幕的输出坐标 printf("\33[x;yH"); 将输出坐标定位在第x行,第y列 printf("\33[%d;%dH", x,y); 将输出坐标定位在第x行,第y列*/六、strchr字符串查找函数#include "stdio.h"#include "string.h"int main(){ char buff1[]="123 456 789"; char buff2[]="abc,uuu,oooo"; printf("%s\n",strchr(buff1,' ')); printf("%s\n",strchr(buff2,',')); return 0;}/* * * #include <string.h> * char *strchr(const char *s, int c); * * */七、windows下C语言操作目录获得当前工作目录char* _getcwd( char *buffer, int maxlen );更改当前工作目录int _chdir( const char *dirname );文件遍历(查找)long _findfirst( char *filespec, struct _finddata_t *fileinfo );int _findclose( long handle );关闭搜寻句柄并释放相应资源创建目录int _mkdir( const char *dirname );删除目录int _rmdir( const char *dirname );其他操作int _access( const char *path, int mode );int _chdrive( int drive ); 功 能 : 更改当前工作驱动器char* _getdcwd( int drive, char *buffer, int maxlen ); 功 能 : 获得指定驱动器的当前工作路径.八、windows下调用应用程序#include <windows.h>system("notepad.exe");九、short整型的scanf正确输入格式#pragma warning(disable:4996)#include <stdio.h>#include <string.h>struct stu{ short grade; //如果这里是短整型,scanf输入必须是%hd, 不能使用%d, %d是int类型,按照4个字节输入。会导致空间溢出到下面的name数组里. char name[21];};int main(){ struct stu STU = {11,"小米"}; printf("结构体大小:Õ6be90d7-ec09-4ca3-867b-6a782bdc474cn",sizeof(struct stu)); //STU.grade = 12; //年龄 //memcpy(STU.name,"小明",5); //姓名 printf("输入姓名:"); scanf("%s", STU.name); printf("输入年龄:"); scanf("%hd",&STU.grade); printf("年龄:%hd\n", STU.grade); printf("姓名:%s\n", STU.name); return 0;}十、C语言结构体位段位段(bit-field)是以位为单位来定义结构体(或联合体)中的成员变量所占的空间。含有位段的结构体(联合体)称为位段结构。采用位段结构既能够节省空间,又方便于操作。位段的定义格式为: type [var]: digits其中type只能为int,unsigned int,signed int三种类型(int型能不能表示负数视编译器而定,比如VC中int就默认是signed int,能够表示负数)。位段名称var是可选参数,即可以省略。digits表示该位段所占的二进制位数。那么定义一个位段结构可以像下面这段代码去定义:struct node{ unsigned int a:4; //位段a,占4位 unsigned int :0; //无名位段,占0位 unsigned int b:4; //位段b,占4位 int c:32; //位段c,占32位 int :6; //无名位段,占6位};十一、C语言可变形参示例代码(1)示例代码#include <stdio.h>#include <string.h>#include <sys/types.h>#include <dirent.h>#include <stdlib.h>#include <stdarg.h>void func(int a,...);int main(int argc,char **argv){ //func(123,456,789,"8888"); func(123); return 0;}//可变形参一般都是约定个数 或者根据第一个参数,决定个数void func(int a,...){ int b=0,c=0; char *p=NULL; va_list ap; va_start(ap,a); b=va_arg(ap,int); c=va_arg(ap,int); p=va_arg(ap,char*); printf("a=%d,b=%d,c=%d,p=%s\n",a,b,c,p); va_end(ap);}(2)示例代码#include <stdio.h>#include <string.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <stdlib.h>#include <stdarg.h> void foo(char *fmt, ...);int main(int argc,char **argv){ foo("%d,%s,%f,Å6be90d7-ec09-4ca3-867b-6a782bdc474cn",123,"456",789.123,'A'); return 0;}void foo(char *fmt, ...){ va_list ap; int d; char c, *s; double f; /*1. 初始化形参列表*/ va_start(ap, fmt); while(*fmt) switch(*fmt++) { case 's': /* string */ s = va_arg(ap,char *); printf("string %s\n", s); break; case 'd': /* int */ d = va_arg(ap, int); printf("int Õ6be90d7-ec09-4ca3-867b-6a782bdc474cn", d); break; case 'c': /* char */ c = (char) va_arg(ap, int); printf("char Å6be90d7-ec09-4ca3-867b-6a782bdc474cn", c); break; case 'f': /* char */ f = (char) va_arg(ap,double); printf("double õ6be90d7-ec09-4ca3-867b-6a782bdc474cn",f); break; } /*释放参数列表,结束读取*/ va_end(ap);}
  • [技术干货] 数据上云 | OneMO通信核心板接入OneNET云平台操作全过程
    前言最近在做物联网实训项目,要求将传感器的数据采集上传到平台上,便于查看数据的情况。经过几番查阅,最终选择使用OneMO通信核心板和OneNET平台来完成我的作业。一顿折腾,小白选手的我,终于摸出了大致结构,复盘下来发现不算复杂,通过简单的三步即可完成数据上云的操作。本着互联网分享精神,特将这呕心熬来的“沥血“分享给大家,希望能帮助大家少掉一点头发。第一步 OneNET云平台基础搭建1、OneNET平台侧创建产品注册完OneNET后,点击进入OneNET“多协议接入”产品栏,在MQTT(旧版)界面下添加产品,任意输入产品名“MQTT_TEST”点击确认,示例如下:2、获取OneNET平台侧参数通过产品名“MQTT_TEST”进入产品概况界面,获取“产品ID”(示例为538410)如下,然后点击设备列表进入设备管理界面:获取设备管理界面的“设备注册码”如下:注:平台侧仅需创建产品,获取“产品ID”和“设备注册码”即可,不需要单独创建设备。第二步 DTU参数配置首先我们需要下载串口调试工具并完成安装,并将OneMO通信核心板通过USB连接电脑,USB接线图如下:打开串口调试工具,进入通信核心板配置模式(我是前往OneMO官网下载的DTU指令介绍),通过AT+MQTTCFG=,,配置OneNET参数,示例如下:通过AT+WKMOD=MQTT1配置MQTT透传模式,执行AT+S保存配置并自动重启,示例如下:重启之后,模组会自动在平台上创建设备并连接,示例如下:注:设备名称默认为模组IMEI号,用户可根据需求在平台侧自行修改。第三步 成果展示那照上述操作已完成平台接入,下面我给大家演示一下我的接入成果。通过串口助手输入任意数据内容(示例为字符串类型)并点击发送,示例如下:平台侧可通过数据流界面观察数据,示例如下:同时,平台侧可以在设备列表界面,通过下发指令下发任意数据给模组,示例如下:通过串口助手,可以看到模组已收到平台下发的数据123456789ABC,示例如下:到此,设备数据上云的实例已介绍完毕,利用上面的例子可以实现设备端-OneNET云平台双向透传。在写文章的过程中,查看购买订单,发现这款DTU通信核心板最近有抢购的活动,心痛、肉痛、钱包痛!友友们,我要不再买一块补个仓拉低成本?!羊毛地址:https://dl-9.cn/111CY
  • [技术干货] C语言——链表总结
    本篇文章介绍C语言链表相关知识点,涉及链表的创建、单向链表、循环链表、双向链表、单向循环链表,链表常见问题总结等,还列出了结构体数组与链表的练习题,将在下篇文章贴出完整代码。1. 链表1.1 结构体对比数组特性: 内存空间连续、只能存放相同类型的数据 结构体特性: 内存空间连续、可以存放不同类型的数据#include <stdio.h> struct MyStruct { int a; char b; }; int main() { struct MyStruct *p; struct MyStruct data={12,'A'}; data.a=123; data.b='B'; p=&data; printf("Ñbace130-38b5-4ab1-8793-02493cd42100n",p->a); printf("Ábace130-38b5-4ab1-8793-02493cd42100n",p->b); return 0; }数组学生管理系统作业:作业: 学生管理系统 需求: (每一个功能都是使用函数进行封装) 1.实现从键盘上录入学生信息。 (姓名、性别、学号、成绩、电话号码) 2.将结构体里的学生信息全部打印出来。 3.实现根据学生的姓名或者学号查找学生,查找到之后打印出学生的具体信息。 4.根据学生的成绩对学生信息进行排序。 5.根据学号删除学生信息。1.2 单向链表的创建与运用链表: 单链表、双链表 区分: 循环和不循环链表 链表的特性: 一种数据结构的运行--->结构体。学习结构体数组(编写学生管理系统): 学生的人数问题不好确定。 链表本身就是一个结构体。单向链表的创建与运用:#include <stdio.h> #include <stdlib.h> #include <string.h> //定义结构体 struct MyListStruct { int a; struct MyListStruct *next; //结构体指针。存放下一个节点的地址 }; //定义链表头 struct MyListStruct *ListHead=NULL; //函数声明 struct MyListStruct *CreateListHead(struct MyListStruct *head); void PintListInfo(struct MyListStruct *head); void AddListNode(struct MyListStruct *head,struct MyListStruct NewNode); void DeleteListNode(struct MyListStruct *head,int a); int main() { int i; struct MyListStruct temp; int a; //1. 创建链表头 ListHead=CreateListHead(ListHead); //2. 添加节点 for(i=0; i<5; i++) { printf("输入新节点数据:"); scanf("%d",&temp.a); AddListNode(ListHead,temp); } //3. 打印所有节点数据 PintListInfo(ListHead); //4. 删除节点数据 printf("输入删除的节点数据值:"); scanf("%d",&a); DeleteListNode(ListHead,a); //5. 打印所有节点数据 PintListInfo(ListHead); return 0; } /* 函数功能: 创建链表头 返回值 : 链表头指针 */ struct MyListStruct *CreateListHead(struct MyListStruct *head) { if(head==NULL) { head=malloc(sizeof(struct MyListStruct)); head->next=NULL; } return head; //返回链表头 } /* 函数功能: 添加链表节点 说明: 链表头一般不存放数据 */ void AddListNode(struct MyListStruct *head,struct MyListStruct NewNode) { struct MyListStruct *p=head; //保存头地址 struct MyListStruct *new_p=NULL; //新的节点 /*1. 寻找链表尾*/ while(p->next!=NULL) { p=p->next; //移动到下一个地址 } /*2. 创建新节点*/ new_p=malloc(sizeof(struct MyListStruct)); if(new_p==NULL) { printf("新节点内存申请失败!\n"); return; } /*3. 新节点赋值*/ memcpy(new_p,&NewNode,sizeof(struct MyListStruct)); //内存拷贝 new_p->next=NULL; //尾节点指向空 /*4. 新节点添加*/ p->next=new_p; } /* 函数功能: 删除链表节点 */ void DeleteListNode(struct MyListStruct *head,int a) { struct MyListStruct *p=head; //保存头地址 struct MyListStruct *tmp=NULL; /*查找数据存在的节点位置*/ while(p->next!=NULL) { tmp=p; //保存上一个节点的地址 p=p->next; if(p->a==a) //查找成功 { tmp->next=p->next; //将要删除节点的指针值赋值给删除节点的上一个节点指针域 //tmp->next=tmp->next->next; free(p); //直接释放p节点空间 //break; p=head; //重新指向链表头 } } } /* 函数功能: 遍历所有链表信息 */ void PintListInfo(struct MyListStruct *head) { int cnt=0; struct MyListStruct *p=head; printf("\n链表遍历的数据如下:\n"); while(p->next!=NULL) { p=p->next; cnt++; printf("第%d个节点的数据=Ñbace130-38b5-4ab1-8793-02493cd42100n",cnt,p->a); } }作业:1.看代码、理解链表的创建流程 2.编写出单向链表的基础运用 3.将之前的学生管理系统使用链表方式做出来 链表的功能: (1)创建链表头 (2)在链表结尾添加一个节点 (3)删除指定的一个链表节点 (4)遍历链表,打印出所有的数据。 (5)在链表的指定节点的后面添加一个节点。 (6)根据链表节点里的数据对链表进行排序。 双向链表: (1)使用逆向+顺向两种遍历方式删除链表节点,目的: 提高效率。 类似于二分法查找。2. 链表问题总结动态空间分配: #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char *p=malloc(100); //申请动态空间。 if(p==NULL) { return -1; } strcpy(p,"1234567890"); printf("%s\n",p); return 0; } #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char *p1=malloc(100); //申请动态空间。 printf("%p\n",p1); char *p2=malloc(100); //申请动态空间。 printf("%p\n",p2); char *p3=malloc(100); //申请动态空间。 printf("%p\n",p3); char *p4=malloc(100); //申请动态空间。 printf("%p\n",p4); return 0; } 错误解决: 1.第一个错误开始解决 2.常规错误: 函数没有声明、分号每加、括号没有加、= 3.括号没有对齐。3. 双向链表和循环链表3.1 单向循环链表:#include <stdio.h> #include <stdlib.h> #include <string.h> //定义结构体 struct MyListStruct { int a; struct MyListStruct *next; //结构体指针。存放下一个节点的地址 }; //定义链表头 struct MyListStruct *ListHead=NULL; //函数声明 struct MyListStruct *CreateListHead(struct MyListStruct *head); void PintListInfo(struct MyListStruct *head); void AddListNode(struct MyListStruct *head,struct MyListStruct NewNode); void DeleteListNode(struct MyListStruct *head,int a); int main() { int i; struct MyListStruct temp; int a; //1. 创建链表头 ListHead=CreateListHead(ListHead); //2. 添加节点 for(i=0; i<5; i++) { printf("输入新节点数据:"); scanf("%d",&temp.a); AddListNode(ListHead,temp); } //3. 打印所有节点数据 PintListInfo(ListHead); //4. 删除节点数据 printf("输入删除的节点数据值:"); scanf("%d",&a); DeleteListNode(ListHead,a); //5. 打印所有节点数据 PintListInfo(ListHead); return 0; } /* 函数功能: 创建链表头 返回值 : 链表头指针 */ struct MyListStruct *CreateListHead(struct MyListStruct *head) { if(head==NULL) { head=malloc(sizeof(struct MyListStruct)); head->next=head; } return head; //返回链表头 } /* 函数功能: 添加链表节点 说明: 链表头一般不存放数据 */ void AddListNode(struct MyListStruct *head,struct MyListStruct NewNode) { struct MyListStruct *p=head; //保存头地址 struct MyListStruct *new_p=NULL; //新的节点 /*1. 寻找链表尾*/ while(p->next!=head) { p=p->next; //移动到下一个地址 } /*2. 创建新节点*/ new_p=malloc(sizeof(struct MyListStruct)); if(new_p==NULL) { printf("新节点内存申请失败!\n"); return; } /*3. 新节点赋值*/ memcpy(new_p,&NewNode,sizeof(struct MyListStruct)); //内存拷贝 new_p->next=head; //尾节点指向头 /*4. 新节点添加*/ p->next=new_p; } /* 函数功能: 删除链表节点 */ void DeleteListNode(struct MyListStruct *head,int a) { struct MyListStruct *p=head; //保存头地址 struct MyListStruct *tmp=NULL; /*查找数据存在的节点位置*/ while(p->next!=head) { tmp=p; //保存上一个节点的地址 p=p->next; if(p->a==a) //查找成功 { tmp->next=p->next; //将要删除节点的指针值赋值给删除节点的上一个节点指针域 //tmp->next=tmp->next->next; free(p); //直接释放p节点空间 //break; p=head; //重新指向链表头 } } } /* 函数功能: 遍历所有链表信息 */ void PintListInfo(struct MyListStruct *head) { int cnt=0; struct MyListStruct *p=head; printf("\n链表遍历的数据如下:\n"); while(p->next!=head) { p=p->next; cnt++; printf("第%d个节点的数据=Ñbace130-38b5-4ab1-8793-02493cd42100n",cnt,p->a); } }3.2 双向链表#include <stdio.h> #include <stdlib.h> #include <string.h> //定义结构体 struct MyListStruct { int a; struct MyListStruct *next; //结构体指针。存放下一个节点的地址 struct MyListStruct *old; //结构体指针。存放上一个节点的地址 }; //定义链表头 struct MyListStruct *ListHead=NULL; //函数声明 struct MyListStruct *CreateListHead(struct MyListStruct *head); void PintListInfo(struct MyListStruct *head); void AddListNode(struct MyListStruct *head,struct MyListStruct NewNode); void DeleteListNode(struct MyListStruct *head,int a); void PintListInfo_old(struct MyListStruct *head); int main() { int i; struct MyListStruct temp; int a; //1. 创建链表头 ListHead=CreateListHead(ListHead); //2. 添加节点 for(i=0; i<5; i++) { printf("输入新节点数据:"); scanf("%d",&temp.a); AddListNode(ListHead,temp); } //3. 打印所有节点数据 PintListInfo(ListHead); PintListInfo_old(ListHead); //4. 删除节点数据 printf("输入删除的节点数据值:"); scanf("%d",&a); DeleteListNode(ListHead,a); //5. 打印所有节点数据 PintListInfo(ListHead); PintListInfo_old(ListHead); return 0; } /* 函数功能: 创建链表头 返回值 : 链表头指针 */ struct MyListStruct *CreateListHead(struct MyListStruct *head) { if(head==NULL) { head=malloc(sizeof(struct MyListStruct)); head->next=NULL; //尾节点指向空 head->old=head; //上一个节点指向头 } return head; //返回链表头 } /* 函数功能: 添加链表节点 说明: 链表头一般不存放数据 */ void AddListNode(struct MyListStruct *head,struct MyListStruct NewNode) { struct MyListStruct *p=head; //保存头地址 struct MyListStruct *new_p=NULL; //新的节点 /*1. 寻找链表尾*/ while(p->next!=NULL) { p=p->next; //移动到下一个地址 } /*2. 创建新节点*/ new_p=malloc(sizeof(struct MyListStruct)); if(new_p==NULL) { printf("新节点内存申请失败!\n"); return; } /*3. 新节点赋值*/ memcpy(new_p,&NewNode,sizeof(struct MyListStruct)); //内存拷贝 new_p->next=NULL; //尾节点指向NULL new_p->old=p; //保存上一个节点的地址 /*4. 新节点添加*/ p->next=new_p; } /* 函数功能: 删除链表节点 顺向删除。 */ void DeleteListNode(struct MyListStruct *head,int a) { struct MyListStruct *p=head; //保存头地址 struct MyListStruct *tmp=NULL; /*查找数据存在的节点位置*/ while(p->next!=NULL) { tmp=p; //保存上一个节点的地址 p=p->next; if(p->a==a) //查找成功 { tmp->next=p->next; //将要删除节点的指针值赋值给删除节点的上一个节点指针域 //tmp->next=tmp->next->next; p->next->old=tmp; //保存上一个节点的地址 free(p); //直接释放p节点空间 //break; p=head; //重新指向链表头 } } } /* 函数功能: 遍历所有链表信息 从头到尾遍历 */ void PintListInfo(struct MyListStruct *head) { int cnt=0; struct MyListStruct *p=head; printf("\n(顺向)链表遍历的数据如下:\n"); while(p->next!=NULL) { p=p->next; cnt++; printf("第%d个节点的数据=Ñbace130-38b5-4ab1-8793-02493cd42100n",cnt,p->a); } } /* 函数功能: 遍历所有链表信息 从尾到头遍历 */ void PintListInfo_old(struct MyListStruct *head) { int cnt=0; struct MyListStruct *p=head; printf("\n(逆向)链表遍历的数据如下:\n"); /*1. 找到链表尾*/ while(p->next!=NULL) { p=p->next; } /*2. 通过链表尾遍历链表的数据*/ while(p!=head) { cnt++; printf("第%d个节点的数据=Ñbace130-38b5-4ab1-8793-02493cd42100n",cnt,p->a); p=p->old; //访问上一个节点 } }
  • [问题求助] uart_exmple和外接设备不能正常通信
    小熊派nano有个案例B6_basic_uart,是uart的Demo。按照原程序,把TX和RX短接,然后通过调试工具查看,是正常的收发。但是把串口输出连接到外部设备,就不正常了。比如usb转485的线连接到电脑上,有串口工具查看收发程序代码/* * Copyright (c) 2020 Nanjing Xiaoxiongpai Intelligent Technology Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */#include #include #include #include "ohos_init.h"#include "cmsis_os2.h"#include "wifiiot_errno.h"#include "wifiiot_gpio.h"#include "wifiiot_gpio_ex.h"#include "wifiiot_adc.h"#include "wifiiot_uart.h"#define UART_TASK_STACK_SIZE 1024 * 8#define UART_TASK_PRIO 25#define UART_BUFF_SIZE 1000static const char *data = "Hello, BearPi!\r\n";static void UART_Task(void){ uint8_t uart_buff[UART_BUFF_SIZE] = {0}; uint8_t *uart_buff_ptr = uart_buff; uint32_t ret; WifiIotUartAttribute uart_attr = { //baud_rate: 9600 .baudRate = 9600, //data_bits: 8bits .dataBits = 8, .stopBits = 1, .parity = 0, }; //Initialize uart driver ret = UartInit(WIFI_IOT_UART_IDX_1, &uart_attr, NULL); if (ret != WIFI_IOT_SUCCESS) { printf("Failed to init uart! Err code = Üece653f-8bdd-4b7d-a762-5a1f288d4c22n", ret); return; } printf("UART Test Start\n"); while (1) { printf("=======================================\r\n"); printf("*************UART_example**************\r\n"); printf("=======================================\r\n"); //通过串口1发送数据 UartWrite(WIFI_IOT_UART_IDX_1, (unsigned char *)data, strlen(data)); //通过串口1接收数据 UartRead(WIFI_IOT_UART_IDX_1, uart_buff_ptr, UART_BUFF_SIZE); printf("Uart1 read data:%s", uart_buff_ptr); usleep(1000000); }}static void UART_ExampleEntry(void){ osThreadAttr_t attr; attr.name = "UART_Task"; attr.attr_bits = 0U; attr.cb_mem = NULL; attr.cb_size = 0U; attr.stack_mem = NULL; attr.stack_size = UART_TASK_STACK_SIZE; attr.priority = UART_TASK_PRIO; if (osThreadNew((osThreadFunc_t)UART_Task, NULL, &attr) == NULL) { printf("[ADCExample] Falied to create UART_Task!\n"); }}APP_FEATURE_INIT(UART_ExampleEntry);
  • [问题求助] NB-IoT模块不能激活
    请帮忙看看什么问题,该如何调试?
  • [技术干货] 部署Linux下开发环境,完成Linux下代码开发(合集)
    一、前言linux是一款自由软件,公开源码的、可以免费获取的操作系统,且linux下许多软件也都是可以免费获取的,linux已经被广泛使用在超级计算机、服务器、PC机、手机、嵌入式系统中。 很多人其实对Linux并不了解,只知道它是个开源操作系统,有一个内核,仅此而已。很多人以为做Linux就是做运维,其实不然,运维只是其中一个方向而已,还有另一个很重要的方向是开发。 开发主要有两个方向:应用开发、底层开发。 应用开发工程师主要在Linux 环境下开发各种应用程序,就如同你们在 Windows 下开发的应用程序一样(比如QQ,PC微信等)。这些应用程序有些带有界面,也有很多是不带界面的。带界面的很多是由 QT 开发,当然也有其他的开发工具。 底层开发工程师就会跟内核、uboot、文件系统、驱动等比较底层的东西打交道。但是,很多移植类的工作芯片厂商都已经给你做好了,很多底层工程师都是做维护的工作,以及驱动开发的工作。 目前很多服务器都是部署在Linux系统上的,比如:很多物联网云平台的MQTT服务器、很多公司的网站等等。 二、文章集合2.1 Linux系统下基础命令介绍链接:https://bbs.huaweicloud.com/forum/thread-0264102134841708046-1-1.htmlLinux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。它能运行主要的UNIX工具软件、应用程序和网络协议。它支持32位和64位硬件。Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。Linux操作系统诞生于1991 年10 月5 日(这是第一次正式向外公布时间)。Linux存在着许多不同的Linux版本,但它们都使用了Linux内核。Linux可安装在各种计算机硬件设备中,比如手机、平板电脑、路由器、视频游戏控制台、台式计算机、大型机和超级计算机。2.2 华为云ECS服务器部署_CentOS7.4服务器安装NFS,ffmpeg、Qt环境链接:https://bbs.huaweicloud.com/forum/thread-0216102134937528112-1-1.html在CentOS7.4服务器版本的环境下搭建NFS服务器、安装ffmpeg、安装nginx服务器、部署Qt编译环境。 (1)配置NGINX为HTTP服务器,安装rtmp模块,完成rtmp视频推流,支持缓存视频到本地目录、支持转为HLS流,通过浏览器访问直播流。 (2)部署Qt编译环境,可以编译Qt代码,运行Qt程序。 因为是服务器,我这里部署Qt环境,运行Qt程序是属于控制台版本,不用Qt的GUI模块,只是用核心模块完成一些功能设计。 (3)安装、编译 ffmpeg : 支持源码编译安装ffmpeg、在线安装ffmpeg,通过ffmpeg可以完成音视频的一些开发。比如:视频转码、视频合并、视频剪切、视频特效处理。----这里用ffmpeg并不是做播放器,是在服务器上用,主要是处理视频文件。 (4)安装NFS服务器,主要是方便与本地Linux系统进行通信,编译代码,文件共享。 (5)部署开机自动启动程序:方便设置自己的程序为开机启动。 2.3  华为云ECS服务器安装CentOS7.4镜像,部署GINX服务器、搭建物联网视频监控系统链接:https://bbs.huaweicloud.com/forum/thread-0228102135095482106-1-1.html在CentOS7.4服务器版本的环境下安装nginx服务器、配置文件服务器、流媒体服务器。(1)配置NGINX为HTTP服务器,安装rtmp模块,完成rtmp视频推流,支持缓存视频到本地目录、支持转为HLS流,通过浏览器访问直播流。(2)部署开机自动启动程序:方便设置自己的程序为开机启动。2.4 Linux系统-Makefile规则介绍、基本使用链接:https://bbs.huaweicloud.com/forum/thread-0299102303834721003-1-1.html一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,也可以执行操作系统的命令。2.5  Linux下(pthread)线程创建与使用链接:https://bbs.huaweicloud.com/forum/thread-0288102303912787002-1-1.html前面文章介绍了Linux下进程的创建、管理、使用、通信,了解了多进程并发;这篇文章介绍Linux下线程的基本使用。 线程与进程的区别 (1)进程: 是操作系统调度最小单位。 Linux下可以通过ps、top等命令查看进程的详细信息。 (2)线程: 是进程调度的最小单位,每个进程都有一个主线程。在进程里主要做事情就是线程。 (3)在全系统中,进程ID是唯一标识,对于进程的管理都是通过PID来实现的。每创建一个进程,内核去中就会创建一个结构体来存储该进程的全部信息,每一个存储进程信息的节点也都保存着自己的PID。需要管理该进程时就通过这个ID来实现(比如发送信号)。当子进程结束要回收时(子进程调用exit()退出或代码执行完),需要通过wait()系统调用来进行,未回收的消亡进程会成为僵尸进程,其进程实体已经不复存在,但会虚占PID资源,因此回收是有必要的。 对于线程而言,若要主动终止需要调用pthread_exit() ,主线程需要调用pthread_join()来回收(前提是该线程没有设置 “分离属性”)。像线发送线程信号也是通过线程ID实现 进程间的通信方式: A.共享内存 B.消息队列 C.信号量 D.有名管道 E.无名管道 F.信号 G.文件 H.socket 线程间的通信方式: A.互斥量 B.自旋锁 C.条件变量 D.读写锁 E.线程信号 F.全局变量 进程间采用的通信方式要么需要切换内核上下文,要么要与外设访问(有名管道,文件)。所以速度会比较慢。而线程采用自己特有的通信方式的话,基本都在自己的进程空间内完成,不存在切换,所以通信速度会较快。也就是说,进程间与线程间分别采用的通信方式,除了种类的区别外,还有速度上的区别。 说明: 当运行多线程的进程捕获到信号时,只会阻塞主线程,其他子线程不会影响会继续执行。 2.6  Linux系统下(pthread)线程的使用案例(分离属性、清理函数等)链接:https://bbs.huaweicloud.com/forum/thread-0254102303978648006-1-1.html这篇文章介绍Linux下线程的创建与基本使用案例,主要是案例代码为主;相关的函数详细介绍在上篇文章里已经介绍过了。2.7 Linux系统下(pthread)线程通信(读写锁) 使用介绍链接:https://bbs.huaweicloud.com/forum/thread-0235102304161444005-1-1.html 读写锁与互斥锁类似,读写锁比互斥锁有更高的并行性,读写锁特点如下: ​ 1. 读写锁有三种状态,读模式下加锁(共享)、写模式下加锁(独占)以及不加锁。 ​ 2. 一次只有一个线程可以占有写模式下的读写锁;但是多个线程可以同时占有读模式下的读写锁。 ​ 3. 读写锁在写加锁状态时,其他试图以写状态加锁的线程都会被阻塞。读写锁在读加锁状态时,如果有线程希望以写模式加锁时,必须阻塞,直到所有线程释放锁。 ​ 4. 当读写锁以读模式加锁时,如果有线程试图以写模式对其加锁,那么读写锁会阻塞随后的读模式锁请求,以避免读锁长期占用,而写锁得不到请求。  读写锁总结: 读写锁分为读锁和写锁。 如果资源被读写锁保护,多个线程可以同时获取读锁—也就是读支持多个线程同时读。 资源加了写锁之后,在写资源的时候只能被一个线程占用,其他读锁就会阻塞。 读锁和写锁也是互斥的关系。读的时候不能写,写的时候不能读。 但是读的时候可以支持多个线程同时读,写的时候只能被一个线程写,其他线程也不能读。 
  • [技术干货] 20. C语言_文件操作相关练习题
    当前文章列出了4个文件编程相关的练习题。文件拷贝实现、 文件加密、学生管理系统链表模板(未添加文件操作)、学生管理系统模板(通过文件系统保存信息)、等4个例子。1.1 文件拷贝实现#include #include #include int main() { /*1. 打开源文件*/ FILE *file_src; FILE *file_new; unsigned char buff[1024]; unsigned int cnt; file_src=fopen("D:/123.pdf","rb"); if(file_src==NULL) { printf("源文件打开失败!\n"); return -1; } /*2. 创建新文件*/ file_new=fopen("D:/456.pdf","wb"); if(file_new==NULL) { printf("新文件创建失败!\n"); return -1; } /*3. 拷贝文件*/ while(!feof(file_src)) { cnt=fread(buff,1,1024,file_src); fwrite(buff,1,cnt,file_new); } /*4. 关闭文件*/ fclose(file_new); fclose(file_src); printf("文件拷贝成功!\n"); return 0; }1.2 文件加密使用: 异或 ^ 密码类型: (1) 整型密码 (2) 字符串密码 比如: 银行提款机的密码、QQ密码 加密代码:#include #include #include int main() { /*1. 打开源文件*/ FILE *file_src; FILE *file_new; unsigned char buff[1024]; unsigned int cnt; unsigned int password=123456; //密码数据 unsigned int data; //存放读取的数据 file_src=fopen("D:/123.pdf","rb"); if(file_src==NULL) { printf("源文件打开失败!\n"); return -1; } /*2. 创建新文件*/ file_new=fopen("D:/456.pdf","wb"); if(file_new==NULL) { printf("新文件创建失败!\n"); return -1; } /*3. 文件加密*/ while(!feof(file_src)) { cnt=fread(&data,1,4,file_src); data=data^password;//文件数据加密 fwrite(&data,1,cnt,file_new); } /*4. 关闭文件*/ fclose(file_new); fclose(file_src); printf("文件加密成功!\n"); return 0; }解密代码: #include #include #include int main() { /*1. 打开源文件*/ FILE *file_src; FILE *file_new; unsigned char buff[1024]; unsigned int cnt; unsigned int password=123456; //密码数据 unsigned int data; //存放读取的数据 file_src=fopen("D:/456.pdf","rb"); if(file_src==NULL) { printf("源文件打开失败!\n"); return -1; } /*2. 创建新文件*/ file_new=fopen("D:/789.pdf","wb"); if(file_new==NULL) { printf("新文件创建失败!\n"); return -1; } /*3. 文件加密*/ while(!feof(file_src)) { cnt=fread(&data,1,4,file_src); data=data^password;//文件数据加密 fwrite(&data,1,cnt,file_new); } /*4. 关闭文件*/ fclose(file_new); fclose(file_src); printf("文件解密成功!\n"); return 0; }1.3 学生管理系统链表模板(未添加文件操作)#include #include #include //存放信息的结构体 struct MyStruct { char Name[50]; //存放姓名 int Number; //存放编号 struct MyStruct *next; //存放下一个节点的地址 }; //链表相关的函数接口 struct MyStruct *ListHead=NULL; //链表头 struct MyStruct *CreateListHead(struct MyStruct *head); void AddrListInfo(struct MyStruct *head,struct MyStruct data); void DeleteListInfo(struct MyStruct *head,int number); void PrintListAllInfo(struct MyStruct *head); int main() { struct MyStruct data1={"张三",123}; struct MyStruct data2={"李四",456}; struct MyStruct data3={"小王",789}; ListHead=CreateListHead(ListHead); //添加信息 AddrListInfo(ListHead,data1); AddrListInfo(ListHead,data2); AddrListInfo(ListHead,data3); //删除节点 DeleteListInfo(ListHead,123); DeleteListInfo(ListHead,789); //打印 PrintListAllInfo(ListHead); return 0; } /* 函数功能: 创建链表头 */ struct MyStruct *CreateListHead(struct MyStruct *head) { if(head==NULL) { head=malloc(sizeof(struct MyStruct)); head->next=NULL; } return head; } /* 函数功能: 在链表结尾添加节点 */ void AddrListInfo(struct MyStruct *head,struct MyStruct data) { struct MyStruct *p=head; struct MyStruct *tmp=NULL; while(p->next!=NULL) { p=p->next; } tmp=malloc(sizeof(struct MyStruct)); memcpy(tmp,&data,sizeof(struct MyStruct)); p->next=tmp; tmp->next=NULL; } /* 函数功能: 根据结构体里特有的成员区分进行删除链表节点信息 函数参数: int numbe 编号 */ void DeleteListInfo(struct MyStruct *head,int number) { struct MyStruct *p=head; struct MyStruct *tmp=NULL; while(p->next!=NULL) { tmp=p; //保存上一个节点的信息 p=p->next; if(p->Number==number) { tmp->next=tmp->next->next; free(p); p=head; //链表头归位 } } } /* 函数功能: 打印所有节点信息 函数参数: int numbe 编号 */ void PrintListAllInfo(struct MyStruct *head) { struct MyStruct *p=head; int cnt=0; printf("\n链表全部信息如下:\n"); while(p->next!=NULL) { p=p->next; cnt++; printf("第%d个节点信息: %s,Ý78d8cf5-73b4-4098-95e0-053cb812f30en",cnt,p->Name,p->Number); } }1.4 学生管理系统模板(通过文件系统保存信息)#include #include #include //存放信息的结构体 struct MyStruct { char Name[50]; //存放姓名 int Number; //存放编号 struct MyStruct *next; //存放下一个节点的地址 }; //链表相关的函数接口 struct MyStruct *ListHead=NULL; //链表头 struct MyStruct *CreateListHead(struct MyStruct *head); void AddrListInfo(struct MyStruct *head,struct MyStruct data); void DeleteListInfo(struct MyStruct *head,int number); void PrintListAllInfo(struct MyStruct *head); //文件操作相关函数 void SaveListAllInfo(struct MyStruct *head,char *path); void GetListAllInfo(struct MyStruct *head,char *path); #if 0 int main() { struct MyStruct data1={"张三",123}; struct MyStruct data2={"李四",456}; struct MyStruct data3={"小王",789}; ListHead=CreateListHead(ListHead); AddrListInfo(ListHead,data1); AddrListInfo(ListHead,data2); AddrListInfo(ListHead,data3); //保存节点信息 SaveListAllInfo(ListHead,"D:/list.ini"); //打印 PrintListAllInfo(ListHead); return 0; } #endif #if 1 int main() { ListHead=CreateListHead(ListHead); //获取节点信息 GetListAllInfo(ListHead,"D:/list.ini"); //打印 PrintListAllInfo(ListHead); return 0; } #endif /* 函数功能: 创建链表头 */ struct MyStruct *CreateListHead(struct MyStruct *head) { if(head==NULL) { head=malloc(sizeof(struct MyStruct)); head->next=NULL; } return head; } /* 函数功能: 在链表结尾添加节点 */ void AddrListInfo(struct MyStruct *head,struct MyStruct data) { struct MyStruct *p=head; struct MyStruct *tmp=NULL; while(p->next!=NULL) { p=p->next; } tmp=malloc(sizeof(struct MyStruct)); memcpy(tmp,&data,sizeof(struct MyStruct)); p->next=tmp; tmp->next=NULL; } /* 函数功能: 根据结构体里特有的成员区分进行删除链表节点信息 函数参数: int numbe 编号 */ void DeleteListInfo(struct MyStruct *head,int number) { struct MyStruct *p=head; struct MyStruct *tmp=NULL; while(p->next!=NULL) { tmp=p; //保存上一个节点的信息 p=p->next; if(p->Number==number) { tmp->next=tmp->next->next; free(p); p=head; //链表头归位 } } } /* 函数功能: 打印所有节点信息 函数参数: int numbe 编号 */ void PrintListAllInfo(struct MyStruct *head) { struct MyStruct *p=head; int cnt=0; printf("\n链表全部信息如下:\n"); while(p->next!=NULL) { p=p->next; cnt++; printf("第%d个节点信息: %s,Ý78d8cf5-73b4-4098-95e0-053cb812f30en",cnt,p->Name,p->Number); } } /* 函数功能: 保存链表节点信息 */ void SaveListAllInfo(struct MyStruct *head,char *path) { struct MyStruct *p=head; FILE *file; file=fopen(path,"a+b"); if(file==NULL) { printf("保存信息的文件打开失败!\n"); return ; } while(p->next!=NULL) { p=p->next; fwrite(p,1,sizeof(struct MyStruct),file); } fclose(file); } /* 函数功能: 从文件里获取链表节点信息 */ void GetListAllInfo(struct MyStruct *head,char *path) { struct MyStruct *p=head; FILE *file; struct MyStruct data; file=fopen(path,"rb"); if(file==NULL) { printf("保存信息的文件打开失败!\n"); return; } //循环读取文件里的数据 while(!feof(file)) { fread(&data,1,sizeof(struct MyStruct),file); //读取链表节点数据 AddrListInfo(head,data); //添加链表节点 } fclose(file); }
总条数:641 到第 页
上滑加载中