正题开始前,先进行一些对于ARM开发的科普,ARM开发是一个统称,是对于A系列,R系列,M系统内核的微控制器的开发。
cotex-A: 用于大量数据处理的领域 6818-A53,I.MX6U阿尔法开发板(Cortex-A7)
cotex-R: 用于实时性特别强的领域---刹车制动
cotex-M: 主要用于工业控制领域(STM32-M系列单片机,GB系列单片机)
ARM Cortex-A核:
特点:Cortex-A核是ARM架构中的应用处理器核。它通常用于高性能计算和通用操作系统的执行,如Android、Linux等。Cortex-A核具有多级流水线、超标量执行、乱序执行等高级特性,使其能够高效处理多线程和多任务。
应用场景:Cortex-A核广泛应用于智能手机、平板电脑、服务器、网络设备、物联网网关等需要高性能和通用计算的领域。
ARM Cortex-R核:
特点:Cortex-R核是ARM架构中的嵌入式实时处理器核。它专注于实时性能和可预测性,适用于处理实时控制任务,如汽车电子、工业控制、嵌入式系统等。Cortex-R核具有低延迟、高吞吐量和硬实时性能。
应用场景:Cortex-R核广泛用于汽车电子控制器、工业自动化、无线通信基站、嵌入式控制系统等需要实时响应和可靠性的领域
ARM Cortex-M核:
特点:Cortex-M核是ARM架构中的微控制器处理器核。它专注于低功耗、紧凑尺寸和实时控制,适用于小型嵌入式系统。Cortex-M核通常具有单一周期执行、中断处理能力和低功耗模式。
应用场景:Cortex-M核广泛应用于微控制器(MCU)、传感器节点、物联网设备、嵌入式传感器、医疗设备等需要低功耗和实时控制的领域。
下面主要是对A系列内核处理器中常用的Linux系统中系统编程和网络编程的细小知识点的总结
一、系统编程
1. 进程
1.1 进程的状态
可以划分为就绪态、执行态、睡眠态/挂起态、暂停态、僵尸态、死亡态
1.2 进程的API接口
1.2.1 创建进程
#include
pid_t fork(void);
1.2.2 获取进程号
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); ---获取当前进程的进程号
pid_t getppid(void); ---获取当前进程的父进程的进程号
1.2.3 excel函数族
使用fork创建子进程后,除了直接在代码中写要运行的程序外,还可以
借助execl函数族来完成需要运行的程序
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char arg, ... / (char *) NULL */);
int execlp(const char *file, const char arg, ... / (char *) NULL */);
int execle(const char *path, const char arg, ... /, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
参数:
path --- 要执行的程序的路径
file --- 要执行的程序的名字
argv --- 以数组的方式作为形参
envp --- 用户自定义的变量数组
含义:
名称带 l 表示 参数以列表的形式传参
名称带 v 表示 参数以矢量数组(二级指针)的形式传参
名字带 p 表示 参数会利用环境变量来寻找指定的文件
名字带 e 表示 参数会利用用户自定义的环境变量
1.2.4 进程返回值
程序在结束时写的return 0 ,exit(-1)这些都是会交给父进程的。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status); //接收一个子进程的信息,保存到status里面
pid_t waitpid(pid_t pid, int *status, int options);
//指定等待进程号为pid的子进程计数,保存到status里面
//options是指接收的模式
宏定义 WIFEXITED(status)
returns true if the child terminated normally, that is, by call‐ing exit(3) or _exit(2), or by returning from main().
宏定义 WEXITSTATUS(sta)
获取子进程的返回值
1.2.5 进程结束的缓冲区
exit()的功能和return是一样的
_exit()不会刷新IO缓冲区
#include
int atexit(void (*function)(void));
1.2.6 精灵进程/守护进程
代码注释:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
int main(int argc, char const *argv[])
{
// 忽略终端关闭信号
signal(SIGHUP,SIG_IGN);
// 创建子进程,父进程退出
int ret = fork();
if (ret > 0)
{
return 0;
}
// 创建心的回话终端
setsid();
// 创建子进程,父进程退出
ret = fork();
if (ret > 0)
{
return 0;
}
// 创建新的会话组
setpgrp();
// 关闭所有文件描述符
int maxfd = sysconf(_SC_OPEN_MAX);
int i;
for (i = 0; i < maxfd; i++)
{
close(i);
}
// 去掉所有文件权限
umask(0);
// 修改工作路径
chdir("/");
// 精灵进程需要做的事就放在while循环中
while (1)
{ }
return 0;
}
流程如图:
2. 管道和信号
2.1 无名管道的API接口
#include
int pipe(int pipefd[2]);
参数: int类型的具有2个元素的数组,当创建无名管道成功以后,管道的读端和写端会分别放入到数组中去。
成功后:
pipefd[0] 是读端
pipefd[1] 是写端
在linux下一切都是文件,所以管道也可以用read.write操作。
2.2 有名管道的API接口
#include
#include
int mkfifo(const char *pathname, mode_t mode);
参数: pathname 有名管道的文件名
mode 有名管道的权限
成功后:创建管道文件
2.3 文件是否存在以及权限函数
#include
int access(const char *pathname, int mode);
检查某一个文件的权限
pathname: 要被检查的文件
mode: 要被检查的权限
R_OK 读权限
W_OK 写权限
X_OK 执行权限
F_OK 是否存在
成功返回0,失败返回-1
有名管道和无名管道可以通过 ulimited -a 来查看大小,一般情况下,管道中存储数据的上限是65536个字节,但是也可以通过ulimited指令来修改。
2.4 信号
linux中现有62个信号,编号为 1~31, 34~64 kill -l查看当前版本中linux支持的信号
信号的特点:
(1)1~31号信号是linux已经有默认的动作,当进程接收到这些信号时,可以执行
默认的动作;34~64号信号没有默认的动作,必须自行设置。
(2)1~31号信号也被称为不可靠信号,非实时信号。接收n次,可能只会执行1~2次;
34~64号信号称为可靠信号,实时信号。接收n次,一定会执行n次。
(3)当所有信号一起从挂起状态转入响应状态时,非实时信号不会排队,会相互嵌套
甚至丢包,实时信号会按照从大到小的顺序依次执行。
2.4.1 非实时信号有默认动作
SIGINT 关闭终端--ctrl+C关闭
SIGHUP 关闭terminal终端
SIGKILL 杀死信号
SIGSTOP 暂停进程--让进程进入暂停态
SIGCONT 继续进程--让进程由暂停态进入就绪态
在shell终端中向某一个进程发送信号
kill 信号编号 进程号
在代码中向某一个进程发送信号
#include <signal.h>
int kill(pid_t pid, int sig);
参数:
pid就是要发送信号的进程的进程号
sig就是要发送的信号的编号
2.4.2 信号处理函数
#include <signal.h>
typedef void (*sighandler_t)(int);
---函数指针:指向一个参数为int,返回值为void的函数的指针,
其中这个参数int,就是接收到的信号的编号
sighandler_t signal(int signum, sighandler_t handler);
参数: signum: 要响应的信号
handler: 要响应的函数的指针
也可以设置为 SIG_DFL SIG_IGN
信号处理的三个动作:
(1)执行linux默认的信号动作,设置 handler为SIG_DFL
(2)执行用户自己定义的信号动作,设置 handler为自定义函数
(3)忽略系统发来的信号,设置 handler为 SIG_IGN
2.4.3 信号挂起
SIGKILL和SIGSTOP信号是不可能被挂起或者忽略的。因为这两个信号涉及到进程的状态改变。
SIGKILL让进程由执行态转为僵尸态
SIGSTOP让进程由执行态转为暂停态
SIGCONT和SIGSTP虽然也会改变进程状态,但仍然可以被忽略或者挂起
例子及代码
有名管道:
fifo_read.c
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <strings.h>
int main(int argc, char const *argv[])
{
if (access("/home/gec/123",F_OK) != 0)
{
// 创建管道文件
mkfifo("/home/gec/123",0666);
}
int fd = open("/home/gec/123",O_RDONLY);
if (fd == -1)
{
perror("open failed!\n");
return -1;
}
char buf[32];
while (1)
{
bzero(buf,32);
read(fd,buf,32);
printf("buf = %s\n",buf);
}
close(fd);
return 0;
}
fifo_write.c
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <strings.h>
#include <string.h>
int main(int argc, char const *argv[])
{
if (access("/home/gec/123",F_OK) != 0)
{
// 创建管道文件
mkfifo("/home/gec/123",0666);
}
int fd = open("/home/gec/123",O_WRONLY);
if (fd == -1)
{
perror("open failed!\n");
return -1;
}
char buf[32];
while (1)
{
bzero(buf,32);
scanf("%s",buf);
write(fd,buf,strlen(buf));
}
close(fd);
return 0;
}
3 消息队列
ipcs -q 只查看消息队列的信息
ulimit -q 消息队列的缓冲区大小
3.1 创建键值
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
参数: pathname 路径
proj_id 模数
相同的路径+相同模数一定会得到一样的键值
如果两个进程需要用到同一个消息队列,那么这两个进程创建键值时
使用的路径与模数一定是一模一样的,如果两个进程不需要同一个消息队列,
那么使用路径与模数至少要有一个不同。
返回值:
>0 新的合法的键值
-1 失败
3.2 获取消息队列的ID号
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
参数: key 键值
msgflg 模式
IPC_CREAT 假设键值对应的消息队列不存在,则创建
IPC_EXCL 假设键值对应的消息队列存在,则报错 EEXIST
返回值:
>0 消息队列的ID号
-1 失败
3.3 消息队列的信息传输
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//通过消息队列发送消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数: msqid 消息队列ID号
msgp 要传输的数据的首地址,它一定是struct msgbuf类型的结构体指针
msgsz 要传输的数据大小
msgflg 模式,一般设置是否阻塞,可以默认写0
//通过消息队列接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
参数: msqid 消息队列ID号
msgp 要接收的数据的缓冲区首地址
msgsz 要传输的数据大小
msgtyp 消息标识
msgflg 模式,一般设置是否阻塞,可以默认写0
消息队列在传输时,一定是以结构体的方式传输。这个结构体内参数的顺序是不能改的。
struct msgbuf
{
long mtype; //标识位,必须>0
char mtext[1]; //具体传输的数据
};
3.4 控制消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:
msqid 消息队列的ID号
cmd 指令 IPC_STAT ,第三个参数是存在的,表示把消息队列的信息保存到第三个参数的结构体中
IPC_SET ,第三个参数存在,表示通过第三个参数设置修改消息队列
PC_RMID ,第三个参数不存在,为NULL,表示删除消息队列
3.5 在终端中删除消息队列
ipcrm -Q 键值
ipcrm -q ID号
3.6(重要,面试)消息队列的用途
三大作用:
削峰填谷,异步处理,模块解耦
双十二电商购买活动节
(1) 削峰填谷 在活动节期间,流量暴增,平常的服务器负荷不够。每个用户单独处理要500ms,现在突然有 100个用户,那么现在一共需要 50000 才能处理完毕。可以把这100个用户拆分,拆分到10个消息队列 中,那么每个消息队列最多只需要处理5000ms的内容,当人数减少以后,可以通过销毁消息队列来减少 服务器负荷。
(2) 异步处理 在活动节期间,每个用户的购买流程有主要流程和次要流程。例如 购买就是主要流程,要400ms, 领消费券,做打折活动是次要流程,分别要50ms。这次次要流程可以放入到另一个消息队列里面,那么 主要流程的响应时间就只有400ms了。
(3) 模块解耦 在活动期间,需要一个数据库来分析保存用户的购买习惯,这些保存和分析数据也是需要时间的, 如果这些也被放入到购买流程中,那么会影响到用户体验,并且不同的活动期间,你的数据库分析算法 也有修改,直接把这个代码加入到购买流程的代码,会加大程序员的工作量。解决办法就是把算法分析 的进程与购买流程的进程完全分离,两个是完全不同的进程。通过消息队列把购买时的数据发送给算法 分析的进程。
例子及代码
消息队列接收端
msgrcv
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/msg.h>
#include <errno.h>
struct msgbuf{
long mtype;
char mtext[100];
};
int main()
{
//创建键值
key_t key = ftok(".",1);
if(key == -1)
{
perror("ftok failed");
return -1;
}
//创建或者打开消息队列的ID号
int msgid = msgget(key,IPC_CREAT | IPC_EXCL | 0666);
if(msgid == -1)
{
//只有当消息队列是因为已存在而报错的情况下,才能直接打开
if( errno == EEXIST)
{
msgid = msgget(key,0666);
if(msgid == -1)
{
perror("msgget failed");
return -1;
}
}
}
struct msgbuf buf;
while(1)
{
//接收标志为2的消息
msgrcv(msgid,&buf,sizeof(struct msgbuf), 2 ,0);
printf("recv:[%s]\n",buf.mtext);
if(strcmp(buf.mtext,"88")==0)
break;
}
}
发送端
msgsnd
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/msg.h>
#include <errno.h>
//结构体中成员的顺序绝对不能改,因为内核在处理消息队列时是先读4字节,再读n-4字节
struct msgbuf{
long mtype;
char mtext[100];
};
int main()
{
//创建键值
key_t key = ftok(".",1);
if(key == -1)
{
perror("ftok failed");
return -1;
}
//创建或者打开消息队列的ID号
int msgid = msgget(key,IPC_CREAT | IPC_EXCL | 0666);
if(msgid == -1)
{
//只有当消息队列是因为已存在而报错的情况下,才能直接打开
if( errno == EEXIST)
{
msgid = msgget(key,0666);
if(msgid == -1)
{
perror("msgget failed");
return -1;
}
}
}
//发送数据时是向消息队列中写入标识符为2的信息
struct msgbuf buf;
buf.mtype = 2;
while(1)
{
scanf("%s",buf.mtext);
msgsnd(msgid,&buf,sizeof(struct msgbuf),0);
if(strcmp(buf.mtext,"88")==0)
break;
}
//删除消息队列
//msgctl(msgid,IPC_RMID,NULL);
}
系统编程知识点还有很多,篇幅有限,不再展现,在最后会有我的笔记相关链接
下面接收一波网络编程
二 、网络编程
网络编程也是属于进程之间的通信方式,和系统编程中的其他几种通信方式的区别:
系统编程:作用于同一台主机下的两个不同进程之间的通信,使用管道、IPC对象
网络编程:作用域同一台主机或跨主机之间的通信,使用局域网进行通信
2.1 网络编程的专业术语
2.1.1 网络体系结构模型
OSI模型(7层)
OSI模型在发送的过程中,数据从引用层出发,每经过一层就会在数据后面加上这一层的"头",除了最后的物理层
OSI模型在接收的过程中,数据从物理层传入,每经过一层就会去掉对应层的"头",最后只剩数据来到应用层
除了OSI模型,还有TCP/IP模型(4层)
应用层 老板将自己要说的话写成信。封装好后在信封上附上对方的地址
传输层 快递员拿到信决定以什么方式去送
网络层 分析地址在哪
网络接口与物理层 选择一条正确的路就出发
2.1.2 什么是IP地址
例如:"192.168.33.9"点分制
在同一个局域网下,每一台主机都必须要有一个IP地址,且IP地必须是相同网段:IP地址的前3个数字
所以只能通过最后一个数字来划分给同一局域网下的不同主机
每一个IP地址都是32位,如果要在网络编程中使用IP地址,就要将这32位的数据转换成网络字节序
我们在写代码时写的是主机字节序,要将IP地址转换成网络字节序后再将数据发出
会使用一些字节转换函数
2.1.3 什么是端口号
在同一台主机下的每一个进程都共用一个IP地址,如何区分同一IP地址下的不同进程?
通过端口号,每一个进程都会有被分配一个端口号用来接收数据
端口号是16位,取值范围:0 ~ 65535
系统默认占用端口:0 ~ 1023
用户可以自定义的:1024 ~ 65535
端口号在结束使用后会被回收,所以最好过一段时间再用
2.1.4 ubuntu网络命令
要让ubuntu和主机处于同一局域网下,就要去配置主机IP地址
查看/配置linux网络信息:ifconfig
gec@ubuntu:/mnt/hgfs/2210/05网络编程/day1$ ifconfig
ens33(网卡名) Link encap:Ethernet 以太网
HWaddr 00:0c:29:4b:3d:ee 硬件地址
inet addr:192.168.33.5 当前ubuntu的IP地址
Bcast:192.168.33.255 当前局域网的广播地址
Mask:255.255.255.0 子网掩码
设置ubuntuIP地址
sudo ifconfig 网卡名称 IP地址
注意:网卡名称要看清楚,不同的linux,它的网卡名也会有不同,还有IP地址最好的同一网段下的
例如:
sudo ifconfig ens33 192.168.33.6
然后可以再使用ifconfig查看一下是否配置成功了
检查是否可以与同一局域网下的其他主机进行通信
ping 其他主机的IP地址
例如:
gec@ubuntu:/mnt/hgfs/2210/05网络编程/day1$ ping 192.168.33.9
PING 192.168.33.9 (192.168.33.9) 56(84) bytes of data.
64 bytes from 192.168.33.9: icmp_seq=1 ttl=128 time=0.160 ms
64 bytes from 192.168.33.9: icmp_seq=2 ttl=128 time=0.557 ms
出现"time=0.557 ms"就说明可以进行通信,有一点延迟
如果ping不通,检查一下电脑防火墙关了没
2.1 通信协议
在不同计算机中,双方需要遵循同一个规则的通信协议来进行通信
TCP/IP协议 传输控制协议/以太网互联协议
TCP协议 用于检查传输过程中出现的问题
IP协议 负责不同网络之间的数据交换
传输的过程中,一旦发现错误,要求重新传输数据,直到数据安全为止
2.1.1 TCP协议(打电话)
特点:字节传输、面向连接、效率相对较低
优点:提供可靠传输,确保数据无丢失
缺点:点对点,一次只能和一个对象进行通信
应用场合:重要信息的传输,长时间的传输
2.1.2 UDP协议(写信)
特点:数据包传输、面向无连接、效率相对较高
优点:可以实现点对多的通信
缺点:传输时可能会发生数据包丢失
应用场合:网络媒体的视频、直播
2.1.3 TCP协议和UDP协议的区别
UDP:数据包式套接字
一种对网络要求不高的传输方式,它的传输数据不会受到接收端的影响
UDP不可靠传输,只管发送,不负责对方是否接收成功
TCP:数据流式套接字
一种对网络要求较高的传输方式,每次传输数据过去,都必须接收到对方的响应
在连接时,会有一个"三次握手"的操作
在断开时,会有一个"四次挥手"的操作
TCP的可靠传输就是建立在这个三次握手和四次挥手的操作上,需要连接成功才能传输/接收数据
2.1.4 三次握手和四次挥手
专用术语:
SYN: 同步位,表示请求连接,一般为1
seq: 序号,一般是随机数
ACK: 确认位,ACK=1表示有效,无值表示无效
ack: 确认号,在对方发送的序号基础上+1
FIN: 取消位,表示请求断开,一般为1
三次握手:
四次挥手:
TCP通信是一种面向连接的通信方式,要确保和对方建立了连接后才能开始通信
而且在通信结束时也会和对方进行断开连接的动作,建立连接时的动作就是三次握手,断开连接时的操作就是四次挥手
三、参考笔记
由于篇幅有限,系统编程和网络编程知识点也比较多,具体可以参考我的笔记