嵌入式 Linux 学习笔记(06):输入系统、网络编程与多线程

input 输入子系统、TCP/UDP socket 编程、多线程创建与同步(信号量/互斥量)。

📚 本文是《嵌入式 Linux 应用开发学习笔记》系列第 06 / 7 篇:输入系统、网络编程与多线程。返回系列目录。

第7章 输入系统应用编程(未完成)

7.1输入设备

键盘、鼠标、遥控杆、书写板、触摸屏等与linux完成数据交换

输入设备种类繁多,能否统一它们的接口?

Linux 为了统一管理输入设备,实现了一套能兼容所有输入设备的框架:输入系统

7.2输入系统框架及调试

输入系统框架

image-20250811152406142

示例:

在开发板上执行,可以看到触摸屏数据

hexdump /dev/input/event1

hexdump: 是一个命令行工具,它将文件或数据流的内容以十六进制和 ASCII 格式输出

/dev/input/event1: 是一个特殊的设备文件,代表一个输入设备,比如键盘、鼠标或触摸板。Linux 内核会把来自这些设备的原始输入事件(例如按键、鼠标移动或点击)写入这些文件

7.2.2编写APP需要掌握的知识

驱动程序上报的数据含义三项重要内容:

1.type:哪类,如EV_KEY为按键类

2.code:哪个,比如KEY_A

3.value:值,比如0按下,1松开

  • 此外APP 读取数据时,可以得到一个或多个数据,比如一个触摸屏的一个触点会上报 X、Y 位置信息,也可能会上报压力值
  • 驱动程序上报完一系列的数据后,会上报一个“同步事件”,表示数据上报完毕。APP 读到“同步事件”时,就知道已经读完了当前的数据
  • 同步事件也是一个 input_event 结构体,它的 type、code、value 三项都是 0

image-20250811160100821

7.2.3调试技巧

确定设备信息

ls /dev/input/* -l

可以看到设备

image-20250811160521672

确定设备对应的硬件

cat /proc/bus/input/devices

7.3不用库的应用编程

APP访问硬件有4种方式:查询方式、休眠-唤醒、poll方式、异步通知

通过 ioctl 获取设备信息:

int ioctl(int fd, unsigned long request, ...);

比如要读取输入设备的 evbit 时,ioctl 的 request 要写为“EVIOCGBIT(0, size)

image-20250811171735718

7.3.1查询、休眠唤醒方式

if (argc < 2)
	{
		printf("Usage: %s <dev> [noblock]\n", argv[0]);
		return -1;
	}

	if (argc == 3 && !strcmp(argv[2], "noblock"))
	{
		fd = open(argv[1], O_RDWR | O_NONBLOCK);
	}
	else
	{
		fd = open(argv[1], O_RDWR);
	}
while (1)
	{
		len = read(fd, &event, sizeof(event));
		if (len == sizeof(event))
		{
			printf("get event: type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
		}
		else
		{
			printf("read err %d\n", len);
		}
	}

7.3.2poll/select方式

POLL 机制、SELECT 机制是完全一样的,只是 APP 接口函数不一样

第8章 网络通信

8.1网络通信概述

实际还是数据传输

数据传输三要素:源、目的、长度。

现在假设A、B两台计算机通讯,一台为服务器,一台为客户端。

访问网站的话,设计网站服务器、浏览器,网站服务器平时安静地呆着,浏览器主动发起数据请求

服务器:被动地响应请求

客户端:主动地发起请求

image-20250811194932169

数据传输两种方式:TCP、UDP。

TCP:可靠的、有重传机制(确保数据真到达)、适合控制命令

UDP:不可靠的(适合视频、语音等),视频通话时,使用 UDP,偶尔的丢包、偶尔的花屏时可以忍受的

怎么写程序?

仿照文件读写

将fd=open(”文件名“);

read(fd,buf,len);//源、目的、长度

8.1.1IP和端口

数据传输:源、目的、长度

image-20250811194346820

源端口区分不同浏览器,目的端口区分想要服务

8.1.3两种传输方式TCP/UDP

网络协议5层:物理层、数据链路层、网络层、传输层、应用层

应用层:直接为用户进程提供服务,如支持www的HTTP协议、电子邮箱SMTP协议、文件传输FTP协议,DNS、POP3、FNMP、Telnet

传输层:负责向两个主机中进程之间的通信提供服务,主要TCP、UDP

8.2TCP网络编程

服务端

  • socket函数
int socket(int domain, int type,int protocol);

用于创建套接字,根本不涉及什么源、目的、长度,

  • bind函数
fd = socket(.....);
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

从函数用于将地址绑定到一个套接字(自己的IP端口)

把fd和ip端口绑定,以后就检测这个ip端口看看数据有没有

  • listen函数
int listen(int sockfd,int backlog);

启动检测数据

  • accept函数
int accept(int sockfd, struct sockaddr *addr,int *addrlen);

建立一条连接,接收连接

  • send函数
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

发数据

  • recv函数
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

收数据

客户端

  • socket函数
fd=socket(......)
  • connect函数
int connect(int sockfd, struct sockaddr * serv_addr,int addrlen);

建立连接

  • recv
  • send

8.3UDP网络编程

服务端

  • socket函数
int socket(int domain, int type,int protocol);

用于创建套接字,根本不涉及什么源、目的、长度,

  • bind函数
fd = socket(.....);
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

从函数用于将地址绑定到一个套接字(自己的IP端口)

  • recvfrom
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
				 struct sockaddr *src_addr, socklen_t *addrlen);

recvfrom 通常用于无连接套接字,因为此函数可以获得发送者的地址。

src_addr 是一个 struct sockaddr 类型的变量,该变量保存源机的 IP 地

址及端口号。

addrlen 常置为 sizeof (struct sockaddr)。

  • sendto
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
 			   const struct sockaddr *dest_addr, socklen_t addrlen);

客户端

  • socket函数
fd=socket(......)
  • connect函数
int connect(int sockfd, struct sockaddr * serv_addr,int addrlen);

建立连接

  • recvfrom,recv
  • sendto,send

第9章 多线程编程

9.1线程的使用

9.1.1为什么使用多线程

编写代码时有时候要做2件事,如一边屏幕播放视频,一边等待用户按键操作

如果使用多线程,线程1单独处理按键,线程2单独处理播放

9.1.2线程概念

操作系统所能调度的最小单位,普通的进程,只有一个线程执行逻辑。

我们可以通过多线程编程,使一个进程可以去执行多个不同的任务

相比多进程编程,线程享有共享资源,在进程中出现的全局变量,每个线程都可以访问

9.1.3线程的标识pthread_t

对进程:每个进程有唯一PID

对线程:有tid,本质是pthread_t类型的变量

测试例程

#include <pthread.h>
#include <stdio.h>
int main()
{
	pthread_t tid = pthread_self();//获取主线程的 tid 号
	printf("tid = %lu\n",(unsigned long)tid);
	return 0;
}

gcc xxx.c -lpthread 方可编译多线程程序

9.1.4线程的创建

  • pthread_create
int pthread_create( pthread_t *thread, 
					const pthread_attr_t *attr,
					void *(*start_routine) (void *), 
					void *arg);

例程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
static void *my_thread_func(void *data)
{
    while (1)
    {
        sleep(1);
    }
    
}
int main(int argc, char const *argv[])
{
    pthread_t tid;
    int ret;
    /*1.创建接收线程*/
    ret = pthread_create(&tid,NULL,my_thread_func,NULL);
    if(ret)
    {
        printf("pthread_create err\n");
        return -1;
    }
    /*2.主线程读取标准输入,发送给接受线程*/
    while(1)
    {
        sleep(1);
    }
    return 0;
}

9.1.5同步操作-向线程传数据(等待)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

static char g_buf[1000];
static int g_hasData = 0;

static void *my_thread_func(void *data)
{
    while (1)
    {
        /*等待通知*/
        while(g_hasData == 0);
        /*打印*/
        printf("recv: %s",g_buf);
        g_hasData = 0;
    }
    
}

int main(int argc, char const *argv[])
{
    pthread_t tid;
    int ret;
    /*1.创建接收线程*/
    ret = pthread_create(&tid,NULL,my_thread_func,NULL);
    if(ret)
    {
        printf("pthread_create err\n");
        return -1;
    }
    /*2.主线程读取标准输入,发送给接受线程*/
    while(1)
    {
       fgets(g_buf,1000,stdin);
       /*通知接受线程*/
       g_hasData = 1;
    }
    return 0;
}

9.1.6同步操作(信号量)

// 初始化
#include <semaphore.h>
static sem_t sem;
int sem_init(sem_t *sem,int pshared,unsigned int value);
等待/释放:
#include <pthread.h>
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>

static char g_buf[1000];
static sem_t g_sem;

static void *my_thread_func(void *data)
{
    while (1)
    {
        /*等待通知*/
        sem_wait(&g_sem);
        /*打印*/
        printf("recv: %s",g_buf);
    }
}
int main(int argc, char const *argv[])
{
  
    pthread_t tid;
    int ret;
    /*初始化信号量*/
    sem_init(&g_sem,0,0);
    /*1.创建接收线程*/
    ret = pthread_create(&tid,NULL,my_thread_func,NULL);
    if(ret)
    {
        printf("pthread_create err\n");
        return -1;
    }
    /*2.主线程读取标准输入,发送给接受线程*/
    while(1)
    {
       fgets(g_buf,1000,stdin);
       /*通知接受线程*/
       sem_post(&g_sem);
    }
    return 0;
}

9.1.7同步操作(互斥量)

static pthread_mutex_t g_tMutex  = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&g_tMutex);
pthread_mutex_unlock(&g_tMutex);

给不希望打断的内容加锁

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>

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("recv: %s",g_buf);
        pthread_mutex_unlock(&g_tMutex);
    }
}

int main(int argc, char const *argv[])
{
  
    pthread_t tid;
    int ret;
    char buf[1000];
    /*初始化信号量*/
    sem_init(&g_sem,0,0);
    /*1.创建接收线程*/
    ret = pthread_create(&tid,NULL,my_thread_func,NULL);
    if(ret)
    {
        printf("pthread_create err\n");
        return -1;
    }
    /*2.主线程读取标准输入,发送给接受线程*/
    while(1)
    {
        fgets(buf,1000,stdin);
        pthread_mutex_lock(&g_tMutex);
        memcpy(g_buf,buf,1000);
        pthread_mutex_unlock(&g_tMutex);
        /*通知接受线程*/
        sem_post(&g_sem);
    }
    return 0;
}
使用 Hugo 构建
主题 Stack 由 Jimmy 设计