前言
本文主要是一些学习笔记的汇总,主要参考公众号:嵌入式与Linux那些事,GDB多线程调试,自实现unique_ptr,share_ptr,宏,线程池,socket,vector,coredump仅供自己学习使用。
中断与异常有何区别?
中断是指外部硬件产生的一个电信号从CPU的中断引脚进入,打断CPU的运行;
异常是指软件运行过程中发生了一些必须作出处理的事件,CPU自动产生一个陷入来打断CPU的运行.异常在处理的时候必须考虑与处理器的时钟同步,实际上异常也称为同步中断,在处理器执行到因编译错误而导致的错误指令时,或者在执行期间出现特殊错误,必须靠内核处理的时候,处理器就会产生-个异常。
中断与DMA
DMA: 是一种无须CPU的参与,就可以让外设与系统内存之间进行双向数据传输的硬件机制,使用DMA可以使系统CPU从实际的I/O数据传输过程中摆脱出来,从而大大提高系统的吞吐率。
中断:是指CPU在执行程序的过程中,出现了某些突发事件时,CPU必须暂停执行当前的程序,转去处理突发事件,处理完毕后CPU又返回源程序被中断的位置并继续执行。
中断的响应执行流程是什么?
中断的响应流程: cpu接受中断->保存中断上下文跳转到中断处理历程->执行中断上半部->执行中断下半部->恢复中断上下文。
SPI的应用
SPI(Serial PeripheralInterface)协议即串行外围设备接口,是一种高速全双工的通信总线。SPl总线系统是一种同步串行外设接口,它可以使MCU与各种外围设备以串行方式进行通信以交换信息。SPI总线可直接与各人厂家生产的多种标准外围器件相连,包括ELASHRAM、网络控制器、LCD显示驱动器、A/D转换器和MCU等。
IIC协议
IC协议是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据,是一个多主机的半双工通信方式每个挂接在总线上的器件都有个唯一的地址。位速在标准模式下可达 100kbit/s,在快速模式下可达400kbit/s,在高速模式下可待3.4Mbit/s。
GDB调试
多线程调试命令
进入主线程后可以使用bt查看主线程堆栈,或者使用pstack/gstack直接打印堆栈信息。
coredump造成的原因分析:
1、内存越界访问:(a)由于使用错误的下标,导致数组越界访问,(b)搜索字符串时使用字符串结束符判断字符是否结束,但是字符没有正常使用结束符。
2、多线程的一些线程不安全操作,比如数据读写没有加锁;
3、非法指针:(a)使用空指针,(b)随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型(A类型),否则不要将它转换为这种A类型的指针,而应该将这段内存拷贝到一个这种A类型中,再访问A类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它时就很容易因为bus error而core dump。
4、堆栈溢出:不要使用大量的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误。
gdb + [可执行文件名] + [core文件名]
然后再通过bt查看调试信息,顺着去查看源码可以找到问题
内存泄漏原因
1.单例造成内存泄漏:由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。
2.delete掉一个void*类型的指针,导致没有调用到对象的析构函数,析构的所有清理工作都没有去执行从而导致内存的泄露;
3.new创建了一组对象数组,内存回收的时候却只调用了delete而非delete []来处理,导致只有对象数组的第一个对象的析构函数得到执行并回收了内存占用,数组的其他对象所占内存得不到回收,导致内存泄露;
内存泄漏问题排查
对于小工程来说,简单去检查代码中new和delete的匹配对数就基本能定位到问题,但是一旦代码量上升到以万单位时,仅靠肉眼检查来定位问题那就非常困难了,所以我们需要利用工具帮助我们找出问题所在。在Linux系统下内存检测工具首推Valgrind,一款非常好用的开源内存管理框架。Valgrind其实是一个工具集,内存错误检测只是它众多功能的一个,但我们用得最多的功能正是它——memcheck。使用这个工具可以查看申请和释放内存的次数,并且可以进一步判断释放内存泄漏的位置。
RCU:是一种同步机制,其将同步开销的非对称分布发挥到逻辑极限。RCU 首先将需要修改的内容复制出一份副本,然后在副本上进行修改操作。在写者进行修改操作的过程中,旧数据没有做任何更新,不会产生读写竞争,因此依然可以被读者并行访问。当写者修改完成后,写者直接将新数据内存地址替换掉旧数据的内存地址,由于内存地址替换操作是原子的,因此可以保证读写不会产生冲突。内存地址替换后,原有读者访问旧数据,新的读者将访问新数据。当原有读者访问完旧数据,进入静默期后,旧数据将被写者删除回收。当然,通常写者只进行更新、删除指针操作,旧数据内存的回收由另一个线程完成。
RCU 的关键思想有两个:1)复制后更新;2)延迟回收内存。典型的RCU更新时序如下:
复制:将需要更新的数据复制到新内存地址;
更新:更新复制数据,这时候操作的新的内存地址;
替换:使用新内存地址指针替换旧数据内存地址指针,此后旧数据将无法被后续读者访问;
等待:所有访问旧数据的读者进入静默期,即访问旧数据完成;
回收:当没有任何持有旧数据结构引用的读者后,安全地回收旧数据内存。
适用场景:RCU适用多读少写场景,RCU和读写锁相似.但RCU的读者占锁没有任何的系统开销。写者与写者之间必须要保持同步,且写者必须要等它之前的读者全部都退出之后才能释放之前的资源。
多线程/单例/智能指针/构造函数/宏/socket/vector,实例
单例模式
class Singleton
{
public:
static Singleton* GetInstance() {
// 注意这里一定要使用Double-Check的方式加锁,才能保证效率和线程安全
if (nullptr == m_Instance) {
m_mtx.lock();
if (nullptr == m_Instance) {
m_Instance = new Singleton();
}
m_mtx.unlock();
}
return m_Instance;
}// 实现一个内嵌垃圾回收类
class CGarbo {
public:
~CGarbo(){
if (Singleton::m_Instance)
delete Singleton::m_Instance;
}
};// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
static CGarbo Garbo;
private:
// 构造函数私有
Singleton(){};
// 防拷贝
Singleton(Singleton const&) = delete;
Singleton& operator=(Singleton const&) = delete;
static Singleton* m_Instance; // 单例对象指针
static mutex m_mtx; //互斥锁
};
Singleton* Singleton::m_Instance = nullptr;
Singleton::CGarbo Garbo;
uniqur_ptr实现
函数中使用了很多noexcept,noexcept 是C++11 为了替代 throw() 而提出的一个新的关键字,在
C++ 中使用函数异常声明列表来查看函数可能抛出的异常。
namespace cpp_lib{
class Deletor{//先完成一个删除器
public:
Deletor() = default;
template <typename U>
void operator()(U *p) noexcept{
if(p){
delete p;
}
}
};
template <typename T, typename U = Deletor>
class unique_ptr{
public:
explicit unique_ptr():ptr(nullptr){}; //显示构造函数
explicit unique_ptr(T* p):ptr(p){}; //显示构造函数
explicit unique_ptr(unique_ptr&& p){ //移动构造函数
ptr = std::forward<T*>(p.ptr);
deletor = std::forward<U>(p.deletor);
p.ptr = nullptr;//结尾都要加上nullptr否则如果错误使用会导致程序崩溃
}
void operator=(unique_ptr&& p){ //移动赋值运算符
ptr = std::forward<T*>(p.ptr);
deletor = std::forward<U>(p.deletor);
p.ptr = nullptr;
}
~unique_ptr(){//析构函数
if(ptr)
deletor(ptr);
ptr = nullptr;
}
unique_ptr(const unique_ptr& p) = delete; //不支持复制构造函数
unique_ptr operator=(const unique_ptr& p) = delete; //不支持复制运算符函数
unique_ptr operator=(T* p) = delete; //不支持裸指针赋值
T* release(){
T *p = ptr;
ptr = nullptr;
return p;
}
void reset(T* p = nullptr) noexcept{
if(ptr)
deletor(ptr);
ptr = p;
}
void swap(unique_ptr& rhs) noexcept{
std::swap(ptr, rhs.ptr);
std::swap(deletor, rhs.deletot);)
}
T* get() noexcept{
return ptr;
}
U& get_deletor() noexcept{
return deletor;
}
explicit operator bool() const noexcept{
if(ptr)
return true;
else return false;
}
T& operator*() const noexcept{
return ptr;
}
T* operator->() const noexcept{
return ptr;
}
private:
T *ptr; //值指针
U deletor; //删除器
};
}//自定义命名空间为了防止和标准库里的unique_ptr冲突
share_ptr
template<typename T>
class smart
{
private:
T* _ptr;
int* _count; //reference couting
public:
//构造函数
smart(T* ptr = nullptr) :_ptr(ptr)
{
if (_ptr){
_count = new int(1);
}
else{
_count = new int(0);
}
}
//拷贝构造
smart(const smart& ptr) {
if (this != &ptr){
this->_ptr = ptr._ptr;
this->_count = ptr._count;
(*this->_count)++;
}
}
//重载operator=
smart& operator=(const smart & ptr){
if (this->_ptr == ptr._ptr) {
return *this;
}
if (this->_ptr){
(*this->_count)--;
if (*this->_count == 0) {
delete this->_ptr;
delete this->_count;
}
}
this->_ptr = ptr._ptr;
this->_count = ptr._count;
(*this->_count)++;
return *this;
}
//operator*重载
T& operator*(){
if (this->_ptr) {
return *(this->_ptr);
}
}
//operator->重载
T* operator->() {
if (this->_ptr) {
return this->_ptr;
}
}
//析构函数
~smart(){
(*this->_count)--;
if (*this->_count == 0) {
delete this->_ptr;
delete this->_count;
}
}
//return reference couting
int use_count() {
return *this->_count;
}
};
拷贝构造函数-深拷贝(默认构造函数已经是浅拷贝了):
template<typename T>
class Rect
{
public:
Rect()
{
p=new T(100);
}
Rect(const Rect& r)
{
width=r.width;
height=r.height;
p=new T(100);
*p= *(r.p);
}
~Rect()
{
assert(p!=NULL);
delete p;
}
private:
T width;
T height;
T *p;
};
最大宏(需要考虑变量自增,运算符优先级,支持任意类型)
#define max(x, y) ({
typeof(x) _max1 = (x);//typeof查看变量类型
typeof(y) _max2 = (y);
(void) (&_max1 == &_max2);//对于不同类型的比较编译器会报警,两个值比较但是结果没有用到也会报警使用void可以消除这个warning
_max1 > _max2 ? _max1 : _max2;
})
多线程编程
class ThreadPool{
private:
struct NWORKER{//工作队列
pthread_t threadid; //线程id
bool terminate; //是否需要结束worker的标志
int isWorking; //判断是否在工作
ThreadPool *pool; //隶属于的线程池
} *m_workers;
struct NJOB{//任务队列
void (*func)(void *arg); //任务函数
void *user_data; //函数参数
};
public:
//线程池初始化
//numWorkers:线程数量
ThreadPool(int numWorkers, int max_jobs);
//销毁线程池
~ThreadPool();
//面向用户的添加任务
int pushJob(void (*func)(void *data), void *arg, int len);
private:
//向线程池中添加任务
bool _addJob(NJOB* job);
//回调函数
static void* _run(void *arg);
void _threadLoop(void *arg);
private:
std::list<NJOB*> m_jobs_list;
int m_max_jobs; //任务队列中的最大任务数
int m_sum_thread; //worker总数
int m_free_thread; //空闲worker数
pthread_cond_t m_jobs_cond; //线程条件等待
pthread_mutex_t m_jobs_mutex; //为任务加锁防止一个任务被两个线程执行等其他情况
};
//面向用户添加线程
int ThreadPool::pushJob(void (*func)(void *), void *arg, int len) {
struct NJOB *job = (struct NJOB*)malloc(sizeof(struct NJOB));
if (job == NULL){
perror("malloc");
return -2;
}
memset(job, 0, sizeof(struct NJOB));
job->user_data = malloc(len);
memcpy(job->user_data, arg, len);
job->func = func;
_addJob(job);
return 1;
}
//run为static函数,回调函数
void* ThreadPool::_run(void *arg) {
NWORKER *worker = (NWORKER *)arg;
worker->pool->_threadLoop(arg);
}
//线程池
void ThreadPool::_threadLoop(void *arg) {
NWORKER *worker = (NWORKER*)arg;
while (1){
//线程只有两个状态:执行\等待
//查看任务队列前先获取锁
pthread_mutex_lock(&m_jobs_mutex);
//当前没有任务
while (m_jobs_list.size() == 0) {
//检查worker是否需要结束生命
if (worker->terminate) break;
//条件等待直到被唤醒
pthread_cond_wait(&m_jobs_cond,&m_jobs_mutex);
}
//检查worker是否需要结束生命
if (worker->terminate){
pthread_mutex_unlock(&m_jobs_mutex);
break;
}
//获取到job后将该job从任务队列移出,免得其他worker过来重复做这个任务
struct NJOB *job = m_jobs_list.front();
m_jobs_list.pop_front();
//对任务队列的操作结束,释放锁
pthread_mutex_unlock(&m_jobs_mutex);
m_free_thread--;
worker->isWorking = true;
//执行job中的func
job->func(job->user_data);
worker->isWorking = false;
free(job->user_data);
free(job);
}
free(worker);
pthread_exit(NULL);
}
//添加任务
bool ThreadPool::_addJob(NJOB *job) {
//尝试获取锁
pthread_mutex_lock(&m_jobs_mutex);
//判断队列是否超过任务数量上限
if (m_jobs_list.size() >= m_max_jobs){
pthread_mutex_unlock(&m_jobs_mutex);
return false;
}
//向任务队列添加job
m_jobs_list.push_back(job);
//唤醒休眠的线程
pthread_cond_signal(&m_jobs_cond);
//释放锁
pthread_mutex_unlock(&m_jobs_mutex);
return true;
}
//构造函数
ThreadPool::ThreadPool(int numWorkers, int max_jobs = 10) : m_sum_thread(numWorkers), m_free_thread(numWorkers), m_max_jobs(max_jobs){ //numWorkers:线程数量
if (numWorkers < 1 || max_jobs < 1){
perror("workers num error");
}
//初始化jobs_cond
if (pthread_cond_init(&m_jobs_cond, NULL) != 0)
perror("init m_jobs_cond fail\n");
//初始化jobs_mutex
if (pthread_mutex_init(&m_jobs_mutex, NULL) != 0)
perror("init m_jobs_mutex fail\n");
//初始化workers
m_workers = new NWORKER[numWorkers];
if (!m_workers){
perror("create workers failed!\n");
}
//初始化每个worker
for (int i = 0; i < numWorkers; ++i){
m_workers[i].pool = this;
int ret = pthread_create(&(m_workers[i].threadid), NULL, _run, &m_workers[i]);
if (ret){
delete[] m_workers;
perror("create worker fail\n");
}
if (pthread_detach(m_workers[i].threadid)){
delete[] m_workers;
perror("detach worder fail\n");
}
m_workers[i].terminate = 0;
}
}
//析构函数
ThreadPool::~ThreadPool(){
//terminate值置1
for (int i = 0; i < m_sum_thread; i++){
m_workers[i].terminate = 1;
}
//广播唤醒所有线程
pthread_mutex_lock(&m_jobs_mutex);//因为使用了detach创造线程,必须唤醒所有条件等待的线程
pthread_cond_broadcast(&m_jobs_cond);
pthread_mutex_unlock(&m_jobs_mutex);
delete[] m_workers;
}
//测试用例,打印1-1000的数
#include "threadpool.hpp"
void testFun(void* arg){
printf("i = %d\n", *(int *)arg);
}
int main(){
ThreadPool *pool = new ThreadPool(1000, 2000);
printf("线程池初始化成功\n");
int i = 0;
for (i = 0; i < 1000; ++i) {
pool->pushJob(testFun, &i, sizeof(int));
}
}
socket编程,服务端的
#define MAXSIZE 1024
#define IP_ADDR "127.0.0.1"
#define IP_PORT 8888
int main()
{
int i_listenfd, i_connfd;
struct sockaddr_in st_sersock;
char msg[MAXSIZE];
int nrecvSize = 0;
struct epoll_event ev, events[MAXSIZE];
int epfd, nCounts; //epfd:epoll实例句柄, nCounts:epoll_wait返回值
if((i_listenfd = socket(AF_INET, SOCK_STREAM, 0) ) < 0) //建立socket套接字{
printf("socket Error: %s (errno: %d)\n", strerror(errno), errno);
exit(0);
}
memset(&st_sersock, 0, sizeof(st_sersock));
st_sersock.sin_family = AF_INET; //IPv4协议
st_sersock.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP,因为有些机子不止一块网卡,多网卡的情况下,这个就表示所有网卡ip地址的意思。
st_sersock.sin_port = htons(IP_PORT);
if(bind(i_listenfd,(struct sockaddr*)&st_sersock, sizeof(st_sersock)) < 0) //将套接字绑定IP和端口用于监听 {
printf("bind Error: %s (errno: %d)\n", strerror(errno), errno);
exit(0);
}
if(listen(i_listenfd, 20) < 0) //设定可同时排队的客户端最大连接个数{
printf("listen Error: %s (errno: %d)\n", strerror(errno), errno);
exit(0);
}
if((epfd = epoll_create(MAXSIZE)) < 0) //创建epoll实例{
printf("epoll_create Error: %s (errno: %d)\n", strerror(errno), errno);
exit(-1);
}
ev.events = EPOLLIN;
ev.data.fd = i_listenfd;
if(epoll_ctl(epfd, EPOLL_CTL_ADD, i_listenfd, &ev) < 0){
printf("epoll_ctl Error: %s (errno: %d)\n", strerror(errno), errno);
exit(-1);
}
printf("======waiting for client's request======\n");
//准备接受客户端连接
while(1){
if((nCounts = epoll_wait(epfd, events, MAXSIZE, -1)) < 0){
printf("epoll_ctl Error: %s (errno: %d)\n", strerror(errno), errno);
exit(-1);
}
else if(nCounts == 0){
printf("time out, No data!\n");
}
else{
for(int i = 0; i < nCounts; i++){
int tmp_epoll_recv_fd = events[i].data.fd;
if(tmp_epoll_recv_fd == i_listenfd) //有客户端连接请求{
if((i_connfd = accept(i_listenfd, (struct sockaddr*)NULL, NULL)) < 0) //阻塞等待客户端连接 {
printf("accept Error: %s (errno: %d)\n", strerror(errno), errno);
// continue;
}
else{
printf("Client[%d], welcome!\n", i_connfd);
}
ev.events = EPOLLIN;
ev.data.fd = i_connfd;
if(epoll_ctl(epfd, EPOLL_CTL_ADD, i_connfd, &ev) < 0) {
printf("epoll_ctl Error: %s (errno: %d)\n", strerror(errno), errno);
exit(-1);
}
}
else //若是已连接的客户端发来数据请求{
//接受客户端发来的消息并作处理(小写转大写)后回写给客户端
memset(msg, 0 ,sizeof(msg));
if((nrecvSize = read(tmp_epoll_recv_fd, msg, MAXSIZE)) < 0) {
printf("read Error: %s (errno: %d)\n", strerror(errno), errno);
continue;
}
else if( nrecvSize == 0) //read返回0代表对方已close断开连接。 {
printf("client has disconnected!\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, tmp_epoll_recv_fd, NULL);
close(tmp_epoll_recv_fd); //
continue;
}
else{
printf("recvMsg:%s", msg);
for(int i=0; msg[i] != '\0'; i++){
msg[i] = toupper(msg[i]);
}
if(write(tmp_epoll_recv_fd, msg, strlen(msg)+1) < 0){
printf("write Error: %s (errno: %d)\n", strerror(errno), errno);
}
}
}
}
}
}//while
close(i_listenfd);
close(epfd);
return 0;
}
下面是客户端的
#define MAXSIZE 1024
#define IP_ADDR "127.0.0.1"
#define IP_PORT 8888
int i_sockfd = -1;
void SigCatch(int sigNum) //信号捕捉函数(捕获Ctrl+C){
if(i_sockfd != -1){
close(i_sockfd);
}
printf("Bye~! Will Exit...\n");
exit(0);
}
int main(){
struct sockaddr_in st_clnsock;
char msg[1024];
int nrecvSize = 0;
signal(SIGINT, SigCatch); //注册信号捕获函数
if((i_sockfd = socket(AF_INET, SOCK_STREAM, 0) ) < 0) //建立套接字 {
printf("socket Error: %s (errno: %d)\n", strerror(errno), errno);
exit(0);
}
memset(&st_clnsock, 0, sizeof(st_clnsock));
st_clnsock.sin_family = AF_INET; //IPv4协议
//IP地址转换(直接可以从物理字节序的点分十进制 转换成网络字节序)
if(inet_pton(AF_INET, IP_ADDR, &st_clnsock.sin_addr) <= 0) {
printf("inet_pton Error: %s (errno: %d)\n", strerror(errno), errno);
exit(0);
}
st_clnsock.sin_port = htons(IP_PORT); //端口转换(物理字节序到网络字节序)
if(connect(i_sockfd, (struct sockaddr*)&st_clnsock, sizeof(st_clnsock)) < 0) //主动向设置的IP和端口号的服务端发出连接 {
printf("connect Error: %s (errno: %d)\n", strerror(errno), errno);
exit(0);
}
printf("======connect to server, sent data======\n");
while(1) //循环输入,向服务端发送数据并接受服务端返回的数据{
fgets(msg, MAXSIZE, stdin);
printf("will send: %s", msg);
if(write(i_sockfd, msg, MAXSIZE) < 0) //发送数据{
printf("write Error: %s (errno: %d)\n", strerror(errno), errno);
exit(0);
}
memset(msg, 0, sizeof(msg));
if((nrecvSize = read(i_sockfd, msg, MAXSIZE)) < 0) //接受数据{
printf("read Error: %s (errno: %d)\n", strerror(errno), errno);
}
else if(nrecvSize == 0){
printf("Service Close!\n");
}
else{
printf("Server return: %s\n", msg);
}
}
return 0;
}
vector
template<typename T>
class Vector{
private:
T* Data;
int Len,Size;
public:
inline Vector() {
Data=NULL;
Len=Size=0;
}
inline Vector(const Vector& other){
if(this==&other||!Len)return;
Data=(T*)malloc(sizeof(T)*other.Len);
for(register int i=0;i<other.Size;i++)Data[i]=other.Data[i];
Len=other.Len,Size=other.Size;
}
inline T &operator[](const int x){
return Data[x];
}
const Vector&push_back(const T x){
if(Size==Len){
Len=Len==0?1:Len<<1;
T* newData=(T*)malloc(sizeof(T)*Len);
memcpy(newData,Data,Size*sizeof(T));
free(Data);
Data=newData;
}
Data[Size++]=x;
return *this;
}
const Vector&pop_back(){
Size--;
if(Size==(Len>>2)) {
Len=Len>>1;
T* newData=(T*)malloc(sizeof(T)*Len);
memcpy(newData,Data,Size*sizeof(T));
free(Data);
Data=newData;
}
return *this;
}
inline unsigned int size(){
return Size;
}
inline unsigned int len(){
return Len;
}
};
BUG汇总
https://blog.csdn.net/qq_42518941/article/details/125650156
大端模式:低位字节存在高地址上,高位字节存在低地址上
小端模式:高位字节存在高地址上,低位字节存在低地址上