linux开发随笔

/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服务

linux开发随笔_第1张图片

虚拟机的挂载目录为    /home/book/nfs_rootfs

开发板的挂载目录为    /mnt/

权限

ubuntu自己挂载自己测试

linux开发随笔_第2张图片

挂载,将ubuntu目录挂载到开发板,ubuntu就像云盘。

测试

在ubuntu新建文件,立即在开发板访问

linux开发随笔_第3张图片

 linux开发随笔_第4张图片

当然,在开发板新建文件,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装载驱动程序,

linux开发随笔_第5张图片

 linux开发随笔_第6张图片

开发板如何访问到插入的SD卡数据呢,答案也是挂载,但这个不是网络文件系统的挂载。

将sD卡的sda1分区挂载到开发板的/mnt/下,就可以读写SD卡了

当然,还有虚拟的挂载,比如,开发板自动挂载的内核文件,可以查看内核资源

linux开发随笔_第7张图片 当然也可以手动挂载,因为是虚拟的,所以不用设备节点

c是char设备,b是block设备,那两个数字呢,第一个是设备的主设备号,另一个是次设备号,次设备号对应一个设备的不同硬件部位

linux开发随笔_第8张图片

读写文件,查看linux的读写文件函数,可以用man命令

linux开发随笔_第9张图片

FrameBuffer 帧缓冲是存储图像像素数据的内存区域

linux开发随笔_第10张图片

实验

在屏幕实现描点函数

步骤,打开 LCD 设备节点,获取屏幕参数,映射 Framebuffer,最后实现描点函数。

linux开发随笔_第11张图片

 linux开发随笔_第12张图片

获取屏幕的bit_per_pixel,就是每个像素点用多少位表示颜色

linux开发随笔_第13张图片

 linux开发随笔_第14张图片

源码

#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个字节。

linux开发随笔_第15张图片

 怎么解决呢,在gcc编译时加入参数即可,如下,-finput-charset是源文件字符编码,-fexec-charset是生成的可执行文件的字符编码,这样就能在编译阶段转换编码格式。

 可以看到,utf-8的源文件,生成的执行文件是GB2312的,同样GB2312源文件生成的可执行文件是UTF-8的。

显示汉字实验

linux开发随笔_第16张图片

HZK16文件是一些常用汉字的点阵数据,通过GB2312索引,所以源c文件要以GB2312格式保存(或者如上在编译时指定编码格式),在虚拟机编译,拷贝到nfs目录,到开发板执行

 试验成功。但是"中"显示错误,变成了涓,为什么。因为源文件是UTF-8格式,虽然Linux编译默认也是UTF-8。但是我们使用的汉字库使用GB2312索引,所以,在编译时,要指定可执行文件的编码格式为GB2312

 linux开发随笔_第17张图片

 

linux开发随笔_第18张图片

代码分析

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

linux开发随笔_第19张图片

linux开发随笔_第20张图片

 linux开发随笔_第21张图片

 万能命令

linux开发随笔_第22张图片

解压FreeType

linux开发随笔_第23张图片

 进入,可以看到有config文件

linux开发随笔_第24张图片

 使用万能命令,生成makefile

 执行make,成功,如果失败,原因是FreeType依赖于其他库,不过,imx_6ull工具链已经有了zlib,按理来说还要编译安装libpng,不过这里成功了,不管了

  make成功

linux开发随笔_第25张图片

make install,如下,成功,在./temp里有了include和lib文件夹

linux开发随笔_第26张图片

将include里和lib里全部拷贝到工具链下的/usr/include和/usr/lib,-rf表示递归

 

 到这里,我们就手动编译安装了FreeType

wchar测试

 每一个wchar类型的字符,都将以unicode码保存,即使文件保存格式不是unicode

linux开发随笔_第27张图片

第一次报错,因为

linux开发随笔_第28张图片

FreeType显示汉字

错误,找不到头文件,问题是因为之前编译的FreeType在工具链的usr/include/freetype2/目录,所以找不到,只需要将freetype2下所有文件移动到include即可

 移动

 再次编译,报错,函数未定义,只需要加上参数编译即可

 成功

 拷贝到网络文件系统的

linux开发随笔_第29张图片

 板子执行

linux开发随笔_第30张图片

linux开发随笔_第31张图片

 还可以让字体发生旋转,代码略微变化,编译参数要指定-lm,表示数学库

 拷贝运行,第一个参数必须是角度,第二个才是大小

linux开发随笔_第32张图片

输入系统

linux开发随笔_第33张图片

如下,查看event0设备节点数据,假设屏幕是event0,在没有点击屏幕时,是没有输出的,这时候的hexdump程序在休眠,当点击屏幕后,hexdump会被唤醒,就会输出数据

 当点击屏幕后,会触发中断,中断服务函数就会到驱动程序去获取数据,传给核心层,转化为统一格式后再传给事件层,事件层再唤醒应用程序,并将数据传给它。

如下,这些数据不是一个事件,每一行都是一个事件,因为人点击屏幕不是一瞬间完成的。

上面的数据表示什么意思呢,linux通过一个结构体描述一个事件,上面的输入事件同理。当然,结构体内还有事件发生的时间(从系统运行到现在)。

linux开发随笔_第34张图片

 

 

事件类型

linux开发随笔_第35张图片

如何查看设备节点的详细信息呢,如下 

linux开发随笔_第36张图片

描述输入设备的结构体,可以看到,一个输入设备有十个数组,就像bitmap,对于evbit,就是支持的事件类型。

 比如上面的event0设备,EV=b,二进制为1011,即支持0,1,3号事件。分别是同步事件,按键事件,绝对位移事件。

 

 绝对位移事件又分为多种属性,或者说子事件,如下B:ABS=2658000 3linux开发随笔_第37张图片

再次分析之前的hexbump输出的数据

如下,0003表示支持绝对位移事件,0039表示ID,0035是x方向的位移,0036是y方向的位移。

linux开发随笔_第38张图片

编写程序,查看输入设备的信息,比如支持哪些事件

其实linux已经有完整的库函数,不过为了训练,我们还是手动实现,首先man 2 open查看open函数需要哪些头文件

linux开发随笔_第39张图片

 另外,还要用到evdev.c里的

evdev_do_ioctl,ioctl不就是获取信息吗,cmd参数定义在input.h,不过不是linux内核里,而是编译链里 input.h

如下,input.h在编译链下include/linux/里,那个alsa是声卡的,不管他,即程序要包含

linux开发随笔_第40张图片

 还要用linux自己的ioctl函数,open ioctl,即要包含

linux开发随笔_第41张图片

代码

#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<

linux开发随笔_第42张图片

上面的程序是查看屏幕支持的事件,下面将以此为基础,编程实现读取屏幕数据,当点击屏幕时,输出,type,code,value等等。有多种方式,有直接读取,休眠唤醒读取,POLL读取,直接读取如果没有数据就立即返回,休眠唤醒的话无数据休眠,有数据唤醒,POLL就是在超时时间内一直读取,时间一到就返回。

直接读取和休眠唤醒读取

#include //strcmp要
#include //read函数需要

传入参数O_NOOBLOCK就是以非阻塞方式打开

linux开发随笔_第43张图片

 然后读取数据,

linux开发随笔_第44张图片

 linux开发随笔_第45张图片

POLL方式读取

在打开设备节点时,必须以非阻塞方式打开,因为POLL就是在超时时间内一直读取。

 用man查看poll函数,包含头文件poll.h,关于poll函数用法,第一个参数是一个结构体数组,第二个是nfds_t结构体,然后是超时时间

linux开发随笔_第46张图片

异步通知方式

步骤大概如下

linux开发随笔_第47张图片

 查看signal函数

linux开发随笔_第48张图片

 getpid

linux开发随笔_第49张图片

fcntl

linux开发随笔_第50张图片

linux开发随笔_第51张图片

 linux开发随笔_第52张图片

 部分代码

linux开发随笔_第53张图片

linux开发随笔_第54张图片

tslib,即toach screen库,是关于触摸屏的开源库,有很多封装好的程序。

1-5是为了配置开发环境,以后自己编程时,编译会用到

1,。先解压

 2.再配置

 3.make编译

4.安装

linux开发随笔_第55张图片

5.拷贝到工具链

 6.挂载

7.拷贝到开发板,这里是为了配置开发板的运行环境

 测试

ts_print_mt,成功

linux开发随笔_第56张图片

 ts_test_mt

无反应,要关闭GUI

linux开发随笔_第57张图片

网络编程

TCP,可靠的,面向链接的

 先写服务端

linux开发随笔_第58张图片

 domain选择AF_INET,因为是ipv4

linux开发随笔_第59张图片

 type参数

linux开发随笔_第60张图片

 bind函数

linux开发随笔_第61张图片

 可以用这个结构体,但不太好,用sockaddr_in结构体更好linux开发随笔_第62张图片

 将端口转为字节序的函数linux开发随笔_第63张图片

memset

linux开发随笔_第64张图片

listen,参数backlog是最大同时监听数

linux开发随笔_第65张图片

 accept

linux开发随笔_第66张图片

inet_ntoa,将网络ip转为字符串,ascll码

linux开发随笔_第67张图片

fork,创建子进程的函数,是为了实现接收多个客户端

linux开发随笔_第68张图片

 fork函数返回值,创建成功的话,子进程的pid将会返回给父进程,并且将0返回给子进程,如果创建子进程失败,就将-1返回给父进程。

recv函数

linux开发随笔_第69张图片

 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

linux开发随笔_第70张图片

 fgets,从标准输入获得数据

linux开发随笔_第71张图片

 send,发送数据

linux开发随笔_第72张图片

 测试

先在ubuntu直接运行server,再用mobax连接ubuntu运行client,运行client要带参数192.168.15.134,这是NAT网卡ip,本实验的服务端和客户端是同一台机器。

linux开发随笔_第73张图片

 linux开发随笔_第74张图片

 如上,试验成功

ps -A,可以看到,有两个tcp_server,因为获得连接后创建了一个子进程。

linux开发随笔_第75张图片

 当退出client时,client和server子进程本应退出,client确实彻底退出了,但server子进程为什么变成了僵尸进程,因说白了,需要父进程给他收尸,有点FreeRTOS的钩子函数的意思。 

linux开发随笔_第76张图片

 linux开发随笔_第77张图片

 解决办法

,在tcp_server.c里调用函数signal ,要包含signal.h

linux开发随笔_第78张图片

 重新编译,重新运行server和client,退出client,ps -A查看进程,可以看到。server子进程彻底被杀,只留了父进程

linux开发随笔_第79张图片

udp编程

服务端,udp服务端不再需要Listen

recvfrom函数

linux开发随笔_第80张图片

 udp的服务端程序,变化主要在while循环里

socket函数的参数变为了SOCK_DGRAM

 while循环里调用recvfromlinux开发随笔_第81张图片

客户端

第一种,相较于tcp的客户端,只改变socket函数参数为SOCK_DGRAM,也就是说,connect函数依然存在,不过并未真正连接。这样的话,只需调用send,因为conect会包含发送数据的目的地tSocketServerAddr。

第二种,删除connect,调用sendto,指定目的地

linux开发随笔_第82张图片

linux开发随笔_第83张图片

 第一种测试

linux开发随笔_第84张图片

 第二个,测试

出现问题,修改代码

linux开发随笔_第85张图片

 问题依然存在,客户端输入数据按下回车键,客户端程序立马退出,服务端未收到数据

多线程编程

为什么要用多线程呢,因为线程之间通信方便,开销小,进程间通信往往通过队列交换数据,开销大。同一个进程的不同线程可以访问到全局变量。

linux资源分配以进程为单位,而调度以线程为单位

 pthread_create,线程创建函数

linux开发随笔_第86张图片

代码编写

linux开发随笔_第87张图片

上传虚拟机

编译,发现要链接库,使用-lpthread

 加上&后台运行,ps查看进程可以看到一个pthread

linux开发随笔_第88张图片

 使用ps -T查看线程,可以发现两个线程

linux开发随笔_第89张图片

或者cd  /proc/61599,ls task/,可以看到两个线程号

linux开发随笔_第90张图片

在此代码基础上修改,实现一个线程获取标准输入,另一个线程打印出数据,数据存在全局变量

#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

linux开发随笔_第91张图片

 怎么修改降低cpu占用

通过信号量机制

linux开发随笔_第92张图片

 代码如下

#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时,这时候输入了新数据,这个数组里就会有新老数据混合,怎么解决呢。

互斥量

linux开发随笔_第93张图片

代码主要增加了

linux开发随笔_第94张图片

 编译运行后发现,无法打印出数据,分析问题,是因为main函数里while太快,还没来得及打印,互斥量就又被主线程获取了,因为g_buf被互斥锁锁住了,导致一直无法打印

修改方法,加一个局部变量缓冲数组,因为memcpy速度是很快的。memcpy要包含string.h

linux开发随笔_第95张图片

编译运行,收到了数据,但是出现了乱码

 具体代码如下

#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;
}

关于同步互斥,还有一种办法,条件变量。条件变量是和互斥量一起用的,相当于代替信号量。用作同步

 linux开发随笔_第96张图片

 主要变化代码,没有了信号量

linux开发随笔_第97张图片

测试发现没问题,关于乱码,经过分析,是fgets参数错了。将g_buf改为temp_buf即可 

TTY体系是什么

是由电传打字机演变而来的,键盘打字输入,然后纸张输出,这是物理终端,对于linux虚拟机,每开一个Terminal窗口,在./dev下就会多一个tty,从1开始,tty1,tty2等等,这些都是虚拟终端。

linux开发随笔_第98张图片

 如下,tty3输出了第一个hello,第二个没输出,因为第二个是要在tty4输出

tty3

tty4

上面说到虚拟终端从tty1开始,那tty0是啥呢,是指的当前前台终端,只有前台终端能接受键盘输入

下图,从tty3输入命令,发送数据到tty0,即发送到前台窗口,后续切换到tty4等窗口,也能收到。谁是前台谁收到

linux开发随笔_第99张图片

/dev/tty表示此终端,表示自己的终端,在tty1里表示tty1,在tty2里表示tty2,将上图红框替换为/dev/tty,就只有tty3能收到数据,其他窗口收不到。

控制台和终端

linux开发随笔_第100张图片

linux开发随笔_第101张图片 

 修改内核cmdline

linux开发随笔_第102张图片

 修改内核cmdline后,因为指定了两个console,所以/dev/console指的是后一个,下面的消息只有tty3能接收到,换一个比如tty4执行这段命令,消息依旧会显示到tty3。

linux开发随笔_第103张图片

linux开发随笔_第104张图片

 

tty驱动程序框架

linux开发随笔_第105张图片

linux开发随笔_第106张图片

 形象解释,比如开发板通过串口和电脑连接,在电脑用串口显示板子的命令行界面,此时输入ls,将会显示所有文件,这个过程是怎样的,首先ls\n被从串口发送到板子,板子里的串口驱动接收到,将会把字符发给行规层line discipine,行规层检查,发现是ls换行,就把ls上传给app,这里app是shell,app查看文件,把结果发回行规层,再发给串口驱动程序,通过串口硬件发给PC的串口驱动,然后PC就能看到ls执行结果。

linux开发随笔_第107张图片

 上面的行规层,可以设为原始模式,将检查字符等工作完全交给应用层。后面的串口编程,也就是多了行规层的设置。

串口应用程序实验

你可能感兴趣的:(Linux专栏,ubuntu,linux,运维)