/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/include/
如果将网卡比喻为身份证
插入的usb网卡代表windows(当然,windows可以有很多网卡),开发板自身有两网卡,有两个身份证,用eth0。虚拟机有两个虚拟网卡。桥接网卡用于和开发板,windows主机沟通。
usb网卡ip为192.168.5.10
ubuntu的桥接网卡(ens36)ip为192.168.5.11,桥接网卡为192.168.15.134
开发板etn0网卡ip为192.168.5.9,注意,开发板ip可能会失效。每次应该检查。
配置完后,三个网卡可以互相ping通
手工设置的方法很简单,但是每次启动开发板都要重新设置,在开发板串口 中执行命令即可: ifconfig eth0 192.168.5.9
开发板出厂自带linux系统。通过mobaexterm创建串口连接,可以将开发板当成一台真linux电脑。
NFS,网络文件系统,类似云盘。
前提,
启动NFS服务
虚拟机的挂载目录为 /home/book/nfs_rootfs
开发板的挂载目录为 /mnt/
权限
ubuntu自己挂载自己测试
挂载,将ubuntu目录挂载到开发板,ubuntu就像云盘。
测试
在ubuntu新建文件,立即在开发板访问
当然,在开发板新建文件,ubuntu也能立即访问到。挂载的两个文件夹就像是一个文件夹,不论另一个怎么改变,另一个也会很快改变。
注意,ubuntu或开发板重启,数据线断开等等,挂载会断开,建议每次都挂载,
第一个应用程序
注意,由于不同设备cpu架构可能不同,指令集不同,所以就需要不同的编译链。同样的源码,经不同的编译链编译后,可以运行在不同架构的设备,在一台设备上编译要运行在另一个平台的程序,就叫交叉编译,必须要用适应目标架构的交叉编译链。否则无法运行。
比如arm-buildroot-linux-gnueabihf-gcc -o hello hello.c,在ubuntu虚拟机将hello.c文件用arm-buildroot-linux-gnueabihf-gcc编译链生成的可执行文件hello,才能运行在板子上。而不能直接用gcc。
第一个驱动程序
么编译驱动程序之前要先编译内核
开发板上运行到内核是出厂时烧录的,你编译驱动时用的内核是你自己编译 的,这两个内核不一致时会导致一些问题。所以我们编译驱动程序前,要把自己 编译出来到内核放到板子上去,替代原来的内核。
板子使用新编译出来的内核时,板子上原来的其他驱动也要更换为新编译出 来的。所以在编译我们自己的第 1 个驱动程序之前,要先编译内核、模块,并且 放到板子上去。
编译内核
不 同 的 开 发 板 对 应 不 同 的 配 置 文 件 , 配 置 文 件 位 于 内 核 源 码 arch/arm/configs/目录。编译完成 zImage 后才可编译设备树文件。
编译完成后,在 arch/arm/boot 目录下生成 zImage 内核文件, 在 arch/arm/boot/dts 目录下生成设备树的二进制文件 100ask_imx6ull14x14.dtb。把这 2 个文件复制到/home/book/nfs_rootfs 目录备用,这个目录是挂载到了开发板的/mnt目录。
编译内核模块
进入内核源码目录后,就可以编译内核模块了:
book@100ask: cd ~/100ask_imx6ull-sdk/Linux-4.9.88/
book@100ask:~/100ask_imx6ull-sdk/Linux-4.9.88$ make module
把模块安装在 nfs 目录“/home/book/nfs_rootfs/”下备用
book@100ask:~/100ask_imx6ull-sdk/Linux-4.9.88$ make ARCH=arm INSTALL_MOD_PATH=/home /book/nfs_rootfs modules_install(这两行是一行)
这时候,在 Ubuntu 的/home/book/nfs_rootfs 目录下,已经有了 zImage、 dtb 文件,并且有 lib/modules 子目录(里面含有各种模块)。 接下来要把这些文件复制到开发板上。
安装内核和模块到开发板上
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt(再挂载,防止已经断开)
cp /mnt/zImage /boot
cp /mnt/100ask_imx6ull-14x14.dtb /boot
cp /mnt/lib/modules /lib -rfd
sync
最后重启开发板,它就使用新的 zImage、dtb、模块了。
编译驱动
将驱动源码从windows复制到ubuntu,修改驱动文件里的makefile文件的linux内核源码路径,在ubuntu编译后,备份到nfs_rootfs文件夹。当然也可以直接从windows复制到nfs_rootfa文件夹,然后修改makefile并编译,一样的。
开发板安装驱动
首先检查开发板ip,测试是否能ping通ubuntu,再挂载,如果重复挂载会提示busy,没影响。
cd进入开发板/mnt/01_hello_drv目录,执行insmod hello_drv.ko装载驱动程序,
开发板如何访问到插入的SD卡数据呢,答案也是挂载,但这个不是网络文件系统的挂载。
将sD卡的sda1分区挂载到开发板的/mnt/下,就可以读写SD卡了
当然,还有虚拟的挂载,比如,开发板自动挂载的内核文件,可以查看内核资源
c是char设备,b是block设备,那两个数字呢,第一个是设备的主设备号,另一个是次设备号,次设备号对应一个设备的不同硬件部位
读写文件,查看linux的读写文件函数,可以用man命令
FrameBuffer 帧缓冲是存储图像像素数据的内存区域
实验
在屏幕实现描点函数
步骤,打开 LCD 设备节点,获取屏幕参数,映射 Framebuffer,最后实现描点函数。
获取屏幕的bit_per_pixel,就是每个像素点用多少位表示颜色
源码
#include
#include
#include
#include
#include
#include
#include
#include
#include
static int fd_fb;
static struct fb_var_screeninfo var; /* Current var */
static int screen_size;
static unsigned char *fb_base;
static unsigned int line_width;
static unsigned int pixel_width;
void lcd_put_pixel(int x, int y, unsigned int color)
{
unsigned char *pen_8 = fb_base+y*line_width+x*pixel_width;
unsigned short *pen_16;
unsigned int *pen_32;
unsigned int red, green, blue;
pen_16 = (unsigned short *)pen_8;
pen_32 = (unsigned int *)pen_8;
switch (var.bits_per_pixel)//获取每个像素用多少位表示颜色
{
case 8:
{
*pen_8 = color;
break;
}
case 16:
{
/* 565 */
red = (color >> 16) & 0xff;
green = (color >> 8) & 0xff;
blue = (color >> 0) & 0xff;
color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
*pen_16 = color;
break;
}
case 32:
{
*pen_32 = color;
break;
}
default:
{
printf("can't surport %dbpp\n", var.bits_per_pixel);
break;
}
}
}
int main(int argc, char **argv)
{
int i;
fd_fb = open("/dev/fb0", O_RDWR);//打开设备节点
if (fd_fb < 0)
{
printf("can't open /dev/fb0\n");//打开失败
return -1;
}
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))//获取屏幕可变参数
{
printf("can't get var\n");
return -1;
}
line_width = var.xres * var.bits_per_pixel / 8;
pixel_width = var.bits_per_pixel / 8;
screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if (fb_base == (unsigned char *)-1)
{
printf("can't mmap\n");//映射失败
return -1;
}
/* 清屏: 全部设为白色 */
memset(fb_base, 0xff, screen_size);
/* 随便设置出100个为红色 */
for (i = 0; i < 100; i++)
lcd_put_pixel(var.xres/2+i, var.yres/2, 0xFF0000);
munmap(fb_base , screen_size);//解除对内存区域的映射关系
close(fd_fb);//关闭设备节点
return 0;
}
字符和编码
首先,字符集和编码格式并不是一一对应的关系,比如unicode字符集,就有几种不同的utf编码,常用的是utf-8,在编写c语言文件时,保存的时候会有编码格式,而在linnux编译执行时,默认是utf-8,所以在使用gcc时,可以加入参数,指定源文件编码格式和可执行文件编码格式。
如下,同样是"A中"这两个字符,由于源文件保存编码不同,执行结果也不同,GB2312一个汉字两个字节,而UTF83个字节。
怎么解决呢,在gcc编译时加入参数即可,如下,-finput-charset是源文件字符编码,-fexec-charset是生成的可执行文件的字符编码,这样就能在编译阶段转换编码格式。
可以看到,utf-8的源文件,生成的执行文件是GB2312的,同样GB2312源文件生成的可执行文件是UTF-8的。
显示汉字实验
HZK16文件是一些常用汉字的点阵数据,通过GB2312索引,所以源c文件要以GB2312格式保存(或者如上在编译时指定编码格式),在虚拟机编译,拷贝到nfs目录,到开发板执行
试验成功。但是"中"显示错误,变成了涓,为什么。因为源文件是UTF-8格式,虽然Linux编译默认也是UTF-8。但是我们使用的汉字库使用GB2312索引,所以,在编译时,要指定可执行文件的编码格式为GB2312
代码分析
int fd_fb;
struct fb_var_screeninfo var; /* Current var */
int screen_size;
unsigned char *fbmem;
unsigned int line_width;
unsigned int pixel_width;
int fd_hzk16;
struct stat hzk_stat;
unsigned char *hzkmem;
/**********************************************************************
* 函数名称: lcd_put_pixel
* 功能描述: 在LCD指定位置上输出指定颜色(描点)
* 输入参数: x坐标,y坐标,颜色
* 输出参数: 无
* 返 回 值: 会
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/05/12 V1.0 zh(angenao) 创建
***********************************************************************/
void lcd_put_pixel(int x, int y, unsigned int color)
{
unsigned char *pen_8 = fbmem+y*line_width+x*pixel_width;
unsigned short *pen_16;
unsigned int *pen_32;
unsigned int red, green, blue;
pen_16 = (unsigned short *)pen_8;
pen_32 = (unsigned int *)pen_8;
switch (var.bits_per_pixel)
{
case 8:
{
*pen_8 = color;
break;
}
case 16:
{
/* 565 */
red = (color >> 16) & 0xff;
green = (color >> 8) & 0xff;
blue = (color >> 0) & 0xff;
color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
*pen_16 = color;
break;
}
case 32:
{
*pen_32 = color;
break;
}
default:
{
printf("can't surport %dbpp\n", var.bits_per_pixel);
break;
}
}
}
/**********************************************************************
* 函数名称: lcd_put_ascii
* 功能描述: 在LCD指定位置上显示一个8*16的字符
* 输入参数: x坐标,y坐标,ascii码
* 输出参数: 无
* 返 回 值: 无
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
* 2020/05/12 V1.0 zh(angenao) 创建
***********************************************************************/
void lcd_put_ascii(int x, int y, unsigned char c)
{
unsigned char *dots = (unsigned char *)&fontdata_8x16[c*16];
int i, b;
unsigned char byte;
for (i = 0; i < 16; i++)
{
byte = dots[i];
for (b = 7; b >= 0; b--)
{
if (byte & (1<=0; b--)
{
if (byte & (1<
FreeType实验
如下是Ubuntu虚拟机上适用于IMX_6ULL开发板的交叉编译工具链位置,可以看到几个目录,我们可以将自己写的头文件和库放到对应位置,头文件一般放到sysroot/user/include下。库文件(.so)一般放到sysroot/user/lib下
比如我们使用交叉编译链时,包含的stdio.h在哪里呢,也在/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/include/,如下,当然,如果使用linux自带的gcc,包含的头文件在/usr/include
万能命令
解压FreeType
进入,可以看到有config文件
使用万能命令,生成makefile
执行make,成功,如果失败,原因是FreeType依赖于其他库,不过,imx_6ull工具链已经有了zlib,按理来说还要编译安装libpng,不过这里成功了,不管了
make install,如下,成功,在./temp里有了include和lib文件夹
将include里和lib里全部拷贝到工具链下的/usr/include和/usr/lib,-rf表示递归
到这里,我们就手动编译安装了FreeType
wchar测试
每一个wchar类型的字符,都将以unicode码保存,即使文件保存格式不是unicode
第一次报错,因为
FreeType显示汉字
错误,找不到头文件,问题是因为之前编译的FreeType在工具链的usr/include/freetype2/目录,所以找不到,只需要将freetype2下所有文件移动到include即可
移动
再次编译,报错,函数未定义,只需要加上参数编译即可
成功
拷贝到网络文件系统的
板子执行
还可以让字体发生旋转,代码略微变化,编译参数要指定-lm,表示数学库
拷贝运行,第一个参数必须是角度,第二个才是大小
输入系统
如下,查看event0设备节点数据,假设屏幕是event0,在没有点击屏幕时,是没有输出的,这时候的hexdump程序在休眠,当点击屏幕后,hexdump会被唤醒,就会输出数据
当点击屏幕后,会触发中断,中断服务函数就会到驱动程序去获取数据,传给核心层,转化为统一格式后再传给事件层,事件层再唤醒应用程序,并将数据传给它。
如下,这些数据不是一个事件,每一行都是一个事件,因为人点击屏幕不是一瞬间完成的。
上面的数据表示什么意思呢,linux通过一个结构体描述一个事件,上面的输入事件同理。当然,结构体内还有事件发生的时间(从系统运行到现在)。
事件类型
如何查看设备节点的详细信息呢,如下
描述输入设备的结构体,可以看到,一个输入设备有十个数组,就像bitmap,对于evbit,就是支持的事件类型。
比如上面的event0设备,EV=b,二进制为1011,即支持0,1,3号事件。分别是同步事件,按键事件,绝对位移事件。
绝对位移事件又分为多种属性,或者说子事件,如下B:ABS=2658000 3
再次分析之前的hexbump输出的数据
如下,0003表示支持绝对位移事件,0039表示ID,0035是x方向的位移,0036是y方向的位移。
编写程序,查看输入设备的信息,比如支持哪些事件
其实linux已经有完整的库函数,不过为了训练,我们还是手动实现,首先man 2 open查看open函数需要哪些头文件
另外,还要用到evdev.c里的
evdev_do_ioctl,ioctl不就是获取信息吗,cmd参数定义在input.h,不过不是linux内核里,而是编译链里 input.h
如下,input.h在编译链下include/linux/里,那个alsa是声卡的,不管他,即程序要包含
还要用linux自己的ioctl函数,open ioctl,即要包含
代码
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
char *ev_names[]={
"EV_SYN",
"EV_KEY",
"EV_REL",
"EV-ABS",
"EV_MSC",
"EV_SW",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"NULL",
"EV_LED",
"EV_SND",
"NULL",
"EV_REP",
"EV_FF",
"EV-PWR"
};
int fd; // 句柄
int err;
struct input_id id; // 结构体在input.h里面
int i,j;
unsigned int evbit[2];
int len;
/*用法 ./view_dev_info /dev/input/eventX*/
if (argc != 2)
{
printf("用法 ./view_dev_info /dev/input/eventX");
return -1; // 失败
}
fd = open(argv[1], O_RDWR); // 可读可写
if (fd < 0)
{
printf("ERR: %s open failed", argv[1]);
return -1;
}
// 第二个参数,根据input.h里的cmd宏定义设置,是获取id还是支持的事件位图evbit
err = ioctl(fd, EVIOCGID, &id);
if (err == 0)
{
printf("bustype = 0x%s\n", id.bustype);
printf("vendor = 0x%s\n", id.vendor);
printf("product = 0x%s\n", id.product);
printf("version = 0x%s\n", id.version);
}
else
return -1 ;
// 存在evbit里,len只是长度,LEN==2
len = ioctl(fd, EVIOCGBIT(0, 8), &evbit); // 0表示读取evbit,改为1就是读取keybit第二个参数是读取的字节数
if (len > 0 && len <= 8)
{
for(i=0;i<8;i++){
unsigned char *byte=(unsigned char *)evbit[i];
for(j=0;j<8;i++){
if(*byte &= (1<
上面的程序是查看屏幕支持的事件,下面将以此为基础,编程实现读取屏幕数据,当点击屏幕时,输出,type,code,value等等。有多种方式,有直接读取,休眠唤醒读取,POLL读取,直接读取如果没有数据就立即返回,休眠唤醒的话无数据休眠,有数据唤醒,POLL就是在超时时间内一直读取,时间一到就返回。
直接读取和休眠唤醒读取
#include
#include
传入参数O_NOOBLOCK就是以非阻塞方式打开
然后读取数据,
POLL方式读取
在打开设备节点时,必须以非阻塞方式打开,因为POLL就是在超时时间内一直读取。
用man查看poll函数,包含头文件poll.h,关于poll函数用法,第一个参数是一个结构体数组,第二个是nfds_t结构体,然后是超时时间
异步通知方式
步骤大概如下
查看signal函数
getpid
fcntl
部分代码
tslib,即toach screen库,是关于触摸屏的开源库,有很多封装好的程序。
1-5是为了配置开发环境,以后自己编程时,编译会用到
1,。先解压
2.再配置
3.make编译
4.安装
5.拷贝到工具链
6.挂载
7.拷贝到开发板,这里是为了配置开发板的运行环境
测试
ts_print_mt,成功
ts_test_mt
无反应,要关闭GUI
网络编程
TCP,可靠的,面向链接的
先写服务端
domain选择AF_INET,因为是ipv4
type参数
bind函数
可以用这个结构体,但不太好,用sockaddr_in结构体更好
memset
listen,参数backlog是最大同时监听数
accept
inet_ntoa,将网络ip转为字符串,ascll码
fork,创建子进程的函数,是为了实现接收多个客户端
fork函数返回值,创建成功的话,子进程的pid将会返回给父进程,并且将0返回给子进程,如果创建子进程失败,就将-1返回给父进程。
recv函数
tcp_server完整代码
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int iSocketClient_fd;
int iAddrLen;
int iSocketServer_fd;
int iRet;
struct sockaddr_in tSocketServerAddr;
struct sockaddr_in tSocketClientAddr;
int iRecvLen;
unsigned char ucRecvBuf[1000];
int iClient_ID = -1;
// 创建socket句柄
// protocol,协议
iSocketServer_fd = socket(AF_INET, SOCK_STREAM, 0);
if (iSocketServer_fd == -1)
{
printf("Scoket Error\r");
return -1;
}
// 通过结构体设置服务端的ip端口等
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(8888); // 将8888端口转为网络字节序
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; // 本机所有ip
memset(&tSocketServerAddr.sin_zero, 0, 8);
// 将socket句柄与ip结构体绑定
iRet = bind(iSocketServer_fd, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (iRet == -1)
{
printf("Bind Error\r");
return -1;
}
// listen,监听socket句柄,最大同时监听10个
iRet = listen(iSocketServer_fd, 10);
if (iRet == -1)
{
printf("Listen Error\r");
return -1;
}
while (1)
{
iAddrLen = sizeof(struct sockaddr);
iSocketClient_fd = accept(iSocketServer_fd, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (iSocketClient_fd != -1) // 有客户端连接成功
{
iClient_ID++; // 区分不同的客户端
printf("Get Connect from %d : %s\n", iClient_ID, inet_ntoa(tSocketClientAddr.sin_addr));
if (!fork())
{
// 子进程源码
while (1)
{
// 接收客户端数据并显示
// 源,目的,长度
iRecvLen = recv(iSocketClient_fd, ucRecvBuf, 999, 0);
if (iRecvLen <= 0)
{
close(iSocketClient_fd);
return -1;
}
else
{
ucRecvBuf[iRecvLen] = '\0'; // 结束符
printf("Get Msg From Client %d : %s\n", iClient_ID, ucRecvBuf);
}
}
}
}
}
close(iSocketServer_fd);
return 0;
}
客户端
connect
fgets,从标准输入获得数据
send,发送数据
测试
先在ubuntu直接运行server,再用mobax连接ubuntu运行client,运行client要带参数192.168.15.134,这是NAT网卡ip,本实验的服务端和客户端是同一台机器。
如上,试验成功
ps -A,可以看到,有两个tcp_server,因为获得连接后创建了一个子进程。
当退出client时,client和server子进程本应退出,client确实彻底退出了,但server子进程为什么变成了僵尸进程,因说白了,需要父进程给他收尸,有点FreeRTOS的钩子函数的意思。
解决办法
,在tcp_server.c里调用函数signal ,要包含signal.h
重新编译,重新运行server和client,退出client,ps -A查看进程,可以看到。server子进程彻底被杀,只留了父进程
udp编程
服务端,udp服务端不再需要Listen
recvfrom函数
udp的服务端程序,变化主要在while循环里
socket函数的参数变为了SOCK_DGRAM
客户端
第一种,相较于tcp的客户端,只改变socket函数参数为SOCK_DGRAM,也就是说,connect函数依然存在,不过并未真正连接。这样的话,只需调用send,因为conect会包含发送数据的目的地tSocketServerAddr。
第二种,删除connect,调用sendto,指定目的地
第一种测试
第二个,测试
出现问题,修改代码
问题依然存在,客户端输入数据按下回车键,客户端程序立马退出,服务端未收到数据
多线程编程
为什么要用多线程呢,因为线程之间通信方便,开销小,进程间通信往往通过队列交换数据,开销大。同一个进程的不同线程可以访问到全局变量。
linux资源分配以进程为单位,而调度以线程为单位
pthread_create,线程创建函数
代码编写
上传虚拟机
编译,发现要链接库,使用-lpthread
使用ps -T查看线程,可以发现两个线程
或者cd /proc/61599,ls task/,可以看到两个线程号
在此代码基础上修改,实现一个线程获取标准输入,另一个线程打印出数据,数据存在全局变量
#include
#include
#include
static char g_buf[1000];
static int g_has_data;
static void *my_thread_func(void *data)
{
while (1)
{
while (!g_has_data);
printf("rec:%s\n", g_buf);
g_has_data = 0;
}
}
int main(int argc, char **argv)
{
int ret;
pthread_t t_id;
ret = pthread_create(&t_id, NULL, my_thread_func, NULL);
if (ret != 0)
{
printf("线程创建失败");
return -1;
}
while (1)
{
fgets(g_buf, 1000, stdin); // 从标准输入获取数据
g_has_data = 1;
// 通知接收线程
}
return 0;
}
编译同样-lpthread,运行前杀掉上次的进程,kill -9
运行如下,测试成功
top命令查看cpu,发现一个pthread1就消耗了百分百cpu
怎么修改降低cpu占用
通过信号量机制
代码如下
#include
#include
#include
#include
static char g_buf[1000];
static int g_has_data;
static sem_t g_sem;
static void *my_thread_func(void *data)
{
while (1)
{
sem_wait(&g_sem);
printf("rec:%s\n", g_buf);
g_has_data = 0;
}
}
int main(int argc, char **argv)
{
int ret;
pthread_t t_id;
sem_init(&g_sem,0,0);
ret = pthread_create(&t_id, NULL, my_thread_func, NULL);
if (ret != 0)
{
printf("线程创建失败");
return -1;
}
while (1)
{
fgets(g_buf, 1000, stdin); // 从标准输入获取数据
sem_post(&g_sem);//唤醒接收线程
}
return 0;
}
运行后,top命令查看,在前面根本找不到pthread2,说明资源占用极少。
分析以上代码,发现还有问题,比如当正在打印g_buf时,这时候输入了新数据,这个数组里就会有新老数据混合,怎么解决呢。
互斥量
代码主要增加了
编译运行后发现,无法打印出数据,分析问题,是因为main函数里while太快,还没来得及打印,互斥量就又被主线程获取了,因为g_buf被互斥锁锁住了,导致一直无法打印
修改方法,加一个局部变量缓冲数组,因为memcpy速度是很快的。memcpy要包含string.h
编译运行,收到了数据,但是出现了乱码
具体代码如下
#include
#include
#include
#include
#include
static char g_buf[1000];
static sem_t g_sem;
static pthread_mutex_t g_tMutex = PTHREAD_MUTEX_INITIALIZER;
static void *my_thread_func(void *data)
{
while (1)
{
sem_wait(&g_sem);
pthread_mutex_lock(&g_tMutex);
printf("rec:%s\n", g_buf);
pthread_mutex_unlock(&g_tMutex);
}
}
int main(int argc, char **argv)
{
char temp_buf[1000];
int ret;
pthread_t t_id;
sem_init(&g_sem, 0, 0);
ret = pthread_create(&t_id, NULL, my_thread_func, NULL);
if (ret != 0)
{
printf("线程创建失败");
return -1;
}
while (1)
{
fgets(g_buf, 1000, stdin); // 从标准输入获取数据
pthread_mutex_lock(&g_tMutex); // 上锁
memcpy(g_buf, temp_buf, 1000);
pthread_mutex_unlock(&g_tMutex); // 开锁
sem_post(&g_sem); // 唤醒接收线程
}
return 0;
}
关于同步互斥,还有一种办法,条件变量。条件变量是和互斥量一起用的,相当于代替信号量。用作同步
主要变化代码,没有了信号量
测试发现没问题,关于乱码,经过分析,是fgets参数错了。将g_buf改为temp_buf即可
TTY体系是什么
是由电传打字机演变而来的,键盘打字输入,然后纸张输出,这是物理终端,对于linux虚拟机,每开一个Terminal窗口,在./dev下就会多一个tty,从1开始,tty1,tty2等等,这些都是虚拟终端。
如下,tty3输出了第一个hello,第二个没输出,因为第二个是要在tty4输出
tty3
tty4
上面说到虚拟终端从tty1开始,那tty0是啥呢,是指的当前前台终端,只有前台终端能接受键盘输入
下图,从tty3输入命令,发送数据到tty0,即发送到前台窗口,后续切换到tty4等窗口,也能收到。谁是前台谁收到
/dev/tty表示此终端,表示自己的终端,在tty1里表示tty1,在tty2里表示tty2,将上图红框替换为/dev/tty,就只有tty3能收到数据,其他窗口收不到。
控制台和终端
修改内核cmdline
修改内核cmdline后,因为指定了两个console,所以/dev/console指的是后一个,下面的消息只有tty3能接收到,换一个比如tty4执行这段命令,消息依旧会显示到tty3。
tty驱动程序框架
形象解释,比如开发板通过串口和电脑连接,在电脑用串口显示板子的命令行界面,此时输入ls,将会显示所有文件,这个过程是怎样的,首先ls\n被从串口发送到板子,板子里的串口驱动接收到,将会把字符发给行规层line discipine,行规层检查,发现是ls换行,就把ls上传给app,这里app是shell,app查看文件,把结果发回行规层,再发给串口驱动程序,通过串口硬件发给PC的串口驱动,然后PC就能看到ls执行结果。
上面的行规层,可以设为原始模式,将检查字符等工作完全交给应用层。后面的串口编程,也就是多了行规层的设置。
串口应用程序实验