--- url: /blog/2017/hexo-CentOS自带python26升级到27.md --- # CentOS自带python26升级到27 1.下载安装 ``` #下载 wget http://python.org/ftp/python/2.7.3/Python-2.7.3.tar.bz2 #解压并更改目录 tar -jxvf Python-2.7.3.tar.bz2 cd Python-2.7.3 #编译安装 ./configure make all make install make clean make distclean ``` 安装需要gcc(如果系统没有gcc需要自己安装) ``` yum install -y gcc ``` 查看版本信息 ``` [root@host ~]# /usr/local/bin/python2.7 -V Python 2.7.3 [root@host ~]# python -V Python 2.6.6 ``` python2.7已经安装,但系统的python还是2.6 2.建立软连接 使系统默认的python指向python2.7 ``` rm /usr/bin/python /usr/bin/python2.6 ln -s /usr/local/bin/python2.7 /usr/bin/python ``` 但yum命令依赖python2.6.6,需要更改一些配置文件 ``` su root ##切换到root vi /usr/bin/yum ##打开文件 ``` \~~/usr/bin/python~~ ##删除此行\ /usr/bin/python2.6.6 ##改为此行\ 更改需要管理员权限 参考资料 1. --- url: /blog/2017/hexo-C实现简单TCP通信.md --- # C实现简单TCP通信 C实现简单TCP通信 ### 1.添加所需的window库 在VC6++下编程\ 代码用到了其他的lib库,需要添加\ 工程--->设置--->Link--->library modules添加ws2\_32.lib(与其他lib用空格隔开) ### 2.服务器端代码 ```c #include"winsock2.h" #include "stdio.h" #define SERVER_PORP 8884//服务器端口号 void main() { //加载套接字(winsock)库, //加载这段代码拷贝于MSDN中WSAStartup的介绍 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 1, 1 ); //版本号为1.1 err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; } //创建套接字 SOCKET sockServer=socket(AF_INET,SOCK_STREAM,0); //AF_INET指协议簇 //SOCK_STREAM参数设置为TCP连接 SOCKADDR_IN addrServer; //设置服务器端套接字的相关属性 addrServer.sin_addr.S_un.S_addr=htonl(INADDR_ANY); //设置IP addrServer.sin_family=AF_INET; addrServer.sin_port=htons(SERVER_PORP); //设置端口号 //将套接字绑定到本地地址和指定端口上 bind(sockServer,(SOCKADDR*)&addrServer,sizeof(SOCKADDR)); //将套接字设置为监听模式,并将最大请求连接数设置成5,超过此数的请求全部作废 listen(sockServer,5); SOCKADDR_IN addrClient; //用来接收客户端的设置,包括IP和端口 int len=sizeof(SOCKADDR); while(1) //不断监听 { //得到创建连接后的一个新的套接字,用来和客户端进行沟通,原套接字继续监听客户的连接请求 SOCKET sockConn=accept(sockServer,(SOCKADDR*)&addrClient,&len); if(sockConn!=INVALID_SOCKET) //创建成功 { char sendInfo[100]; //inet_ntoa将结构转换为十进制的IP地址字符串 sprintf(sendInfo,"welcome %s to this Server!",inet_ntoa(addrClient.sin_addr)); //成功建立连接后向客户端发送数据,结果将显示在客户端上 send(sockConn,sendInfo,strlen(sendInfo)+1,0); //从客户端接收数据,结果显示在服务器上 char recvInfo[100]; recv(sockConn,recvInfo,100,0); printf("%s\n",recvInfo); while(1) { if(recv(sockConn,recvInfo,100,0)<0) break; //客户端断开recv返回负值 printf("%s\n",recvInfo); sprintf(sendInfo,recvInfo); send(sockConn,sendInfo,strlen(sendInfo)+1,0); sprintf(sendInfo,"\nsever receive this text!\n"); send(sockConn,sendInfo,strlen(sendInfo)+1,0); } //将本次建立连接中得到套接字关闭 closesocket(sockConn); } else { int errCode=WSAGetLastError(); printf("the errcode is:%d\n",errCode); } } //如果本程序不是死循环,那么在此处还应添加以下代码: closesocket(sockServer); //对一直处于监听状态的套接字进行关闭 WSACleanup(); //终止对winsocket库的使用 } ``` ### 3.客户端代码 ```c #include "winsock2.h" #include "stdio.h" #define SERVER_IP "127.0.0.1" //服务器IP地址 #define SERVER_PORP 8884 //服务器端口号 void main() { //加载套接字库 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 1, 1 ); //版本好为1.1 err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; } SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0); //SOCK_STREAM参数设置为TCP连接 SOCKADDR_IN addrServer; //服务器地址结构 addrServer.sin_addr.S_un.S_addr=inet_addr(SERVER_IP); //服务器地址 addrServer.sin_port=htons(SERVER_PORP); //服务器端口号 addrServer.sin_family=AF_INET; //与服务器端建立连接,进行通信 int connReult=connect(sockClient,(SOCKADDR*)&addrServer,sizeof(SOCKADDR)); if(connReult!=WSAEADDRNOTAVAIL) //访问成功 { //成功建立连接后向服务器端发送数据,结果将显示在服务器端上 char sendInfo[100]; send(sockClient,"this is zhangsan!",strlen("this is zhangsan!")+1,0); //接收来自服务器端发送来的信息 char recvInfo[100]; recv(sockClient,recvInfo,100,0); printf("%s\n",recvInfo); while(1) { scanf("%s",sendInfo); send(sockClient,sendInfo,strlen(sendInfo)+1,0); recv(sockClient,recvInfo,100,0); printf("%s",recvInfo); recv(sockClient,recvInfo,100,0); printf("%s",recvInfo); } } else { int errCode=WSAGetLastError(); printf("the errcode is:%d\n",errCode); } closesocket(sockClient); WSACleanup(); } ``` 客户端和服务器应分别属于两个exe\ 先运行服务器,后运行客户端 --- url: /blog/2017/hexo-C语言多线程学习.md --- # C语言多线程学习 VC6++环境下C语言创建多线程 ### 1.简单的创建多线程实例 ```c #include #include //子线程函数 DWORD WINAPI ThreadFun(LPVOID pM){ printf("子线程的线程ID号为:%d\n子线程输出 Hello World\n", GetCurrentThreadId()); return 0; } //主函数,所谓主函数其实就是主线程执行的函数。 int main() { printf("最简单的创建多线程实例\n"); printf("http://zfblog.xyz"); HANDLE handle = CreateThread(NULL, 0, ThreadFun, NULL, 0, NULL); WaitForSingleObject(handle, INFINITE); return 0; } ``` 使用CreateThread函数创建线程 ```c HANDLE WINAPI CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId); ``` 第一个参数表示线程内核对象的安全属性,一般传入NULL表示使用默认设置\ 第二个参数表示线程栈空间大小,传入0表示使用默认大小(1MB)\ 第三个参数表示新线程所执行的线程函数地址\        多个线程可以使用同一个函数地址(线程函数入口)\ 第四个参数是传给线程函数的参数\ 第五个参数指定额外的标志来控制线程的创建\        就是控制线程何时运行\        为0:直接运行\        为CREATE\_SUSPENDED:调用ResumeThread()后运行\ 第六个参数将返回线程的ID号,传入NULL表示不需要返回该线程ID号\ 函数返回值:成功返回新线程的句柄,失败返回NULL ### 2.关于CreateThread()给线程函数传递参数的问题 ```c //创建线程时传递参数 int *parameter; CreateThread(NULL, 0, ThreadFun, (void *)parameter, 0, NULL); //想到一个问题直接使用全局的static变量会出现什么情况????? //parameter是任意类型的地址 //线程函数中调用参数 DWORD WINAPI ThreadFun(LPVOID pM) { //传入的地址指向一个int类型的数据 int p=*PM; return 0; } ``` WaitForSingleObject函数作用 ```c DWORD WINAPI WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds ); ``` 第一个参数为要等待的内核对象\ 第二个参数为最长等待的时间\       为5000:等待5秒\       为0:立即执行\       为INFINITE:无限等待\ 函数返回值:WAIT\_OBJECT\_0在指定的时间内对象被触发\       WAIT\_TIMEOUT超过最长等待时间对象仍未被触发\       WAIT\_FAILED传入参数有错误\ 函数作用\ 首先要知道----线程的句柄在线程运行时是未触发的,结束后触发\ 在主函数中调用等待线程执行完毕,然后主函数结束\ 不用此函数可能会导致线程没有执行完,主函数结束,程序结束 --- url: /blog/2017/hexo-C语言文件读写.md --- # C语言文件读写 C语言文件操作笔记 ``` FILE *fopen(char *filename,char *type); //打开文件 int fclose(FILE *stream); //关闭文件 int fgetc(FILE *stream); //读一个字符指针后移 int getc(FILE *stream); int fputc(int ch,FILE *stream); //写一个字符指针后移 int putc(int ch,FILE *stream); ``` filename指明文件路径\ type可取的至如下\ "r" :打开一个已有的文本文,允许读取文件\ "w" :打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容\ "a" :打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。\ "r+":打开一个文本文件,允许读写文件\ "w+":打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件\ "a+":打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式\ 如果处理的是二进制文件,则需使用下面的访问模式来取代上面的访问模式:\ `"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"` --- url: /blog/2017/hexo-JAVA套接字之TCP实现聊天室.md --- # JAVA套接字之TCP实现聊天室 java实现聊天室,通过多线程实现随时加入,随时退出 ### 1.客户端程序 客户端有两个线程\ 一个线程由主类SocketClient实现向服务器发送消息\ 一个线程由内部类readLineThread实现监听服务器发来的消息并显示 ```java import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; public class SocketClient extends Socket{ private static final String SERVER_IP = "127.0.0.1"; private static final int SERVER_PORT = 2017; private Socket client; private PrintWriter out; private BufferedReader in; /** * 与服务器连接,并输入发送消息 */ public SocketClient() throws Exception{ super(SERVER_IP, SERVER_PORT); client = this; out = new PrintWriter(this.getOutputStream(), true); in = new BufferedReader(new InputStreamReader(this.getInputStream())); new readLineThread(); while(true){ in = new BufferedReader(new InputStreamReader(System.in)); String input = in.readLine(); out.println(input); } } /** * 用于监听服务器端向客户端发送消息线程类 */ class readLineThread extends Thread{ private BufferedReader buff; public readLineThread(){ try { buff = new BufferedReader(new InputStreamReader(client.getInputStream())); start(); } catch (Exception e) { } } @Override public void run() { try { while(true){ String result = buff.readLine(); if("byeClient".equals(result)){//客户端申请退出,服务端返回确认退出 break; }else{//输出服务端发送消息 System.out.println(result); } } in.close(); out.close(); client.close(); } catch (Exception e) { } } } public static void main(String[] args) { try { new SocketClient();//启动客户端 } catch (Exception e) { } } } ``` ### 2.服务器程序 服务器由三个类实现\ 主类Server监听客户端请求,并启用线程处理请求\ 内部类PrintOutThread监听输出消息请求,将消息发送到所有客户端\ 内部类ServerThread提供与每一个用户的连接 ```java import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; public class Server extends ServerSocket { private static final int SERVER_PORT = 2017; private static boolean isPrint = false;// 是否输出消息标志 private static List user_list = new ArrayList();// 登录用户集合 private static List thread_list = new ArrayList();// 服务器已启用线程集合 private static LinkedList message_list = new LinkedList();// 存放消息队列 /** * 创建服务端Socket,创建向客户端发送消息线程,监听客户端请求并处理 */ public Server() throws IOException { super(SERVER_PORT);// 创建ServerSocket new PrintOutThread();// 创建向客户端发送消息线程 try { while (true) {// 监听客户端请求,启用一个线程处理 Socket socket = accept(); new ServerThread(socket); } } catch (Exception e) { } finally { close(); } } /** * 监听是否有输出消息请求线程类,向客户端发送消息 */ class PrintOutThread extends Thread { public PrintOutThread() { start(); } @Override public void run() { while (true) { //没有打印这句,if里面的语句不会执行,可能是多线程访问isPrint造成的 System.out.println("运行中。。。"+isPrint); if (isPrint) {// 将缓存在队列中的消息按顺序发送到各客户端,并从队列中清除。 String message = (String) message_list.getFirst(); for (ServerThread thread : thread_list) { thread.sendMessage(message); } message_list.removeFirst(); isPrint = message_list.size() > 0 ? true : false; } } } } /** * 服务器线程类 */ @SuppressWarnings("unchecked") class ServerThread extends Thread { private Socket client; private PrintWriter out; private BufferedReader in; private String name; public ServerThread(Socket s) throws IOException { client = s; out = new PrintWriter(client.getOutputStream(), true); in = new BufferedReader(new InputStreamReader(client.getInputStream())); //in.readLine(); out.println("成功连上聊天室,请输入你的名字:"); start(); } @Override public void run() { try { int flag = 0; String line = in.readLine(); while (true) { // 查看在线用户列表 if ("showuser".equals(line)) { out.println(this.listOnlineUsers()); } if("bye".equals(line)){ out.println("bye"); break;} // 第一次进入,保存名字 if (flag++ == 0) { name = line; user_list.add(name); thread_list.add(this); out.println(name + "你好,可以开始聊天了..."); this.pushMessage("Client<" + name + ">进入聊天室..."); } else { this.pushMessage("Client<" + name + "> say : " + line); } line = in.readLine(); } out.println("byeClient"); } catch (Exception e) { e.printStackTrace(); } finally {// 用户退出聊天室 try { client.close(); } catch (IOException e) { e.printStackTrace(); } thread_list.remove(this); user_list.remove(name); pushMessage("Client<" + name + ">退出了聊天室"); } } // 放入消息队列末尾,准备发送给客户端 private void pushMessage(String msg) { message_list.addLast(msg); isPrint = true; } // 向客户端发送一条消息 private void sendMessage(String msg) { out.println(msg); } // 统计在线用户列表 private String listOnlineUsers() { String s = "--- 在线用户列表 ---\015\012"; for (int i = 0; i < user_list.size(); i++) { s += "[" + user_list.get(i) + "]\015\012"; } s += "--------------------"; return s; } } public static void main(String[] args) throws IOException { new Server();// 启动服务端 } } ``` 这里好像出现了多线程问题 ```java @Override public void run() { while (true) { //没有打印下面这句,if里面的语句不会执行,可能是这个线程一直访问isPrint,改变它的线程不能访问到它造成的 System.out.println("运行中。。。"+isPrint); if (isPrint) {// 将缓存在队列中的消息按顺序发送到各客户端,并从队列中清除。 String message = (String) message_list.getFirst(); for (ServerThread thread : thread_list) { thread.sendMessage(message); } message_list.removeFirst(); isPrint = message_list.size() > 0 ? true : false; } } } ``` --- url: /blog/2017/hexo-JAVA套接字之TCP简单实用.md --- # JAVA套接字之TCP简单实用 java套接字之TCP ### 1.单客户端排队与服务器建立连接 客户端程序 ```java import java.net.*; import java.io.*; public class Client{ static Socket server; public static void main(String[] args)throws Exception{ //客户端请求与本机在5678端口建立TCP连接 server=new Socket(InetAddress.getLocalHost(),5678); BufferedReader in= new BufferedReader(new InputStreamReader(server.getInputStream())); //获取Socket的输入流,用来接收从服务端发送过来的数据 PrintWriter out=new PrintWriter(server.getOutputStream()); //获取Socket的输出流,用来发送数据到服务端 BufferedReader wt=new BufferedReader(new InputStreamReader(System.in)); //从键盘输入的数据流 while(true){ String str=wt.readLine();//读取键盘输入字符串 out.println(str);//发送数据到服务端 out.flush(); if(str.equals("end")){ break; } System.out.println(in.readLine());//打印服务器返回的字符串 } server.close();//关闭连接 } } ``` 服务器程序1 ```java import java.io.*; import java.net.*; public class Server { public static void main(String[] args) throws IOException{ ServerSocket server=new ServerSocket(5678);//通信端口 while(true){//一个客户端断开连接后,等待与另一个客户端建立连接 Socket client=server.accept();//accept用于产生"阻塞",直到接受到一个连接,并且返回一个客户端的Socket对象实例。 BufferedReader in= new BufferedReader(new InputStreamReader(client.getInputStream())); PrintWriter out=new PrintWriter(client.getOutputStream()); while(true){//直到客户端发送end退出循环 String str=in.readLine(); System.out.println(str); out.println("has receive...."); out.flush(); if(str.equals("end")) break; } client.close();//关闭通信 } } } ``` ### 2.多个客户端同时与服务器建立连接 客户端程序不变,服务器端阻塞等待客户端连接,并给每个链接分配一个线程 服务器程序2 ```java import java.io.*; import java.net.*; public class Server extends Thread {//继承Thread类 private Socket client; public Server(Socket c){//类初始化,接受参数为客户端的请求 this.client = c; } public void run(){//重写run函数 try{ BufferedReader in= new BufferedReader(new InputStreamReader(client.getInputStream())); PrintWriter out=new PrintWriter(client.getOutputStream()); while(true){ String str=in.readLine(); System.out.println(str); out.println("has receive...."); out.flush(); if(str.equals("end")) break; } client.close(); }catch(IOException ex){ }finally{ } } public static void main(String[] args) throws IOException{ ServerSocket server=new ServerSocket(5678); while(true){ Server mu=new Server(server.accept());//每当有客户端请求就新建一个Server类与之通信 mu.start();//启动进程 } } } ``` --- url: /blog/2017/hexo-JAVA实现MD5加密.md --- # JAVA实现MD5加密 java实现MD5加密的两种方法 1,调用MessageDigest实现 ```java import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class CreatMD5 { public String getMd5(String plainText){ try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(plainText.getBytes()); byte b[] = md.digest(); int i; StringBuffer buf = new StringBuffer(""); for (int offset = 0; offset < b.length; offset++) { i = b[offset]; if (i < 0) i += 256; if (i < 16) buf.append("0"); buf.append(Integer.toHexString(i)); } //32位加密 return buf.toString(); // 16位的加密 //return buf.toString().substring(8, 24); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); return null; } } public static void main(String[] args) { // TODO Auto-generated method stub CreatMD5 md=new CreatMD5(); System.out.println(md.getMd5("hello")); } } ``` 2,自己编写函数实现 ```java public class CreateMD5i { static final String hexs[]={"0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"}; //标准的幻数 private static final long A=0x67452301L; private static final long B=0xefcdab89L; private static final long C=0x98badcfeL; private static final long D=0x10325476L; //下面这些S11-S44实际上是一个4*4的矩阵,在四轮循环运算中用到 static final int S11 = 7; static final int S12 = 12; static final int S13 = 17; static final int S14 = 22; static final int S21 = 5; static final int S22 = 9; static final int S23 = 14; static final int S24 = 20; static final int S31 = 4; static final int S32 = 11; static final int S33 = 16; static final int S34 = 23; static final int S41 = 6; static final int S42 = 10; static final int S43 = 15; static final int S44 = 21; //java不支持无符号的基本数据(unsigned) private long [] result={A,B,C,D};//存储hash结果,共4×32=128位,初始化值为(幻数的级联) public static void main(String []args){ CreateMD5i md=new CreateMD5i(); System.out.println("md5(hello)="+md.digest("hello")); } public String digest(String inputStr){ byte [] inputBytes=inputStr.getBytes(); int byteLen=inputBytes.length;//长度(字节) int groupCount=0;//完整分组的个数 groupCount=byteLen/64;//每组512位(64字节) long []groups=null;//每个小组(64字节)再细分后的16个小组(4字节) //处理每一个完整 分组 for(int step=0;step>8; } groups=divGroup(tempBytes,0); trans(groups);//处理分组 }else{ for(int i=0;i>8; } groups=divGroup(tempBytes,0); trans(groups);//处理分组 } //将Hash值转换成十六进制的字符串 String resStr=""; long temp=0; for(int i=0;i<4;i++){ for(int j=0;j<4;j++){ temp=result[i]&0x0FL; String a=hexs[(int)(temp)]; result[i]=result[i]>>4; temp=result[i]&0x0FL; resStr+=hexs[(int)(temp)]+a; result[i]=result[i]>>4; } } return resStr; } /** * 从inputBytes的index开始取512位,作为新的分组 * 将每一个512位的分组再细分成16个小组,每个小组64位(8个字节) * @param inputBytes * @param index * @return */ private static long[] divGroup(byte[] inputBytes,int index){ long [] temp=new long[16]; for(int i=0;i<16;i++){ temp[i]=b2iu(inputBytes[4*i+index])| (b2iu(inputBytes[4*i+1+index]))<<8| (b2iu(inputBytes[4*i+2+index]))<<16| (b2iu(inputBytes[4*i+3+index]))<<24; } return temp; } /** * 这时不存在符号位(符号位存储不再是代表正负),所以需要处理一下 * @param b * @return */ public static long b2iu(byte b){ return b < 0 ? b & 0x7F + 128 : b; } /** * 主要的操作,四轮循环 * @param groups[]--每一个分组512位(64字节) */ private void trans(long[] groups) { long a = result[0], b = result[1], c = result[2], d = result[3]; /*第一轮*/ a = FF(a, b, c, d, groups[0], S11, 0xd76aa478L); /* 1 */ d = FF(d, a, b, c, groups[1], S12, 0xe8c7b756L); /* 2 */ c = FF(c, d, a, b, groups[2], S13, 0x242070dbL); /* 3 */ b = FF(b, c, d, a, groups[3], S14, 0xc1bdceeeL); /* 4 */ a = FF(a, b, c, d, groups[4], S11, 0xf57c0fafL); /* 5 */ d = FF(d, a, b, c, groups[5], S12, 0x4787c62aL); /* 6 */ c = FF(c, d, a, b, groups[6], S13, 0xa8304613L); /* 7 */ b = FF(b, c, d, a, groups[7], S14, 0xfd469501L); /* 8 */ a = FF(a, b, c, d, groups[8], S11, 0x698098d8L); /* 9 */ d = FF(d, a, b, c, groups[9], S12, 0x8b44f7afL); /* 10 */ c = FF(c, d, a, b, groups[10], S13, 0xffff5bb1L); /* 11 */ b = FF(b, c, d, a, groups[11], S14, 0x895cd7beL); /* 12 */ a = FF(a, b, c, d, groups[12], S11, 0x6b901122L); /* 13 */ d = FF(d, a, b, c, groups[13], S12, 0xfd987193L); /* 14 */ c = FF(c, d, a, b, groups[14], S13, 0xa679438eL); /* 15 */ b = FF(b, c, d, a, groups[15], S14, 0x49b40821L); /* 16 */ /*第二轮*/ a = GG(a, b, c, d, groups[1], S21, 0xf61e2562L); /* 17 */ d = GG(d, a, b, c, groups[6], S22, 0xc040b340L); /* 18 */ c = GG(c, d, a, b, groups[11], S23, 0x265e5a51L); /* 19 */ b = GG(b, c, d, a, groups[0], S24, 0xe9b6c7aaL); /* 20 */ a = GG(a, b, c, d, groups[5], S21, 0xd62f105dL); /* 21 */ d = GG(d, a, b, c, groups[10], S22, 0x2441453L); /* 22 */ c = GG(c, d, a, b, groups[15], S23, 0xd8a1e681L); /* 23 */ b = GG(b, c, d, a, groups[4], S24, 0xe7d3fbc8L); /* 24 */ a = GG(a, b, c, d, groups[9], S21, 0x21e1cde6L); /* 25 */ d = GG(d, a, b, c, groups[14], S22, 0xc33707d6L); /* 26 */ c = GG(c, d, a, b, groups[3], S23, 0xf4d50d87L); /* 27 */ b = GG(b, c, d, a, groups[8], S24, 0x455a14edL); /* 28 */ a = GG(a, b, c, d, groups[13], S21, 0xa9e3e905L); /* 29 */ d = GG(d, a, b, c, groups[2], S22, 0xfcefa3f8L); /* 30 */ c = GG(c, d, a, b, groups[7], S23, 0x676f02d9L); /* 31 */ b = GG(b, c, d, a, groups[12], S24, 0x8d2a4c8aL); /* 32 */ /*第三轮*/ a = HH(a, b, c, d, groups[5], S31, 0xfffa3942L); /* 33 */ d = HH(d, a, b, c, groups[8], S32, 0x8771f681L); /* 34 */ c = HH(c, d, a, b, groups[11], S33, 0x6d9d6122L); /* 35 */ b = HH(b, c, d, a, groups[14], S34, 0xfde5380cL); /* 36 */ a = HH(a, b, c, d, groups[1], S31, 0xa4beea44L); /* 37 */ d = HH(d, a, b, c, groups[4], S32, 0x4bdecfa9L); /* 38 */ c = HH(c, d, a, b, groups[7], S33, 0xf6bb4b60L); /* 39 */ b = HH(b, c, d, a, groups[10], S34, 0xbebfbc70L); /* 40 */ a = HH(a, b, c, d, groups[13], S31, 0x289b7ec6L); /* 41 */ d = HH(d, a, b, c, groups[0], S32, 0xeaa127faL); /* 42 */ c = HH(c, d, a, b, groups[3], S33, 0xd4ef3085L); /* 43 */ b = HH(b, c, d, a, groups[6], S34, 0x4881d05L); /* 44 */ a = HH(a, b, c, d, groups[9], S31, 0xd9d4d039L); /* 45 */ d = HH(d, a, b, c, groups[12], S32, 0xe6db99e5L); /* 46 */ c = HH(c, d, a, b, groups[15], S33, 0x1fa27cf8L); /* 47 */ b = HH(b, c, d, a, groups[2], S34, 0xc4ac5665L); /* 48 */ /*第四轮*/ a = II(a, b, c, d, groups[0], S41, 0xf4292244L); /* 49 */ d = II(d, a, b, c, groups[7], S42, 0x432aff97L); /* 50 */ c = II(c, d, a, b, groups[14], S43, 0xab9423a7L); /* 51 */ b = II(b, c, d, a, groups[5], S44, 0xfc93a039L); /* 52 */ a = II(a, b, c, d, groups[12], S41, 0x655b59c3L); /* 53 */ d = II(d, a, b, c, groups[3], S42, 0x8f0ccc92L); /* 54 */ c = II(c, d, a, b, groups[10], S43, 0xffeff47dL); /* 55 */ b = II(b, c, d, a, groups[1], S44, 0x85845dd1L); /* 56 */ a = II(a, b, c, d, groups[8], S41, 0x6fa87e4fL); /* 57 */ d = II(d, a, b, c, groups[15], S42, 0xfe2ce6e0L); /* 58 */ c = II(c, d, a, b, groups[6], S43, 0xa3014314L); /* 59 */ b = II(b, c, d, a, groups[13], S44, 0x4e0811a1L); /* 60 */ a = II(a, b, c, d, groups[4], S41, 0xf7537e82L); /* 61 */ d = II(d, a, b, c, groups[11], S42, 0xbd3af235L); /* 62 */ c = II(c, d, a, b, groups[2], S43, 0x2ad7d2bbL); /* 63 */ b = II(b, c, d, a, groups[9], S44, 0xeb86d391L); /* 64 */ /*加入到之前计算的结果当中*/ result[0] += a; result[1] += b; result[2] += c; result[3] += d; result[0]=result[0]&0xFFFFFFFFL; result[1]=result[1]&0xFFFFFFFFL; result[2]=result[2]&0xFFFFFFFFL; result[3]=result[3]&0xFFFFFFFFL; } /** * 下面是处理要用到的线性函数 */ private static long F(long x, long y, long z) { return (x & y) | ((~x) & z); } private static long G(long x, long y, long z) { return (x & z) | (y & (~z)); } private static long H(long x, long y, long z) { return x ^ y ^ z; } private static long I(long x, long y, long z) { return y ^ (x | (~z)); } private static long FF(long a, long b, long c, long d, long x, long s, long ac) { a += (F(b, c, d)&0xFFFFFFFFL) + x + ac; a = ((a&0xFFFFFFFFL)<< s) | ((a&0xFFFFFFFFL) >>> (32 - s)); a += b; return (a&0xFFFFFFFFL); } private static long GG(long a, long b, long c, long d, long x, long s, long ac) { a += (G(b, c, d)&0xFFFFFFFFL) + x + ac; a = ((a&0xFFFFFFFFL) << s) | ((a&0xFFFFFFFFL) >>> (32 - s)); a += b; return (a&0xFFFFFFFFL); } private static long HH(long a, long b, long c, long d, long x, long s, long ac) { a += (H(b, c, d)&0xFFFFFFFFL) + x + ac; a = ((a&0xFFFFFFFFL) << s) | ((a&0xFFFFFFFFL) >>> (32 - s)); a += b; return (a&0xFFFFFFFFL); } private static long II(long a, long b, long c, long d, long x, long s, long ac) { a += (I(b, c, d)&0xFFFFFFFFL) + x + ac; a = ((a&0xFFFFFFFFL) << s) | ((a&0xFFFFFFFFL) >>> (32 - s)); a += b; return (a&0xFFFFFFFFL); } } ``` --- url: /blog/2017/hexo-Matlab函数freqs和freqz.md --- # Matlab函数freqs和freqz matlab中的freqs和freqz函数 ### 1.freqs 模拟滤波器的频率响应\ 语法: ```matlab h = freqs(b,a,w) [h,w] = freqs(b,a) [h,w] = freqs(b,a,f) freqs(b,a) ``` #### 1.1描述 freqs 返回一个模拟滤波器的H(jw)的复频域响应(拉普拉斯格式) ![](./imgs/freqs.png) h = freqs(b, a, w) 根据系数向量计算返回模拟滤波器的复频域响应\ freqs 计算在复平面虚轴上的频率响应h,角频率w确定了输入的实向量,因此必须包含至少一个频率点。\ \[h, w] = freqs(b, a) 自动挑选200个频率点来计算频率响应h\ \[h, w] = freqs(b, a, f) 挑选f个频率点来计算频率响应h #### 1.2例子 找到并画出下面传递函数的频率响应 ![](./imgs/example1.png) Matlab代码: ```matlab a = [1 0.4 1]; b = [0.2 0.3 1]; w = logspace(-1, 1); freqs(b, a, w); ``` logspace 功能:生成从10的a次方到10的b次方之间按对数等分的n个元素的行向量\ n如果省略,则默认值为50。 ```matlab h=freqs(b,a,w); mag = abs(h);phase = angle(h); subplot(2,1,1), loglog(w,mag); subplot(2,1,2), semilogx(w,phase); f = w/(2*pi);mag = 20*log10(mag);phase = phase*180/pi; ``` ![频率响应](./imgs/frequency.png) ### 2.freqz MATLAB提供了专门用于求离散系统频响特性的函数freqz()\ ![](./imgs/freqz.png) 调用freqz()的格式有以下两种: #### 2.1\[H,w]=freqz(B,A,N) B和A分别为离散系统的系统函数分子、分母多项式的系数向量,N为正整数,返回量H则包含了离散系统频响 在 0------pi范围内N个频率等分点的值,向量w则包含范围内N个频率等分点。调用中若N默认,默认值为512。 #### 2.2\[H,w]=freqz(B,A,N,’whole’) 该调用格式将计算离散系统在0---pi范内的N个频率等分店的频率响应的值。因此,可以先调用freqz()函数计算系统的频率响应,然后利用abs()和angle()函数及plot()函数,即可绘制出系统在 或 范围内的频响曲线。\ 例:绘制如下系统的频响曲线\ H(z)=(z-0.5)/z\ MATLAB命令如下: ``` B=[1 -0.5]; A =[1 0]; [H,w]=freqz(B,A,400,'whole'); ``` H是频率响应的幅度,w是0---pi内的400个点 ``` Hf=abs(H); Hx=angle(H); clf figure(1) plot(w,Hf) title('离散系统幅频特性曲线') figure(2) plot(w,Hx) title('离散系统相频特性曲线') ``` ![](./imgs/amp.png) ![](./imgs/ang.png) 这样画出来的是线性的,要想获得db格式的幅度,需要转换 20\*log10(Hf)\ 之后再画就是db格式的\ 也可以直接用freqz(b,a,w)这样就会画出幅频响应和相频响应,幅频响应直接是db格式的幅度。 --- url: /blog/2017/hexo-Python2与Python3共存.md --- # Python2与Python3共存 在windows下安装多个版本的python # 安装两个版本的Python 选择想要安装的版本下载例如:\ python2下载地址:\ python3下载地址: 进行安装(安装顺序随便)\ 尽量安装到同一目录下的两个文件夹例如:\ python2安装在"D:\program\python27\\"下\ python3安装在"D:\program\python36\\"下 # 添加环境变量 Path中要有一下四个路径 ``` D:\program\python\Python36\Scripts\ D:\program\python\Python36\ D:\program\python\python27\Scripts D:\program\python\python27\ ``` # 更改python.exe名字 找到找到两个python的安装目录\ `D:\program\python\Python36\python.exe`\ 更改为\ `D:\program\python\Python36\python3.exe`\ `D:\program\python\Python27\python.exe`\ 更改为\ `D:\program\python\Python36\python2.exe`\ 此时两个版本的python已经共同存在\ 但还要对pip进行修改 # 更改pip名称 重装python2和python3的pip ``` python2 -m pip install --upgrade pip --force-reinstall python3 -m pip install --upgrade pip --force-reinstall ``` 重装成功后进行如下测试\ test.PNG...\ 可见python2和python3已经共存\ pip的问题也解决了\ 但直接调用pip是还能执行并且是后安装的pip的版本\ python命令已经不存在\ 直接运行py结尾的文件时会出现问题\ python自带的编辑器出现了两个 --- url: /blog/2017/hexo-VC6聊天室程序设计.md --- # VC6聊天室程序设计 了解了C语言多线程的实例和简单的TCP通信,来编写一个简单的聊天室\ 曾经java实现的聊天室程序在这里 ### 1.客户端程序 客户端需要两个线程,主线程接受用户输入并发送到服务器\ 另一个线程监听服务器发来的消息,显示在屏幕上 ```c #include "winsock2.h" #include "stdio.h" #define SERVER_IP "10.80.167.248" #define SERVER_PORP 8884 //blog:zfblog.xyz //author:Frey DWORD WINAPI ThreadFun(LPVOID pM) { SOCKET sockClient=*(SOCKET *)pM; char recvInfo[100]; while(1) { if(recv(sockClient,recvInfo,100,0)>0) { printf("%s",recvInfo); } } return 0; } void main() { //加载套接字库 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 1, 1 ); //版本好为1.1 err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; } SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0); //SOCK_STREAM参数设置为TCP连接 SOCKADDR_IN addrServer; //服务器地址结构 addrServer.sin_addr.S_un.S_addr=inet_addr(SERVER_IP); //服务器地址 addrServer.sin_port=htons(SERVER_PORP); //服务器端口号 addrServer.sin_family=AF_INET; //与服务器端建立连接,进行通信 char name[100]; printf("请输入姓名:"); scanf("%s",name); int connReult=connect(sockClient,(SOCKADDR*)&addrServer,sizeof(SOCKADDR)); if(connReult!=WSAEADDRNOTAVAIL) //访问成功 { CreateThread(NULL, 0, ThreadFun, (void *)&sockClient, 0, NULL); printf("连接成功\n"); //成功建立连接后向服务器端发送数据,结果将显示在服务器端上 char sendInfo[100]; sprintf(sendInfo,name); send(sockClient,sendInfo,strlen(sendInfo)+1,0); //接收来自服务器端发送来的信息 //char recvInfo[100]; //recv(sockClient,recvInfo,100,0); //printf("%s\n",recvInfo); while(1) { scanf("%s",sendInfo); send(sockClient,sendInfo,strlen(sendInfo)+1,0); } } else { int errCode=WSAGetLastError(); printf("the errcode is:%d\n",errCode); } closesocket(sockClient); WSACleanup(); } ``` ### 2.服务器程序 用一个链表来存储用户的套接字和姓名\ 主线程监听用户的连接,每当有新用户连接,将其信息加入链表,并传入第二个线程\ 第二个线程接受客户端发来的信息,并将信息传入第三个线程\ 第三个线程从链表中读取用户套接字,将接到的信息转发给所有在线用户 ```c #include "winsock2.h" #include "stdio.h" #include //blog:zfblog.xyz //author:Frey #define SERVER_PORP 8884 struct client_info{ SOCKET sockConn; char name[100]; client_info *next; };//存放每一个用户的信息 client_info * C_info_head; DWORD WINAPI Threadmes(LPVOID pM) { char sendInfo[100]; sprintf(sendInfo,(char *)pM); for(int i=0;i<=c_info_num;i++){ SOCKET sockConn=C_info[i].sockConn; //if(C_info[i].num!=-1){ send(sockConn,sendInfo,strlen(sendInfo)+1,0); //} } return 0; } //将某条消息群发给所有客户端 void sendmessage(char *message) { CreateThread(NULL, 0, Threadmes, (void *)message, 0, NULL); } //接收每个用户的信息 DWORD WINAPI ThreadFun(LPVOID pM) { client_info c_info = *(client_info *)pM; SOCKET sockConn=c_info.sockConn; char sendInfo[100]; //inet_ntoa将结构转换为十进制的IP地址字符串 //sprintf(sendInfo,"welcome %s to this Server!",inet_ntoa(addrClient.sin_addr)); //成功建立连接后向客户端发送数据,结果将显示在客户端上 //send(sockConn,sendInfo,strlen(sendInfo)+1,0); //从客户端接收数据,结果显示在服务器上 char recvInfo[100]; recv(sockConn,recvInfo,100,0); sprintf(c_info.name,recvInfo); printf("欢迎%s进入聊天室\n",recvInfo); sprintf(sendInfo,"欢迎%s进入聊天室\n",recvInfo); sendmessage(sendInfo); while(1) { if(recv(sockConn,recvInfo,100,0)<0) break; printf("[%s]:%s\n",c_info.name,recvInfo); sprintf(sendInfo,"[%s]:%s\n",c_info.name,recvInfo); sendmessage(sendInfo); } //将本次建立连接中得到套接字关闭 closesocket(sockConn); return 0; } void main() { //加载套接字(winsock)库,加载这段代码拷贝于MSDN中WSAStartup的介绍 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 1, 1 ); //版本号为1.1 err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return; } if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; } //消息存储及发送 //---------------------------------------------------- //创建套接字 SOCKET sockServer=socket(AF_INET,SOCK_STREAM,0); //SOCK_STREAM参数设置为TCP连接 SOCKADDR_IN addrServer; //设置服务器端套接字的相关属性 addrServer.sin_addr.S_un.S_addr=htonl(INADDR_ANY); //设置IP addrServer.sin_family=AF_INET; addrServer.sin_port=htons(SERVER_PORP); //设置端口号 //将套接字绑定到本地地址和指定端口上 bind(sockServer,(SOCKADDR*)&addrServer,sizeof(SOCKADDR)); //将套接字设置为监听模式,并将最大请求连接数设置成5,超过此数的请求全部作废 listen(sockServer,5); SOCKADDR_IN addrClient; //用来接收客户端的设置,包括IP和端口 int len=sizeof(SOCKADDR); while(1) //不断监听 { //得到创建连接后的一个新的套接字,用来和客户端进行沟通,原套接字继续监听客户的连接请求 SOCKET sockConn=accept(sockServer,(SOCKADDR*)&addrClient,&len); if(sockConn!=INVALID_SOCKET) //创建成功 { c_info_num++; C_info[c_info_num].num=c_info_num; C_info[c_info_num].sockConn=sockConn; CreateThread(NULL, 0, ThreadFun, (void *)&C_info[c_info_num], 0, NULL); } else { int errCode=WSAGetLastError(); printf("the errcode is:%d\n",errCode); } } //如果本程序不是死循环,那么在此处还应添加以下代码: closesocket(sockServer); //对一直处于监听状态的套接字进行关闭 WSACleanup(); //终止对winsocket库的使用 } ``` 未完成的问题\ 用户离线时出现bug\ 全部代码: --- url: /blog/2017/hexo-powershell卸载自带应用.md --- # powershell卸载自带应用 通过powershell卸载windows自带应用 ### 1.卸载全部自带应用 `Get-AppxPackage -User $env:USERNAME | Remove-AppxPackage` ### 2.其他更多卸载 OneNote:\ `Get-AppxPackage OneNote | Remove-AppxPackage`\ 3D:\ `Get-AppxPackage 3d | Remove-AppxPackage`\ Camera相机:\ `Get-AppxPackage camera | Remove-AppxPackage`\ 邮件和日历:\ `Get-AppxPackage communi | Remove-AppxPackage`\ 新闻订阅:\ `Get-AppxPackage bing | Remove-AppxPackage`\ Groove音乐电影与电视:\ `Get-AppxPackage zune | Remove-AppxPackage`\ 人脉:\ `Get-AppxPackage people | Remove-AppxPackage`\ 手机伴侣Phone Companion:\ `Get-AppxPackage phone | Remove-AppxPackage`\ 照片:\ `Get-AppxPackage photo | Remove-AppxPackage`\ 纸牌游戏(还敢要钱的那货):\ `Get-AppxPackage solit | Remove-AppxPackage`\ 录音机:\ `Get-AppxPackage soundrec | Remove-AppxPackage`\ Xbox:\ `Get-AppxPackage xbox | Remove-AppxPackage` --- url: /blog/2017/hexo-powershell脚本遍历目录.md --- # powershell脚本遍历目录 powershell脚本遍历目录 ### 1.遍历目录及子目录 ``` function filesize ([string]$filepath) { if ($filepath -eq $null) { throw "路径不能为空" } dir -Path $filepath | ForEach-Object -Process { if ($_.psiscontainer -eq $true) { $length = 0 dir -Path $_.fullname -Recurse | ForEach-Object { $length += $_.Length } $l = $length / 1KB $_.name + "文件夹的大小为: {0:n1} KB" -f $l } } } filesize -filepath "D:\" ``` ### 2.运行示例 ``` PS F:\python> function filesize ([string]$filepath) { >> if ($filepath -eq $null) { >> throw "路径不能为空" >> } >> dir -Path $filepath | ForEach-Object -Process { >> if ($_.psiscontainer -eq $true) { >> $length = 0 >> dir -Path $_.fullname -Recurse | ForEach-Object { >> $length += $_.Length >> } >> $l = $length / 1KB >> $_.name + "文件夹的大小为: {0:n1} KB" -f $l >> } >> } >> } PS F:\python> filesize -filepath "f:\python" ipluyou文件夹的大小为: 82,196.5 KB loginip_jlu文件夹的大小为: 16.8 KB 嗅事百科文件夹的大小为: 4.3 KB 小说文件夹的大小为: 31,043.6 KB 笑料文件夹的大小为: 1,578,575.8 KB PS F:\python> ``` --- url: /blog/2017/hexo-python遇到的错误.md --- # python遇到的错误 python使用在中遇到的错误 1.DLL load failed python ImportError: DLL load failed: %1 不是有效的 Win32 应用程序\ 解决方法:去下载与你所安装的Python版本对应的pywin32并安装\ 下载链接:\ 对应版本和位数\ Python 2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 20:42:59) \[MSC v.1500 32 bit (Intel)] on win32\ Type "help", "copyright", "credits" or "license" for more information.\ 还有一种可能是导入的模块是64位的,而你的python是32位的 2.使用libsvm for python时出错\[1] ``` Traceback (most recent call last): File "C:\Python36\lib\libsvm\python\svm.py", line 28, in libsvm = CDLL(path.join(dirname, r'..\windows\libsvm.dll')) File "C:\Python36\lib\ctypes\__init__.py", line 348, in __init__ self._handle = _dlopen(self._name, mode) OSError: [WinError 126] 找不到指定的模块。 During handling of the above exception, another exception occurred: Traceback (most recent call last): File "train.py", line 3, in from libsvm.python.svmutil import * File "C:\Python36\lib\libsvm\python\svmutil.py", line 5, in from svm import * File "C:\Python36\lib\libsvm\python\svm.py", line 38, in raise Exception('LIBSVM library not found.') Exception: LIBSVM library not found. ``` 出错原因:在github上下载的libsvm中的libsvm.lib是win64版本的,而自己的python版本是32位的\ 解决方案:\ 方法1.更换python版本(太麻烦了,还要重新下载好多库)\ 方法2.去[万能宝库](http://www.lfd.uci.edu/~gohlke/pythonlibs/)上下载对应版本的.whl文件,(不用安装)直接解压后找到[libsvm.lib](https://download.lfd.uci.edu/pythonlibs/o4uhg4xd/libsvm-3.22-cp27-cp27m-win32.whl)更换即可\ 2018-09-07 22:11:58 参考资料 1. --- url: /blog/2017/hexo-sublime_Text3插入当前时间.md --- # sublime\_Text3插入当前时间 sublime\_Text3插入当前时间 1.创建新snippet tool → new snippet 创建一个新的snippet,并保存为"author.sublime-snippet"(最好在该目录(User)下再创建一个MySnippet目录):\ 其内容: ``` author ``` 2.创建新插件 Tools → New Plugin. 创建时间插件,保存在User目录,命名为addCurrentTime.py:\ 其内容为: ``` import sublime, sublime_plugin import datetime class AddCurrentTimeCommand(sublime_plugin.TextCommand): def run(self, edit): self.view.run_command("insert_snippet", { "contents": "%s" % datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") } ) ``` 3.绑定快捷键 Preference → Key Bindings → User.绑定快捷键: ``` [ { "command": "add_current_time", "keys": [ "alt+t" ] } ] ``` 其中绑定的命令的名字是由所创建的插件的类名而来\ AddCurrentTimeCommand--->add\_current\_time\ 这应该是Sublime创建插件的规则 参考资料 1,\ 2, --- url: /blog/2017/hexo-windows系统盘瘦身.md --- # windows系统盘瘦身 C盘满了,瘦一下身 ### 1.关闭系统休眠功能 删除Hiberfil.sys文件\ 管理员运行cmd输入下面命令\ `powercfg -h of` 2.设置虚拟内存 控制面板--->(系统和安全)--->系统--->高级系统设置--->(高级)--->设置--->(高级)--->更改\ 把虚拟内存设置到其它盘 ### 3.删除临时文件 以下三个目录里的文件是临时文件,可以删除\ C:\windows\temp\ C:\ProgramData\TEMP\ C:\Users\你的用户名\AppData\Local\Temp --- url: /blog/2017/hexo-位运算的妙用之二进制1的个数.md --- # 位运算的妙用之二进制1的个数 求出一个正整数转换成二进制形式中数字"1"的个数\ 题目:求出一个正整数转换成二进制形式中数字"1"的个数\ 如:\ int 型数值为 80\ 转化成二进制形式:80 = 00000000 00000000 00000000 01010000\ 因此 1 的个数为 2 1.普通解法 一位一位判断 ```c int bitCount1(int n) { int count = 0; while (n != 0) { if (n & 0x01 == 1) count++; n = n >> 1; } return count; } ``` 2.大神的解法 ```c int bitCount2(int n) { n = n - ((n >> 1) & 0x55555555);//n = (n & 0x55555555) + ((n >> 1) & 0x55555555); n = (n & 0x33333333) + ((n >> 2) & 0x33333333); n = (n & 0x0f0f0f0f) + ((n >> 4) & 0x0f0f0f0f); n = n + (n >> 8); //n = (n & 0x00ff00ff) + ((n >> 8) & 0x00ff00ff); n = n + (n >> 16); //n = (n & 0x0000ffff) + ((n >> 16) & 0x0000ffff); return n & 0x0000003f; //return n; } ``` 参考文章 1. --- url: /blog/2017/hexo-图片头文件解析.md --- # 图片头文件解析 各种类型的图片头文件解析 ### 1.PNG图片文件头 #### 1.1PNG文件结构 PNG图像格式文件由一个8字节的PNG文件标识(file signature)域和3个以上的后续数据块(IHDR、IDAT、IEND等)组成。\ PNG文件包括8字节文件署名(89 50 4E 47 0D 0A 1A 0A,十六进制),用来识别PNG格式。 | 十六进制 |含义| |:---------- |:-----------------------------------------| | 89 |用于检测传输系统是否支持8位的字符编码(8 bit data),用以减少将文本文件被错误的识别成PNG文件的机会,反之亦然。| | 50 4E 47 |PNG每个字母对应的ASCII,让用户可以使用文本编辑器查看时,识别出是PNG文件。| | 0D 0A |DOS风格的换行符(CRLF)。用于DOS-Unix数据的换行符转换。| | 1A |在DOS命令行下,用于阻止文件显示的文件结束符。| | 0A |Unix风格的换行符(LF)。用于Unix-DOS换行符的转换。| ### 1.2PNG数据块(chunk) PNG定义了两种类型的数据块,一种是称为关键数据块(critical chunk),这是标准的数据块,另一种叫做辅助数据块(ancillary chunks),这是可选的数据块。\ 关键数据块定义了4个标准数据块,每个PNG文件都必须包含它们,PNG读写软件也都必须要支持这些数据块。虽然PNG文件规范没有要求PNG编译码器对可选数据块进行编码和译码,但规范提倡支持可选数据块。\ 下表就是PNG中数据块的类别,其中,关键数据块部分我们使用红色背景加以区分。 数据块符号 数据块名称 多数据块 可选否 位置限制 *** ``` IHDR 文件头数据块 否 否 第一块 cHRM 基色和白色点数据块 否 是 在PLTE和IDAT之前 gAMA 图像γ数据块 否 是 在PLTE和IDAT之前 sBIT 样本有效位数据块 否 是 在PLTE和IDAT之前 PLTE 调色板数据块 否 是 在IDAT之前 bKGD 背景颜色数据块 否 是 在PLTE之后IDAT之前 hIST 图像直方图数据块 否 是 在PLTE之后IDAT之前 tRNS 图像透明数据块 否 是 在PLTE之后IDAT之前 oFFs (专用公共数据块) 否 是 在IDAT之前 pHYs 物理像素尺寸数据块 否 是 在IDAT之前 sCAL (专用公共数据块) 否 是 在IDAT之前 IDAT 图像数据块 是 否 与其他IDAT连续 tIME 图像最后修改时间数据块 否 是 无限制 tEXt 文本信息数据块 是 是 无限制 zTXt 压缩文本数据块 是 是 无限制 fRAc (专用公共数据块) 是 是 无限制 gIFg (专用公共数据块) 是 是 无限制 gIFt (专用公共数据块) 是 是 无限制 gIFx (专用公共数据块) 是 是 无限制 IEND 图像结束数据 否 否 最后一个数据块 ``` --- url: /blog/2017/hexo-字符串匹配.md --- # 字符串匹配 ## 1.BM 从左到右依次比较\ s:主串\ r:模式串 ```c int BM(char *s,int slen,char *r,int rlen){ int i=1,j=1; while(i<=slen-rlen+1){ while(j<=rlen&&s[i]==r[j]){ i++;j++; } if(j>rlen){ return i-j+1; }else{ j=1;i++; } } return 0; } ``` ## 2.KMP算法 通过一个next数字\ 当每次发生不匹配的时候\ 模式串不必回到开头 ```c int* getnext(char *r, int n) { //next数组求解 int i, j; int *next = (int *)malloc((n + 1) * sizeof(int)); next[0] = n; next[1] = 0; j = 0; i=1; while(irlen){ return i-j+1; }else{ j=next[j];i++; } } return 0; } ``` ## 参考文章 1.[字符串匹配的KMP算法](http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html) --- url: /blog/2017/hexo-批处理for循环使用.md --- # 批处理for循环使用 FOR %variable IN (set) DO command \[command-parameters] variable是一个变量 (set)相当于一个数组,variable遍历这个数组 command \[command-parameters]这是每次循环要做的事 ### 在cmd中输入使用时应写为 ``` FOR %variable IN (set) DO command [command-parameters] ``` command \[command-parameters]中用到变量也要写为%variable ### 写进.bat或者.cmd时应写为 ``` FOR %%variable IN (set) DO command [command-parameters] ``` command \[command-parameters]中用到变量也要写为%%variable ### Windows自带的help解释 ``` FOR %variable IN (set) DO command [command-parameters] %variable 指定一个单一字母可替换的参数。 (set) 指定一个或一组文件。可以使用通配符。 command 指定对每个文件执行的命令。 command-parameters 为特定命令指定参数或命令行开关。 ``` 在批处理程序中使用 FOR 命令时,指定变量请使用 %%variable 而不要用 %variable。变量名称是区分大小写的,所以 %i 不同于 %I. 如果启用命令扩展,则会支持下列 FOR 命令的其他格式: ``` FOR /D %variable IN (set) DO command [command-parameters] ``` 如果集中包含通配符,则指定与目录名匹配,而不与文件名匹配。 ``` FOR /R [[drive:]path] %variable IN (set) DO command [command-parameters] ``` 检查以 \[drive:]path 为根的目录树,指向每个目录中的 FOR 语句。 如果在 /R 后没有指定目录规范,则使用当前目录。如果集仅为一个单点(.)字符,则枚举该目录树。 ``` FOR /L %variable IN (start,step,end) DO command [command-parameters] ``` 该集表示以增量形式从开始到结束的一个数字序列。因此,(1,1,5)将产生序列1 2 3 4 5,(5,-1,1)将产生序列(5 4 3 2 1) ``` FOR /F ["options"] %variable IN (file-set) DO command [command-parameters] FOR /F ["options"] %variable IN ("string") DO command [command-parameters] FOR /F ["options"] %variable IN ('command') DO command [command-parameters] ``` 或者,如果有 usebackq 选项: ``` FOR /F ["options"] %variable IN (file-set) DO command [command-parameters] FOR /F ["options"] %variable IN ("string") DO command [command-parameters] FOR /F ["options"] %variable IN ('command') DO command [command-parameters] ``` fileset 为一个或多个文件名。继续到 fileset 中的下一个文件之前,每份文件都被打开、读取并经过处理。处理包括读取文件,将其分成一行行的文字,然后将每行解析成零或更多的符号。然后用已找到的符号字符串变量值调用 For 循环。 以默认方式,/F 通过每个文件的每一行中分开的第一个空白符号。跳过空白行。你可通过指定可选 "options" 参数替代默认解析操作。这个带引号的字符串包括一个 或多个指定不同解析选项的关键字。这些关键字为: ``` eol=c - 指一个行注释字符的结尾(就一个) skip=n - 指在文件开始时忽略的行数。 delims=xxx - 指分隔符集。这个替换了空格和制表符的 默认分隔符集。 tokens=x,y,m-n - 指每行的哪一个符号被传递到每个迭代 的 for 本身。这会导致额外变量名称的分配。m-n 格式为一个范围。通过 nth 符号指定 mth。如果 符号字符串中的最后一个字符星号, 那么额外的变量将在最后一个符号解析之后 分配并接受行的保留文本。 usebackq - 指定新语法已在下类情况中使用: 在作为命令执行一个后引号的字符串并且一个单 引号字符为文字字符串命令并允许在 file-set 中使用双引号扩起文件名称。 ``` 某些范例可能有助: ``` FOR /F "eol=; tokens=2,3* delims=, " %i in (myfile.txt) do @echo %i %j %k ``` 会分析 myfile.txt 中的每一行,忽略以分号打头的那些行,将每行中的第二个和第三个符号传递给 for 函数体,用逗号和/空格分隔符号。请注意,此 for 函数体的语句引用 %i 来获得第二个符号,引用 %j 来获得第三个符号,引用 %k来获得第三个符号后的所有剩余符号。对于带有空格的文件名,你需要用双引号将文件名括起来。为了用这种方式来使用双引号,还需要使用 usebackq 选项,否则,双引号会被理解成是用作定义某个要分析的字符串的。%i 在 for 语句中显式声明,%j 和 %k 是通过 tokens= 选项隐式声明的。可以通过 tokens= 一行指定最多 26 个符号,只要不试图声明一个高于字母 "z" 或 "Z" 的变量。请记住,FOR 变量是单一字母、分大小写和全局的变量;而且,不能同时使用超过 52 个。 还可以在相邻字符串上使用 FOR /F 分析逻辑,方法是,用单引号将括号之间的 file-set 括起来。这样,该字符串会被当作一个文件中的一个单一输入行进行解析。 最后,可以用 FOR /F 命令来分析命令的输出。方法是,将括号之间的 file-set 变成一个反括字符串。该字符串会被当作命令行,传递到一个子 CMD.EXE,其输出会被捕获到内存中,并被当作文件分析。如以下例子所示: ``` FOR /F "usebackq delims==" %i IN (`set`) DO @echo %i ``` 会枚举当前环境中的环境变量名称。 另外,FOR 变量参照的替换已被增强。你现在可以使用下列 选项语法: ``` %~I - 删除任何引号("),扩展 %I %~fI - 将 %I 扩展到一个完全合格的路径名 %~dI - 仅将 %I 扩展到一个驱动器号 %~pI - 仅将 %I 扩展到一个路径 %~nI - 仅将 %I 扩展到一个文件名 %~xI - 仅将 %I 扩展到一个文件扩展名 %~sI - 扩展的路径只含有短名 %~aI - 将 %I 扩展到文件的文件属性 %~tI - 将 %I 扩展到文件的日期/时间 %~zI - 将 %I 扩展到文件的大小 %~$PATH:I - 查找列在路径环境变量的目录,并将 %I 扩展 到找到的第一个完全合格的名称。如果环境变量名 未被定义,或者没有找到文件,此组合键会扩展到 空字符串 ``` 可以组合修饰符来得到多重结果: ``` %~dpI - 仅将 %I 扩展到一个驱动器号和路径 %~nxI - 仅将 %I 扩展到一个文件名和扩展名 %~fsI - 仅将 %I 扩展到一个带有短名的完整路径名 %~dp$PATH:I - 搜索列在路径环境变量的目录,并将 %I 扩展 到找到的第一个驱动器号和路径。 %~ftzaI - 将 %I 扩展到类似输出线路的 DIR ``` 在以上例子中,%I 和 PATH 可用其他有效数值代替。%~ 语法 用一个有效的 FOR 变量名终止。选取类似 %I 的大写变量名 比较易读,而且避免与不分大小写的组合键混淆。 --- url: /blog/2017/hexo-搬瓦工VPS搭建Web服务器.md --- # 搬瓦工VPS搭建Web服务器 记录在centos上安装apache+php+mysql\ 搬瓦工VPS[购买地址](https://bandwagonhost.com/aff.php?aff=17697) # 安装apache 安装`yum install httpd`\ 启动`service httpd start`\ 暂停`service httpd stop`\ 重启`service httpd restart` 检查开机启动情况`chkconfig --list httpd`\ 默认情况下是全部关闭的,尽量设置开机启动,以防主机意外关机 设置开机启动命令`chkconfig httpd on`\ 再次检查启动情况,看到2,3,4,5为启动\ 关闭开机启动命令`chkconfig httpd off` 到此Apache安装完成,在自己浏览器中输入主机IP和端口号能正常打开 # 安装php 命令为: ``` yum install php yum install php-mysql php-gd php-imap php-ldap php-odbc php-pear php-xml php-xmlrpc yum install php-mysql ``` 以上命令为安装php及其支持的组件 # 安装mysql ## 安装 *** 首先查看主机是否已经安装`rpm -qa|grep mysql`\ 注意:可能检查中安装有php-mysql XXXXX 和 mysql-libXXXX,这是刚刚第二步安装的php的组件,不必理会 已经安装的话就执行删除命令`rpm -e mysql` 然后继续安装\ 使用命令yum list | grep mysql\ 来查看yum上提供的mysql数据库可下载的版本 安装命令`yum install -y mysql-server mysql mysql-devel`\ 等待一段时间安装成功 ## 配置 *** 接下来是对mysql的配置(注意:命令中是"mysqld",不是mysql)\ 初始化:`service mysqld start`\ 重启:`service mysqld restart`\ 配置开机自动启动:`chkconfig mysqld on` 为mysql的root账户设置密码:`mysqladmin -u root password '*********'`\ 登录命令`mysql -u root -p` --- url: /blog/2017/hexo-搬瓦工安装ss并加速.md --- # 搬瓦工安装ss并加速 通过搬瓦工的VPS安装ss的几种方法 搬瓦工(BandwagonHost)是美国IT7公司旗下的一家提供便宜年付OVZ架构的VPS主机方案的服务商。价格便宜、且依托的商家比较靠谱,具有较高的性价比。拿来搭个FQ是非常合适的,做个小站也是可以的。 1.购买地址 > 搬瓦工所有配置及购买地址: > 以前有小内存的VPS年费三刀,很便宜,但现在没有了,缺货,连521M的有时候也会缺货,看来挺火的。 2.安装Shadowsocks 2.1搬瓦工后台一键安装(不推荐) 登录账号->Client Area->Services->My Services->KiwiVM Control Panel\ 来到管理面板,点击 Shadowsocks Server 就可以一键安装 Shadowsocks\ 测试过它自带的一键安装,速度太慢,yotube视频看不了,有点慢。 2.2使用shell脚本安装(推荐) 2.2.1安装 shadowsoksR Shadowsocks的一个分支,比较不错。但是默认是 aes-256-cfb 加密,如果需要 chacha20 加密,需要手动切换。 ``` wget --no-check-certificate https://raw.githubusercontent.com/teddysun/shadowsocks_install/master/shadowsocksR.sh chmod +x shadowsocksR.sh ./shadowsocksR.sh 2>&1 | tee shadowsocksR.log ``` 安装成功需要等几分钟\ 这个速度可以看yotube无压力 卸载方法:\ 使用 root 用户登录,运行以下命令: ``` ./shadowsocksR.sh uninstall ``` 安装完成后即已后台启动 ShadowsocksR,运行: ``` /etc/init.d/shadowsocks status ``` 可以查看 ShadowsocksR 进程是否已经启动。\ 本脚本安装完成后,已将 ShadowsocksR 自动加入开机自启动。 其它命令:\ 启动:/etc/init.d/shadowsocks start\ 停止:/etc/init.d/shadowsocks stop\ 重启:/etc/init.d/shadowsocks restart\ 状态:/etc/init.d/shadowsocks status 配置文件路径:/etc/shadowsocks.json\ 日志文件路径:/var/log/shadowsocks.log\ 代码安装目录:/usr/local/shadowsocks 2.2.2安装 shadowsocks-go版本 据说有buff,比python版Shadowsocks要快: ``` wget --no-check-certificate https://raw.githubusercontent.com/teddysun/shadowsocks_install/master/shadowsocks-go.sh chmod +x shadowsocks-go.sh ./shadowsocks-go.sh 2>&1 | tee shadowsocks-go.log ``` 2.2.3其他版本Shadowsocks一键安装: 来自 teddysun: 3.遇到的一些问题 3.1搬瓦工默认系统Centos没有wget命令需要安装 安装命令: ``` yum -y install wget ``` 3.2关于配置多用户 ``` vi /etc/shadowsocks.json ``` 只需要在port\_password项下添加"端口号":"密码" ``` { "server":"0.0.0.0", "server_ipv6":"[::]", "local_address":"127.0.0.1", "local_port":1080, "port_password":{ "12345": "123456", "23456": "123456" }, "timeout":120, "method":"aes-256-cfb", "protocol":"origin", "protocol_param":"", "obfs":"plain", "obfs_param":"", "redirect":"", "dns_ipv6":false, "fast_open":false, "workers":1 } ``` 参考资料 1. --- url: /blog/2017/hexo-涉及PyQt5的一些命令.md --- # 涉及PyQt5的一些命令 记录pyqt5中用到的几个命令 安装 ``` pip install python-qt5 ``` 把ui转py ``` python -m PyQt5.uic.pyuic ``` lineEdit输入隐藏,密码 ``` lineEdit.setEchoMode(QtWidgets.QLineEdit.Password) ``` py转exe ``` pip install pyinstaller pyinstaller demo.py ``` 参数 含义 *** ``` -F 指定打包后只生成一个exe格式的文件 -D --onedir创建一个目录,包含exe文件,但会依赖很多文件(默认选项) -c --console,--nowindowed使用控制台,无界面(默认) -F 指定打包后只生成一个exe格式的文件 -w --windowed,--noconsole使用窗口,无控制台 -p 添加搜索路径,让其找到对应的库。 -i 改变生成程序的icon图标 ``` --- url: /blog/2017/hexo-自己写一个Android照相机应用-1.md --- # 自己写一个Android照相机应用-1 Android相机的相关知识 # Android Camera相关知识 调用系统相机 ```java Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); //使用action启动系统相机 //startActivity(intent); //仅调用相机拍照,不返回数据 //startActivityForResult(intent,REQ_1); //调用相机并通过onActivityResult函数data返回数据; Uri photoUri = Uri.fromFile(new File(mFilePath));//图片保存路径 intent.putExtra(MediaStore.EXTRA_OUTPUT,photoUri); startActivityForResult(intent,REQ_2); //从文件中读取数据 ``` 路径获取 ```java mFilePath = Environment.getExternalStorageDirectory().getPath(); mFilePath = mFilePath + "/" + "tmp.png"; ``` 读取内存卡权限 ```xml ``` onActivityResult函数 ```java protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if(resultCode == RESULT_OK){ if (requestCode == REQ_1) {//从data获取照片 Bundle bundle = data.getExtras(); Bitmap bitmap = (Bitmap)bundle.get("data"); mImageView.setImageBitmap(bitmap); } else if (requestCode == REQ_2) {//从文件获取照片 FileInputStream fis = null; try { fis = new FileInputStream(mFilePath); Bitmap bitmap = BitmapFactory.decodeStream(fis); mImageView.setImageBitmap(bitmap); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } } } ``` 注册action,实现照相功能 ```xml ``` --- url: /blog/2017/hexo-自己写一个Android照相机应用-2.md --- # 自己写一个Android照相机应用-2 Android自定义相机的实现\ 首先是要创建camera的生命周期 ```java getCamera();//获取Camera对象 setStartPreview(Camera camera,SurfaceHolder holder);//预览相机内容 releaseCamera();//释放相机资源 activity生命周期与camera绑定 protected void onResume() { super.onResume(); if (mCamera == null) { mCamera = getCamera(); if (mHolder != null) { setStartPreview(mCamera,mHolder); } } } protected void onPause() { super.onPause(); releaseCamera(); } ``` camera与surfaceview绑定 ```java //预览图像与camera绑定 @Override public void surfaceCreated(SurfaceHolder holder) { setStartPreview(mCamera,mHolder); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { mCamera.stopPreview(); setStartPreview(mCamera,mHolder); } @Override public void surfaceDestroyed(SurfaceHolder holder) { releaseCamera(); } ``` 声明相机使用权限 ```xml ``` 实现照相,通过回调将照片数据保存到文件,并将文件路径传递到其它activity(ResultAty) ```java private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera camera) {//data中存储照片的全部信息 File tempFile = new File("/sdacrd/temp.png"); try { FileOutputStream fos = new FileOutputStream(tempFile); fos.write(data); fos.close(); Intent intent = new Intent(CustomCamera.this,ResultAty.class); intent.putExtra("picPath",tempFile.getAbsolutePath()); startActivity(intent); CustomCamera.this.finish(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }; public void capture(View view){ Camera.Parameters parameters = mCamera.getParameters(); parameters.setPictureFormat(ImageFormat.JPEG);//设置拍照格式jpg格式 parameters.setPictureSize(800,400);//设置照片大小 parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);//设置自动对焦 mCamera.autoFocus(new Camera.AutoFocusCallback() { //回调,对焦最清晰时拍照 @Override public void onAutoFocus(boolean success, Camera camera) { if (success) { mCamera.takePicture(null,null,mPictureCallback); } } }); } ``` --- url: /blog/2017/hexo-谷歌浏览器运行安卓APK.md --- # 谷歌浏览器运行安卓APK 使用谷歌浏览器的插件ARChon实现安卓APK在电脑上运行 用谷歌插件在电脑上运行安卓APK 1,直接谷歌应用商店下载出现错误\ 程序包无效:"CRX\_SIGNATURE\_VERIFICATION\_FAILED"。 2,直接下载crx包参考百度经验 3,出现错误:Cannot load extension with file or directory name *metadata. Filenames starting with "*" are reserved for use by the system.\ 更改解压后文件夹\_metadata为metadata 4,出现错误:'import' extension is not installed.\ 打开文件manifest.json去掉import一句 ``` "import":[{"id":"mfaihdlpglflfgpfjcifdjdjcckigekc"}], ``` 继续谷歌去掉这句话不对\ 看这篇CSDN文章\ 说要下载插件ARChon\ (以下废话,直接看5) 4.1,安装成功,但启用后一直白屏 ![](http://upload-images.jianshu.io/upload_images/2950376-a275f201f7fdead8.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 怀疑是不是去掉import一句出问题了 4.2,网上看到开启Native Client\ 谷歌浏览器地址栏输入chrome://flags/开启Native Client\ 无用 4.3,发现另一个谷歌商店的应用twerk这个应用是把 APK 文件转换成 Chrome App用的 ![](http://upload-images.jianshu.io/upload_images/2950376-2372cf692c93b5a9.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 5ARChon是谷歌插件要在扩展程序,打钩开发者模式,点击加载已解压扩展程序,选择解压文件夹\ 加载ARChon时有警告,可忽略\ 先加载ARC Welder出现4的错误后\ 再加载ARChon,然后ARC Welder就好了,不好的话重新加载就好了\ 安装好了就是APK运行不了 4.3安装的twerk可以把 APK 文件转换一个文件夹\ 然后ARC Welder运行\ 谷歌浏览器如何运行APK博客CSDN可以试一试 ## 又遇到的问题 Chrome加载Android应用,提示"There is no "message" element for key extName"错误,如: ![](http://upload-images.jianshu.io/upload_images/2950376-0c0702ed3e06b504.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ## 解决方法: 找到该Crx文件夹下的"\_locales\en"目录下的"messages.json"文件,在"extName"节点下,添加"message"字段标签,值对应应用的包名: ![](http://upload-images.jianshu.io/upload_images/2950376-c5a40ac2756b5a87.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) WebGL 不受支持\ 知乎有教程\ 浏览器快捷方式属性增加---ignore-gpu-blacklist\ 开启硬件加速\ 试了一下掌阅 ![](http://upload-images.jianshu.io/upload_images/2950376-f4ed0ddb2b2d06e1.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 试了一下简书\ 可以运行 ![](http://upload-images.jianshu.io/upload_images/2950376-ed37c301d49d7485.PNG?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) --- url: /blog/2018/hexo-DEBUG常用功能.md --- # DEBUG常用功能 window 10中DEBUG的安装,以及DEBUG调试中一些常用的命令 ## 1.安装 window 10中好像已经没有了Debug的调试工具,需要下载另外的软件 下载安装DOSBox.百度云下载地址:(DOSBOX)\[]\ 下载后先安装DOSBox0.74-win32-installer.exe\ 然后找到文件dos-box0.74.conf(C:\Users\username\AppData\Local\DOSBox)\ 添加两行 ``` MOUNT C E:\DEBUG # 将目录E:\DEBUG挂载为DOSBOX下的C: set PATH=$PATH$;E:\DEBUG # 将E:\DEBUG写入环境变量PATH中 ``` 并将下载的MASM.exe,LINK.exe,debug.exe三个文件放入目录E:\DEBUG\ 打开软件,输入`c:`就可以使用debug命令了 ## 2.常用命令: R命令:查看改变CPU寄存器的内容\ D命令:查看内存中的内容\ E命令:改写内存中的内容\ U命令:将内存中的机器指令翻译成汇编指令\ T命令:执行一条机器指令\ A命令:以汇编指令的格式在内存中写入一条机器指令\ Q命令:退出DEBUG调试 ## 3.命令具体使用实例 ### 3.1R命令 R命令:查看改变CPU寄存器的内容 ``` -r ;显示寄存器的值 -r reg ;改变寄存器reg的值 ``` ![img](./imgs/2_12_R命令.PNG) ### 3.2D命令 D命令:查看内存中的内容\ 默认显示128字节的内容 ``` -d ;默认地址 -d 段地址:偏移地址 ;指定地址 -d 段地址:偏一偏二 ;两个地址间的内容 ``` ![img](./imgs/2_12_D命令.PNG) ### 3.3E命令 E命令:改写内存中的内容 ``` -e 起始地址 数据 数据 ... -e 起始地址 ``` ![img](./imgs/2_12_E命令.PNG) ### 3.4U命令 U命令:将内存中的机器指令翻译成汇编指令\ 与D命令有些类似\ ![img](./imgs/2_12_U命令.PNG) ### 3.5T命令 T命令:执行内存中的一条机器指令\ 指令位置由cs:ip确定\ ![img](./imgs/2_12_T命令.PNG) ### 3.6A命令 A命令:以汇编指令的格式在内存中写入一条机器指令\ ![img](./imgs/2_12_A命令.PNG) --- url: /blog/2018/hexo-Git使用中的一些问题.md --- # Git使用中的一些问题 ## 1-配置问题 全局配置 ``` $ git config --list 查看当前用户信息 $ git config --global user.name "username" 配置用户名 $ git config --global user.email emailname@example.com 配置邮箱 ``` 项目配置(进入项目目录) ``` $ cat .git/config 项目配置信息 $ git config user.name "username" 配置用户名 $ git config user.email emailname@example.com 配置邮箱 ``` 全局的配置就是加上---global\ 项目未设置的配置默认使用全局配置 ## 2-密钥问题 ``` $ cd ~/.ssh 查看是否存在密钥 $ ssh-keygen -t rsa -C "emailname@example.com" 生成密钥 $ ssh -T git@github.com 测试是否配置成功 ``` 生成密钥过程中回车三次,是密码为空,不然每次push都要输入密码\ 生成密钥后主要有两个文件\ \~/.ssh/id\_rsa\ 私钥进行处理后的一些内容\ \~/.ssh/id\_rsa.pub\ 公钥进行处理后的内容,提交到服务器(github或coding)的内容\ \~/.ssh/known\_hosts\ 这个文件可能会有,是ssh对服务器的一些记录 ## 3-工作区、暂存区和版本库 ![img](./imgs/git.jpg) `git add`:暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中。 `git commit`:暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。 `git reset HEAD` :暂存区的目录树会被重写,被 master 分支指向的目录树所替换,但是工作区不受影响。 `git rm --cached `:直接从暂存区删除文件,工作区则不做出改变。 `git checkout .` 或者 `git checkout -- `:用暂存区全部或指定的文件替换工作区的文件。**这个操作很危险,会清除工作区中未添加到暂存区的改动。** `git checkout HEAD .`或者 `git checkout HEAD `:用 HEAD 指向的 master 分支中的全部或者部分文件替换暂存区和以及工作区中的文件。**这个命令也是极具危险性的,因为不但会清除工作区中未提交的改动,也会清除暂存区中未提交的改动。** ## 参考资料 1. --- url: /blog/2018/hexo-python3之模块.md --- # python3之模块 ## 1.python3模块 简单讲,一个模块就是一个\*.py文件,这个文件里面可以定义类,函数,变量,也可以包含可执行代码。 ## 2.模块的引入 2.1import引入 ``` import module1,[module2[,.........moduleN]] ``` 一个模块只会被导入一次,不管执行了多少次import,这样可以防止导入模块被重复执行。 2.2from \* import语句 ``` from modname import name1,name2..........nameN from mod import func_1 ``` 这个声明不会把整个mod模块导入到当前命名空间中,它只会将mod里的func\_1单个引入到执行这个声明的模块的全局符号表。 from ... import \* :这样会把模块中的所有内容导入到当前命名空间,一般不建议使用,消耗内存空间,也容易出现未预知的问题。 ## 3.搜索路径 搜索顺序:当前目录->系统环境变量PATHONHOME->标准链接库目录\ 通过`sys.path`可查看搜索路径 ``` >>> import sys >>> sys.path ['', 'C:\\Python36\\python36.zip', 'C:\\Python36\\DLLs', 'C:\\Python36\\lib', 'C:\\Python36', 'C:\\Python36\\lib\\site-packages'] ``` ## 4.命名空间 变量是拥有匹配对象的名字(标识符)。命名空间是一个包含了变量名称(键)和它们各自相应的对象们(值)的字典。\ 函数内用到全局变量要用global语句,否则会当作局部变量处理 ## 其他问题: 1.python模块里的可执行代码何时执行?\ 当导入模块时,python顶层的代码会执行一次,若要重新执行模块的顶层代码需要通道reload()函数\ 另外python3中的reload()需要从imp包中导入\ 2.关于python模块循环引入的问题? ## 参考资料 1. --- url: /blog/2018/hexo-python中关于路径的知识.md --- # python中关于路径的知识 ```python os.getcwd() #输出当前路径 os.listdir() #输出当前路径下的所有文件夹名和文件名 os.remove('filename.xxx')和os.unlink('filename.xxx')功能一样 #删除文件filename.xxx os.rmdir('path') #删除目录(目录必须为空) os.removedirs('p1//p2//p3') #依次删除目录p3,p2,p1直到某一目录不为空 os.chdir('path') #更改当前路径 ``` 参考资料 1.\ 2. --- url: /blog/2018/hexo-python类和实例的一些属性.md --- # python类和实例的一些属性 ## 1.self和\_\_init\_\_() self代表类的实例,如下: ```python class Test: a,b='classa','classb'#类的属性 def __init__(self): self.b='selfb'#实例的属性 self.c='selfc' print(Test.a) print(Test.b) #-output:classa classb-访问类的属性 a=Test() print(a.a) #-output:classa-通过实例访问类的属性 print(a.b) #-output:selfb-当实例和类都具有某属性时,输出实例属性 print(a.c) print(Test.c) #output ``` 当实例和类有相同的属性时,如何通过实例访问类的属性呢?\ 可以通过\_\_class\_\_访问 ## 2.**class** \_\_class\_\_是指实例所对应的类 ```python # -*- coding: utf-8 -*- class Test: a='classa' print(count) def __init__(self): self.a='selfa' print(self) #-output:<__main__.Test object at 0x055A1270> print(self.__class__) #-output: a=Test() print('实例a',a) #-output:<__main__.Test object at 0x055A1270> print('类Test',Test) #-output: print(a.a,a.__class__.a) #-output:selfa classa ``` 通过\_\_init\_\_()可以实现对类的实例的统计 ```python # -*- coding: utf-8 -*- class Test(object): count=0 def __init__(self): super(Test, self).__init__() self.__class__.count+=1 if __name__=="__main__": a=Test() print(Test.count) b=Test() print(Test.count) Test() print(Test.count) ``` 输出为 ``` 1 2 3 ``` 每创建一个Test类的实例,Test.count都加1 ## 3.**dict** \_\_dict\_\_是一个字典,键是属性名,值为属性值。\ 类有自己的\_\_dict\_\_,类的实例也有自己的\_\_dict\_\_ ```python # -*- coding: utf-8 -*- class Test(object): classa='classa' def __init__(self): super(Test, self).__init__() self.selfb='selfb' if __name__=="__main__": print(Test.__dict__) a=Test() print(a.__dict__) ``` 输出为 ``` {'__module__': '__main__', 'classa': 'classa', '__init__': , '__dict__': , '__weakref__': , '__doc__': None} {'selfb': 'selfb'} ``` 可见类Test有classa属性以及一些其他属性\ 类Test的实例a只有self.b属性 ## 4.**len**(self)函数 返回元素个数,实现len()方法。即类实现了\_\_len\_\_()函数就可以使用len()函数 ## 5.**getitem**(self,key) 实现字典一样的功能,当类的实例对象(假设为P)调用P\[key]时,调用此函数返回。 ## 参考资料 1,[https://docs.python.org](https://docs.python.org/) --- url: /blog/2018/hexo-socket网络编程.md --- # socket网络编程 ### socket网络编程 ``` int inet_aton(const char *cp, struct in_addr *inp); ``` 转换网络主机地址为二进制数值并存储与第二个参数中\ 函数返回0表示主机地址无效,非0表示主机地址有效\ 转化完后需要调用htons或htonl函数才能将主机字节序转换为网络字节序用于网络传输 ``` char *inet_ntoa(struct in_addr in); ``` 转换网络字节序为标准的ASCII以点分开的地址,函数返回字符串指针\ 该字符串空间为静态分配,第二次调用时会覆盖第一次的内容 ``` in_addr_t inet_addr(const char *cp); ``` 转换网络主机地址为网络字节序二进制值\ 参数无效,返回-1(INADDR\_NONE)\ 注意:转换255.255.255.255时也返回-1 ``` int inet_pton(int af, const char *src, void *dst); ``` 转换字符串到网络地址,af是地址簇,*src是来源地址,* dst接收转换后的数据。 ``` const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt); ``` 转换网络字节序二进制值到ASCII类型的地址,参数的作用和inet\_pton相同,\ socklen\_t cnt指所指向缓存区dst的大小,避免溢出,如果缓存区太小无法存储地址的值,则返回一个空指针,并将errno置为ENOSPC。 ``` int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout); ``` int maxfdp 指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。\ struct fd\_set 可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄。监视这些文件描述符的读变化 ``` int setsockopt(SOCKET s,int level,int optname,const char* optval,int optlen); ``` s(套接字): 指向一个打开的套接口描述字\ level:(级别): 指定选项代码的类型。\ SOL\_SOCKET: 基本套接口\ IPPROTO\_IP: IPv4套接口\ IPPROTO\_IPV6: IPv6套接口\ IPPROTO\_TCP: TCP套接口\ optname(选项名): 选项名称\ optval(选项值): 是一个指向变量的指针 类型:整形,套接口结构, 其他结构类型:linger{}, timeval{ }\ optlen(选项长度) :optval 的大小 ``` int PASCAL FAR recvfrom( SOCKET s, char FAR* buf, int len, int flags,struct sockaddr FAR* from, int FAR* fromlen); ``` s:标识一个已连接套接口的描述字。\ buf:接收数据缓冲区。\ len:缓冲区长度。\ flags:调用操作方式。\ from:(可选)指针,指向装有源地址的缓冲区。\ fromlen:(可选)指针,指向from缓冲区长度值。 ### 参考资料 1.\ 2. --- url: /blog/2018/hexo-vim的使用.md --- # vim的使用 1.光标移动 `h`,`j`,`k`,`l`(空格):向左,下,上,右移动光标 `Ctrl`+`(f,b,d,u)`:屏幕向下移一页,向上移一页,向下移半页,向上移半页\ +-:下一行,上一行\ `H`,`M`,`L`:光标移动到这个屏幕的最上方,中央,最下方那一行的第一个字符\ `G`,`nG`,`gg`:移动都文档末,移动到第n行,移动到第一行\ `n`:向下移动n行 2.搜索替换 `/word`:向光标之下寻找word\ `?word`:向光标之上寻找word\ `n`,`N`:重复搜索(不变方向,改变方向) 参考资料 1. --- url: /blog/2018/hexo-形式语言与自动机基础知识.md --- # 形式语言与自动机基础知识 形式语言与自动机这门课需要有离散数学的基础,但本科通信工程里没有学过这门课,总结一些这门课中需要的基础知识 ## 1.集合及其运算 ### 1.1子集和真子集 $A$子集:$A \subseteq B​$或$B \subseteq A​$\ $A​$是$B​$的真子集:$A \subset B​$或$B \subset A​$\ $x​$是$A​$的一个元素:$x \in A​$\ $x​$不是$A​$的一个元素:$x \notin A​$ ### 1.2集合的交并差补 $A$和$B$的并集:$A\cup B={x|x\in A或x\in B}$\ $A$和$B$的交集:$A\cap B={x|x\in A且x\in B}$\ $A$和$B$的差集:$A - B={x|x\in A且x\notin B}$\ 若$B\subseteq A$我们也称$A−B$为$B$的(关于$A$)补,记作:$\overline B(A)​$ ### 1.3集合的并的推广 设$I​$是某些标号的集合我们将$\displaystyle \bigcup\_{i\in I}A\_i={x|存在i\in I,使得x\in A\_i}​$ ### 1.4A的幂集 $A$的所有子集的集合,记作$2^A={B|B\subseteq A}​$ ### 1.5笛卡尔乘积 $A\times B={(a,b)|a\in A且b\in B}$ ### 1.6集合之间的关系 由$A$到$B$的关系是$A×B$的任何子集。若$A=B$,则称为$A$上的关系。若$R$为$A$到$B$的关系,当$(a,b)$在$R$内时,可写成$aRb​$ ### 1.7集合关系的性质 设$R$是集合$A$上的关系,则有\ (1)若对$A$中的任一元素$a$,都有$aRa$,则称$R$是**自反的**;\ (2)若对$A$中的任何元素$a,b$,从$aRb$能够推到出$bRa$,则称$R$是**对称的**;\ (3)若$a,b,c$是$A$中的元素,从$aRb$和$bRc$能够推出$aRc$,则称$R$是**传递的**;\ 若关系$R​$同时是自反的,对称的和传递的,则称之为**等价关系**。\ 2018-09-22 15:28:12 ## 参考资料 1.《形式语言与自动机》陈有祺编著 --- url: /blog/2018/hexo-梯度旋度和散度.md --- # 梯度旋度和散度 有关梯度旋度和散度的定义和计算,记录一下 ## 1.定义 ### 1.1梯度 设函数$ u=f(x,y,z) $在空间区域$ G $内具有一阶连续偏导数,其中点$ P(x,y,z) \in G $ 则向量 ```math \left( \frac {\partial f}{\partial x}, \frac {\partial f}{\partial y}, \frac {\partial f}{\partial z} \right)= \frac {\partial f}{\partial x}\vec i+ \frac {\partial f}{\partial y}\vec j+ \frac {\partial f}{\partial z}\vec k ``` 为函数$ u=f(x,y,z) $在点$ P(x,y,z) $的**梯度** 记为$ grad;f(x,y,z) $ 或$ \nabla f(x,y,z) $ (: $ \nabla = \frac {\partial}{\partial x}\vec i+\frac {\partial}{\partial y}\vec j+\frac {\partial}{\partial z}\vec k $为三维的向量微分算子) ### 1.2旋度 在三维空间$ G $ 中有三维直角坐标系$ O\_{xyz} $,设向量场: ```math \vec v=v_x\vec i+v_y\vec j+v_z\vec k ``` 其中$ v\_x,v\_y,v\_z $具有一阶连续偏导数,点$ P(x,y,z) \in G $ 向量 ```math \begin{vmatrix} \vec i & \vec j & \vec k \cr \frac {\partial}{\partial x} & \frac {\partial}{\partial y} & \frac {\partial}{\partial z} \cr v_x & v_y & v_z \cr \end{vmatrix} = (\frac {\partial v_z}{\partial y} - \frac {\partial v_y}{\partial z})\vec i+ (\frac {\partial v_x}{\partial z} - \frac {\partial v_z}{\partial x})\vec j+ (\frac {\partial v_y}{\partial x} - \frac {\partial v_x}{\partial y})\vec k ``` 为向量场$ \vec v $ 在点$ P(x,y,z) $的**旋度** 记为$ curl;v $或者$ \nabla \times v $ ### 1.3散度 在三维空间$ G $ 中有三维直角坐标系$ O\_{xyz} $,设向量场: ```math \vec v=v_x\vec i+v_y\vec j+v_z\vec k ``` 其中$ v\_x,v\_y,v\_z $具有一阶连续偏导数,点$ P(x,y,z) \in G $ 标量 ```math \frac {\partial v_x}{\partial x}+ \frac {\partial v_y}{\partial y}+ \frac {\partial v_z}{\partial z} ``` 为向量场在点$ P(x,y,z) \in G $的散度 记为$div;v$或$\nabla \cdot v$ --- url: /blog/2018/hexo-简单了解汇编.md --- # 简单了解汇编 ### 1.汇编是什么 汇编语言的主体是汇编指令,汇编指令是由机器指令发展而来,例如:\ 操作:将寄存器bx的内容送到ax\ 机器指令:101100000000000000000011\ 记起来太过于复杂,从而有了替代它的\ 汇编指令:`mov ax,bx`\ 方便记忆,也符合人们的逻辑思维\ 程序员们编写汇编指令,在通过编译器编译成机器指令,然后就可以在计算机中运行了(如今的大部分高级语言是通过编译器,将高级语言转换成汇编语言,在编译成机器语言在电脑中运行)\ 不管什么语言,都要转换成机器语言在电脑中运行,汇编语言和高级语言都是人们为了简化程序制作过程而已 ### 2.计算机的总线 从逻辑上总线分为三类(8086)\ 数据总线:传送数据,总线宽度决定一次读取数据多少(16)\ 地址总线:指定地址,总线宽度决定寻址能力(20)\ 控制总线:控制读写 ### 3.存储器 RAM:程序和数据都要加载到RAM即内存中,才能通过CPU运行,可读可写\ ROM:一些芯片的主要参数,及操作指令,芯片出厂时已经固定,仅可读\ 计算机运行时,全部程序和数据都要加载到RAM中\ 通过ROM中的一些必要信息,CPU才能对外设进行操作 --- url: /blog/2018/hexo-简单的iptables配置过程.md --- # 简单的iptables配置过程 简单的记一下iptables的配置过程,以后用到了就不用再google了\ 总的来说分为四步,清楚规则,预设规则,添加自定义规则,保存规则 ## iptables配置过程 1.清除规则: 清楚旧的规则\ iptables -F 清除预设表filter中的所有规则链的规则\ iptables -X 清除预设表filter中使用者自定链中的规则 2.设定预设规则 默认情况下对各种包的处理方式\ iptables -p INPUT DROP\ iptables -p OUTPUT ACCEPT\ iptables -p FORWARD DROP 3.添加用户自定义规则 对于特定端口协议的包的规则进行设置\ iptables -A INPUT -p tcp ---dport 22 -j ACCEPT\ -A:表示添加规则到INPUT(OUTPUT,FORWARD)链\ -p:表示tcp(udp)协议\ \---dport(---sport):目的端口号(源端口号)\ -j():添加规则为接受 4.保存规则 不保存的话重启后就没有了\ service iptables save 参考资料 1. --- url: /blog/2019/hexo-Linux常用命令.md --- # Linux常用命令 Linux中的一些常用命令,tar ## 1.tar解压打包相关 ```bash tar -cvf log.tar log2019.log #仅打包,不压缩 tar -zcvf log.tar.gz log2019.log #打包后,以 gzip 压缩 tar -jcvf log.tar.bz2 log2019.log #打包后,以 bzip2 压缩 tar -ztvf log.tar.gz #gzip查阅 tar -jtvf log.tar.gz #bzip2查阅 tar -zxvf log.tar.gz #gzip解压 tar -jxvf log.tar.gz #bzip2解压 unzip log.zip -d dirname #zip解压到dirname目录 ``` 补充 c: 建立压缩档案\ -x:解压\ -t:查看内容\ -r:向压缩归档文件末尾追加文件\ -u:更新原压缩包中的文件 这五个是独立的命令,压缩解压都要用到其中一个,可以和别的命令连用但只能用其中一个。下面的参数是根据需要在压缩或解压档案时可选的。 -z:有gzip属性的\ -j:有bz2属性的\ -Z:有compress属性的\ -v:显示所有过程\ -O:将文件解开到标准输出 ## 2.wget设置代理 2.1环境变量中设置 ``` export http_proxy=http://127.0.0.1:8087 ``` 2.2使用配置文件 ``` # cp /etc/wgetrc ~/.wgetrc # vim ~/.wgetrc # You can set the default proxies for Wget to use for http, https, and ftp. # They will override the value in the environment. https_proxy = http://127.0.0.1:8087/ http_proxy = http://127.0.0.1:8087/ ftp_proxy = http://127.0.0.1:8087/ # If you do not want to use proxy at all, set this to off. use_proxy = on ``` ## 3.ps相关命令 ``` ps -ef # 查看进程信息和执行的命令 ``` ## 4.用户文件管理相关 4.1Linux系统用户账号的管理 4.1.1添加新的用户账号 ``` useradd [-d dirname] username # [-d dirname]指定用户主目录 ``` 4.1.2删除帐号 ``` userdel [-r] username # [-R]把用户的主目录一起删除 ``` 4.1.3修改帐号 ``` usermod [] username ``` 4.1.4用户密码管理 ``` passwd [-I][-u][-d][-f] username # [-I]锁定密码,即禁用账号。 # [-u]密码解锁。 # [-d]使账号无密码。 # [-f]强迫用户下次登录时修改密码。 ``` 4.2Linux系统用户组的管理 4.2.1增加一个新的用户组 ``` groupadd [-g GID] username # [-g GID]指定新用户组的组标识号(GID) ``` 4.2.2删除一个已有的用户组 ``` groupdel groupname ``` 4.2.3修改用户组的属性 ``` groupmod [-g GID] groupname # [-g GID]指定新用户组的组标识号(GID) ``` 4.2.4更改用户组 **如果一个用户同时属于多个用户组,那么用户可以在用户组之间切换,以便具有其他用户组的权限。用户可以在登录后,使用命令newgrp切换到其他用户组,这个命令的参数就是目的用户组。** ``` newgrp root ``` 4.3Linux文件用户属性修改 ``` chown [-R] user_name filename_OR_dirname # 更改文件或目录的所有者 chgrp [-R] group_name filename_OR_dirname # 更改文件或目录所在组 # [-R]参数递归更改目录下所有文件的用户属性 ``` ## 参考资料 1. 2. 3. 4. --- url: /blog/2019/hexo-OWL基础.md --- # OWL基础 OWL基础 网络本体语言Web Ontologoy Language OWL Lite -> OWL DL -> OWL Full 递进关系 基本元素 1. 类(Class) 任何东西都是类owl:Thing 的一个成员(实例);子类:Subclass;相等关系:equivalentClass 2. 个体(Individual) 与实例的概念差不多 一个个体可以属于多个类(没有指名是哪个类) 某个类的实例(指名了类) 子类与个体是不同的东西 3. 属性(Property) 一个二元关系,OWL中包括两种属性: 1. 类型属性(datatype properties):描述类与其实例之间关系的属性。 2. 对象属性(object properties):描述两个不同类的实例之间关系的属性。 属性有两个端点:起点和终点,都应该是两个个体(实例) 用原集(domain)起点的实例的类,用象集(range)描述终点的实例的类. 属性也有子属性(Subproperty) 公理和约束 rdf资源描述框架(Resource Description Framework) W3C提出的一组标记语言的技术规范,是一种数据模型,rdf数据集的序列化方法 OWL是由DAML(DARPA Agent Markup Language)+OIL(Ontology Inference Layer)演变而来。 OWL是RDF的扩张,为我们提供了更广泛的定义RDFS词汇的功能,更广泛意指可以定义词汇之间的关系,类与类间的关系,属性与属性之间的关系等 foaf(Friend-of-a-Friend)是一种XML/RDF词汇表,不管通过那种那种方法表示数据资源,都要指定词汇表 1. RDF/XML XML的技术程序;格式太冗长,不便于阅读 2. [N-Triples](https://www.w3.org/TR/n-triples/) 三元组表示;开放领域知识图谱[DBpedia](https://wiki.dbpedia.org/)通常使用这种格式发布数据. 3. [Turtle](https://www.w3.org/TR/turtle/) 使用最多的一种RDF序列化方法,比RDF/XML紧凑,可读性比N-Triples好 4. RDFa The Resource Description Framework in Attributes,HTML5的一个扩展,不改变任何显示效果的情况下,然网站更容易被搜索引擎解析 5. JSON-LD JSON for Linking Data,用键值对的方法来存储RDF RDF/XML ``` Bob Dylan USA Columbia 10.90 1985 Bonnie Tyler UK CBS Records 9.90 1988 . . . ``` RDF 文档的第一行是 XML 声明。这个 XML 声明之后是 RDF 文档的根元素:。 *xmlns:rdf* 命名空间,规定了带有前缀 rdf 的元素来自命名空间 "[http://www.w3.org/1999/02/22-rdf-syntax-ns#"。](http://www.w3.org/1999/02/22-rdf-syntax-ns#%22。) *xmlns:cd* 命名空间,规定了带有前缀 cd 的元素来自命名空间 "[http://www.recshop.fake/cd#"。](http://www.recshop.fake/cd#%22。) 元素包含了对被 *rdf:about* 属性标识的资源的描述。 元素:、、 等是此资源的属性。 Turtle序列化方法总结 * URI用 **<>** 描述 ``` ``` * 前缀缩写(类似于RDF/XML的命名空间) ``` @prefix foo: @prefix : :a :b :c ``` * 字面量 一行或者多行, `@en`限定其语言, `^^xsd:decimal`限定其数据类型 ``` "string" """ many lines of string many lines of string many lines of string """ "chat"@en "chat"@fr "10"^^xsd:decimal ``` * 空节点(RDF模型可能会存在未命名的空节点) `_:me`,`_a1234`分别代表一个空节点 ``` _:me _:a1234 ``` * base URI base URI定义后,接下来的**URI, 前缀缩写,qualified names 和base URI**都要受其作用 ``` # this is a complete turtle document @base . # base URIs 是 http://example.org/ns/ @base . # base URI 是 http://example.org/ns/foo/ @prefix : . :a4 :b4 :c4. ``` * 对三元组进行缩写 ``` :a :b :c, :d. #the last triple is :a :b :d. ``` * 一个简单的完整turtle标准文件 ``` @prefix info: @prefix rel: @prefix person: person:Tom info:name "Tom"; info:job "worker"; info:age 56; rel:fatherof person:Jim. person:Jim info:name "Jim"; info:job "programmer"; info:age 28; rel:fatherof person:Cherry. person:Cherry info:name "Cherry"; info:age 8; . ``` --- url: /blog/2019/hexo-Python中遇到的问题.md --- # Python中遇到的问题 1.列表的初始化 当初始化一个n×n的列表时不能使用如下方法, ``` In [1]: l=[[0]*3]*3 #如此初始化会导致其它行仅是第一行的引用而不是copy In [2]: l Out[2]: [[0, 0, 0], [0, 0, 0], [0, 0, 0] In [3]: l[0][0]=1 #改变其中一行的某个元素 In [4]: l Out[4]: [[1, 0, 0], [1, 0, 0], [1, 0, 0]] #其他行跟着改变 ``` 正确的方法应该如下 ``` In [5]: l=[[0 for _ in range(3)] for _ in range(3)] #或者l=[[0]*3 for _ in range(3)] In [6]: l Out[6]: [[0, 0, 0], [0, 0, 0], [0, 0, 0]] In [7]: l[0][0]=1 In [8]: l Out[8]: [[1, 0, 0], [0, 0, 0], [0, 0, 0]] ``` 2.a is b与a==b 的区别 ``` a='vhcffh.com' b='vhcffh.com' a==b # True,a和b对应实例的内容是相同的 a is b # False,a和b指向不同的实例 b=a a is b # True,a和b指向同一个实例 # 好奇怪啊!!! In [43]: a='vhcffh.com' In [44]: b='vhcffh.com' In [45]: a==b Out[45]: True In [46]: a is b Out[46]: False In [47]: a='abcde' In [48]: b='abcde' In [49]: a == b Out[49]: True In [50]: a is b Out[50]: True ``` --- url: /blog/2019/hexo-Transformers.md --- # Transformers [transformers](https://github.com/huggingface/transformers)(与*pytorch-transformers*和*pytorch-pretrained-bert*相似)是python的一个库,它提供了用于自然语言理解(NLU)和自然语言生成(NLG)的多种预训练模型(BERT,GPT-2,RoBERTa,XLM,DistilBert,XLNet.....),为100多种语言提供了超过32种的预训练模型,并实现Tensorflow 2.0和Pytorch的深度互操作。 特点 * 与pytorch-transformers一样容易使用 * 如Keras一样强大简洁 * 在NLU和NLG任务上有很好的表现 * 容易学习 模型 1. [BERT](https://github.com/google-research/bert) (from Google) released with the paper [BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding](https://arxiv.org/abs/1810.04805) by Jacob Devlin, Ming-Wei Chang, Kenton Lee and Kristina Toutanova. 2. [GPT](https://github.com/openai/finetune-transformer-lm) (from OpenAI) released with the paper [Improving Language Understanding by Generative Pre-Training](https://blog.openai.com/language-unsupervised) by Alec Radford, Karthik Narasimhan, Tim Salimans and Ilya Sutskever. 3. [GPT-2](https://blog.openai.com/better-language-models) (from OpenAI) released with the paper [Language Models are Unsupervised Multitask Learners](https://blog.openai.com/better-language-models) by Alec Radford\*, Jeffrey Wu\*, Rewon Child, David Luan, Dario Amodei **and Ilya Sutskever**. 4. [Transformer-XL](https://github.com/kimiyoung/transformer-xl) (from Google/CMU) released with the paper [Transformer-XL: Attentive Language Models Beyond a Fixed-Length Context](https://arxiv.org/abs/1901.02860) by Zihang Dai\*, Zhilin Yang\*, Yiming Yang, Jaime Carbonell, Quoc V. Le, Ruslan Salakhutdinov. 5. [XLNet](https://github.com/zihangdai/xlnet) (from Google/CMU) released with the paper [XLNet: Generalized Autoregressive Pretraining for Language Understanding](https://arxiv.org/abs/1906.08237) by Zhilin Yang\*, Zihang Dai\*, Yiming Yang, Jaime Carbonell, Ruslan Salakhutdinov, Quoc V. Le. 6. [XLM](https://github.com/facebookresearch/XLM) (from Facebook) released together with the paper [Cross-lingual Language Model Pretraining](https://arxiv.org/abs/1901.07291) by Guillaume Lample and Alexis Conneau. 7. [RoBERTa](https://github.com/pytorch/fairseq/tree/master/examples/roberta) (from Facebook), released together with the paper a [Robustly Optimized BERT Pretraining Approach](https://arxiv.org/abs/1907.11692) by Yinhan Liu, Myle Ott, Naman Goyal, Jingfei Du, Mandar Joshi, Danqi Chen, Omer Levy, Mike Lewis, Luke Zettlemoyer, Veselin Stoyanov. 8. [DistilBERT](https://huggingface.co/transformers/model_doc/distilbert.html) (from HuggingFace) released together with the paper [DistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter](https://arxiv.org/abs/1910.01108) by Victor Sanh, Lysandre Debut and Thomas Wolf. The same method has been applied to compress GPT2 into [DistilGPT2](https://github.com/huggingface/transformers/tree/master/examples/distillation). 9. [CTRL](https://github.com/pytorch/fairseq/tree/master/examples/ctrl) (from Salesforce), released together with the paper [CTRL: A Conditional Transformer Language Model for Controllable Generation](https://www.github.com/salesforce/ctrl) by Nitish Shirish Keskar\*, Bryan McCann\*, Lav R. Varshney, Caiming Xiong and Richard Socher. 10. [CamemBERT](https://huggingface.co/transformers/model_doc/camembert.html) (from FAIR, Inria, Sorbonne Université) released together with the paper [CamemBERT: a Tasty French Language Model](https://arxiv.org/abs/1911.03894) by Louis Martin, Benjamin Muller, Pedro Javier Ortiz Suarez, Yoann Dupont, Laurent Romary, Eric Villemonte de la Clergerie, Djame Seddah, and Benoît Sagot. 11. [ALBERT](https://github.com/google-research/ALBERT) (from Google Research), released together with the paper a [ALBERT: A Lite BERT for Self-supervised Learning of Language Representations](https://arxiv.org/abs/1909.11942) by Zhenzhong Lan, Mingda Chen, Sebastian Goodman, Kevin Gimpel, Piyush Sharma, Radu Soricut. 12. [XLM-RoBERTa](https://github.com/pytorch/fairseq/tree/master/examples/xlmr) (from Facebook AI), released together with the paper [Unsupervised Cross-lingual Representation Learning at Scale](https://arxiv.org/abs/1911.02116) by Alexis Conneau\*, Kartikay Khandelwal\*, Naman Goyal, Vishrav Chaudhary, Guillaume Wenzek, Francisco Guzmán, Edouard Grave, Myle Ott, Luke Zettlemoyer and Veselin Stoyanov. 使用 对于每个模型,这个库主要涉及3个类 * model classes:提供模型的结构,如:`BertModel` * configuration classes:存储构建模型的所有参数,如`BertConfig` * tokenizer classes:提供模型的词汇表和用于对字符串解码/编码的方法,如`BertTokenizer` 所有的实例可以通过`from_pretrained()`加载预训练模型,通过`save_pretrained()`保存模型 BERT example 从字符串获得输入向量 ``` import torch from transformers import BertTokenizer, BertModel, BertForMaskedLM # OPTIONAL: if you want to have more information on what's happening under the hood, activate the logger as follows import logging logging.basicConfig(level=logging.INFO) # Load pre-trained model tokenizer (vocabulary) tokenizer = BertTokenizer.from_pretrained('bert-base-uncased') # 中文bert模型'bert-base-chinese' # bert 特殊的符号 # '[MASK]' 用于mask language model # '[CLS]' 开头,用于分类的标记 # '[PAD]' # '[SEP]' 结尾,句子分隔符 # '[UNK]' # Tokenize input text = "[CLS] Who was Jim Henson ? [SEP] Jim Henson was a puppeteer [SEP]" tokenized_text = tokenizer.tokenize(text) # Mask a token that we will try to predict back with `BertForMaskedLM` masked_index = 8 tokenized_text[masked_index] = '[MASK]' assert tokenized_text == ['[CLS]', 'who', 'was', 'jim', 'henson', '?', '[SEP]', 'jim', '[MASK]', 'was', 'a', 'puppet', '##eer', '[SEP]'] # Convert token to vocabulary indices indexed_tokens = tokenizer.convert_tokens_to_ids(tokenized_text) # Define sentence A and B indices associated to 1st and 2nd sentences (see paper) segments_ids = [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] # Convert inputs to PyTorch tensors tokens_tensor = torch.tensor([indexed_tokens]) segments_tensors = torch.tensor([segments_ids]) ``` 对输入进行编码 ``` # Load pre-trained model (weights) model = BertModel.from_pretrained('bert-base-uncased') # Set the model in evaluation mode to deactivate the DropOut modules # This is IMPORTANT to have reproducible results during evaluation! model.eval() # If you have a GPU, put everything on cuda tokens_tensor = tokens_tensor.to('cuda') segments_tensors = segments_tensors.to('cuda') model.to('cuda') # Predict hidden states features for each layer with torch.no_grad(): # See the models docstrings for the detail of the inputs outputs = model(tokens_tensor, token_type_ids=segments_tensors) # Transformers models always output tuples. # See the models docstrings for the detail of all the outputs # In our case, the first element is the hidden state of the last layer of the Bert model encoded_layers = outputs[0] # We have encoded our input sequence in a FloatTensor of shape (batch size, sequence length, model hidden dimension) assert tuple(encoded_layers.shape) == (1, len(indexed_tokens), model.config.hidden_size) ``` 使用`BertForMaskedLM`预测masked token ``` # Load pre-trained model (weights) model = BertForMaskedLM.from_pretrained('bert-base-uncased') model.eval() # If you have a GPU, put everything on cuda tokens_tensor = tokens_tensor.to('cuda') segments_tensors = segments_tensors.to('cuda') model.to('cuda') # Predict all tokens with torch.no_grad(): outputs = model(tokens_tensor, token_type_ids=segments_tensors) predictions = outputs[0] # confirm we were able to predict 'henson' predicted_index = torch.argmax(predictions[0, masked_index]).item() predicted_token = tokenizer.convert_ids_to_tokens([predicted_index])[0] assert predicted_token == 'henson' ``` 参考 1. --- url: /blog/2019/hexo-UML图介绍.md --- # UML图介绍 ## 1.用例视图 ### 1.1用例图 描述角色以及角色与用例之间的连接关系。说明的是谁要使用系统,以及他们使用该系统可以做些什么。 ## 2.设计视图 ### 2.1类图 根据用例图抽象成类,描述类的内部结构和类与类之间的关系,是一种静态结构图。 在UML类图中,常见的有以下几种关系: 泛化(Generalization), 实现(Realization),关联(Association),聚合(Aggregation),组合(Composition),依赖(Dependency)。 类图是描述系统中的类,以及各个类之间的关系的静态视图。能够让我们在正确编写代码以前对系统有一个全面的认识。类图是一种模型类型,确切的说,是一种静态模型类型。 ### 2.2对象图 描述的是参与交互的各个对象在交互过程中某一时刻的状态。对象图可以被看作是类图在某一时刻的实例。 与类图极为相似,它是类图的实例,对象图显示类的多个对象实例,而不是实际的类。它描述的不是类之间的关系,而是对象之间的关系。 ## 3.进程视图 ### 3.1序列图(顺序图) 交互图的一种,描述了对象之间消息发送的先后顺序,强调时间顺序。 序列图是用来显示你的参与者如何以一系列顺序的步骤与系统的对象交互的模型。顺序图可以用来展示对象之间是如何进行交互的。顺序图将显示的重点放在消息序列上,即强调消息是如何在对象之间被发送和接收的。 ### 3.2协作图 交互图的一种,描述了收发消息的对象的组织关系,强调对象之间的合作关系。时序图按照时间顺序布图,而写作图按照空间结构布图 和序列图相似,显示对象间的动态合作关系。可以看成是类图和顺序图的交集,协作图建模对象或者角色,以及它们彼此之间是如何通信的。如果强调时间和顺序,则使用序列图;如果强调上下级关系,则选择协作图;这两种图合称为交互图。 ### 3.3状态图 是一种由状态、变迁、事件和活动组成的状态机,用来描述类的对象所有可能的状态以及时间发生时状态的转移条件。 描述类的对象所有可能的状态,以及事件发生时状态的转移条件。可以捕获对象、子系统和系统的生命周期。他们可以告知一个对象可以拥有的状态,并且事件(如消息的接收、时间的流逝、错误、条件变为真等)会怎么随着时间的推移来影响这些状态。一个状态图应该连接到所有具有清晰的可标识状态和复杂行为的类;该图可以确定类的行为,以及该行为如何根据当前的状态变化,也可以展示哪些事件将会改变类的对象的状态。状态图是对类图的补充。 ### 3.4活动图 是状态图的一种特殊情况,这些状态大都处于活动状态。本质是一种流程图,它描述了活动到活动的控制流。 描述用例要求所要进行的活动,以及活动间的约束关系,有利于识别并行活动。能够演示出系统中哪些地方存在功能 ## 4.实现视图 ### 4.1构件图 (组件图) 描述代码构件的物理结构以及各种构建之间的依赖关系。用来建模软件的组件及其相互之间的关系,这些图由构件标记符和构件之间的关系构成。在组件图中,构件时软件单个组成部分,它可以是一个文件,产品、可执行文件和脚本等。 ## 5.拓扑视图 ### 5.1部署图 是用来建模系统的物理部署。例如计算机和设备,以及它们之间是如何连接的。部署图的使用者是开发人员、系统集成人员和测试人员。 ## 6.软件设计及过程 在需求阶段:采用用例图来描述需求\ 在分析阶段:采用类图来描述静态结构\ 在设计阶段:采用类图、包图对类的接口进行设计\ 在实现阶段:将类用某个面向对象的语言实现\ 在集成与交付阶段:构件图、包图、部署图\ 在测试阶段:单元测试使用类图和类的规格说明书\ 集成测试阶段使用类图、包图、构件图和合作图\ 系统测试使用用例图来测试系统功能 --- url: /blog/2019/hexo-java注解的使用.md --- # java注解的使用 ### 1.三种标准注解 * @Override,表示当前的方法定义覆盖了父类中的方法。必须要有相同的方法签名即(方法名,参数类型,参数顺序,参数个数)都一样。否则在编译过程中发出错误提示。 * @Deprecated,对不应该再使用的方法添加注解,当使用这个方法的时候,会在编译时候显示提示信息。 * @SuppressWarnings,关闭不当的编译器报警信息 ### 2.四种元注解: * @Target,表示该注解可以用什么地方。如CONSTRUCTOR,构造器声明;FIELD,域声明;METHOD,方法声明;TYPE,类,接口或enum声明 * @Retention,表示需要在什么级别保存该注解信息。如SOURCE,注解将被编译器丢弃;CLASS,注解在class文件可用,但会被VM丢弃RUNTIME,VM将在运行期间也保留注解,可以使用反射机制读取注解信息 * @Documented,将此注解包含到Javadoc中。 * @Inherited,允许子类继承父类的注解。 Java 7开始,额外添加了 3 个注解: * @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生 * @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。 * @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。 --- url: /blog/2019/hexo-mathjax配置问题.md --- # mathjax配置问题 使用hexo时,想要实现网页中公式的渲染\ 发现不管怎么改,都不能渲染单行公式\ 最后发现是在mathjax的2.3版本以后,配置方法变了 mathjax的配置方法 一般网上大部分的mathjax的配置如下 ```html ``` 其中配置中这一句主要是增加对单行公式的渲染 ``` inlineMath: [ ['$','$'], ["\\(","\\)"] ], ``` 有时候会发现无论如修改单行公式总是不能渲染\ 原因是在mathjax的2.3版本以后,应该这样配置 ```html ``` 对于`hexo`默认转义规则使单行公式显示错误的问题,查看[这篇博客](https://ranmaosong.github.io/2017/11/29/hexo-support-mathjax/) 参考资料 1, --- url: /blog/2019/hexo-nvidia显卡驱动错误.md --- # nvidia显卡驱动错误 系统使用`bumblebee`实现双显卡(N卡和集成显卡)切换,\ 同时有N卡的开源驱动nouveau和专有显卡驱动nvidia 使用命令 ``` optirun glxspheres64 # 或者optirun glxspher ``` 出现错误`[XORG] (EE) Failed to load module "nouveau"`\ 通过命令`lsmod |grep nouveau`\ 显示模块已经加载 通过`/etc/modprobe.d/blacklist.conf`文件禁用`nouveau`(需要重建内核`mkinitcpio -P`并重启),但`nouveau`驱动仍然加载了,`nvidia`未加载 解决方法修改文件`/etc/bumblebee/xorg.conf.nouveau`\ 去掉`BusID "PCI:01:00:0"`的注释\ `PCI:01:00:0`通过命令`lspci | egrep (3D|VGA)` 重启后`nvidia`驱动加载成功,`nouveau`没有加载 --- url: /blog/2019/hexo-pytorch使用和损失函数.md --- # pytorch使用和损失函数 # 激励函数 ```python #激活函数 x=np.arange(-12.5,12.5,0.05) tanh = (np.power(np.e,x)-np.power(np.e,-x))/(np.power(np.e,x)+np.power(np.e,-x)) relu = np.maximum(0.0,x) sigmoid = 1.0/(1.0+np.power(np.e,-x)) torch.nn.Sigmoid() torch.nn.Tanh() ``` BatchNorm2d 对每一个特征进行正则 pytorch中的正则化函数 ```python torch.nn.functional.normalize(input, p=2, dim=1, eps=1e-12, out=None) torch.norm(input, p='fro', dim=None, keepdim=False, out=None, dtype=None) # 对维度dim求p范数 torch.Tensor.squeeze()->Tensor#维度压缩 torch.cat(tensors, dim=0, out=None)->Tensor #维度拼接 torch.stack(tensors,dim=0,out=None)->Tensor #张量拼接 # cat是把多张纸拼成一张纸,stack是把纸摞起来 torch.Tensor.repeat()->Tensor #矩阵扩展 torch.Tensor.transpose()->Tensor #矩阵转置 torch.eq() #张量比较 torch.chunk() #张量分块 torch.split(tensor, split_size_or_sections, dim=0) #张量分块 ``` # 损失函数 L1Loss ```python torch.nn.L1Loss(size_average=None, reduce=None, reduction='mean') ``` MSELoss ```python torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean') ``` CrossEntropyLoss 交叉熵损失函数,多分类 ```python torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean') torch.nn.CTCLoss(blank=0, reduction='mean', zero_infinity=False) ``` NLLLoss 负对数似然损失函数(Negative Loss Likelihood),多分类 ```python torch.nn.NLLLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean') ``` PoissonNLLLoss ```python torch.nn.PoissonNLLLoss(log_input=True, full=False, size_average=None, eps=1e-08, reduce=None, reduction='mean') ``` KLDivLoss KL散度,又叫相对熵,计算两个分布之间的距离,越相近越接近零 ```python torch.nn.KLDivLoss(size_average=None, reduce=None, reduction='mean') ``` BCELoss 二分类用的交叉熵 ```python torch.nn.BCELoss(weight=None, size_average=None, reduce=None, reduction='mean') ``` BCEWithLogitsLoss 增加了一个Sigmoid层 ```python torch.nn.BCEWithLogitsLoss(weight=None, size_average=None, reduce=None, reduction='mean', pos_weight=None) ``` MarginRankingLoss 评价相似度的损失 ```python torch.nn.MarginRankingLoss(margin=0.0, size_average=None, reduce=None, reduction='mean') ``` HingeEmbeddingLoss 用于学习非线性嵌入或半监督学习 ```python torch.nn.HingeEmbeddingLoss(margin=1.0, size_average=None, reduce=None, reduction='mean') ``` MultiLabelMarginLoss 多类别多分类的Hinge损失 ```python torch.nn.MultiLabelMarginLoss(size_average=None, reduce=None, reduction='mean') ``` 其中$ x \in \lbrace 0, ; \cdots , ; \text{x.size}(0) - 1 \rbrace $, $ y \in \lbrace 0, ; \cdots , ; \text{y.size}(0) - 1 \rbrace $, $ 0 \leq y\[j] \leq \text{x.size}(0)-1 $,$i \neq y\[j] $ SmoothL1Loss 也叫Huber Loss,误差在(-1,1)上是平方损失,其他情况是L1损失 ```python torch.nn.SmoothL1Loss(size_average=None, reduce=None, reduction='mean') ``` SoftMarginLoss 多标签二分类问题 ```python torch.nn.SoftMarginLoss(size_average=None, reduce=None, reduction='mean') ``` MultiLabelSoftMarginLoss 多标签多分类 ```python torch.nn.MultiLabelSoftMarginLoss(weight=None, size_average=None, reduce=None, reduction='mean') ``` 其中$ i \in \lbrace 0, ; \cdots , ; \text{x.nElement}() - 1\rbrace $, $ y\[i] \in \lbrace 0, ; 1 \rbrace $ CosineEmbeddingLoss 余玄相似度损失 ```python torch.nn.CosineEmbeddingLoss(margin=0.0, size_average=None, reduce=None, reduction='mean') ``` MultiMarginLoss 多分类的Hinge损失 ```python torch.nn.MultiMarginLoss(p=1, margin=1.0, weight=None, size_average=None, reduce=None, reduction='mean') ``` 其中$x \in \lbrace 0, ; \cdots , ; \text{x.size}(0) - 1 \rbrace $,$ i \neq y $ TripletMarginLoss ```python torch.nn.TripletMarginLoss(margin=1.0, p=2.0, eps=1e-06, swap=False, size_average=None, reduce=None, reduction='mean') ``` --- url: /blog/2019/hexo-sklearn中的广义线性模型.md --- # sklearn中的广义线性模型 模型的通用公式 ```math \hat y(w,x)=w_0+w_1x_1+\dots+w_px_p ``` 普通最小二乘法 ```math w=\min_w{\Vert Xw-y\Vert_2}^2 ``` LinearRegression 岭回归 ```math w=\min_w{\Vert Xw-y\Vert_2}^2+\alpha {\Vert w \Vert_2}^2 ``` $ \alpha $ 是控制系数收缩量的复杂性参数: $ \alpha $ 的值越大,收缩量越大,模型对共线性的鲁棒性也更强。 共线性:线性回归模型中的解释变量之间由于存在精确相关关系或高度相关关系而使模型估计失真 Ridge,RigdeCV:广义交叉验证(GCV),默认留一验证(LOO-CV) Lasso ```math w = \min_w\frac{1}{2n_{samples}}\Vert Xw-y \Vert_2^2 + \alpha \Vert w \Vert_1 ``` $ \alpha $ 是常数,$ \Vert w \Vert \dot 1 $ 是参数向量的$ l \dot {1-norm} $范数 Lasso,lasso\_path:通过搜索所有可能的路径上的值来计算系数 LassoCV,LassoLarsCV,LassoLarsIC 多任务Lasso ```math w = \min_w\frac{1}{2n_{samples}}\Vert XW-Y\Vert_{Fro}^2+\alpha\Vert W \Vert_{21} ``` ```math \Vert A \Vert_{Fro}=\sqrt{\sum_{ij}a_{ij}^2} ``` ```math \Vert A \Vert_{21}= \sum_i\sqrt{\sum_j a_{ij}^2} ``` MultiTaskLasso 弹性网络 ```math w = \min_w\frac{1}{2n_{samples}}\Vert Xw-Y\Vert_2^2+\alpha\rho\Vert w \Vert_{1}+\frac{\alpha(1-\rho)}{2}\Vert w\Vert_2^2 ``` ElasticNetCV通过交叉验证来设置参数`alpha`($ \alpha $)和`l1_rati0`($ \rho $) 多任务弹性网络 ```math W = \min_W\frac{1}{2n_{samples}}\Vert XW-Y\Vert_{Fro}^2+\alpha\rho\Vert W \Vert_{21}+\frac{\alpha(1-\rho)}{2}\Vert w\Vert_{Fro}^2 ``` MultiTaskElasticNet --- url: /blog/2019/hexo-交叉熵损失的反向传播.md --- # 交叉熵损失的反向传播 对于一个单标签多分类问题,假设网络的输出层的输入为$Z\_{in}=\[z\_1,\cdots,z\_i,\cdots,z\_n]$, 输出为$\hat Y=\[\hat y\_1,\cdots,\hat y\_i,\cdots,\hat y\_n]$, 真实类标签为$Y = \[y\_1,\cdots,y\_i,\cdots,y\_n]$,$n$为类别数(输出层神经元数),通常有: ```math \hat Y = Softmax(Z_{in})\tag{1} ``` ```math \hat y_i = \frac {e^{z_i}}{\sum_{j=0}^n e^{z_j}}\tag{2} ``` 其中$Softmax$为: ```math Softmax(Z_{in}) = [\cdots,\frac {e^{z_i}}{\sum_{j=1}^n e^{z_j}},\cdots]\tag{3} ``` ## 交叉熵损失 ```math Loss(Y,\hat Y) = -\sum_{i=1}^ny_i*\ln(\hat y_i)\tag{4} ``` 损失对神经网络输出的偏导([标量对向量求偏导](https://www.vhcffh.com/2019/矩阵中的求导/))为: ```math \frac {\partial Loss(Y,\hat Y)}{\partial \hat Y} = [-\frac {y_1}{\hat y_1},\cdots,-\frac {y_i}{\hat y_i},\cdots,-\frac {y_n}{\hat y_n}]\label{5}\tag{5} ``` 后向传播推导中遇到的所有量都是变量,最终的目的是找到损失关于某变量的偏导,程序中也只用这个公式求得对应输入点的梯度 ## Softmax的偏导 求$\hat y\_i​$对$z\_i​$的偏导,根据{2}可得: 求$\hat Y$对$Z$的偏导([向量对向量求导](https://blog.vhcffh.com/hexo-ju-zhen-zhong-de-qiu-dao/)) 这里把$y$的坐标写作$k$ $k=i$时: ```math \begin{split} \frac {\partial \hat y_i}{\partial z_i} &= e^{z_i} * \frac {1}{\sum_{j=1}^ne^{z_j}} + e^{z_i} * (-\frac {1}{(\sum_{j=1}^n e^{z_j})^2}) * e^{z_i} \cr &= \frac {e^{z_i}}{\sum_{j=1}^n e^{z_j}} - (\frac {e^{z_i}}{\sum_{j=1}^n e^{z_j}})^2 \cr &= \hat y_i - \hat y_i^2 \end{split} \tag{6} ``` $k \neq i$时: ```math \begin{split} \frac {\partial \hat y_k}{\partial z_i} &= e^{z_k} * (-\frac {1}{(\sum_{j=1}^n e^{z_j})^2}) * e^{z_i} \cr &= -\frac {e^{z_k}*e^{z_i}}{(\sum_{j=1}^n e^{z_j})^2} \cr &= - \hat y_k\hat y_i \end{split} \tag{7} ``` 写成矩阵: ```math \frac {\partial \hat Y}{\partial Z_{in}} = \begin{bmatrix} \hat y_1-\hat y_1^2 & -\hat y_2\hat y_1 & \cdots & -\hat y_n\hat y_1\cr -\hat y_1\hat y_2 & \hat y_2-\hat y_2^2 & \cdots & -\hat y_n\hat y_2\cr \vdots & \vdots & \ddots & \vdots\cr -\hat y_1\hat y_n & -\hat y_2\hat y_n & \cdots & \hat y_n-\hat y_n^2\cr \end{bmatrix}\tag{8} ``` 这是一个对称矩阵,在链式求导时加不加转置都一样 根据(5)和(8),损失$L$对输入$Z$的偏导([标量对向量求偏导](https://blog.vhcffh.com/hexo-ju-zhen-zhong-de-qiu-dao/)): ```math \begin{align} \frac {\partial L(Y,\hat Y)}{\partial Z} &= \frac {\partial L(Y,\hat Y)}{\partial \hat Y} (\frac {\partial \hat Y}{\partial Z})^T\cr &= [-\frac {y_1}{\hat y_1},\cdots,-\frac {y_i}{\hat y_i},\cdots,-\frac {y_n}{\hat y_n}] \begin{bmatrix} \hat y_1-\hat y_1^2 & -\hat y_2\hat y_1 & \cdots & -\hat y_n\hat y_1\cr -\hat y_1\hat y_2 & \hat y_2-\hat y_2^2 & \cdots & -\hat y_n\hat y_2\cr \vdots & \vdots & \ddots & \vdots\cr -\hat y_1\hat y_n & -\hat y_2\hat y_n & \cdots & \hat y_n-\hat y_n^2\cr \end{bmatrix}^T\cr &= [(\hat y_1-1)y_1+\hat y_1y_2+\cdots+\hat y_1y_n,\hat y_2y_1+(\hat y_2-1)y_2+\cdots+\hat y_1y_n,\cdots]\cr &= [\hat y_1\sum_{i=1}^ny_i-y_1,\cdots,\hat y_j\sum_{i=1}^ny_i-y_j,\cdots,\hat y_n\sum_{i=1}^ny_i-y_n]\cr &= [\hat y_1-y_1,\cdots,\hat y_j-y_j,\cdots,\hat y_n-y_n]\ \ \ \ (\sum_{i=1}^ny_i=1)\cr &= \hat Y-Y \end{align} ``` --- url: /blog/2019/hexo-优化算法总结.md --- # 优化算法总结 ### AdaGrad-Adaptive-Gradient-自适应梯度 ```math g_t = \nabla_\theta J(\theta_{t-1}) ``` ```math \theta_{t+1} = \theta_t-\alpha\cdot g_t/\sqrt{\sum_{i=1}^tg_t^2} \ \ \ \ \ (\alpha=0.01) ``` 随梯度的变化改变学习率 ### RMSProp ```math g_t = \nabla_\theta J(\theta_{t-1}) ``` ```math v_t= \gamma(v_{t-1})+(1-\gamma)g_t^2 \ \ \ \ \ (\gamma=0.9) ``` ```math \theta_{t} = \theta_{t-1}-\alpha* g_t/(\sqrt v_t + \varepsilon) \ \ \ \ \ (\alpha=0.001,\varepsilon=10^{-8}) ``` 结合梯度平方的指数移动平均数来调节学习率的变化 克服AdaGrad梯度急剧减小的问题 ### Adam优化器 ```math g_t = \nabla_\theta J(\theta_{t-1}) ``` ```math m_t = \beta_1m_{t-1}+(1-\beta_1)g_t \ \ \ \ \ (\beta_1=0.9,m_0=0) ``` ```math v_t=\beta_2v_{t-1}+(1-\beta_2)g_t^2 \ \ \ \ \ (\beta_2=0.999,v_0=0) ``` ```math \hat m_t = m_t/(1-\beta_1^t) ``` ```math \hat v_t = v_t/(1-\beta_2^t) ``` ```math \theta_t = \theta_{t-1}-\alpha * \hat m_t/(\hat v_t + \varepsilon) \ \ \ \ \ (\alpha=0.001,\varepsilon=10^{-8}) ``` 对梯度的一阶矩估计(First Moment Estimation,即梯度的均值)和二阶矩估计(Second Moment Estimation,即梯度的未中心化的方差)进行综合考虑,计算出更新步长。 ### 下面是对几种梯度下降算法的模拟 其中包括随机梯度下降算法SGD,小批量梯度下降算法MSGD,批量梯度下降算法BGD 都是根据损失函数计算参数梯度,更新参数;不一样的地方是他们使用使用训练集的方式 ```python # 随机梯度下降算法SGD x=np.random.rand(10000,5) w=np.array([[1,2,3,4,5],[20,30,40,50,60]]) b=np.array([[10],[100]]) y=(w.dot(x.T)+b).T #100,2 lr=0.5 ww = np.random.rand(2,5) bb = np.random.rand(2,1) mb=master_bar(range(1000000)) mb.names=['loss'] lossy=[] for i in mb: r=np.random.randint(1000) xi=x[r:r+1,:] #1,5 yi=y[r:r+1,:] #1,2 yy = ww.dot(xi.T)+bb #2,1 loss = np.mean(np.power(yy-yi.T,2)/2) lossy.append(loss) graphs = [[np.arange(len(lossy)),lossy]] mb.update_graph(graphs) ww= ww - lr*(yy-yi.T).dot(xi) bb= bb - lr*(yy-yi.T) if loss < 1e-10: break #print(loss) print(ww,bb) print(w,b) # 小批量梯度下降算法MSGD x=np.random.rand(1000,5) w=np.array([[1,2,3,4,5],[20,30,40,50,60]]) b=np.array([[10],[100]]) y=(w.dot(x.T)+b).T #100,2 lr=0.3 ww = np.random.rand(2,5) bb = np.random.rand(2,1) mb=master_bar(range(200*3)) mb.names=['loss'] lossy=[] batch = 50 for i in mb: lossg,wwg,bbg=0,0,0 for j in progress_bar(range(batch),parent=mb): r=np.random.randint(1000) xi=x[r:r+1,:] #1,5 yi=y[r:r+1,:] #1,2 yy = ww.dot(xi.T)+bb #2,10 lossg += np.mean(np.power(yy-yi.T,2)/2) wwg += (yy-yi.T).dot(xi) bbg += (yy-yi.T) lossy.append(lossg/batch) graphs = [[np.arange(len(lossy)),lossy]] mb.update_graph(graphs) ww= ww - lr*wwg/batch bb= bb - lr*bbg/batch if lossg/batch < 1e-4: break #print(loss) print(ww,bb) print(w,b) # 批量梯度下降算法BGD x=np.random.rand(1000,5) w=np.array([[1,2,3,4,5],[20,30,40,50,60]]) b=np.array([[10],[100]]) y=(w.dot(x.T)+b).T #100,2 lr=0.3 #ww = np.random.rand(2,5) #bb = np.random.rand(2,1) mb=master_bar(range(200*3)) mb.names=['loss'] lossy=[] batch = 1000 for i in mb: lossg,wwg,bbg=0,0,0 for j in progress_bar(range(batch),parent=mb): r=j xi=x[r:r+1,:] #1,5 yi=y[r:r+1,:] #1,2 yy = ww.dot(xi.T)+bb #2,10 lossg += np.mean(np.power(yy-yi.T,2)/2) wwg += (yy-yi.T).dot(xi) bbg += (yy-yi.T) lossy.append(lossg/batch) graphs = [[np.arange(len(lossy)),lossy]] mb.update_graph(graphs) ww= ww - lr*wwg/batch bb= bb - lr*bbg/batch if lossg/batch < 1e-4: break #print(loss) print(ww,bb) print(w,b) ``` --- url: /blog/2019/hexo-信息论的一些基本概念.md --- # 信息论的一些基本概念 **信息熵** ```math H(X)=E[I(X)]=E[-ln(P(X))] ``` 其中$ P $ 为$ X $的概率质量函数,$ E $为期望函数,而$ I(x) $是$ X $的信息量(又称自信息). ```math H(X)=\sum_iP(x_i)I(x_i)=-\sum_iP(x_i)\log_bP(x_i) ``` ```math \begin{matrix} b & S\cr 2 & bit\cr e & nat\cr 10 & Hart \end{matrix} ``` **条件熵(Conditional Entropy)** 特征$ x $ 固定为$ x\_i $时:$ H(c|x\_i) $ 特征$ x$ 整体分布已知时:$ H(x|X) $ **信息增益(Information Gain)** ```math IG(X) = H(c)-H(c|X) ``` **基尼系数(基尼不纯度Gini impurity)** ```math Gini(D)=1-\sum_i^np_i^2 ``` ```math Gini(D|A)=\sum_i^n\frac {D_i}{D} ``` **信息增益比率(Information Gain Ratio)与分裂信息(Split information)** ```math GR(D|A)=\frac {IG(D|A)}{SI(D|A)} ``` ```math SI(D|A)=-\sum_i^n\frac {N_i}{N}\log_2\frac{N_i}{N} ``` **边界熵(boundary entropy)** ```math BE(w_1w_2\cdots w_k) = -\sum_{w \in C}p(w\vert w_1w_2\cdots w_k)\log p(w\vert w_1w_2\cdots w_k) ``` $ w $是邻接于$ w\_1w\_2 \cdots w\_k $ 的字符. **边界多样性(Accessor veriety,AV)** ```math AV(w_1w_2\cdots w_k)=\log RL_{av}(w_1w_2\cdots w_k) ``` $ RL\_{av} $ 表示邻接于字符串$ w\_1w\_2 \cdots w\_k $的不同字符个数. --- url: /blog/2019/hexo-基于统计的分词方法.md --- # 基于统计的分词方法 ## 1.n元语言模型(n-gram) 假设$S$表示长度为$i$,由$(w\_1,w\_2,\dots,w\_m)$字序列组成的句子,则代表$S​$的概率为: ```math P(S) = P(w_1,w_2,\dots,w_m) = P(w_1)*P(w_2|w_1)*P(w_3|w_2,w_1)\cdots P(w_i|w_1,w_2,\dots,w_{m-1}) ``` * $n=1$,uni-gram ```math P(w_1,w_2,\dots,w_m) =\prod_{i=1}^mP(w_i) ``` * $n=2$,bi-gram ```math P(w_1,w_2,\dots,w_m) =\prod_{i=1}^mP(w_i|w_{i-1}) ``` * $n=3$,tri-gram ```math P(w_1,w_2,\dots,w_m) =\prod_{i=1}^mP(w_i|w_{i-2}w_{i-1}) ``` ## 2.基于HMM的分词 **HMM的参数** 观测序列(输出状态序列),状态序列(隐藏状态序列),初始概率,转移概率(转移概率矩阵),发射概率(发射概率矩阵) ![](./imgs/9_8_ma.png) **数学定义** 状态值集合(隐藏状态):$Q={q\_1,q\_2,\cdots,q\_N}$,$N$为可能的状态数,对应状态序列$I​$ 观测值集合(输出状态):$V={v\_1,v\_2,\cdots,v\_M}$,$M$为可能的观测数,对应观测序列$O$ 转移概率矩阵:$A=\[a\*{ij}];i,j\in{1,2,\cdots,N}$,从$i$状态到$j$状态的转换概率($\sum\*{j=1}^Na\_{ij}=1$) 发射概率矩阵(观测概率矩阵):$B=\[b\_j(k)];j\in{1,2,\cdots,N},k\in{1,2,\cdots,M}$,从状态$j$生成观测$k$的概率 初始状态分布:$\pi$ 模型:$\lambda=(A,B,\lambda)$,状态序列$I$,观测序列$O$ **HMM中的三个问题:** * 概率计算问题:已知模型,求观测序列$O$出现的概率;前向后向算法\\ * 学习问题:已知观测序列,求模型参数,最大化$P(O|\lambda)$;鲍姆-韦尔奇(Baum-Welch)算法 * 已知隐藏序列和观测序列(通过频数估计) $A\_{ij}$表示隐藏状态$q\_i$转移到$q\_j$的频率计数 ```math A=[a_{ij}];a_{ij}=\frac{A_{ij}}{\sum_{s=1}^NA_{is}} ``` $B\_{jk}$表示隐藏状态$q\_j$转移到观测状态$v\_k$的频率计数 ```math B=[b_j(k)];b_j(k)=\frac{B_{jk}}{\sum_{s=1}^MB_{js}} ``` $C(i)$为所有样本中初始隐藏状态$q\_j$的频率计数 ```math \Pi = \pi(i)=\frac{C(i)}{\sum_{s=1}^NC(s)} ``` * 仅知观测序列(鲍姆-韦尔奇算法,EM算法) EM算法:最大似然估计用于没有隐变量的概率模型,EM算法可以用于有隐变量的算法模型 模型参数: ```math \overline{\lambda} = arg\;\max_{\lambda}\sum\limits_{I}P(I|O,\overline{\lambda})logP(O,I|\lambda) ``` * 解码问题:已知模型$\lambda$与观测序列$O$,求状态序列$I$最大化$P(I|O)$;维特比(Viterbi)算法 HMM是一种序列模型,不仅可以用到自然语言处理这个领域,其他领域的应用也很常见:[股指预测](http://tecdat.cn/%e7%94%a8%e6%9c%ba%e5%99%a8%e5%ad%a6%e4%b9%a0%e8%af%86%e5%88%ab%e4%b8%8d%e6%96%ad%e5%8f%98%e5%8c%96%e7%9a%84%e8%82%a1%e5%b8%82%e7%8a%b6%e5%86%b5-%e9%9a%90%e9%a9%ac%e5%b0%94%e7%a7%91%e5%a4%ab/),[语音识别](https://www.jianshu.com/p/16fc3712fdf6),[网络安全](http://www.c-a-m.org.cn/CN/abstract/abstract4637.shtml),[基因序列](https://arxiv.org/abs/1901.06286)等方面 在自然语言处理中,HMM可以应用在分词,词性标注,命名实体识别等各个方面. 在分词方面可以这样理解HMM 观测序列(输出状态序列)----序列构成的句子或短文 状态序列(隐藏状态序列)----标注 初始概率----统计的第一个字序的概率 转移概率(转移概率矩阵)----第$i$个字序到第$i+1$个自序的标注变换概率 发射概率(发射概率矩阵)----从隐藏状态到输出状态的转换概率 如果把观测序列看作标注,状态序列看作句子,从解码问题转变成学习问题会怎样 在序列预测问题中 HMM模型:当前tag仅依赖前一个tag,当前输出仅依赖当前tag(文档的单词序列是由隐藏状态的标签决定的) MEMM(最大熵马尔科夫模型)模型:当前tag取决于观察值x(单词)和前一个tag(序列的标签取决于前一个标签和当前的单词) CRF模型:计算损失时把一句话看作一个整体计算损失 ## 参考资料 1. 2. 3. 4. --- url: /blog/2019/hexo-小样本学习的边缘标签图神经网络.md --- # 小样本学习的边缘标签图神经网络 ### Edge-Labeling Graph Neural Network for Few-shot Learning\[[原文](https://arxiv.org/abs/1905.01436)] 小样本学习的边缘标签图神经网络 将样本映射到图中的节点,边表示节点之间的相似度,通过已知样本预测未知样本 #### Graph Neural Network 主要利用邻域聚合框架(通过对相邻节点特征的递归聚合和转换,计算出节点特征)进行表征学习 #### Edge-Labeling Graph **Correlation clustering** 相关性聚类分析:一个通过同时最大化簇内相似性和簇间差异性来实现边标注推理的图分割算法 * 通过结构化向量机实现名词短语聚类和新闻文章聚类 #### Few-Shot Learning C-way K-shot:一个meta-task,包括support set随机抽取C个类别,每个类别K个样本(共CK个数据);和batch set从这C各类别种抽取一批样本. Mode Based, Metric Based,Optimization Based #### Problem definition: Few-shot classification * 在每个类只有很少样本的情况下学习一个分类器 * 每一个few-shot分类任务$ \mathcal T $包括support set $ \mathcal S $和query set $ \mathcal Q $ * episodic training:在training task中抽样,模拟少样本测试时的场景 * $ \mathcal T = \mathcal S \cup \mathcal U $, $ \mathcal S = {(x\_i,y\_i)}\_{i=1} ^{N \times K} $ and $ \mathcal Q={(x\_i,y\_i)}\_{i=N \times K + 1}^{N \times K+T} $ * $x\_i,y\_i\in{C\_1,...,C\_N}=\mathcal C\*{\mathcal T}\subset\mathcal C$同时$\mathcal C\*{train}\cap\mathcal C\*{test} = \empty$ #### Model 1. 通过卷积神经网络提取样本特征,$ \theta\_{emb} $是卷积网络的参数 ```math \mathbf v_i^0=f_{emb}(x_i;\theta_{emb}) ``` 2. 图构建:$ \mathcal g=(\mathcal V,\mathcal E;\mathcal T) $每一个节点代表一个样本,全连接图,每一条边代表一种关系种类 其中$ \mathcal V :={V\_i}\_{i=1,\dots,|\mathcal T|}$,$\mathcal E :={E\*{ij}}\*{i,j=1,\dots,|\mathcal T|}$, 1. ground truth: edge-label 2. 边特征$\mathbf e\*{ij}={e\*{ijd}}^2\_{d=1} \in \[0,1]^2$是一个二维矩阵,表示两个之节点间的簇内关系和簇间关系的强弱,||表示连接 3. 传播:设从$\ell -1 $层得到的特征为 $ \mathbf v\*i^{\ell-1}$ 和$\mathbf e\*{ij}^{\ell-1}$ 1. 更新节点特征 其中$ \tilde e\_{ij1}^{\ell-1}=\frac {e\_{ijd}}{\sum\_ke\_{ikd}}$,$f\_v^\ell(\theta)$是节点特征的转变网络(MLP). 2. 更新边特征 其中 $f\_e^\ell$是metric network计算节点相似度 4. 输出结果:节点属于某个集合的分布$P(y\_i=\mathcal C\_k|\mathcal T)=p\_i^{(k)}$ #### Training 其中模型参数有$ \theta\*{emb} \cup{\theta\_v^\ell,\theta\_e^\ell}\_{\ell=1}^L $,对于M个训练任务${ \mathcal T\_m^{train}}\_{m=1}^M $,损失函数: 其中$Y\*{m,e}$和$\hat Y\*{m,e}^\ell$是m个任务,e条边的真实值和预测值(第$\mathcal L$层的),$\mathcal L$是交叉熵损失函数 --- url: /blog/2019/hexo-常见损失函数.md --- # 常见损失函数 1.损失函数 > **损失函数**是指一种将一个事件(在一个[样本](https://wiki.mbalib.com/wiki/%E6%A0%B7%E6%9C%AC)空间中的一个元素)映射到一个表达与其事件[相关](https://wiki.mbalib.com/wiki/%E7%9B%B8%E5%85%B3)的[经济成本](https://wiki.mbalib.com/wiki/%E7%BB%8F%E6%B5%8E%E6%88%90%E6%9C%AC)或[机会成本](https://wiki.mbalib.com/wiki/%E6%9C%BA%E4%BC%9A%E6%88%90%E6%9C%AC)的实数上的一种函数,较常运用在[统计学](https://wiki.mbalib.com/wiki/%E7%BB%9F%E8%AE%A1%E5%AD%A6),[统计决策理论](https://wiki.mbalib.com/wiki/%E7%BB%9F%E8%AE%A1%E5%86%B3%E7%AD%96%E7%90%86%E8%AE%BA)和[经济学](https://wiki.mbalib.com/wiki/%E7%BB%8F%E6%B5%8E%E5%AD%A6)中。损失函数参数的真值为(θ),[决策](https://wiki.mbalib.com/wiki/%E5%86%B3%E7%AD%96)的结果为*d* > ,两者的不一致会带来一定的损失,这种损失是一个[随机变量](https://wiki.mbalib.com/wiki/%E9%9A%8F%E6%9C%BA%E5%8F%98%E9%87%8F),用*L*(θ,*d*)表示。 1.1 0-1损失函数(0-1 loss function) ```math L(Y,f(X))= \begin{cases} 1,Y \neq f(X)\cr 0,Y = f(X) \end{cases} ``` 1.2平方损失函数(quadratic loss function) ```math L(Y,f(X))=(Y-f(X))^2 ``` 1.3绝对损失函数(absolute loss function) ```math L(Y,f(X))=|Y-f(X)| ``` 1.4对数损失函数(logarithmic loss function)或对数似然损失函数(log likelihood loss function) ```math L(Y,P(Y|X))=-logP(Y|X) ``` 2.风险函数 **风险函数**是损失函数的期望值,表示为: ```math R(\theta,d)=E[L(d,\theta)] ``` 决策的目标是要找出一个决策方案$d$,使其对各个自然状态风险值均为最小。应用时,常常对θ(参数的真值)确定一个概率分布,并使其平均的风险值$r(d,\theta)$达到最小,其中: ```math r(d,\theta) = E[R(d,\theta)]=\sum_{j=1}^LR(d,\theta)p(\theta_j) ``` 有结构风险函数和经验风险函数 参考资料 1, --- url: /blog/2019/hexo-数据可视化开源项目.md --- # 数据可视化开源项目 ## 1.[D3](https://d3js.org/) github地址: ![d3](./imgs/8_10_d3.png) ## 2.[chartjs](https://www.chartjs.org) github地址: ![charts](./imgs/8_10_charts.png) ## 3.[leafletjs](https://leafletjs.com/examples.html) github地址: 主要是移动端地图的支持 ![leafletjs](./imgs/8_10_leafletjs.png) ## 4.[echarts](https://echarts.baidu.com/examples/#chart-type-line) 百度家的 github地址: ![echarts](./imgs/8_10_echarts.png) ## 5.[Chartist-js](https://gionkunz.github.io/chartist-js/examples.html) ## 6.[sigmajs](http://sigmajs.org/) ## 7.[metricsgraphicsjs](https://metricsgraphicsjs.org/examples.htm) ## 8.[DC.js,开源(D3)](https://dc-js.github.io/dc.js/) ### 9.[阿里数据可视化](https://antv.alipay.com/zh-cn/index.html) [G2](https://antv.alipay.com/zh-cn/g2/3.x/demo/index.html),[G6](https://antv.alipay.com/zh-cn/g6/2.x/demo/index.html),[F2](https://antv.alipay.com/zh-cn/f2/3.x/demo/index.html),[L7](https://antv.alipay.com/zh-cn/l7/1.x/demo/index.html) --- url: /blog/2019/hexo-机器学习笔记.md --- # 机器学习笔记 ## 分类 准确率:所有样本中预测正确的占比 ```math accuracy =\frac {TP+TN}{TP+TN+FP+FN} = \frac {T}{T+F} ``` 精确率:**预测为正的样本**中真正的正样本占比 ```math precision = \frac {TP}{TP+FP} = \frac {TP}{P'} ``` 召回率:**正样本**中预测为正的占比 ```math recall = \frac{TP}{TP+FN} = \frac{TP}{P} ``` F1:精确率和召回率的**调和均值** ```math \begin{align*} \frac{2}{F_1} & = \frac{1}{precision} + \frac{1}{recall}\cr F_1 & = \frac{2\cdot precision \cdot recall}{precision+recall}\cr F_1 & = \frac{2TP}{2TP + FP + FN} \cr F_1 & = \frac{2TP}{P' + P} \cr \end{align*} ``` F-score: ```math F_{score}=(1+\beta^2)\cdot \frac{precision \cdot recall}{\beta^2\cdot precision + recall} ``` | | P | N | | --- | --- | --- | | P' | TP | FP | | N' | FN | TN | ## 序列 BLEU(Bilingual Evaluation understudy) ```math CP_n(C,S)=\frac {\sum_i\sum_k\min(h_k(c_i),max_{j \in m}h_k(s_{ij}))}{\sum_i\sum_kh_k(c_j)} ``` **惩罚因子BP(Brevity Penalty)** ```math b(C,S)=\begin{cases} 1, &l_c \lt l_s \cr e^{1-\frac{l_s}{l_c}}, &l_c \geq l_s \end{cases} ``` ```math BLEU_N(C,S)=b(C,S)\exp(\sum_{n=1}^N\omega_n\log CP_n(C,S)) ``` 机器翻译 ROUGE(Recall-Oriented Understudy for Gisting Evaluation) | ROUGE-N |基于N-gram公现性统计| | --------- |------------------------------------------------------| | ROUGE-L |基于最长公有子句共现性精确度和召回率Fmeasure统计| | ROUGE-W |代权重的最长公有子句共现性精确度和召回率Fmeasure统计| | ROUGE-S |不连续二元组共现性精确度和召回率Fmeasure统计| **ROUGE-N** ```math ROUGE-N=\frac {\sum_{S \in ReferencesSummaries}\sum_{gram_n\in S}Count_{match}(gram_n)} {\sum_{S \in ReferencesSummaries}\sum_{gram_n\in S}Count(gram_n)} ``` **ROUGE-L** 最长公共子句longest common subsequence(LCS) ```math R_{lcs}=\frac {LCS(X,Y)}{m} ,m=len(X) ``` ```math P_{lcs}=\frac {LCS(X,Y)}{n} ,n=len(Y) ``` ```math F_{lcs}=\frac{(1+\beta^2)R_{lcs}P_{lcs}}{R_{lcs}+\beta^2P_{lcs}} ``` --- url: /blog/2019/hexo-深度学习中Keras-vs-Pytorch.md --- # 深度学习中 Keras vs Pytorch 深度学习框架 Keras 与 Pytorch 的区别与优劣[翻译](https://towardsdatascience.com/keras-vs-pytorch-for-deep-learning-a013cb63870d)\ ![img](./imgs/7_11_pytorchvskears.png "Keras vs PyTorch") 对于许多科学家,工程师和开发人员来说,TensorFlow 是他们的第一个深度学习框架。 TensorFlow 1.0 于 2017 年 2 月发布;但它对用户来说不是很友好。 在过去几年中,两个主要的深度学习库已经获得了巨大的普及,主要是因为它们比 TensorFlow 更容易使用:**Keras**和**Pytorch**。 本文将介绍 Keras 与 Pytorch 的 4 个不同点,以及为什么您可以选择一个库而不是另一个库。 ## Keras Keras 本身不是一个框架,但实际上是一个位于其他 Deep Learning 框架之上的高级 API。目前它支持[TensorFlow](https://www.tensorflow.org/),[Theano](http://deeplearning.net/software/theano/)和[CNTK](https://github.com/microsoft/CNTK)。 Keras 的优势在于它的易用性。它是迄今为止最容易去快速启动和运行的框架。定义神经网络非常直观,使用功能 API 允许人们将层定义为函数。 ## Pytorch Pytorch 是由 Facebook 的 AI 研究小组开发的深度学习框架(如 TensorFlow)。像 Keras 一样,它也对深度网络编程中比较复杂的一大部分进行了抽象。 在高级和低级编码风格方面,Pytorch 位于 Keras 和 TensorFlow 之间。你比 Keras 有更多的灵活性和控制力,但与此同时你不必做任何疯狂的声明性编程。 深度学习练习者整天都在争论应该使用哪个框架。一般来说,这取决于个人喜好。但是在选择时你应该记住 Keras 和 Pytorch 的一些特性。 ![img](./imgs/7_10_keras.jpeg "It's keras") ![img](./imgs/7_10_pytorch.jpeg "It's Pytorch!") ## (1)用于定义模型的 类(Pytorch) vs 函数(Keras) 要定义深度学习模型,Keras 提供 Functional API。 使用 Functional API,神经网络被定义为一组顺序函数,一个接一个地应用。 例如,第一层的输出是第二层的输入。 ```python img_input = layers.Input(shape=input_shape) x = layers.Conv2D(64, (3, 3), activation='relu')(img_input) x = layers.Conv2D(64, (3, 3), activation='relu')(x) x = layers.MaxPooling2D((2, 2), strides=(2, 2))(x) ``` 在 Pytorch 中,您将网络设置为一个类,该类继承了 Torch 库中的 torch.nn.Module。 与 Keras 类似,Pytorch 为您提供了层作为构建块,但由于它们位于 Python 类中,因此它们在类的\_\_init\_\_()方法中引用,并由类的 forward()方法执行。 ```python class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(3, 64, 3) self.conv2 = nn.Conv2d(64, 64, 3) self.pool = nn.MaxPool2d(2, 2) def forward(self, x): x = F.relu(self.conv1(x)) x = self.pool(F.relu(self.conv2(x))) return x model = Net() ``` 因为 Pytorch 允许您访问所有 Python 的类功能而不是简单的函数调用,所以定义网络可以更清晰,更优雅地包含。 这真的没什么不好的,除非你觉得最重要的是尽可能快地编写代码,那么 Keras 会更容易使用。 ## (2)张量和计算图(Pytorch) vs 标准阵列(Keras) Keras API 对程序员隐藏了的许多复杂细节。定义网络层非常直观,默认设置通常足以让您入门。 只有当你需要实现一个相当尖端或"异国情调"的模型时,你才真正需要去了解底层的 TensorFlow。 棘手的部分是,当你真正去了解底层的 TensorFlow 代码时,你将获得随之而来的所有非常具有挑战性的部分! 您需要确保所有矩阵乘法都排成一行。 哦,甚至不要去考虑尝试打印出图层的一个输出,因为您只能在终端上打印出一个漂亮的 Tensor 定义。 Pytorch 在这些方面倾向于更加方便。 您需要知道每个层的输入和输出大小,但这可以很快掌握。 您不必处理构建一个您无法在调试中看到的抽象计算图。 Pytorch 的另一个好处是你可以在 Torch Tensors 和 Numpy 阵列之间来回滑动。 如果你需要实现自定义的东西,那么在 TF 张量和 Numpy 阵列之间来回转换可能会很麻烦,需要开发人员对 TensorFlow 的 Session 有充分的了解。 Pytorch 的交互性比想象中要简单得多。 您只需要知道两个操作:一个将 Torch Tensor(一个 Variable 对象)转变到 Numpy,另一个是相反的转换。 ```python a = torch.Tensor(2,2) print(a) b = a.numpy() #tensor 变为numpy print(b) print(torch.from_numpy(b)) # numpy 变为tensor ``` 当然,如果你不需要实现任何花哨的东西,那么 Keras 会做得很好,因为你不会遇到任何 TensorFlow 障碍。 但如果你这样做,那么 Pytorch 可能会更顺畅。 ## (3) Training models 在 Keras 训练模型非常容易! 只是一个简单的.fit()函数,你可以很轻松的进行训练。 ```python history = model.fit_generator( generator=train_generator, epochs=10, validation_data=validation_generator) ``` 在 Pytorch 中训练一些模型需要一些步骤 * 每一批次的训练开始时初始化梯度 * 在模型中运行前向传播 * 运行后向传播 * 计算损失和更新权重 所以,就训练模型来说,PyTorch 较为繁琐。 ```python for epoch in range(2): # loop over the dataset multiple times running_loss = 0.0 for i, data in enumerate(trainloader, 0): # Get the inputs; data is a list of [inputs, labels] inputs, labels = data # (1) Initialise gradients optimizer.zero_grad() # (2) Forward pass outputs = net(inputs) loss = criterion(outputs, labels) # (3) Backward loss.backward() # (4) Compute the loss and update the weights optimizer.step() ``` ## (4)控制 CPU vs GPU 模式 如果你已经安装了 tensorflow-gpu,则在 Keras 中能够使用 GPU 并且会默认完成。然后,如果你想要将某些运算转移至 CPU,则可以以单行方式完成。 ```python with tf.device('/cpu:0'): y = apply_non_max_suppression(x) ``` 但对于 PyTorch 来说,你必须显式地为每个 torch 张量和 numpy 变量启动 GPU。这样代码会比较混乱。并且如果你想在 CPU 和 GPU 之间来回移动以执行不同运算,则很容易出错。 例如,为了将之前的模型转移到 GPU 上运行,则需要以下步骤: ```python # Get the GPU device device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # Transfer the network to GPU net.to(device) # Transfer the inputs and labels to GPU inputs, labels = data[0].to(device), data[1].to(device) ``` 因而,Keras 在简洁性和默认设置方面优于 PyTorch。 ## 选择 Keras 或 PyTorch 的一般性建议 作者通常建议初学者从 Keras 开始。Keras 绝对是理解和使用起来最简单的框架,能够很快地上手运行。你完全不需要担心 GPU 设置、处理抽象代码以及其他任何复杂的事情。你甚至可以在不接触任何 TensorFlow 单行代码的情况下,实现自定义层和损失函数。 但如果你开始深度了解到深度网络的更细粒度层面或者正在实现一些非标准的事情,则 PyTorch 是你的首选库。使用 PyTorch 需要进行一些额外操作,但这不会减缓你的进程。你依然能够快速实现、训练和测试网络,并享受简单调试带来的额外益处。 --- url: /blog/2019/hexo-深度学习在中文分词和词性标注中的应用.md --- # 深度学习在中文分词和词性标注中的应用 得到字向量->通过窗口方法得到字与上下文有关的向量(矩阵)->通过两个线性层和一个非线性激活函数->字的标注得分(窗口方法)->一个句子的评分矩阵$ f \times \theta (c \times {\[1:n]}) $ (句子中的第$ i $ 个子为标签$ t $ 的得分)->定义转换分数$ A\_{ij} $,得到tag path 得分 ->最大化得分,得到最优tag path ```math s(c_{[1:N]},t_{[1:N]},\theta)=\sum_{i=1}^n(A_{t_{i-1}t_i}+f_\theta (t_i\vert i)) ``` log likelihood ```math \sum_{\forall(c,t)\in R}\log p(t\vert c,\theta) ``` 将目标函数转换为条件概率 ```math p(t\vert c,\theta)=\frac {e^{s(c,t,\theta)}}{\sum_{\tilde t}{e^{s(c,\tilde t,\theta)}}} ``` 取对数 ```math \log p(t\vert c,\theta) = s(c,t,\theta)-log\sum_{\tilde t}{e^{s(c,\tilde t,\theta)}} ``` 维特比算法(viterbi) 一种动态规划算法(穷举法,A\*算法,beam search,Viterbi算法) A\*算法和Viterbi算法的区别? 新的训练方法 将维特比算法在当前参数下得出的最优路径结果与正确结果进行比较对比,定义出损失函数对$ A\_{t\_{i-1}t\_i} $ 和$f\_\theta (t\_i|i) $ 的偏导数,通过后向传播更新参数 收敛性的证明:Discriminative training methods for hidden Markov models --- url: /blog/2019/hexo-矩阵中的求导.md --- # 矩阵中的求导 ### 标量对向量求导 ```math y = f(x_1,\cdots,x_i,\cdots,x_n) ``` ```math X = [x_1,\cdots,x_i,\cdots,x_n] ``` ```math \frac {\partial y}{\partial X} = [\frac {\partial f}{\partial x_1},\cdots,\frac {\partial f}{\partial x_i},\cdots,\frac {\partial f}{\partial x_n}] ``` ### 向量对向量求导 ```math Y = [f_1(x_1,\cdots,x_i,\cdots,x_n),\cdots, f_i(x_1,\cdots,x_i,\cdots,x_n),\cdots, f_m(x_1,\cdots,x_i,\cdots,x_n)] ``` ```math X = [x_1,\cdots,x_i,\cdots,x_n] ``` ```math \frac {\partial Y}{\partial X} = \begin{bmatrix} \frac {\partial f_1}{\partial x_1} & \frac {\partial f_2}{\partial x_1} & \cdots & \frac {\partial f_m}{\partial x_1}\cr \frac {\partial f_1}{\partial x_2} & \frac {\partial f_2}{\partial x_2} & \cdots & \frac {\partial f_m}{\partial x_1}\cr \vdots & \vdots & \ddots & \vdots\cr \frac {\partial f_1}{\partial x_n} & \cdots & \cdots & \frac {\partial f_m}{\partial x_n}\cr \end{bmatrix} ``` 这是一个n行m列的矩阵,有时也会写成m行n列,都是一样的,区别在于加不加转置 --- url: /blog/2019/hexo-神经图灵机.md --- # 神经图灵机 ### Neural Turing Machines[原文](https://arxiv.org/abs/1410.5401) * 读记忆(Read Heads) 把时刻$t$的记忆看作是一个$ N \times M $的矩阵$ M\_t $,读的过成首先生成长度为$N$的定位权重向量$w\_t$,表示$N$个记忆位置的权值大小,读出的记忆向量为$r\_t$: ```math r_t=\sum_i^Nw_t(i)M_t(i) \ \ \ \sum_iw_t(i)=1 ``` 对$N$条记忆进行加权求和 * 写记忆(Write Heads) 类似LSTM:擦除向量$e\_t$,增加向量$a\_t$ * 擦除操作: ```math M_t'(i)=M_{t-1}(1-w_t(i)e_t(i)) ``` * 增加操作: ```math M_t(i)=M_t'(i)+w_t(i)a_t(i) ``` 神经图灵机的关键是定位向量$w\_t$,其它的是由控制器(LSTM,MLP)输出 * 定位机制(Addressing Mechanism) 结合了基于内容和基于位置的两种方法 * 基于内容(Content-based Addressing) ```math w_t^c(i)=\frac {\exp(\beta_tK[k_t,M_t(i)])}{\sum_j\exp(\beta_tK[k_t,M_t(j)])} ``` $K\[...]$是余弦相似度计算: ```math K[u,v]=\frac{u \cdot v}{\Vert u\Vert \cdot \Vert v\Vert} ``` $\beta\_t$是控制器输出 * 基于位置(Location-based Addressing) * 插值(Interpolation) ```math w_t^g=g_tw_t^c+(1-g_t)w_{t-1} ``` $g\_t$有控制器生成 * 偏移(shift) ```math \tilde w_t(i)=\sum_{j=0}^{N-1}w_t^g(j)s_t(i-j) ``` 每一个$\tilde w\_t(i)$都与相邻元素有关 * 重塑(Sharping) ```math w_t(i)=\frac{\tilde w_t(i)^{\gamma_t}}{\sum_j\tilde w_t(j)^{\gamma_t}} ``` --- url: /blog/2019/hexo-神经网络中的前向传播与后向传播.md --- # 神经网络中的前向传播与后向传播 ![img](./imgs/7_10_neu.png) $ f(z) $为激励函数,关于激励函数(又称激活函数)的总结\ 隐藏层1输入 ```math z^{(1)}=W^{(1)}x^T+b^{(1)}\tag{1} ``` 隐藏层1输出 ```math n^{(1)}=f^{(1)}(z^{(1)})\tag{2} ``` 隐藏层2输入 ```math z^{(2)}=W^{(2)}n^{(1)}+b^{(2)}\tag{3} ``` 隐藏层2输出 ```math n^{(2)}=f^{(2)}(z^{(2)})\tag{4} ``` 隐藏层3输入 ```math z^{(3)}=W^{(3)}n^{(2)}+b^{(3)}\tag{5} ``` 隐藏层3输出即输出层 ```math \widehat y = n^{(3)}= f^{(3)}(z{(3)})\tag{6} ``` 损失函数 ```math L(y,\widehat y)\tag{7} ``` 即隐藏层k+1输入 ```math z^{(k+1)}=W^{(k+1)}n^{(k)}+b^{(k+1)}\tag{8} ``` 隐藏层k+1输出 ```math n^{(k+1)}= f^{(k+1)}(z{(k+1)})\tag{9} ``` 对损失函数进行总结\ 计算偏导数 ```math \frac {\partial z^{(k)}}{\partial b^{(k)}}= diag(1,1, \ldots ,1)\tag{10} ``` 列向量对列向量求导参见矩阵中的求导 计算偏导数$\frac {\partial L(y,\widehat y)}{\partial z^{(k)}}$ 偏导数$ \frac {\partial L(y,\widehat y)}{\partial z^{(k)}}\ $ 又称误差项(error term,也称"灵敏度"),一般用$ \delta $ 表示,用$ \delta^{(k)} $ 表示第k层神经元的误差项,其值的大小代表了**第k层神经元对最终总误差的影响大小** ```math \begin{align} \delta^{(k)} & = \frac {\partial L(y,\widehat y)}{\partial z^{(k)}}\cr & =\frac {\partial n^{(k)}}{\partial z^{(k)}}* \frac {\partial z^{(k+1)}}{\partial n^{(k)}}* \frac {\partial L(y,\widehat y)}{\partial z^{(k+1)}}\cr & = {f^{(k)}}^{'}(z^{(k)}) * (W^{(k+1)})^T * \delta^{(k+1)} \end{align}\tag{11} ``` 最终需要用的两个导数 ```math \frac {\partial L(y,\widehat y)}{\partial W^{(k)}} =\frac {\partial L(y,\widehat y)}{\partial z^{(k)}}* \frac {\partial z^{(k)}}{\partial W^{(k)}} =\delta^{(k)}*(n^{(k-1)})^T\tag{12} ``` ```math \frac {\partial L(y,\widehat y)}{\partial b^{(k)}} =\frac {\partial L(y,\widehat y)}{\partial z^{(k)}}* \frac {\partial z^{(k)}}{\partial b^{(k)}} =\delta^{(k)}\tag{13} ``` 后向传播参数更新 ```math W^{(k)} = W^{(k)} - \alpha(\delta^{(k)}(n^{(k-1)})^T + W^{(k)})\tag{14} ``` ```math b^{(k)} = b^{(k)}-\alpha\delta^{(k)}\tag{15} ``` 其中$ \alpha $ 是学习率 后向传播中的正则化,L1正则化,L2正则化 --- url: /blog/2019/hexo-记一次GRUB引导修复.md --- # 记一次GRUB引导修复 使用Arch Linux已经2年多了,基本上已经习惯了,可前段时间英雄联盟也出了"自走棋",想要体验一把。 于是就在硬盘剩余的60多个G上装了Windows 10,用了一两个月没有问题,最后在一次Windows 10更新后,蓝屏了。 又手惨的点了Windows 的修复功能。等了半天,结果是修复,重启,蓝屏无限循环。 而且Arch Linux的引导全都没了,感觉应该是Windows的修复动了efi分区。 无奈只能重新修复引导,各种方法尝试了好多遍,都是卡在了`GRUB _`一直闪这个状态, 最终在官网wiki找到了办法 [缺省/后备启动路径](https://wiki.archlinux.org/index.php/GRUB_#Default/fallback_boot_path) grub 安装时添加`--removable`参数 ```bash grub-install --target=x86_64-efi --efi-directory=esp --removable ``` 或者手动移动 ```bash mv esp/EFI/grub esp/EFI/BOOT mv esp/EFI/BOOT/grubx64.efi esp/EFI/BOOT/BOOTX64.EFI ``` grub出现bug的原因应该会千奇百怪,要尽可能多的尝试网上的各种修改方法。 --- url: /blog/2019/hexo-边缘计算系统中延迟敏感任务的成本有效调度.md --- # 边缘计算系统中延迟敏感任务的成本有效调度 ## 摘要 边缘计算作为一种新兴的计算模型,可以将具有有限计算资源和能量的物联网(IoT)设备的延迟敏感计算任务卸载到边缘云。在边缘计算系统中,多个服务器放置在IoT设备附近的网络边缘上以处理卸载的任务。边缘计算系统的一个关键问题是如何在完成卸载任务的同时降低系统成本。在本文中,我们研究任务调度问题,以降低边缘计算系统的成本。我们将任务调度问题建模为优化问题,其目标是在满足所有任务的延迟要求的同时最小化系统成本。然后,我们证明了所提出的优化问题是NP难的。为了有效地解决这个优化问题,我们提出了一种任务调度算法,称为两阶段任务调度成本优化(TTSCO)。我们通过与最优解进行比较来验证算法的有效性。结果表明,对于我们使用的95%的数据集,近似比率小于1.2。性能评估表明,该算法能够有效降低边缘计算系统的成本,同时满足所有任务的延迟要求。索引术语 * 边缘计算;任务调度;延迟敏感的任务;成本效益; ## 云计算与边缘计算的区别和边缘计算要解决的问题 随着物联网技术的发展,延迟敏感应用(例如,健康监测\[1],基于位置的增强现实游戏)的数量正在迅速增加\[2]。由于物联网设备的计算资源和能量有限,因此应将许多处理繁重的任务卸载到远程服务器进行处理。具有强大计算能力的云计算被认为是处理卸载任务的潜在方式。但是,由于传统云和物联网设备之间的距离较远,将大量任务发送到传统云进行处理会导致响应时间过长,网络拥塞严重。为了解决这个问题,最近提出边缘计算作为一种有前景的计算模型\[3],\[4]。边缘计算提供附加的计算基础设施层,其由网络边缘处的一些服务器(即,基站)组成。对于从物联网设备卸载的计算任务,边缘计算提供计算服务并将结果返回给设备。这样,卸载任务的传输延迟和核心网的流量负载将大大降低。 ## 在边缘计算中的任务调度问题 设备如何进行任务卸载决策 降低传输或计算任务所产生的系统成本 边缘计算系统中任务调度问题的成本优化 在边缘计算中,任务调度问题已成为研究的热门话题\[9] - \[18]。为了减少由任务计算引起的能耗,物联网设备会将计算任务卸载到边缘服务器。但是,卸载任务会消耗额外的能量来将任务传输到边缘服务器,卸载任务的完成时间也会增加。在这种情况下,一些工作研究了设备如何进行任务卸载决策\[9] - \[12]。另外,当计算任务被调度到不同的边缘服务器时,传输和计算的成本也不同。因此,一些工作旨在降低传输或计算任务所产生的系统成本\[13] - \[18]。但是,这些工作很少考虑边缘服务器生成的成本。在非高峰时间(例如,在夜间)降低由服务器引起的系统成本的问题在很大程度上未被探索\[5]。 ## 本文研究的问题 边缘计算系统中任务调度问题的成本优化,在本文中,我们研究边缘计算系统中任务调度问题的成本优化。目标是在满足所有任务的QoS要求的同时最小化边缘计算系统的成本。我们为延迟敏感的输入任务开发了一个任务调度模型。然后,我们制定成本优化问题并证明该优化问题为NP-Hard。接下来,我们提出了一种近似算法来解决这个优化问题,称为两阶段任务调度成本优化(TTSCO),并对TTSCO算法进行分析。最后,仿真结果验证了算法的准确性和性能。 边缘计算作为一种新兴的计算模型,可以将具有有限计算资源和能量的物联网(IoT)设备的延迟敏感计算任务卸载到边缘云。在边缘计算系统中,多个服务器放置在IoT设备附近的网络边缘上以处理卸载的任务。 --- url: /blog/2019/hexo-集成学习算法总结.md --- # 集成学习算法总结 集成学习算法是指使用多个分类器提高整体的泛化能力 ## 1.Bagging(Bootstrap Aggregating)算法 通过组合随机生成的训练集而改进分类的集成算法(bootstrap) 使用训练集中的某个子集作为当前训练集(有放回随机抽样);经过T次训练后,得到T个不同的分类器 调用这T个分类器,把这T个分类结果中出现次数多的类赋予测试样例 有效减少噪声影响 ## 2.Boosting算法 初始化样本权重,生成一个弱分类器; 利用弱分类器增加分类错误的样本的权重; 不断重复,生成T个弱分类器; 对噪声敏感 改进算法-AdaBoosting算法 * 对每一次的训练数据样本赋予一个权重,并且每一次样本的权重分布依赖上一次的分类结果 * 基分类器之间采用序列的线性加权方式来组合 ## 3.Gradient Boosting 1,初始化 ```math f_0(x)=\arg\min_\gamma\sum_{i=1}^NL(y_i,\gamma) ``` 2.1计算负梯度 ```math \widetilde y_i = -\frac{\partial L(y_i,f_{m-1}(x_i))}{\partial f_{m-1}(x_i)}, i=1,2,\cdots N ``` 2.2用基学习器$ h\_m(x) $ 拟合$ \widetilde y\_i $ ```math w_m=\mathop{\arg\min}\limits_w\sum_{i=1}^N[\widetilde y_i-h_m(x_i;w)]^2 ``` 2.3确定步长$ \rho\_m $ ```math \rho_m = \mathop{\arg\min}\limits_{\rho} \sum\limits_{i=1}^{N} L(y_i,f_{m-1}(x_i) + \rho h_m(x_i\,;\,w_m)) ``` 2.4更新$ f\_m(x) $ 最终得到$ f\_M(x) $ ```math f_m(x) = f_{m-1}(x) + \rho_m h_m(x\,;\,w_m) ``` Bagging + 决策树 = 随机森林 AdaBoost + 决策树 = 提升树 Gradient Boosting + 决策树 = GBDT --- url: /blog/2020/wp-102.md --- # linux交换分区 ## 新建硬盘分区作为交换分区 硬盘分区情况 ``` 设备 起点 末尾 扇区 大小 类型 /dev/sda1 2048 821247 819200 400M Linux 文件系统 /dev/sda2 821248 1640447 819200 400M Linux 文件系统 /dev/sda3 1640448 840501247 838860800 400G Linux 文件系统 /dev/sda4 840501248 871958527 31457280 15G VMware 诊断 ``` 把`/dev/sda4`作为交换分区 交换分区大小建议 物理内存 不需要休眠 需要休眠 最大值 *** 256M 256M 512M 512M 512M 512M 1024M 1024M 1024M 1024M 2048M 2048M 1G 1G 2G 2G 2G 1G 3G 4G 3G 2G 5G 6G 4G 2G 6G 8G 5G 2G 7G 10G 6G 2G 8G 12G 8G 3G 11G 16G 12G 3G 15G 24G 16G 4G 20G 32G 24G 5G 29G 48G 32G 6G 38G 64G 64G 8G 72G 128G 128G 11G 139G 256G archlinux的wiki中建议交换分区类型为82 安装交换分区`mkswap /dev/sda4` 启用交换分区`swapon /dev/sdxy` 设置自动启动 在文件`/etc/fstab`中添加`UUID= none swap defaults 0 0` 其中UUID`lsblk -no UUID /dev/sda4` ## 参考资料 \[1][Linux交换空间(swap space)](https://segmentfault.com/a/1190000008125116) \[2][Swap (简体中文)](https://wiki.archlinux.org/index.php/Swap_\(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87\)#%E4%BA%A4%E6%8D%A2%E6%96%87%E4%BB%B6) --- url: /blog/2020/wp-111.md --- # vim中的leetcode插件 ## 安装 1.安装依赖 ```bash pip install keyring browser-cookie3 ``` 2.使用`vundle`安装并简单配置 ``` call vundle#begin() ... Plugin 'ianding1/leetcode.vim' ... call vundle#end() "leetcode" let g:leetcode_china=1 "中国区leetcode" let g:leetcode_solution_filetype='python3' "默认使用python3" let g:leetcode_browser='chrome' "登录leetcode-cn.com的浏览器" ``` ## 问题 1. `leetcode.vim`依赖`keyring` 按照[这里](https://github.com/ianding1/leetcode.vim#faq)配置使用GnomeKeyring ``` [backend] default-keyring=keyring.backends.Gnome.Keyring ``` vim中执行`:LeetCodeSignIn`出现错误`browser_cookie3 not installed` 明明已经安装过了 2. 通过`python`直接导入`browser_cookie3` 发现原因`ModuleNotFoundError: No module named 'keyring.backends.Gnome'` 3. 查看`keyring`的[资料](https://github.com/jaraco/keyring)后,发现`keyring`把`keyring.backends.Gnome.Keyring`单独放到了`keyrings.alt`包中 安装`keyrings.alt`,有提示`RuntimeError: GnomeKeyring module required` `GnomeKeyring`是[PyGObject](https://pygobject.readthedocs.io/en/latest/getting_started.html)里面的,[PyGObject](https://pygobject.readthedocs.io/en/latest/getting_started.html)是`Gtk`python接口,在archlinux中需要安装`pygobject-devel`和`libgnome-keyring` 4. \~~`browser-cookie3`无法正确获取`chrome`的cookies,只能获取到key,获得的value为空,猜想应该是`browser-cookie3`解密cookies时出现了问题。原因就是`chrome`更改了对`cookie`的加密方法,因此`browser-cookie3`无法获取`cookie`。 通过更改`leetcode.vim`使用`pycookiecheat`库获取`cookie`也可以。~~ 5. \~~更新`browser-cookie3`库也可以解决这个问题[Pull](https://github.com/borisbabic/browser_cookie3/pull/52)~~ 6. `browser-cookie3`库可以正常使用了。 因此可以尝试从git直接安装browser-cookie3这个库 ```bash pip install git+https://github.com/borisbabic/browser_cookie3.git ``` ## 参考资料 1. [leetcode.vim插件](https://github.com/ianding1/leetcode.vim) 2. [keyring](https://github.com/jaraco/keyring) 3. [pycookiecheat](https://github.com/n8henrie/pycookiecheat) --- url: /blog/2020/wp-122.md --- # 栈实现四则远算 ## 总结 四则运算表达式求值分两步\ 中缀转后缀,栈中存符号\ 后缀算结果,栈中存数字 ## 中缀表达式与后缀表达式转换 中缀表达式`1+(2-3)*4+5/6`\ 后缀表达式`1,2,+`\ 通过栈进行转换 * 数字:直接输出 * (:进栈 * ):出栈直到(,弹出( * 运算符:出栈(高阶等阶运算符),进栈(当前运算符) ``` #当前字符:1+(2-3)*4+5/6 #输出:1 #栈: #当前字符:+(2-3)*4+5/6 #输出:1 #栈:+ #当前字符:(2-3)*4+5/6 #输出:1 #栈:+,( #当前字符:2-3)*4+5/6 #输出:1,2 #栈:+,( #当前字符:-3)*4+5/6 #输出:1,2 #栈:+,(,- #当前字符:3)*4+5/6 #输出:1,2,3 #栈:+,(,- #当前字符:)*4+5/6 #输出:1,2,3,- #栈:+ #当前字符:*4+5/6 #输出:1,2,3,-, #栈:+(低阶不弹出),* #当前字符:4+5/6 #输出:1,2,3,-,4 #栈:+,* #当前字符:+5/6 #输出:1,2,3,-,4,*(高阶弹出),+(等阶弹出) #栈:+ #当前字符:5/6 #输出:1,2,3,-,4,*,+,5 #栈:+ #当前字符:/6 #输出:1,2,3,-,4,*,+,5 #栈:+,/ #当前字符:6 #输出:1,2,3,-,4,*,+,5,6 #栈:+,/ #当前字符: #输出:1,2,3,-,4,*,+,5,6,/,+ #栈: #1+(2-3)*4+5/6 ``` ## 后缀表达式的计算 数字进栈,符号远算 ``` #[1,[[[2,3,-],4,*],[5,6,/],+],+] #2-3=-1 #-1*4=-4 #5/6=0.833 #-4+0.833=-3.167 #1+(-3.167)=-2.167 ``` ## python实现 ``` def calculator(s: str) -> float: s = s.replace(" ", "") s_list = [] for c in list(s): if c.isdigit(): if len(s_list) == 0 or isinstance(s_list[-1], str): s_list.append(int(c)) else: s_list[-1] *= 10+int(c) else: s_list.append(c) # 中缀转后缀 stack = [] s_end = [] for c in s_list: if isinstance(c, int): s_end.append(c) elif c == "(": stack.append(c) elif c == ")": while stack[-1] != "(": s_end.append(stack.pop()) stack.pop() elif c in ["*", "/"]: while len(stack) != 0 and stack[-1] in ["*", "/"]: s_end.append(stack.pop()) stack.append(c) elif c in ["+", "-"]: while len(stack) != 0 and stack[-1] in ["+", "-", "*", "/"]: s_end.append(stack.pop()) stack.append(c) else: continue while stack: s_end.append(stack.pop()) print(s_end) # 计算后缀表达式 stack = [] for c in s_end: if isinstance(c, int): stack.append(c) elif c == "+": stack[-2] += stack[-1] stack.pop() elif c == "-": stack[-2] -= stack[-1] stack.pop() elif c == "*": stack[-2] *= stack[-1] stack.pop() elif c == "/": stack[-2] /= stack[-1] stack.pop() else: continue return stack[0] if __name__ == "__main__": print(calculator("1+(2-3)*4+5/6")) ``` ## leetcode相关题目 1.\ 2.\ 3.\ 4. --- url: /blog/2020/wp-137.md --- # tensorflow2建立模型的方法 1.使用Input ``` tf.keras.Input(shape=None, batch_size=None, name=None, dtype=None, sparse=False, tensor=None, ragged=False, **kwargs) ``` `Input`初始化一个占位符 `shape`:指定模型输入的shape,不包括batch size `batch_size`:可选 `name`:可选,图层名称 `dtype`:字符串,数据类型 `sparse`:布尔,是否稀疏 `tensor`:可选,指定tensor,不再是占位符 ``` from tensorflow.keras import Input, Model from tensorflow.keras.layers import Dense x = Input(shape=(2,)) y = Dense(5, activation='softmax')(x) model = Model(x, y) ``` 2.继承Model ``` import tensorflow as tf class MyModel(tf.keras.Model): def __init__(self): super(MyModel, self).__init__() self.dense = tf.keras.layers.Dense(5, activation=tf.nn.softmax) def call(self, inputs): x = self.dense1(inputs) return self.dense(x) model = MyModel() ``` 3.使用Sequential ``` import tensorflow as tf model1 = tf.keras.Sequential( tf.keras.layers.Dense(5, activation=tf.nn.softmax) ) ``` --- url: /blog/2020/wp-143.md --- # 数独的解法 ## 数独的规则 在 9×9 的方格里面填写数字 1-9,满足 * 每行不重复 * 每列不重复 * 9 个 3×3 的小方格不重复 ## 回溯法 通过 DFS 遍历尝试每一个数字 根据三条规则,可以用三个二维数组记录每行,每列和每个小方格可填数字状态 ``` row = [[True]*9 for _ in range(9)] col = [[True]*9 for _ in range(9)] block = [[True]*9 for _ in range(9)] ``` `row[i][j]`表示第`i`行是否可以填数字`j` `col[i][j]`表示第`i`列是否可以填数字`j` `block[i][j]`表示第`i`个方格是否可以填数字`j` 用二维数组`g`表示数独方格的原始状态,`0`表示空白 ``` def dfs(): # 遍历所有位置 for i in range(9): for j in range(9): # 空白位置 if g[i][j]==0: # 尝试每一个数字 for k in range(9): # 数字满足行,列,块 if row[i][k] and col[j][k] and block[3*(i//3)+j//3][k]: # 填入数字 row[i][k] = col[j][k] = block[3*(i//3)+j//3][k]= False g[i][j] = k+1 if(dfs()): return True # 有填错的,删除数字 row[i][k] = col[j][k] = block[3*(i//3)+j//3][k]= True g[i][j] = 0 # 九个数字都不能填 return False # 所有位置都填了数字 return True ``` 回溯法要注意如何进行记录状态,要方便更新,回溯 ## 参考资料 \[1][数独求解算法(回溯法和唯一解法)java 实现](https://blog.csdn.net/xiaoyiaoyou/article/details/52491187) --- url: /blog/2020/wp-151.md --- # python的整除 python可以通过`//`进行整除运算 例如: ``` 4//2 # 2 5//2 # 2 # 去尾法 ``` 当遇到负数时,最后的结果向下取整数 此时负号运算级高,先构成负数,再除法 ``` -4//2 # -2 -5//2 # -3 -1//2 # -1 ``` 如果是减号,就先计算除法 ``` -1//2 # -1 1-1//2 # 1 ``` python远算符优先级 运算符 描述 *** `**` 指数 `~ + -` 按位翻转, 一元加号和减号(就是正数和负数) `* / % //` 乘,除,取模和取整除 `+ -` 加法和减法 `>> <<` 右移,左移 `&` 位与运算 `^ |` 位异或,位或 `<= < > >=` 比较远算 `== !=` 等于远算 `= %= /= //= -= += *= **=` 赋值 `is is not` 身份 `in not in` 成员 `and or not` 逻辑与或非 `-`负号比`//`整除远算级高 --- url: /blog/2020/wp-155.md --- # 集合合并问题 > 题目: 小明有n只袜子,需要穿m天 第i只袜子的颜色为c\_i > 给出每天要穿的两只袜子的编号(i1,i2) > 保证每天穿的袜子颜色一样,最少要对多少只袜子进行染色 思路: 根据这m天每天穿的两只袜子,可以将所有袜子分成几个集合,每个集合的颜色都要一样,把集合中所有袜子染成颜色最多的袜子即可 将每天穿的袜子标号看作集合,按袜子颜色进行合并,直到每个集合中袜子的标号都不重复 实现函数 ``` def maxTint(c,d): """ c: n只袜子的颜色 d: m天每天穿的袜子的标号 """ pass ``` 1,循环标号进行合并 ``` def maxTint(c,d): d = map(set,d) for ci in range(len(c)): new_d = [] new_g = [] for di in d: if ci in di: new_g.append(di) else: new_d.append(di) d = new_d if len(new_g)!=0: d.append(set()) for gi in new_g: d[-1]=d[-1]|gi ans = 0 for di in d: ansi = 0 for i in di: ansi=max(ansi,c.count(c[i-1])) ans+=ansi return ans ``` 时间复杂度:`n*m` --- url: /blog/2020/wp-160.md --- # 一些有用的python函数 # 基础函数 ## map ``` map(function, iterable, ...) ``` 对迭代器每一给元素进行计算\ 返回新的迭代器 ## filter ``` filter(function, iterable) ``` 过滤掉不满足function的函数\ 返回过滤后的迭代器 ## heapq模块 参考python中文文档 模块提供了一系列的函数用来操作list,从而实现最小堆的特性 ``` import heapq heapq.heapify(x) #原地转化为最小堆 heapq.heappush(heap,item) #堆中加入新项 heapq.heappop(heap) #弹出堆中最小项 heapq.heapreplace(heap,item) #弹出堆中最小项并加入新项 ``` # functools模块中的函数 ## reduce ``` from functools import reduce reduce(function, sequence, startValue) ``` 将一个序列归纳为一个输出\ 返回一个输出 ## lru\_cache ``` from functools import lru_cache @lru_cache(user_function) @lru_cache(maxsize=128, typed=False) ``` 装饰器缓存函数的输出值\ maxsize:最多缓存最近多少个返回值\ typed:类型是否作为缓存中键的一部分(如果为True,3和3.0两次缓存) --- url: /blog/2020/wp-169.md --- # vim及插件编译 ## VIM编译 ### 编译选项 {#编译选项 .has-text-align-left} `--with-features=huge` `--enable-multibyte` `--enable-rubyiniterp` `--enable-pythoninterp` `--with-python-config-dir=/usr/lib/python2.7/config-x86_64-linux-gnu/` `--enable-python3interp` `--with-python3-config-dir=/usr/lib/python3.7/config-3.7m-x86_64-linux-gnu/` `--enable-luainterp` `--enable-cscope` `--enable-perlinterp` `--prefix=~/.local` ``` ./configure --with-features=huge --enable-multibyte --enable-rubyiniterp --enable-pythoninterp --with-python-config-dir=/usr/lib/python2.7/config-x86_64-linux-gnu/ --enable-python3interp --with-python3-config-dir=/usr/lib/python3.7/config-3.7m-x86_64-linux-gnu/ --enable-luainterp --enable-cscope --enable-perlinterp --prefix=/home/frey/.local make -j8 make install ``` ### 错误 `You need to install a terminal library; for example ncurses`\ 安装libncurse5-dev可以解决 ``` apt install libncurse5-dev ``` ## YouCompleteMe编译 --- url: /blog/2020/wp-173.md --- # 初步了解kafka ## kafka简介 kafka[下载地址](https://www.apache.org/dyn/closer.cgi?path=/kafka/0.9.0.0/kafka_2.11-0.9.0.0.tgz) kafka[官网](http://kafka.apache.org/) kafka是一个分布式消息发布订阅系统,主要特点:易于扩展,高吞吐量 **主要术语:** 1. Topic Kafka将消息种子(Feed)分门别类, 每一类的消息称之为话题(Topic). 2. Producer 发布消息的对象称之为话题生产者(Kafka topic producer) 3. Consumer 订阅消息并处理发布的消息的种子的对象称之为话题消费者(consumers) 4. Broker 已发布的消息保存在一组服务器中,称之为Kafka集群。集群中的每一个服务器都是一个代理(Broker). 消费者可以订阅一个或多个话题,并从Broker拉数据,从而消费这些已发布的消息。 ## 启动服务端 1. 启动ZooKeeper,因为Kafka Cluster使用ZooKeeper ZooKeeper是一个分布式服务框架,主要用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。 ``` bin/zookeeper-server-start.sh config/zookeeper.properties ``` 2\. 启动Kafka Broker ``` bin/kafka-server-start.sh config/server.properties ``` 3\. Jps查看进程 ``` $ jps 4407 QuorumPeerMain 4408 Kafka 5291 Jps ``` QuorumPeerMain是ZooKeeper守护进程,Kafka是Kafka守护进程 ## 创建一个主题 ``` bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic test ``` `replication-factor`分区个数,不同分区会存储到不同的主机(负载均衡),分区写入策略(*1、轮询策略*,2、随机策略,3、按键保存策略) `partitions`副本个数,仅用于数据冗余 ## 获取主题列表 ``` bin/kafka-topics.sh --list --bootstrap-server localhost:9092 ``` ## 生产者示例 ``` bin/kafka-console-producer.sh --bootstrap-server localhost:9092 --topic test ``` `bootstrap-server`代理列表,kafka服务端配置文件中指定 ## 消费者示例 ``` bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 —topic topic-name --from-beginning ``` `from-beginning`读取历史未消费的数据 --- url: /blog/2020/wp-176.md --- # confluent系列软件介绍 ## zookeeper 主要功能:配置管理、名字服务、分布式同步、集群管理 [集群配置](https://juejin.im/post/5ba879ce6fb9a05d16588802):每台机器的配置都相同,主要通过`myid`文件区分ip端口 ## [kafka](http://blog.vhcffh.com/2020/%e5%88%9d%e6%ad%a5%e4%ba%86%e8%a7%a3kafka/) 1. Broker:Kafka的broker是无状态的,broker使用Zookeeper维护集群的状态。Leader的选举也由Zookeeper负责。 2. Zookeeper:Zookeeper负责维护和协调broker。当Kafka系统中新增了broker或者某个broker发生故障失效时,由ZooKeeper通知生产者和消费者。生产者和消费者依据Zookeeper的broker状态信息与broker协调数据的发布和订阅任务。 3. Producer:生产者将数据推送到broker上,当集群中出现新的broker时,所有的生产者将会搜寻到这个新的broker,并自动将数据发送到这个broker上。 4. Consumer:因为Kafka的broker是无状态的,所以consumer必须使用partition offset来记录消费了多少数据。如果一个consumer指定了一个topic的offset,意味着该consumer已经消费了该offset之前的所有数据。consumer可以通过指定offset,从topic的指定位置开始消费数据。consumer的offset存储在Zookeeper中。 ## schema-registry 作用:让 Kafka 支持 Avro 序列化,从而减少存储空间的占用,加快读取速度。大部分结构化的数据都有相同的schema,将结构和数据分开存储,减少数据量。 Avro序列化:将结构化数据变成有序的数据格式(一般是二进制),并通过schema记录数据结构。 生产者在使用schema-registry时首先向schema-registry注册数据格式,然后使用Avro序列化将数据发送到kafka;消费者消费数据时,先向schema-registry获取数据格式,然后从kafka的到数据,反序列化。 schema-registry服务端的数据存储在kafka中名为\_schemas的topic schema-registry提供了restful api [参考](https://zhmin.github.io/2019/04/23/kafka-schema-registry/) ## kafka-rest 提供kafka restful api,实现通过http协议实现kafka 的生产者,消费者等。registry存储数据结构,提供了restful api。kafka-rest也提供了api,用于存储数据。 ## connect Kafaka connect 是一种用于在Kafka和其他系统之间可扩展的、可靠的流式传输数据的工具。它使得能够快速定义将大量数据集合移入和移出Kafka的连接器变得简单。Kafka Connect可以从数据库或应用程序服务器收集数据到Kafka topic,使数据可用于低延迟的流处理。导出作业可以将数据从Kafka topic传输到二次存储和查询系统,或者传递到批处理系统以进行离线分析。 用于其他数据库与kafka的连接,实现数据的导入导出。 [参考](https://www.cnblogs.com/laoqing/p/11927958.html) ## ksql-server 实现kafka中数据的sql查询 ## control-center confluent的控制中心,用于监控集群状态等。用商用版和社区版。 ## confluent 本地启动 ``` $ ./bin/confluent local start The local commands are intended for a single-node development environment only, NOT for production usage. https://docs.confluent.io/current/cli/index.html Using CONFLUENT_CURRENT: /tmp/confluent.HWNZqmRc Starting zookeeper zookeeper is [UP] Starting kafka kafka is [UP] Starting schema-registry schema-registry is [UP] Starting kafka-rest kafka-rest is [UP] Starting connect connect is [UP] Starting ksql-server ksql-server is [UP] Starting control-center control-center is [UP] ``` ## confluent 本地关闭 ``` $ ./bin/confluent local stop The local commands are intended for a single-node development environment only, NOT for production usage. https://docs.confluent.io/current/cli/index.html Using CONFLUENT_CURRENT: /tmp/confluent.HWNZqmRc Stopping control-center control-center is [DOWN] Stopping ksql-server ksql-server is [DOWN] Stopping connect connect is [DOWN] Stopping kafka-rest kafka-rest is [DOWN] Stopping schema-registry schema-registry is [DOWN] Stopping kafka kafka is [DOWN] Stopping zookeeper zookeeper is [DOWN] ``` --- url: /blog/2020/wp-179.md --- # 命名实体识别 > 命名实体识别(英语:Named Entity > Recognition,简称 NER),又称作专名识别、命名实体,是指识别文本中具有特定意义的实体,主要包括人名、地名、机构名、专有名词等,以及时间、数量、货币、比例数值等文字。指的是可以用专有名词(名称)标识的事物,一个命名实体一般代表唯一一个具体事物个体,包括人名、地名等。 ## 早期方法 基于词典和规则的方法\ 优点:快速、可解释性强和不需要太多标注语料\ 缺点:需要领域知识、规则和词典的构建与维护繁重、未登录词匹配问题\ 具体步骤: 1. 构建词典\ 综合中文语义词库[CSC](http://www.crsky.com/soft/31795.html),[hownet](http://www.keenage.com/html/e_index.html),[Chinese Open Worldnet](http://compling.hss.ntu.edu.sg/cow/)\ 词典构建的统计方法:词频,TF-IDF,TextRank,借助百科,词法分析,依存句法分析\ 构建词典可以通过人工,基于统计学的方法。主要目的就是分词和词性标注 2. 规则构建\ 规则构建是通过根据对词的标注(词性,特征),通过对标注设计一系列规则,从而得到规则中特定标注的实体类别。 1. 基于词性标注进行规则推理\ 通过特定的词性顺序,建立词性与实体的对应关系 2. 采用一个特征集合来泛化表达命名实体[Tatar and Cicekli,2011](https://www.researchgate.net/publication/220195686_Automatic_rule_learning_exploiting_morphological_features_for_named_entity_recognition_in_Turkish)\ 为每个词生成特征,然后在特征上做规则的合并和构建。\ 提出[一种自动学习规则的方案](https://dl.acm.org/doi/10.1177/0165551511398573),通过处理模式串的相似之处,通过泛化构建规则 3. 规则维护\ 提出[一种基于机器学习长期维护规则的框架](https://www.aclweb.org/anthology/P01-1055.pdf),规则维护是在新增语料的情况下,通过对规则的改进与补充,提升其在新语料上的效果。 ## 传统机器学习 ### 有监督 #### SVM,logistic 回归和决策树等 这类方法将 NER 视为分类问题,根据提取的特征和标注的类别对模型进行训练。捕获上下文关系能力弱,对分词结果要求高。 SVM(支持向量机)是在分类与回归分析中分析数据的监督式学习模型,主要思想是通过一个(p-1)维超平面将数据点(p 维向量)分开,而且 SVM 可以通过核函数将低维线性不可分的数据集影射到高维空间使其线性可分。 决策树是一种概率模型,通过分支对样本特征进行划分,从而划分样本所属类别,决策树的生成主要是通过使用熵的概念。 [HMM](https://www.vhcffh.com/2019/%E5%9F%BA%E4%BA%8E%E7%BB%9F%E8%AE%A1%E7%9A%84%E5%88%86%E8%AF%8D%E6%96%B9%E6%B3%95/),MEMM,CRF 这些都属于序列模型,能够很好的捕获上下文关系。 **HMM**模型主要用于序列标注问题,通过把标签看作隐藏状态变量,把要标注的序列看作可观察输出,可以有效的解决分词,词性标注,命名实体识别等问题。但是受限于 HMM 模型的两个假设,HMM 只能学习到有限的序列特征。 1. 当前观测状态只与当前隐藏状态有关 2. 当前隐藏状态只与前一时刻隐藏状态有关 **MEMM**(最大熵马尔可夫模型)是一种结合了 HMM 和最大熵模型的图模型,可用于序列标注问题。 **HMM**将标注看作隐藏状态,不同的标注序列(隐藏状态)都可以计算出给定观测状态出现的概率,概率最大时对应的隐藏状态序列就是所求结果。而**MEMM**通过输入当前序列状态特征(这里可以加入多个特征,前后多个词或者标注),计算出每个标注的概率。也可以说是简单逻辑回归模型在序列模型上的扩展。 「在求解$latex P(X|Y)$时,可以直接求,也可以间接求$latex P(Y|X)P(X)/P(Y)$」 在训练阶段,**HMM**通过概率估计(频率学派和贝叶斯派)获得模型参数,在预测阶段,可以通过维特比算法(一种 dp 算法,不用计算所有可能隐藏序列对应的观测序列概率)解码(找到最可能对应观测序列的隐藏状态序列)。**MEMM**直接通过定义特征,可以只将当前字符作为特征(此时就是 logistic 回归模型),也可以方便的加入任何其他特征(前后多个词,前面词的标注结果,或者后缀前缀等),通过对每个特征设置一个权重,代表其对最终结果的影响。通过训练,优化权重。预测时可以通过硬解码的方式一个一个给出类别(只考虑当前单词标签),另一种更好的方式是像 HMM 一样使用维特比算法求整个句子的最优解(CRF)。 #### CRF CRF 模型与 MEMM 模型类似,也是通过序列特征计算每个 token 所有可能 label 的概率,取概率最大的 label 作为结果就是 MEMM 模型。这样计算出整个序列的结果后,可能出现不可理得标注(例如:\[B,B...])。为了使输出序列前后相互约束,回想一下 HMM 的实现(当前观测状态依赖当前隐藏状态,当前隐藏状态依赖前一隐藏状态),因此可以考虑将当前 label 的概率与前一了 label 关联,求出整个序列所有可能 label 序列中可能性最大的。这样问题就变的复杂了,本来是 sequence\_lenth 个分类问题(有多少种 label,就有多少种类别),现在变成了一个分类问题,从所有可能 label 序列(共 sequence\_lenth^label\_num 个)选择合适的。 简单来说,CRF 模型就是在 MEMM 模型的基础上增加了对输出的约束,使其输出的标签序列更加合理。 ## 深度学习 ### 有监督 1. NN/CNN-CRF 2. Bi-LSTM-CRF ## 最新研究 ### 有监督 1. Bi-LSTM-CRF+Attention ### 半监督 1. TagLM 2. 迁移学习 ## 参考 \[1][一文看懂命名实体识别 - NER(发展史+4 类方式+数据集+工具推荐)](https://easyai.tech/ai-definition/ner/)\ \[2][命名实体识别调研报告](https://zekizz.github.io/NER_survey_zeki.pdf)\ \[3][中文自然语言处理的一般流程](https://carlos9310.github.io/2018/12/28/nlp-flow/)\ \[4][中文自然语言处理的完整机器处理流程](https://raw.githubusercontent.com/carlos9310/carlos9310.github.io/master/assets/pdf/chinese-nlp.pdf)\ \[5][An Introduction to ConditionalRandom Fields](https://homepages.inf.ed.ac.uk/csutton/publications/crftut-fnt.pdf)\ \[6][最大熵马尔可夫模型](https://zhuanlan.zhihu.com/p/60651380) --- url: /blog/2020/wp-270.md --- # mysql 新建用户 一、新建用户 ``` CREATE USER 'username'@'host' IDENTIFIED BY 'password'; ``` username:用户名 host:允许在那些ip登录(%) password:密码 'username'和@和'host'之间没有空格 二、授权 ``` GRANT privileges ON databasename.tablename TO 'username'@'host'; FLUSH PRIVILEGES; ``` privileges:具体权限`SELECT`,`INSERT`,`UPDATE`,`DELETE`,`CREATE`,`DROP`(`ALL`) databasename:数据库名(\*) tablename:表名(\*) 三、撤权 与授权相似`GRANT`->`REVOKE`,`TO`->`FROM` ``` REVOKE privileges ON databasename.tablename FROM 'username'@'host'; FLUSH PRIVILEGES; ``` privileges:具体权限`SELECT`,`INSERT`,`UPDATE`,`DELETE`,`CREATE`,`DROP`(`ALL`) databasename:数据库名(\*) tablename:表名(\*) --- url: /blog/2020/wp-277.md --- # 初探Tornado > Tornado是一种 Web 服务器软件的开源版本。Tornado 和主流Web > 服务器框架(包括大多数 Python > 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。 > > 得利于其非阻塞的方式和对epoll的运用,Tornado > 每秒可以处理数以千计的连接,因此 Tornado 是实时 Web 服务的一个 > 理想框架。 来自百度百科的解释,其中**异步非阻塞**和**对epoll的运用**是其两大特点 Tornado不同于[Flask](http://docs.jinkan.org/docs/flask/index.html)和[Django](https://docs.djangoproject.com/en/3.1/ "https://docs.djangoproject.com/en/3.1/")两个常见框架,主要特点是Tornado内部实现了自己的Web服务器,而Flask和Django是采用WSGI协议与服务器对接。在开发时,Flask依赖[Werkzeug](https://werkzeug.palletsprojects.com/en/1.0.x/ "https://werkzeug.palletsprojects.com/en/1.0.x/")WSGI 工具集实现一个基于WSGI协议的内置web服务器来满足本地调试,部署的时候需要选择合适的[WSGI服务器](http://docs.jinkan.org/docs/flask/deploying/index.html)来满足生产需求。Django也是同样原理,使用内置WSGI服务器满足开发,使用其他性能更高的WSGI服务器满足生产中的并发需求。(Tornado中的web服务器和web框架都可以实现对WSGI的兼容,可以Tornado服务器模块为其他web框架提供WSGI服务器,也可以使用其他服务器来部署Tornado的web应用。) ## 异步非阻塞 异步和非阻塞是非常相关的而且这两个术语经常交换使用,但它们不完全相同。 ### 为什么异步 通常情况下,web服务器对与每个客户端的请求,都会创建一个线程与其连接,这就是同步模式。在处理高并发时,对每一个请求都会创建一个线程,如此多的线程开销是服务器的压力极大。异步模式在处理了并发请求时,先将请求放到后台处理,通过事件轮询等机制,将处理好的请求发送会客户端,从而减少了线程的创建。 ### 非阻塞 非阻塞是指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回(继续执行下面代码,或者使用重试机制)。阻塞的发生主要是由于**系统IO**(网络IO,文件读写IO等)引起的,或者在处理客户端请求时由于复杂的计算引起的CPU阻塞。总之阻塞的发生是由与服务器处理客户端请求需要一定的时间,直接执行(阻塞)或者直接返回,通过一定机制等到准备好要发送到客户端的内容后,发送到对应客户端。如何处理好系统IO是非阻塞的关键,Tornado是通过**epoll**方式的IO多路复用来实现的。 ## epoll 系统IO主要是程序与硬件间接交互的一种方式,如果每个程序都直接操作硬件,不仅程序开发会变得复杂,而且不同程序不统一的操作方式会使硬件混乱。因此有了操作系统,程序通过系统接口访问硬件,操作系统对所有硬件统一管理。当程序调用了系统的IO接口后,操作系统有不同的处理模式,不同的模式就是不同的IO模型。IO多路复用是一种IO模型,epoll是其中一种方式,常用的IO模型有五种: 1. 阻塞式IO\ 当调用IO请求时直接进入**阻塞状态**,当IO完成(包括准备数据和拷贝数据)时,进入**就绪状态**,等待被再次调度进入**运行状态**。 2. 非阻塞式IO\ 当调用IO请求时,系统内核直接返回一个error信息,程序一边处理其它事情,每隔一段时间都询问内核IO数据是否准备完成,直到返回准备完成,然后把数据由内核拷贝到用户空间。 3. IO多路复用\ 多路复用把一个程序的多个IO请求整合到一起进行阻塞,主要实现方式有select,poll,epoll。 4. 信号驱动IO\ 内核通过信号通知程序IO数据已经准备好。 5. 异步IO\ 发出IO请求后,内核把数据拷贝到用户空间后,通知程序IO已经完成。 IO多路复用的方式 Tornado使用epoll(依赖依赖操作系统)的方式对网络IO进行多路复用,与客户端建立tcp连接后,能继续与其它客户端继续建立连接。接受完某客户端的数据后,调用对应的回掉函数处理。这样就能在一个线程里处理多个客户端的请求了。 --- url: /blog/2020/wp-322.md --- # python标准库模块heapq 堆是一个近似完全二叉树的结构,并同时满足如下性质 > **子节点的键值或索引总是小于(或者大于)它的父节点** python标准库模块heapq实现了堆排序的一些算法,主要提供了以下几个函数 1. `heapify(x)`:本地把列表转换为堆,时间复杂度O(n) 2. `heappush(heap, item)`:将新项添加到堆,并保持堆的不变性 3. `heappop(heap)`:从堆中取出最小项,并保持堆的不变性 4. `heapreplace(heap, item)`:从堆中取出最小项,并将新项添加到堆,并保持堆的不变性 5. `heappushpop(heap, item)`:从堆中取出最小项,并将新项添加到堆,并保持堆的不变性(速度更快) 6. `nsmallest(n, iterable, key=None)`:从可迭代对象中返回前n个最小项 7. `nlargest(n, iterable, key=None)`:从可迭代对象中返回前n个最大项 8. `merge(*iterables, key=None, reverse=False)`:合并多个可排序对象 --- url: /blog/2020/wp-326.md --- # 在wsl上安装Arch Linux 首先启用"适用于 Linux 的 Windows 子系统"可选功能,并从应用商店安装Ubuntu(本文是在wsl1上进行实验的) ## 备份现有Linux分发版 ``` wsl -l #查看所有Linux分发版 wsl --export <分发版> <文件名> #到处分发版到文件 ``` ## 使用 Bootstrap 镜像安装 下载Bootstrap镜像 * * 选择合适版本的`archlinux-bootstrap-2020.10.01-x86_64.tar.gz`进行下载 解压 ``` tar -zxf archlinux-bootstrap-2020.10.01-x86_64.tar.gz ``` 选择软件仓库服务器,去掉`etc/pacman.d/mirrorlist`中的一个注释 进入chroot \[进入失败]{.has-inline-color .has-vivid-red-color},出现错误`mount: /tmp/root.x86_64/dev: unknown filesystem type 'devtmpfs'`和`ERROR: failed to setup chroot /tmp/root.x86_64` 这是参照[官网](https://wiki.archlinux.org/index.php/Install_Arch_Linux_from_existing_Linux_\(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87\) "https://wiki.archlinux.org/index.php/Install_Arch_Linux_from_existing_Linux_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)")的方法,可能不适用于wsl,参照[github](https://github.com/yuk7/ArchWSL "https://github.com/yuk7/ArchWSL")安装一个新的Linux分发版Arch Linux ## 使用ArchWSL安装Arch linux子系统 ArchWSL是基于WSL的Arch linux分发版,专门用于管理Arch Linux分发版 Arch.exe用于Arch Linux子系统安装,其余两个应该是将Arch放到微软应用商店。 Note:安装.appx后开启Arch时出现**系统资源不足,无法完成请求的服务。** 安装后是WSL2中的Arch Linux Note:为了正常使用pacman需要[初始化keyring](https://github.com/yuk7/ArchWSL/wiki/How-to-Setup "https://github.com/yuk7/ArchWSL/wiki/How-to-Setup") ``` sudo pacman-key --init sudo pacman-key --populate ``` Note:安装systemctl alternative代替systemctl(WSL不支持systemctl) WSL2之间安装[genie-systemd](https://aur.archlinux.org/packages/genie-systemd)即可 Note:\[使用`makepkg -si`命令时出错]{.has-inline-color .has-vivid-red-color} `==> ERROR: Cannot find the strip binary required for object file stripping.` 执行`sudo pacman -Sy base-devel`更新即可 --- url: /blog/2020/wp-342.md --- # matplotlib绘制x轴不连续的图 问题:matplotlib在绘制x轴不连续的数据时,会自补全;当使用日期作为横坐标时,会出现一些空值 例如: ``` x = [1,2,3,4,5,8] y = [1,4,9,16,25,36] plt.plot(x,y) ```
matplotlib的x轴是一定连续的,因此先将需要的图形绘制出来,然后使用`plt.xticks(ticks,labels)`替换新的坐标显示 ticks为实际位置,labels为要显示的文本 画出需要的图形 ``` x = [1,2,3,4,5,8] y = [1,4,9,16,25,36] plt.plot(range(y),y) ```
重新定义x轴的显示 ``` plt.xticks(range(len(x)),x) ```
--- url: /blog/2020/wp-351.md --- # git基本用法笔记 ## 分支建立于合并 ``` git commit # 提交 git branch branch_name # 新建分支 git checkout branch_name # 切换分支 git merge branch_name # 合并分支 # 将branch_name合并到当前分支,并增加一次commit git rebase branch_name # 复制分支 # 将当前分支所有commit复制到branch_name,没有新增commit # 当前分支和branch_name 有共同的祖先 ``` ## 在分支树上移动 branch\_name指向分支树上的一个提交记录,每一个提交记录都有唯一的SHA-1 Hash值标识作为commit id 默认HEAD指向当前提交记录,当前记录有分支名,则显示为branch\_name\* HEAD可以指向任何其他的提交记录,只需要checkout到一个指定的commit id,或者使用引用 ``` git checkout da3423* # 相对引用^ git checkout HEAD^^ # 将HEAD指向HEAD的第2父提交 git checkout master^ # 将HEAD指向master的第1父提交 git branch -f master HEAD^^^ # 强制将master指向HEAD的第三父提交 # 相对引用~ git checkout HEAD~10 # 将HEAD指向HEAD前10个commit id # 撤销变更 git reset master^ # 当前分支指向master^,工作区内容不变 git revert master^ # 增加一次提交,使当前分支与master^相同 ``` ## 移动提交记录 ``` git cherry-pick <提交号>... # 将一系列提交copy到当前分支 git rebase -i <提交号> # 以-i参数为基,重新整理提交(排序,删除等) ``` --- url: /blog/2020/wp-41.md --- # 实现让终端走代理 让终端走代理可以通过设置环境变量实现 ``` export http_proxy=socks5://127.0.0.1:1080
 export https_proxy=socks5://127.0.0.1:1080
 export ALL_PROXY=socks5://127.0.0.1:1080 ``` 把代理服务器的配置写到shell配置文件`~/.bashrc`或者`./zshrc`\ 并通过`alias`实现方便的设置代理`setproxy`,关闭代理`unsetproxy`,验证代理`myip` ``` # ~/.zshrc
 # setproxy,unsetproxy,myip
 alias setproxy="export ALL_PROXY=socks://localhost:1080"
 alias unsetproxy="unset ALL_PROXY"
 alias myip="curl http://cip.cc"
 ``` 验证ip通过获取本机外网ip实现\[2] ![file](https://i.imgur.com/Xtdho3W.png) ## 问题 本机使用v2ray开的`socks`代理,设置代理用`sock5`会出现错误,只能设置成socks,可能是终端不支持socks5,v2ray兼容socks\ 查[资料](https://www.v2ray.com/chapter_02/protocols/socks.html)得知`v2ray`中的`socks`兼容 Socks 4、Socks 4a 和 Socks 5\ `curl: (6) Couldn't resolve host name`\ 但是在chrome中使用SwitchyOmega设置为`sock5`可以上网 ## 参考资料 \[1][让终端走代理的几种方法](https://blog.fazero.me/2015/09/15/%E8%AE%A9%E7%BB%88%E7%AB%AF%E8%B5%B0%E4%BB%A3%E7%90%86%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E6%B3%95/ "让终端走代理的几种方法")\ \[2][用Linux命令行获取本机外网IP地址](https://www.cnblogs.com/wjoyxt/p/6288582.html "用Linux命令行获取本机外网IP地址")\ \[3][www.v2ray.com](https://www.v2ray.com/chapter_02/protocols/socks.html) --- url: /blog/2020/wp-60.md --- # vim命令记录 ## 折叠代码 vim中共有六种代码折叠方式,不同的折叠方式有不同的折叠命令 * manual //手工定义折叠 * indent //用缩进表示折叠 * expr  //用表达式来定义折叠 * syntax //用语法高亮来定义折叠 * diff //对没有更改的文本进行折叠 * marker //用标志折叠 ## indent 配置方法 ``` # ~/.vimrc set foldmethod=indent ``` 使用indent方式,vim会利用自动缩进进行折叠代码 ### 全局折叠 关闭折叠 打开折叠 *** `zm`关闭一层折叠 `zr`打开一层折叠 `zM`关闭多层折叠 `zR`打开多层折叠 ### 单行折叠 关闭折叠 打开折叠 *** `zc`关闭一层折叠 `zo`打开一层折叠 `zC`关闭多层折叠 `zO`打开多层折叠 ### 快速移动 后退 前进 *** `[z`折叠开始 `]z`折叠末尾 `zk`上一折叠 `zj`下一折叠 ## 一些其他命令 ### 自动换行等问题 设置行宽`:set textwidth=78` 删除换行符`gq`,`J` 自动折行(默认)`:set wrap` 取消自动折行`:set nowrap` ## 参考资料 \[1] --- url: /blog/2021/wp-376.md --- # top命令笔记 ## 界面介绍 第一行信息(`top -`) | 参数 |含义| |:---|:---| |`05:19:41`|系统当前时间| |`up 10 days, 20:15`|本机己经运行 10 天 20小时 15 分钟| |`1 users`|当前登录了一个用户| |`load average:0.01, 0.01, 0.00`|系统历史平均负载1分钟,5分钟,15分钟一般认为1表示一个CPU核满负载| 第二行信息(`Tasks: `) 任务进程信息 |参数 | 意义 | |-------------------| ----------------------------------------------| |`Tasks: 38 total` | 系统中的进程总数| |`1 running` | 正在运行的进程数| |`37 sleeping` | 睡眠的进程数| |`0 stoped` | 停止的进程数| |`0 zombie` | 僵尸进程数如果不是 0,则需要手工检查僵尸进程| 第三行信息(`%CPU(s)`) CPU状态信息,占用CPU百分比 |参数 | 意义| |----------| ------------------------| |0.0 us | 用户进程| |0.0 sy | 内核进程| |0.0 ni | 已调整优先级的用户进程| |100.0 id | 空闲| |0.0 wa | 等待IO| |0.0 hi | 处理硬件中断| |0.0 si | 处理软件中断| |0.0 st | 虚拟机| 交互命令 |命令 | 意义| |------| ---------------------------------------------| |P | 根据CPU使用百分比(%CPU)大小排序(默认状态)| |M | 根据内存使用量(%MEM)排序| |T | 根据时间(TIME)/累计时间(TIME+)排序| --- url: /blog/2021/wp-386.md --- # C语言中的指针 指针中相对复杂的主要有三类情况:指针数组、数组指针和函数指针,以及三者相互嵌套的情况。弄清楚C中各类符号的运算优先级才能真正理解其含义。 C语言运算符号优先级 |优先级 | 运算符 | 结合律| |--------| -------------------------------------------------| ----------| |1 | 后缀运算符:`[],(),.,->,++,--` | 从左到右| |2 | 一元运算符:`++,--,!,~,+,-,*,&,size_of` | 从右到左| |3 | 类型转换运算:`(int)` | 从右到左| |4 | 乘除运算符:`*,/,%` | 从左到右| |5 | 加减运算符:`+,-` | 从左到右| |6 | 移位运算符:`<<,>>` | 从左到右| |7 | 关系运算符:`<,<=,>,>=` | 从左到右| |8 | 相等运算符:`==,!=` | 从左到右| |9 | 位与运算符:`&` | 从左到右| |10 | 位异或运算符:`^` | 从左到右| |11 | 位或运算符:`\|` | 从左到右| |12 | 逻辑与运算符:`&&` | 从左到右| |13 | 逻辑或运算符:`\|\|` | 从左到右| |14 | 条件运算符:`?:` | 从右到左| |15 | 赋值运算符:`=,+=,-=,*=,/=,%=,&=,^=,\|=,<<=,>>=` | 从右到左| |16 | 逗号运算符:`,` | 从左到右| 运算符优先级和结合律 ```c int *p[3]; // [],*,int:数组,里面是指针,指向int int (*p)[3]; // *,[],int:指针,指向数组,数组中是int int (*p)(int a); // *,(int a),int:指针,指向参数为(int a)的函数,函数返回int int (*p[3])(int a); // [],*,(int a),int:数组,数组中是指针,指向参数为(int a)的函数,函数返回int int(*(*p())[])(); // (),*,[],*,(),int:无参函数,返回一个指针,指向一个数组,数组中是指针,指向一个无参函数,函数返回int ``` \[]运算级比\*高 ## 参考 1. [C语言运算符优先级](http://c.biancheng.net/view/285.html) 2. [C语言总结](https://www.jianshu.com/p/a6d5c9e3dd8c) 3. [C复杂指针](https://www.jianshu.com/p/30bfb7fbc3e8) --- url: /blog/2021/wp-396.md --- # WSL2网络测试 ### 测试网络情况 WSL访问window中的127.0.0.1 127.0.0.1 失败 wsl网关 失败 WSL访问windows中的wsl网关 127.0.0.1 失败 wsl网关 成功 配置转发,在windows中将wsl网关转发到对应localhost端口 ### 配置转发 window可以使用`netsh interface portproxy`进行端口转发配置 添加转发 ```powershell netsh interface portproxy add v4tov4 listenaddress=127.0.0.1 listenport=80 connectaddress=192.168.0.10 connectport=80 ``` 监听`127.0.0.1:80`并转发到`192.168.0.10:80` 需要对防火墙进行配置 成功使用vim连接Joplin windows重新启动后WSL2的IP地址会改变 在WSL内部利用`ip`命令获取主机IP ```bash ip route | grep default | awk '{print $3}' ``` 在windows中需要更改转发地址 ```powershell wsl -d Linux -u root ip addr add 192.168.10.10/24 broadcast 192.168.50.255 dev eth0 label eth0:1 netsh interface ip add address "vEthernet (WSL)" 192.168.10.11 255.255.255.0 ``` 当重启windows时会重置此配置 写一个脚本,实现两个ip配置 使用windows计划任务实现脚本自动启动 创建任务是选择使用最高权限运行和隐藏 经常遇到由于`iphlpsvc`进程异常,导致的端口映射失败,重启此进程可解决 `iphlpsvc`经常异常,不知是何缘故 通过脚本重启 ```powershell net stop iphlpsvc net start iphlpsvc ``` ### 参考 1.[终于找到给 wsl2 分配固定 ip 的方法](https://www.v2ex.com/t/744955) --- url: /blog/2021/wp-566.md --- # SPI协议 串行外设接口(SPI)是一种同步串行通信接口规范,主要用于嵌入式系统的短距离通信。 该接口由摩托罗拉公司在20世纪80年代中期开发,已成为事实上的标准。 典型的应用包括闪存,EEPROM,SD卡和液晶显示器等。 有时SPI被称为四线串行总线,与三线、二线和一线串行总线形成对比。 SPI可以被准确地描述为一个同步串行接口,但它与同步串行接口(SSI)协议不同,后者也是一个四线同步串行通信协议。 SSI协议采用差分信号,只提供一个单工通信通道。 于此相对的是,SPI是一个主机和多个从机的通信。 SPI设备之间使用全双工模式通信,是一个主机和一个或多个从机的主从模式。主机负责初始化帧,这个数据传输帧可以用于读与写两种操作,片选线路可以从多个从机选择一个来响应主机的请求。 ## 接口定义 SPI总线规定了4个保留逻辑信号接口: * SCLK(Serial Clock):串行时钟,由主机发出 * MOSI(Master Output, Slave Input):主机输出从机输入信号(数据由主机发出) * MISO(Master Input, Slave Output):主机输入从机输出信号(数据由从机发出) * SS(Slave Select):片选信号,由主机发出,一般是低电位有效 SPI总线的通信操作可以在单个主设备与一或多个从机之间进行。在只有单一从机的情况下,如果从机允许,可以固定为低电平。大多数从机具有三态逻辑的特性,当器件未被选中时MISO信号变为高阻抗(逻辑断开)。没有三态输出的器件不能与其他器件共享SPI总线段,但是可以使用外接的三态逻辑缓存来解决这个问题。 SPI通信有4种不同的模式,不同的从设备可能在出厂就是配置为某种模式,这是无法改变的;但我们的通信双方必须工作在同一模式下,所以我们可以通过对CPOL(时钟极性)和CPHA(时钟相位)的配置来设置我们可以通过对CPOL(时钟极性)和CPHA(时钟相位)的配置来设置我们的主设备的工作模式;CPOL是用来配置SCLK的电平的空闲态或者有效态时的电平,CPHA是用来配置数据采样是发生在上升沿还是下降沿。具体如下: * Mode0:CPOL=0,CPHA=0;空闲态时SCLK=0,数据在上升沿采样,在下降沿移位 * Mode1:CPOL=0,CPHA=1;空闲态时SCLK=0,数据在下降沿采样,在上升沿移位 * Mode2:CPOL=1,CPHA=0;空闲态时SCLK=1,数据在上升沿采样,在下降沿移位 * Mode3:CPOL=1,CPHA=1;空闲态时SCLK=1,数据在下降沿采样,在上升沿移位
四种模式 ## 数据传输限制 SPI数据传输速率主要受限与三点: 1. SPI的最大时钟频率 2. CPU处理SPI数据的能力 3. 输出端驱动能力(PCB所允许的最大信号传输速率) ## 扩展 一、SPI设备内部详细结构如图2所示,主要组件包括状态寄存器、控制寄存器和数据寄存器,数据位移逻辑单元,波特率发生器,主从控制逻辑单元和端口控制逻辑单元。
SPI设备内部详细结构图 二、由于在SPI Flash的通信中,全双工并不常用,因此扩展了标准SPI中MOSI、MISO的用法,使其工作在半双工,用于加倍数据传输,这就形成了Dual SPI;在Dual SPI的基础上再增加两条信号线(SIO2、SIO3),可以在一个时钟内传输4bit数据,使传输速率再次加倍,就形成了Qual SPI。 ## 参考 1. [硬件设计:接口--SPI总线](https://www.cnblogs.com/wcat/p/13854028.html) 2. [序列周邊介面](https://zh.wikipedia.org/wiki/%E5%BA%8F%E5%88%97%E5%91%A8%E9%82%8A%E4%BB%8B%E9%9D%A2) --- url: /blog/2021/wp-577.md --- # C语言库函数之函数间的goto ## setjmp() [C 库宏 -- setjmp() | 菜鸟教程 (runoob.com)](https://www.runoob.com/cprogramming/c-macro-setjmp.html) ``` void longjmp(jmp_buf environment, int value) ``` 返回0,等待`longjmp()`调用`setjmp()`宏执行处返回新值 ## longjmp() [C 库函数 -- longjmp() | 菜鸟教程 (runoob.com)](https://www.runoob.com/cprogramming/c-function-longjmp.html) ``` int setjmp(jmp_buf environment) ``` 恢复最近一次调用`setjmp()`宏时保存的环境 setjmp()和longjmp()可以实现从一个函数直接跳转到另一个函数继续执行,类似于goto语句(但goto语句仅限于函数内使用)。不过这两个跳转并不保证堆栈正常,因此需要程序自己实现堆栈的保存与恢复(也就是保护现场和恢复现场)。通过sp和bp指针即可访问当前堆栈内容。堆栈寄存器sp(Stack Pointer)一般用来存放栈的偏移指针;基数指针寄存器bp(Base Pointer)一般在函数中用来保存进入函数时的sp的栈顶基址。bp到sp之间的内容即为当前函数的现场。在c语言中获取sp和bp指针的方法如下: ## signal() [C 库函数 -- signal() | 菜鸟教程 (runoob.com)](https://www.runoob.com/cprogramming/c-function-signal.html) ``` void (*signal(int sig, void (*func)(int)))(int) ``` 宏 信号 *** SIGABRT (Signal Abort) 程序异常终止。 SIGFPE (Signal Floating-Point Exception) 算术运算出错,如除数为 0 或溢出(不一定是浮点运算)。 SIGILL (Signal Illegal Instruction) 非法函数映象,如非法指令,通常是由于代码中的某个变体或者尝试执行数据导致的。 SIGINT (Signal Interrupt) 中断信号,如 ctrl-C,通常由用户生成。 SIGSEGV (Signal Segmentation Violation) 非法访问存储器,如访问不存在的内存单元。 SIGTERM (Signal Terminate) 发送给本程序的终止请求信号。 sig参数 *** SIG\_DFL 默认的信号处理程序。 SIG\_IGN 忽视信号。 *** func参数 ## alarm() [alarm()函数的使用总结 - cs\_wu - 博客园 (cnblogs.com)](https://www.cnblogs.com/wuyepeng/p/9788919.html) ``` unsigned int alarm(unsigned int seconds); ``` 设置一定时间后进行中断,执行signal()传入的函数。 signal函数和alarm函数可以通过中断来实现函数间的跳转,signal函数定义中断事件类型和中断发生时需要执行的函数;alarm函数定义多长时间后发生中断。 ## 参考 1. [C语言(6)- 函数调用和栈 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/163852444) 2. [C 库宏 -- setjmp() | 菜鸟教程 (runoob.com)](https://www.runoob.com/cprogramming/c-macro-setjmp.html) 3. [alarm()函数的使用总结 - cs\_wu - 博客园 (cnblogs.com)](https://www.cnblogs.com/wuyepeng/p/9788919.html) 4. [SS、SP、BP寄存器 - dzqdevin - 博客园 (cnblogs.com)](https://www.cnblogs.com/dongzhiquan/p/4960602.html) --- url: /blog/2021/wp-584.md --- # C语言标准输出格式化 c语言输出函数`printf`和`fprintf`使用修饰符格式化输出不同类型的变量。在这里总结一下各种不同的修饰符以及输出数据的格式。 ## 修饰符 科学计数法 c语言输出修饰符 主要的修饰符如上表所示,而且可以在`%`和修饰符之间添加一个整型数字来指定输出内容的宽度,以及对齐方式。超出宽度的字符正常输出,负数表示左对齐: 另外针对整型和浮点型有特别的标识可以使用。 ## 整型 整型主要包括`short`,`int`,`long int`,`long long int`以及它们对应的无符号类型`unsigned short`,`unsigned int`,`unsigned long int`,`unsigned long long int`。整型可以以十进制(%d)、十六进制(%x)、八进制(%o)。 ``` int l = 31; printf( "十进制(%%d)\t%d\n", l ); printf( "十六进制(%%x,%%X)\t%x,%X\n", l, l ); printf( "八进制(%%o)\t%o\n", l ); printf( "十进制(%%04d,%%4d)\t%04d,%4d\n", l, l ); printf( "十六进制(%%04x,%%4X) \t%04x,%4X\n", l, l ); printf( "八进制(%%o04o,%%4o)\t%04o,%4o\n", l, l ); /* 十进制(%d) 31 十六进制(%x,%X) 1f,1F 八进制(%o) 37 十进制(%04d,%4d) 0031, 31 十六进制(%04x,%4X) 001f, 1F 八进制(%o4o,%4o) 0037, 37 */ ``` ## 浮点型 浮点型主要包括`float`,`double`两种类型。可以使用%f,%g,%e进行输出。 其中%g和%s都以科学计数法的方式输出,不过%g在默认情况下包括整数位共输出六位,而%f和%e默认小数位数为六位。不过可以通过.m来指定小数点后面的位数。 ## 参考 1. [C语言printf指定宽度的格式化输出 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/206738818) 2. [C/C++中浮点数输出格式问题 - sfesly - 博客园 (cnblogs.com)](https://www.cnblogs.com/hustxujinkang/p/4179373.html) --- url: /blog/2021/wp-600.md --- # WIFI通信 WIFi是以[IEEE 802.11](https://zh.wikipedia.org/wiki/IEEE_802.11)(一种无线局域网标准)为标准的一种实现。随着标准的不断修订,其传输速度在不断增加。现今第六代的传输速率可达9.6Gbit/s。下表为第一代到第六代的相关信息。对每一代仅列出了比较常见的标准,另外也有一些不常见的[标准](https://zh.wikipedia.org/wiki/IEEE_802.11_\(%E5%8E%9F%E5%A7%8B%E6%A0%87%E5%87%86\))。在多个厂商兼容802.11b后,802.11网络才开始大规模流行。 ## 网络成员和结构 * 站点(Station),网络最基本的组成部分。 * 基本服务单元(Basic Service Set,BSS)。网络最基本的服务单元。最简单的服务单元可以只由两个站点组成。站点可动态连结(associate)到基本服务单元中。 * 分配系统(Distribution System,DS)。分配系统用于连结不同的基本服务单元。逻辑上,分配系统使用的介质(Medium)和基本服务单元使用的介质完全不同,尽管物理上它们可能会是同一个介质,例如同一个无线频段。 * 接入点(Access Point,AP)。接入点即有普通站点的身份,又有连接到分配系统的功能。 * 扩展服务单元(Extended Service Set,ESS)。由分配系统和基本服务单元组合而成。这种组合是逻辑上,并非物理上。不同的基本服务单元物有可能在地理位置相去甚远。分配系统也可以使用各种各样的技术。 * 关口(Portal)。也是一个逻辑成分,用于将无线局域网和有线[局域网](https://zh.wikipedia.org/wiki/%E5%B1%80%E5%9F%9F%E7%BD%91)或其它网络联系起来。 这里有3种介质,站点使用的无线介质,分配系统使用的介质,以及和无线局域网集成一起的其它[局域网](https://zh.wikipedia.org/wiki/%E5%B1%80%E5%9F%9F%E7%BD%91)使用的介质。物理上它们可能互相重叠。[IEEE 802.11](https://zh.wikipedia.org/wiki/IEEE_802.11)只负责在站点使用的无线的介质上的寻址(Addressing)。分配系统和其它局域网的寻址不属无线局域网的范围。 IEEE802.11没有具体定义分配系统,只是定义了分配系统应该提供的服务(Service)。整个无线局域网定义了9种服务: * 5种服务属于分配系统的任务,分别为,连接(Association)、结束连接(Diassociation)、分配(Distribution)、集成(Integration)、再连接(Reassociation)。 * 4种服务属于站点的任务,分别为,鉴权(Authentication)、结束鉴权(Deauthentication)、隐私(Privacy)、MAC数据传输(MSDU delivery)。 ## 帧结构 一般802.11的帧结构由9个字段构成。 802.11帧结构 ### Frame Control {#Frame-Control} Frame control 所有的802.11帧都是由2字节的帧控制字段开始的,包括如下的字段: * Protocol(\[0:1]):表示该帧所使用的MAC版本,目前只有一个版本协议编号为0。 * Type(\[2:3]):表示帧类型。在802.11中定义了三种帧分别是:管理帧(Type=00b)、控制帧(Type=01b)、数据帧(Type=10b)。 * Subtype(\[4:7]):对于Type定义的三种帧,进行了进一步的细化,详细参见表1。 * ToDs(\[8]):表示该帧是BSS向DS发送的帧。 * FromDs(\[9]):表示该帧是DS向BSS发送的帧。 * More Frag(\[10]):说明了长帧分包发送的情况,为1表示该帧之后还有其它帧,为0表示该帧为最后一帧。 * Retry(\[11]):任何重传的帧都会将此位设定为1,以协助接收端剔除重复的帧。 * Pwr Mgmt(\[12]):只是该帧传送结束之后,Station应采用的电源管理模式。1表示Station即将进入省电(power-save)模式, 0表示将进入激活(active)模式。 * More Data(\[13]):针对处于省电模式的Station,AP会将从分布系统中接收来的帧加以缓存。AP将此位置1表示至少还有一帧需要传给休眠中的Station。 * Protected Frame(\[14]):如果该帧经过WEP(Wired Equivalent Privacy)加密处理则置1,否则置0。 * Order(\[15]):在分段传送长帧的时候,将该位置1,表示接收端需要严格按照顺序处理该帧。 ### Duration ID Duration ID(持续时间)占2个字节,主要用来记载网络分配矢量NAC的值,用来限制访问介质的时间。 ### Address address有五种类型: 1. BSSID,基本服务集标识符 2. DA,目的地址 3. SA,源地址 4. RA,接收端地址 5. TA,发送端地址 根据Frame Control中的ToDS和FromDS来确定其具体功能: |功能 |To DS | From DS | Address1(接收端) | Address2(发送端) | Address3 | Address4| |----------------------- |------| ---------| --------------------| --------------------| ----------|---------| |IBSS |0 | 0 | DA | SA | BSSID | 未使用 | |To AP(基础结构型) |1 | 0 | BSSID | SA | DA | 未使用 | |From AP(基础结构型) |0 | 1 | DA | BSSID | SA | 未使用 | |WDS(无线分布式系统) |1 | 1 | RA | TA | DA | SA | ### Seq-crl Seq-crl(Sequence Control)格式式如下,顺序编号4096的模数,从0开始,每处理一个上层封包就加1\ 若上层封包分片处理,所有帧分片采用相同顺序编号;对于重传帧,顺序编号不变 ### Frame Body字段 Frame Body字段中记录的是通信中实际的数据,所以该字段也是数据字段。它最多可以传送2304个字节的数据(为了能够支持[WEP](https://zh.wikipedia.org/wiki/%E6%9C%89%E7%B7%9A%E7%AD%89%E6%95%88%E5%8A%A0%E5%AF%86)(无线加密协议), 各供应商在具体实现的时候通常是支持2312个字节的数据)。802.11的[LLC](https://zh.wikipedia.org/wiki/%E9%80%BB%E8%BE%91%E9%93%BE%E8%B7%AF%E6%8E%A7%E5%88%B6)(逻辑链路控制)需要占8个字节,因此最多可以承载2296个字节数据。 ### FCS字段 FCS字段叫做帧校验序列(Frame Check Sequence, FCS),主要用来进行校验数据,确保传输过程没有发生错误(如果发生错误,将进行重传)。通常使用循环冗余校验[CSC](https://zh.wikipedia.org/wiki/%E5%BE%AA%E7%92%B0%E5%86%97%E9%A4%98%E6%A0%A1%E9%A9%97)(Cyclic Redundancy Check)校验位进行校验。 ## 参考 1. [Wi-Fi - 维基百科,自由的百科全书 (wikipedia.org)](https://zh.wikipedia.org/wiki/Wi-Fi) 2. [IEEE 802.11 - 维基百科,自由的百科全书 (wikipedia.org)](https://zh.wikipedia.org/wiki/IEEE_802.11) 3. [无处不在的小土-802.11帧结构 (gaoyichao.com)](https://gaoyichao.com/Xiaotu/?book=network_and_security\&title=802.11%E5%B8%A7%E7%BB%93%E6%9E%84) 4. [无线射频专题《协议类,IEEE 802.11/802.11b/802.11a/802.11g/802.11n/802.11ac标准简介》\_物联网研究室-BBC的博客-程序员秘密 - 程序员秘密 (cxymm.net)](https://cxymm.net/article/fengfeng0328/112798288) 5. [802.11 PHY层帧格式汇总 - 代码天地 (codetd.com)](http://codetd.com/article/12026114) 6. [802.11帧格式 - 代码天地 (codetd.com)](http://codetd.com/article/8450730) 7. [802.11 帧格式和分类详解 - 肉滚滚和代码 - 博客园 (cnblogs.com)](https://www.cnblogs.com/rougungun/p/14340489.html) 8. [802.11 MAC层 - 北落不吉 - 博客园 (cnblogs.com)](https://www.cnblogs.com/hzl6255/p/4084173.html) --- url: /blog/2021/wp-625.md --- # C语言日期时间 在本教程中,我们将展示如何在 C 中使用日期和时间。在本教程中,我们使用 C99。 ## C 日期时间定义 我们从几个定义开始。**日历时间**,也称为绝对时间,是时间连续体中的一个点,例如 2016 年 2 月 17 日 13:02:5 CET。**时间间隔**是两个日历时间之间时间连续体的连续部分,例如 2000 年 2 月 20 日 13:00 到 14:00 之间的小时。*经过的时间*是间隔的长度,例如 28 分钟。 **时间量**是经过的时间之和。经过的时间不需要是连续的。当工作花了我们十一个小时,我们可能是在不同的日子工作。**CPU 时间**是中央处理单元 (CPU) 用于处理计算机程序或操作系统指令的时间量。它以系统时钟或秒为单位进行测量。**Unix 纪元**是指从1970 年 1 月 1 日 00:00:00 UTC 开始的时间(或 1970-01-01T00:00:00Z ISO 8601)。 **Unix 时间**是自**Unix 纪元**以来经过的时间的秒数。 **Unix 时间**的类型为`time_t`。另外有一个**时间结构体**来表示人类可读的日历时间。它由一组包括年、月、日、时、分、秒等变量组成。**时间结构体**的类型为`struct tm`。 **现实时间**(Wall time、real-world time、wall-clock time)不同于通过计算机微处理器时钟脉冲或周期测量得到的时间。 `time.h`包含了以下函数: * `char *asctime(const struct tm *)` --- 将时间转化为字符串(过时的) * `clock_t clock(void)` --- 返回当前进程启动后使用的CPU时间 * `int clock_getres(clockid_t, struct timespec *)` --- 返回时钟精度 * `int clock_gettime(clockid_t, struct timespec *)` --- 用于计算时间,有秒和纳秒两种精度;比clock精确 * `int clock_settime(clockid_t, const struct timespec *)` --- 设置系统时间 * `char *ctime(const time_t *)` --- 将时间值转换为日期和时间字符串(过时) * `double difftime(time_t, time_t)` --- 计算两个时间的差 * `struct tm *getdate(const char *)` --- 将时间字符串转换为时间结构体 * `struct tm *gmtime(const time_t *)` --- 将Unix时间转换为时间结构体([UTC时间](https://baike.baidu.com/item/%E5%8D%8F%E8%B0%83%E4%B8%96%E7%95%8C%E6%97%B6/787659)) * `struct tm *localtime(const time_t *)` --- 将Unix时间转换为时间结构体(本地时间) * `time_t mktime(struct tm *)` --- 将时间结构体转换为Unix时间 * `size_t strftime(char *, size_t, const char *, const struct tm *)` --- 以指定格式输出时间字符串 * `char *strptime(const char *, const char *, struct tm *)` --- 以指定格式的时间字符串配置时间结构体 * `time_t time(time_t *)` --- 返回Unix时间或设置Unix时间 * `void tzset(void)` --- 设置时区信息 此外,`time.h`文件定义了`CLOCKS_PER_SEC`宏,它保存了每秒的处理器时钟刻度数。 `clock_t`是进程运行时间类型。 ## Unix时间(time\_t) Unix时间是指自Unix纪元以来的秒数。 time()函数返回自1970年1月1日协调世界时0小时0分0秒以来的时间值,单位为秒。 如果发生错误,它返回-1。 ``` time_t time(time_t *t); ``` 如果`t!=NULL`,此函数返回值也会存储到`t`指向的内存。 ``` #include #include // unixtime.c int main(void) { time_t now = time(NULL); if (now == -1) { puts("The time() function failed"); } printf("%ld\n", now); return 0; } //❯ ./unixtime //1638028257 ``` ## 时间结构体(struct tm) 时间结构体是一个人类友好的日期时间。结构体类型为`struct tm`。函数`localtime()`可以将 Unix时间(time\_t) 转换为时间结构体。他使用本地账户的时区。函数原型如下: ``` struct tm *localtime(const time_t *clock); ``` 函数存储了一个结构体并返回指针。 下面关于结构tm的描述来自FreeBSD手册。 ``` struct tm { int tm_sec; /* seconds:0-59 */ int tm_min; /* minutes:0-59 */ int tm_hour; /* hours:0-23 */ int tm_mday; /* day of the month:1-31 */ int tm_mon; /* month:0-11 */ int tm_year; /* year:1900-> */ int tm_wday; /* day of the week:0-6 */ int tm_yday; /* day in the year:0-365 */ int tm_isdst; /* daylight saving time:是否实行夏令时 */ }; #include #include int main(void) { time_t rawtime = time(NULL); // 得到Unix时间 if (rawtime == -1) { puts("The time() function failed"); return 1; } struct tm *ptm = localtime(&rawtime); // 将Unix时间转换为时间结构体 if (ptm == NULL) { puts("The localtime() function failed"); return 1; } printf("The time is: %02d:%02d:%02d\n", ptm->tm_hour, ptm->tm_min, ptm->tm_sec); return 0; } //❯ ./brokendowntime //The time is: 17:00:12 ``` ## 时间结构体转换为Unix时间 ## 参考 1. [C Date time (zetcode.com)](https://zetcode.com/articles/cdatetime/)\[原文] 2. [clock\_gettime的使用,计时比clock()精确 - 木叶火影 - 博客园 (cnblogs.com)](https://www.cnblogs.com/buptmuye/p/3711022.html) 3. [C语言getdate函数、setdate函数的用法 - 简书 (jianshu.com)](https://www.jianshu.com/p/0161e730eb5d) --- url: /blog/2021/wp-631.md --- # tar命令详解 解包命令:`tar -xvf all.tar`\ 打包命令:`tar -cvf all.tar dir1 dir2 1.txt 2.txt`\ 解压命令:`tar -x[zjZ][v]f all.tar.[gz/bz2/Z]`\ 压缩命令:`tar -c[zjZ]f all.tar.[gz/bz2/Z]` 更新压缩档案中的文件 压缩解压参数 这五个参数是完全独立的,必须且只能使用其中一个与另外的参数配合进行某项操作。 压缩为compress(后缀Z) 压缩解压方案参数 这三个参数用来选择一个压缩方案,如果没有选择任何方案,则仅进行打包解包命令(后缀tar)。 输出文件到标准输出 其它配置 这两个参数添加一些其它特性。它们也是可选择。 ## 总结 `unzip file.zip` 压缩解压示例 ## 参考 1. [(21)tar打包命令详解 - 星痕1216 - 博客园 (cnblogs.com)](https://www.cnblogs.com/xinghen1216/p/11307956.html) 2. [tar 压缩命令 - xiondun - 博客园 (cnblogs.com)](https://www.cnblogs.com/xiondun/p/13208881.html) --- url: /blog/2021/wp-661.md --- # cargo报错 遇到一个cargo错误 ``` error: no override and no default toolchain set ``` 用rustup重新下载并设置默认版本 ``` rustup install stable rustup default stable ``` 下载更新完成后就无错误了 ## 参考 1. [HELP GRANTED : \[ SOLVED \] Arch Linux error: no default toolchain configured (hongouru.blogspot.com)](http://hongouru.blogspot.com/2018/11/solved-arch-linux-error-no-default.html?m=0) --- url: /blog/2022/CSS-flex-layout.md --- # CSS flex 布局 ## flex-direction 决定主轴方向 `row`,`row-reverse`,`colum`,`colum-reverse` ## flex-wrap 是否换行 `nowrap`,`wrap`,`wrap-reverse` ## flex-flow flex-direction属性和flex-wrap属性的简写形式 ## justify-content 对齐方式 `flex-start`,`flex-end`,`center`,`space-between`,`space-around` ## align-items 在交叉轴上的对齐方式 `flex-start`,`flex-end`,`center`,`baseline`,`stretch` ## align-content 定义多根轴线的对齐方式 `flex-start`,`flex-end`,`center`,`space-between`,`space-around`,`stretch` ## flex-basis `flex-basis: number|auto|initial|inherit;` `flex-basis`设置弹性项目的初始长度。 | 值 | 描述 | | --- | --- | | number | 长度单位或百分百,规定弹性项目的初始长度。 | | auto | 默认值。长度等于弹性项目的长度。如果该项目未规定长度,则长度将依据其内容。 | | initial | 将此属性设置为其默认值。| | inherit | 从其父元素继承此属性。 | ## flex-grow `flex-grow: number|initial|inherit;` `flex-grow`属性规定在相同的容器中,项目相对于其余弹性项目的增长量。 | 值 | 描述 | | --- | --- | | number | 一个数字,规定项目相对于其他灵活的项目进行扩展的量。默认值是 0。 | | initial | 将此属性设置为其默认值。| | inherit | 从其父元素继承此属性。 | ## flex-shrink `flex-shrink: number|initial|inherit;` `flex-shrink`属性规定当项目溢出时,按比例对所有项目进行空间收缩。 | 值 | 描述 | | --- | --- | | number | 一个数字,规定项目相对于其他灵活的项目进行扩展的量。默认值是 0。 | | initial | 将此属性设置为其默认值。| | inherit | 从其父元素继承此属性。 | ## 参考 1.[Flex布局语法教程|菜鸟教程](https://www.runoob.com/w3cnote/flex-grammar.html) --- url: /blog/2022/Hash-algorithm.md --- # 常见 Hash 算法 ## 什么是 hash 算法 Hash 算法又称散列算法,特点是把任意长度的输出,通过一些列的计算后变成固定长度的输出,这个输出值即为散列值。由于散列值的空间远小于输出空间,因此存在不同输入得到相同输出的情况,这种现象称为碰撞。 ## hash 算法的应用 hash 算法应用广泛,主要有七个方面:安全加密、唯一标识、数据校验、散列函数、负载均衡、数据分片、分布式存储。不同的应用利用的 hash 算法不同的特性,具体的算法实现也需要根据情况设计。比较常见的实现有 md5,sha256 等。本文通过这两个算法来研究具体的实现。 ## MD5 Md5 算法将输入的不定长数据分为 512bit 的块,并对每个块循环调用 MD5 运算。 ![md5](./imgs/md5.png) 一个 MD5 运算由类似的 64 次循环构成,分成 4 组 16 次。F 是一个非线性函数;一个函数运算一次。Mi 表示一个 32-bits 的输入数据,Ki 表示一个 32-bits 常数,用来完成每次不同的计算。 ## md5 的 python 实现 ```python def Md5sum(message: bytes) -> bytes: # 定义常量,用于初始化128位变量,注意字节顺序,A=0x01234567,这里低值存放低字节, # 即01 23 45 67,所以运算时A=0x67452301,其他类似。 # 用字符串的形势,是为了和hex函数的输出统一,hex(10)输出为'0xA',注意结果为字符串。 h0 = 0x67452301 h1 = 0xefcdab89 h2 = 0x98badcfe h3 = 0x10325476 # 定义每轮中循环左移的位数,用元组表示 4*4*4=64 R = (7, 12, 17, 22) * 4 + (5, 9, 14, 20) * 4 + \ (4, 11, 16, 23) * 4 + (6, 10, 15, 21) * 4 # 定义常数K 64 # K[i] = (int(abs(math.sin(i + 1)) * 2 ** 32)) & 0xffffffff K = (0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391) # 定义每轮中用到的函数。L为循环左移, # 左移之后可能会超过32位,所以要和0xffffffff做与运算,确保结果为32位。 F = lambda x, y, z: ((x & y) | ((~x) & z)) G = lambda x, y, z: ((x & z) | (y & (~z))) H = lambda x, y, z: (x ^ y ^ z) I = lambda x, y, z: (y ^ (x | (~z))) L = lambda x, n: ((x << n) | (x >> (32 - n))) & 0xffffffff # 小端 0x12,0x34,0x56,0x78 -> 0x78563412 # 将四个8位无符号数转化为一个32位无符号数 W = lambda i4, i3, i2, i1: (i1 << 24) | (i2 << 16) | (i3 << 8) | i4 # 字节翻转 0x12345678 -> 0x78563412 将一个32位无符号数的高位和低位进行对换 reverse = lambda x: (x << 24) & 0xff000000 | (x << 8) & 0x00ff0000 | \ (x >> 8) & 0x0000ff00 | (x >> 24) & 0x000000ff # 对每一个输入先添加一个'0x80',即'10000000', 即128 ascii_list = list(map(lambda x: x, message)) msg_length = len(ascii_list) * 8 ascii_list.append(128) # 补充0 while (len(ascii_list) * 8 + 64) % 512 != 0: ascii_list.append(0) # 最后64为存放消息长度,以小端数存放。 # 例如,消息为'a',则长度是8,则添加'0x0800000000000000' for i in range(8): ascii_list.append((msg_length >> (8 * i)) & 0xff) # print(ascii_list) # print(len(ascii_list)//64) # 对每一消息块进行迭代 for i in range(len(ascii_list) // 64): # print(ascii_list[i*64:(i+1)*64]) # 对每一个消息块进行循环,每个消息块512bits=16*32bits=64*8bits a, b, c, d = h0, h1, h2, h3 for j in range(64): # 64轮的主循环 if 0 <= j <= 15: f = F(b, c, d) & 0xffffffff g = j elif 16 <= j <= 31: f = G(b, c, d) & 0xffffffff g = ((5 * j) + 1) % 16 elif 32 <= j <= 47: f = H(b, c, d) & 0xffffffff g = ((3 * j) + 5) % 16 else: f = I(b, c, d) & 0xffffffff g = (7 * j) % 16 aa, dd, cc = d, c, b # 第i个chunk,第g个32-bit s = i * 64 + g * 4 w = W(ascii_list[s], ascii_list[s + 1], ascii_list[s + 2], ascii_list[s + 3]) bb = (L((a + f + K[j] + w) & 0xffffffff, R[j]) + b) & 0xffffffff a, b, c, d = aa, bb, cc, dd # print(b) h0 = (h0 + a) & 0xffffffff h1 = (h1 + b) & 0xffffffff h2 = (h2 + c) & 0xffffffff h3 = (h3 + d) & 0xffffffff h0, h1, h2, h3 = reverse(h0), reverse(h1), reverse(h2), reverse(h3) digest = (h0 << 96) | (h1 << 64) | (h2 << 32) | h3 return hex(digest)[2:].rjust(32, '0') if __name__ == '__main__': print("自己实现md5(b'https://blog.vhcffh.com')") print(Md5sum(b"https://blog.vhcffh.com")) import hashlib t = hashlib.md5() t.update(b"https://blog.vhcffh.com") print("调用hashlib库md5(b'https://blog.vhcffh.com')") print(t.hexdigest()) ``` ### Shell 命令 md5sum ```bash $ echo "blog.vhcffh.com" | md5sum 6f5902ac237024bdd0c176cb93063dc4 - $ md5sum test.txt 6f5902ac237024bdd0c176cb93063dc4 test.txt ``` ## SHA256 SHA256 是安全散列算法 2(SHA-2,Secure Hash Algorithm 2)中的一个算法标准,由[美国国家标准与技术研究院](https://zh.wikipedia.org/wiki/%E7%BE%8E%E5%9B%BD%E5%9B%BD%E5%AE%B6%E6%A0%87%E5%87%86%E4%B8%8E%E6%8A%80%E6%9C%AF%E7%A0%94%E7%A9%B6%E9%99%A2)(NIST)在 2001 年发布。 ![sha256](./imgs/sha256.png) SHA-2 的第 t 个加密循环。图中的深蓝色方块是事先定义好的非线性函数。ABCDEFGH 一开始分别是八个初始值,Kt 是第 t 个密钥,Wt 是本区块产生第 t 个 word。原消息被切成固定长度的区块,对每一个区块,产生 n 个 word(n 视算法而定),透过重复运作循环 n 次对 ABCDEFGH 这八个工作区段循环加密。最后一次循环所产生的八段字符串合起来即是此区块对应到的散列字符串。若原消息包含数个区块,则最后还要将这些区块产生的散列字符串加以混合才能产生最后的散列字符串。 ### SHA256 的 python 实现 ```python def Sha256sum(message: bytes) -> bytes: # 定义常量 # 前8个素数2..19的平方根的小数部分的前32位 h0 = 0x6a09e667 h1 = 0xbb67ae85 h2 = 0x3c6ef372 h3 = 0xa54ff53a h4 = 0x510e527f h5 = 0x9b05688c h6 = 0x1f83d9ab h7 = 0x5be0cd19 # 定义常数K 64 # 前64个素数2..311的立方根的小数部分的前32位 K = (0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2) # R为循环右移, # 右移之后可能会超过32位,所以要和0xffffffff做与运算,确保结果为32位。 R = lambda x, n: ((x >> n) | (x << (32 - n))) & 0xffffffff # 大端 0x12,0x34,0x56,0x78 -> 0x12345678 W = lambda i1, i2, i3, i4: (i1 << 24) | (i2 << 16) | (i3 << 8) | i4 # 对每一个输入先添加一个'0x80',即'10000000', 即128 ascii_list = list(map(lambda x: x, message)) msg_length = len(ascii_list) * 8 ascii_list.append(128) # 补充0 while (len(ascii_list) * 8 + 64) % 512 != 0: ascii_list.append(0) # 最后64为存放消息长度,以大端数存放。 # 例如,消息为'a',则长度为'0x0000000000000008' for i in range(8): ascii_list.append(msg_length >> (8 * (7 - i)) & 0xff) # print(ascii_list) # print(len(ascii_list)//64) for i in range(len(ascii_list) // 64): # 64*8=512bits # print(ascii_list[i*64:(i+1)*64]) # 每个512bits的块进行循环 w = [] # 将512bits扩展到64*32bits=2048bits存入32位无符号数数组 for j in range(16): s = i * 64 + j * 4 w.append(W(ascii_list[s], ascii_list[s + 1], ascii_list[s + 2], ascii_list[s + 3])) for j in range(16, 64): s0 = (R(w[j - 15], 7)) ^ (R(w[j - 15], 18)) ^ (w[j - 15] >> 3) s1 = (R(w[j - 2], 17)) ^ (R(w[j - 2], 19)) ^ (w[j - 2] >> 10) w.append((w[j - 16] + s0 + w[j - 7] + s1) & 0xffffffff) # print(hex(s0)+':'+hex(s1)+':' + hex(R(w[j - 2], 17))) # 初始化 a, b, c, d, e, f, g, h = h0, h1, h2, h3, h4, h5, h6, h7 # for j in w: # print(hex(j)[2:]) for j in range(64): s0 = R(a, 2) ^ R(a, 13) ^ R(a, 22) maj = (a & b) ^ (a & c) ^ (b & c) t2 = s0 + maj s1 = R(e, 6) ^ R(e, 11) ^ R(e, 25) ch = (e & f) ^ ((~e) & g) t1 = h + s1 + ch + K[j] + w[j] h = g & 0xffffffff g = f & 0xffffffff f = e & 0xffffffff e = (d + t1) & 0xffffffff d = c & 0xffffffff c = b & 0xffffffff b = a & 0xffffffff a = (t1 + t2) & 0xffffffff h0 = (h0 + a) & 0xffffffff h1 = (h1 + b) & 0xffffffff h2 = (h2 + c) & 0xffffffff h3 = (h3 + d) & 0xffffffff h4 = (h4 + e) & 0xffffffff h5 = (h5 + f) & 0xffffffff h6 = (h6 + g) & 0xffffffff h7 = (h7 + h) & 0xffffffff digest = (h0 << 224) | (h1 << 192) | (h2 << 160) | (h3 << 128) digest |= (h4 << 96) | (h5 << 64) | (h6 << 32) | h7 # print(hex(digest)[2:]) # .rjust(32, '0')) return hex(digest)[2:] # .rjust(32, '0') if __name__ == '__main__': print("自己实现sha256(https://blog.vhcffh.com)") print(Sha256sum(b"https://blog.vhcffh.com")) import hashlib t = hashlib.sha256() t.update(b"https://blog.vhcffh.com") print("调用hashlib库sha256(https://blog.vhcffh.com)") print(t.hexdigest()) ``` ### Shell 命令 md5sum ```bash $ echo "blog.vhcffh.com" | sha256sum a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447 - $ sha256sum test.txt a948904f2f0f479b8f8197694b30184b0d2ed1c1cd2a1ec0fb85d299a192a447 test.txt ``` ## 参考 1. [Hash 算法及相关应用 | Phukety 的个人博客](https://phukety.github.io/2020/11/30/Hash-algorithm/) 2. [YCBlogs/02.哈希算法应用.md](https://github.com/yangchong211/YCBlogs/blob/master/leetcode/12.Hash/02.%E5%93%88%E5%B8%8C%E7%AE%97%E6%B3%95%E5%BA%94%E7%94%A8.md) --- url: /blog/2022/Static-and-Dynamic-Libraries.md --- # 动态库与静态库 ### 链接库 链接库的行为是代码依赖管理的一种形式。 当任何应用程序运行时,其可执行代码都会加载到内存中。 此外,它所依赖的任何代码库也会加载到内存中。 有两种类型的链接:静态的和动态的。 两者都为开发人员提供了不同的好处,应该根据这些好处来选择合适的链接方式。 这篇博文将介绍每种方法提供的好处,然后解释如何在Linux上创建和链接您自己的库的基础知识。 *** ### 动态链接 链接动态库时,没有任何库代码直接包含在链接目标中。 相反,在解析符号之前,这些库会在运行时加载到内存中。 因为代码不是静态链接到可执行二进制文件中的,所以在运行时加载有一些好处。 主要是,可以使用新功能或错误修复来更新库,而无需重新编译和重新链接可执行文件。 此外,在运行时加载意味着各个代码库可以拥有自己的初始化程序,并在从内存中卸载之前在自己的任务之后进行清理。 #### 动态库 动态库是一种 Mach-O 二进制文件,在应用程序启动或运行时加载。 由于动态库中的可执行代码不是静态链接到目标可执行文件,因此在需要重用相同代码时提供了一些好处。 例如,如果您有一个应用程序和一个守护程序或扩展程序需要使用相同的代码,那么该代码只需要存在于一个位置——动态库中,而不是同时存在于可执行文件的二进制文件和守护程序的二进制文件中。 由于动态库是在运行时加载的,因此库负责告诉链接器需要哪些附加代码。 这消除了管理您使用的所有代码需要操作的负担。 接下来通过一个简单的例子演示动态库的使用。 #### 构建 首先构建一个libmax.so的动态库。 ```c // max.c #include "max.h" int max( int a, int b ) { if ( a > b ) { return a; } else { return b; } } ``` ```c // max.h #ifndef _MAX_H_ #define _MAX_H_ int max( int a, int b ); #endif ``` ```c // main.h #include "max.h" #include int main() { int a = 0, b = 1; printf("a: %d, b: %d max: %d\n", a, b, max(a,b)); return 1; } ``` ##### 编译 ```bash # -fPIC是编译选项,PIC是 Position Independent Code 的缩写,表示要生成位置无关的代码,这是动态库需要的特性 gcc -o max.o -c max.c -fPIC gcc -o main.o -c main.c ``` ##### 生成动态库 ```bash # -shared是链接选项,告诉gcc生成动态库而不是可执行文件 gcc -o libmax.so max.o -shared ``` #### 链接 ```bash gcc -o main main.o -L . -lmax ``` #### 运行 ``` $ ./main ./main: error while loading shared libraries: libmax.so: cannot open shared object file: No such file or directory $ ldd ./main linux-vdso.so.1 (0x00007fff00bda000) libmax.so => not found libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007efe58c48000) /lib64/ld-linux-x86-64.so.2 (0x00007efe58e21000) ``` 直接运行会出错,因为动态库需要在运行是加载,需要一些配置指定动态库的位置。 通过ldd命令可以查看到系统确实libmax.so的动态库。 有两种方法可以运行我们的程序 1. 添加环境变量`LD_LIBRARY_PATH` ``` $ LD_LIBRARY_PATH=. ./main a: 0, b: 1 max: 1 ``` 2. 将libmax.so安装到系统,并通过ldconfig更新动态库,可以通过makefile文件编写安装和卸载规则。\ 当然`install`和`uninstall`需要root权限 ```makefile OUTPUT:=./build LIBDIR:=${OUTPUT}/lib BINDIR:=${OUTPUT}/bin main: main.o libmax.so @mkdir -p ${BINDIR} gcc -o ${BINDIR}/main ${OUTPUT}/main.o -L ${LIBDIR} -lmax main.o: main.c @mkdir -p ${OUTPUT} gcc -o ${OUTPUT}/main.o -c main.c max.o: max.c @mkdir -p ${OUTPUT} @# -fPIC是编译选项,PIC是 Position Independent Code 的缩写,表示要生成位置无关的代码,这是动态库需要的特性 gcc -o ${OUTPUT}/max.o -c max.c -fPIC libmax.so: max.o @mkdir -p ${LIBDIR} @# -shared是链接选项,告诉gcc生成动态库而不是可执行文件 gcc -o ${LIBDIR}/libmax.so ${OUTPUT}/max.o -shared install: main @# 安装文件 cp ${LIBDIR}/libmax.so /usr/lib cp ${BINDIR}/main /usr/bin @# ldconfg更新/etc/ld.so.cache ldconfig uninstall: rm -rf /usr/lib/libmax.so rm -rf /usr/bin/main ldconfig clean: rm -rf build ``` #### 运行时加载 还有一种可以直接在程序运行时指定动态库的路径,并通过系统api直接加载,这样的好处是程序可以只更新部分组件。具体的使用方法参考[dlopen(3) - Linux man page](https://linux.die.net/man/3/dlopen)。 ```c #include void *dlopen(const char *filename, int flag); char *dlerror(void); void *dlsym(void *handle, const char *symbol); int dlclose(void *handle); ``` *** ### 静态链接 与动态链接不同,静态链接将库中的代码包含到目标的二进制文件中。 这会导致目标程序占用磁盘空间变大,启动时间变慢。 因为库的代码直接添加到链接目标的二进制文件中,这意味着要更新库中的任何代码,都必须重建链接目标。 #### 静态库 静态库是一组目标文件的容器。 静态库使用来自`ar`进行构建,类型的文件扩展名“.a”。 接下来通过一个简单的例子演示静态库的使用。 #### 构建 这里构建一个libmax.a的动态库。 (相关代码与动态库例子相同。) ##### 编译 ```bash gcc -o max.o -c max.c gcc -o main.o -c main.c ``` ##### 生成静态库 静态库的生成使用`ar`工具,具体的使用可以参考[linux man](https://linux.die.net/man/1/ar) ```bash ar cr libmax.a max.o ``` #### 链接 ```bash gcc -o main main.o -L . -lmax ``` #### 运行 直接运行即可,可以比较一下动态库和静态库生成的两个程序的大小,显然动态库生成的程序更小 ```bash # 静态链接生成的程序 $ ls -all ./main -rwxr-xr-x 1 frey frey 16664 Aug 10 20:54 ./main # 动态链接生成的程序 $ ls -all ./main -rwxr-xr-x 1 frey frey 16640 Aug 11 22:54 ./main ``` ### 参考 1. [Static and Dynamic Libraries](https://pewpewthespells.com/blog/static_and_dynamic_libraries.html) 2. [C语言丨静态库与动态库的区别,你知道多少?](https://zhuanlan.zhihu.com/p/307640255) 3. [Linux动态库生成与使用指南](https://www.cnblogs.com/jiqingwu/p/linux_dynamic_lib_create.html) 4. [dlopen(3) - Linux man page](https://linux.die.net/man/3/dlopen) 5. [ar(1) - Linux man page](https://linux.die.net/man/1/ar) --- url: /blog/2022/WSL转移后无法入网.md --- # WSL转移后无法入网 ## 缘由 新组装了一台台式机,需要将笔记本中的WSL子系统导出到新的台式机中,在转移过程中遇到了子系统DNS解析不正常和子系统与Host端口映射问题,在此进行一些记录。 ## 系统导出 ```powershell # wsl --export <发行版> [选项] wsl --export Linux linux.tar ``` ## 系统导入 导入系统时,在新系统上执行`wsl --help`命令后没有`--import`选项;查找原因后发现需要wsl进行升级,安装[教程](https://docs.microsoft.com/zh-cn/windows/wsl/basic-commands#set-wsl-version-to-1-or-2)对wsl进行升级;旧版wsl可能需要进行手动安装[教程](https://docs.microsoft.com/zh-cn/windows/wsl/install-manual)。更新后导入即可 ```powershell # wsl --import <发行版> [选项] wsl --import Linux d:/Linux/ linux.tar ``` 尽量按照到非系统盘,保留系统盘空间 ## 网络重置 导入系统后,系统无法联网,首先查看子系统机host的ip地址情况 ```bash $ ifconfig eth0: flags=4163 mtu 1500 inet 172.23.202.215 netmask 255.255.240.0 broadcast 172.23.207.255 inet6 fe80::215:5dff:feb6:259d prefixlen 64 scopeid 0x20 ether 00:15:5d:b6:25:9d txqueuelen 1000 (Ethernet) RX packets 19 bytes 4618 (4.5 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 30 bytes 1692 (1.6 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 lo: flags=73 mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 inet6 ::1 prefixlen 128 scopeid 0x10 loop txqueuelen 1000 (Local Loopback) RX packets 18 bytes 1546 (1.5 KiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 18 bytes 1546 (1.5 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 $ route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface default 172.23.192.1 0.0.0.0 UG 0 0 0 eth0 172.23.192.0 0.0.0.0 255.255.240.0 U 0 0 0 eth0 ``` ```cmd ipconfig 以太网适配器 vEthernet (WSL): 连接特定的 DNS 后缀 . . . . . . . : 本地链接 IPv6 地址. . . . . . . . : fe80::6dfb:1789:6e91:f5dd%48 自动配置 IPv4 地址 . . . . . . . : 169.254.245.221 子网掩码 . . . . . . . . . . . . : 255.255.0.0 默认网关. . . . . . . . . . . . . : ``` 主机WSL虚拟网卡地址与wsl子系统地址不在一个网段,需要重置主机网络 ```cmd wsl --shutdown netsh winsock reset netsh int ip reset all netsh winhttp reset proxy ipconfig /flushdns ``` ## 防火墙配置 经过网络重置后,子系统应该可以ping通主机ip,如果无法ping通,可能是windows防火墙配置问题,对windows防火墙添加一条规则 ```powershell New-NetFirewallRule -DisplayName "WSL" -Direction Inbound -InterfaceAlias "vEthernet (WSL)" -Action Allow ``` ## DNS解析问题 如果仅仅可以ping通ip,而不能ping通域名,那是域名解析出现了问题 应该是在导入后域名解析出现了问题,一般是由于文件'/etc/resolv.conf'引起的,有两种方式可以处理 1. 配置此文件自动生成 在文件`/etc/wsl.conf`中注释掉下面一行,这样在wsl重启后会自动生成文件`/etc/resolv.conf` ``` [network] #generateResolvConf = false ``` 2. [自己创建此文件](https://blog.vhcffh.com/wp-675/) 建议用这种方式,可以在子系统中自定义域名解析服务器 ```bash sudo rm /etc/resolv.conf sudo bash -c 'echo "nameserver 8.8.8.8" > /etc/resolv.conf' sudo bash -c 'echo "[network]" > /etc/wsl.conf' sudo bash -c 'echo "generateResolvConf = false" >> /etc/wsl.conf' sudo chattr +i /etc/resolv.conf ``` 首先删除文件,然后新建此文件并重新配置wsl.conf文件,最后chattr命令是更改此文件属性(没有这一句wsl重启后resolv.conf文件会消失) ## 端口直接映射问题 在原本的笔记本中,子系统开启的端口(不论是127.0.0.1还是0.0.0.0)都可以在windows中的127.0.0.1直接访问,但在新系统中无法实现 经排查是进程wslhost.exe的问题,主要是window系统版本不同引起的 在windows更新中加入预览版体验,跟新系统版本到22H2后wsl的端口直接映射功能就有了,而且增加了子系统的窗口组件,可以直接运行子系统的窗口应用了 并且要更新wsl ```cmd wsl --update ``` 但经测试wsl中端口大于2610的仍无法直接映射,原因不明 ## 参考 1. [WSL基本命令](https://docs.microsoft.com/zh-cn/windows/wsl/basic-commands) 2. [旧版 WSL 的手动安装步骤](https://docs.microsoft.com/zh-cn/windows/wsl/install-manual) 3. [WSL2联网问题](https://blog.vhcffh.com/wp-675/) --- url: /blog/2022/python-implements-AES-encryption.md --- # python 实现 AES 加密 摘要:作为新一代的加密标准,AES 旨在取代 [DES](https://blog.vhcffh.com/python-implements-des-encryption/),以适应当今分布式开放网络对数据加密安全性的要求。 本文在分析了 AES 加密原理的基础上着重说明了算法实现的具体步骤,并用 python 实现了对字符串的加密和解密。 ## AES 介绍 AES(高级加密标准,Advanced Encryption Standard),在密码学中又称 Rijndael 加密法,是美国联邦政府采用的一种分组加密标准。这个标准用来替代原先的 DES,目前已经广为全世界所使用,成为对称密钥算法中最流行的算法之一。 在 AES 出现之前,最常用的对称密钥算法是 DES 加密算法,它在 1977 年被公布成为美国政府的商用加密标准。 DES 的主要问题是密钥长度较短,渐渐不适合于分布式开放网络对数据加密安全性的要求。 因此,1998 年美国政府决定不再继续延用 DES 作为联邦加密标准,并发起了征集 AES 候选算法的活动。 征集活动对 AES 的基本要求是: 比三重 DES 快、至少与三重 DES 一样安全、数据分组长度为 128 比特、密钥长度为 128/192/256 比特。 经过三年多的甄选,比利时的密码学家所设计的 Rijndael 算法最终脱颖而出,成为新一代的高级加密标准,并于 2001 年由美国国家标准与技术研究院(NIST)发布于 FIPS PUB 197。 ## AES 算法原理 AES 算法(即 Rijndael 算法)是一个对称分组密码算法。 数据分组长度必须是 128 bits,使用的密钥长度为 128,192 或 256 bits。 对于三种不同密钥长度的 AES 算法,分别称为“AES-128”、“AES-192”、“AES-256”。 (Rijndael 的设计还可以处理其它的分组长度和密钥长度,但 AES 标准中没有采用) 下图是 AES 加密解密的整体流程图: ![AES](./imgs/AES.png) 这里我们需要知道 3 个符号: * Nb: 状态 State 包含的列(32-bit 字)的个数,也就是说 Nb=4; * Nk: 密钥包含的 32-bit 字的个数,也就是说 Nk=4,6 或 8; * Nr: 加密的轮数,对于不同密钥长度,轮数不一样,具体如下表所示: | | 密钥长度(Nk words) | 分组大小(Nb words) | 轮数(Nr) | | :-----: | :----------------: | :----------------: | :------: | | AES-128 | 4 | 4 | 10 | | AES-192 | 6 | 4 | 12 | | AES-256 | 8 | 4 | 14 | 下面分三个部分对 AES 加密解密流程进行梳理 ### 密钥扩展 AES 算法通过密钥扩展程序(Key Expansion)将用户输入的密钥`K`扩展生成`Nb(Nr+1)`个字,存放在一个线性数组`w[Nb*(Nr+1)]`中。具体如下: 1. 位置变换函数`RotWord()`,接受一个字`[a0, a1, a2, a3]`作为输入,循环左移一个字节后输出`[a1, a2, a3, a0]`。 2. S 盒变换函数`SubWord()`,接受一个字`[a0, a1, a2, a3]`作为输入。S 盒是一个 16x16 的表,其中每一个元素是一个字节。对于输入的每一个字节,前四位组成十六进制数`x`作为行号,后四位组成的十六进制数`y`作为列号,查找表中对应的值。最后函数输出 4 个新字节组成的 32-bit 字。 3. 轮常数`Rcon[]`,如何计算的就不说了,直接把它当做常量数组。 扩展密钥数组`w[]`的前`Nk`个元素就是外部密钥`K`,后的元素`w[i]`等于它前一个元素`w[i-1]`与前第`Nk`个元素`w[i-Nk]`的异或,即`w[i] = w[i-1] XOR w[i-Nk]`;但若`i`为`Nk`的倍数,则`w[i] = w[i-Nk] XOR SubWord(RotWord(w[i-1])) XOR Rcon[i/Nk-1]`。 注意,上面的第四步说明仅适合于 AES-128 和 AES-192; ### 加密 AES 加密过程涉及到 4 种变换:S 盒变换,行变换,列变换,与扩展密钥的异或 #### S 盒变换-SubBytes() 在密钥扩展部分已经讲过了,S 盒是一个 16 行 16 列的表,表中每个元素都是一个字节。S 盒变换很简单:函数`SubBytes()`接受一个 4x4 的字节矩阵作为输入,对其中的每个字节,前四位组成十六进制数 x 作为行号,后四位组成的十六进制数 y 作为列号,查找表中对应的值替换原来位置上的字节。 ![SubBytes](./imgs/SubBytes.png) #### 行变换-ShiftRows() 行变换也很简单,它仅仅是将矩阵的每一行以字节为单位循环移位:第一行不变,第二行左移一位,第三行左移两位,第四行左移三位。如下图所示: ![ShiftRows](./imgs/ShiftRows.png) #### 与扩展密钥的异或-AddRoundKey() 扩展密钥只参与了这一步。根据当前加密的轮数,用`w[]`中的 4 个扩展密钥与矩阵的 4 个列进行按位异或。如下图: ![AddRoundKey](./imgs/AddRoundKey.png) ### 解密 解密需要分别实现 S 盒变换、行变换和列变换的逆变换 InvShiftRows(),InvSubBytes(),InvMixColumns() ## python 实现 ```python S_Box = [ [0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76], [0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0], [0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15], [0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75], [0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84], [0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF], [0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8], [0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2], [0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73], [0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB], [0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79], [0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08], [0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A], [0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E], [0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF], [0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16]] Inv_S_Box = [ [0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB], [0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB], [0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E], [0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25], [0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92], [0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84], [0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06], [0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B], [0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73], [0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E], [0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B], [0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4], [0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F], [0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF], [0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61], [0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D]] # 轮常数,密钥扩展中用到。(AES-128只需要10轮) Rcon = [0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000, 0x1b000000, 0x36000000] # 密钥扩展 # 字循环左移一个字节 def RotWord(x): return ((x << 8) | (x >> 24)) & 0xffffffff # S盒变换 def SubWord(x): temp = 0 temp = (temp << 8) | S_Box[(x >> 28) & 0x0f][(x >> 24) & 0x0f] temp = (temp << 8) | S_Box[(x >> 20) & 0x0f][(x >> 16) & 0x0f] temp = (temp << 8) | S_Box[(x >> 12) & 0x0f][(x >> 8) & 0x0f] temp = (temp << 8) | S_Box[(x >> 4) & 0x0f][(x >> 0) & 0x0f] return temp # 加密过程 # 轮密钥加变换 - 将每一列与扩展密钥进行异或 def AddRoundKey(mtx, ikey): rmtx = [] for i in range(4): for j in range(4): rmtx.append((mtx[4 * i + j] ^ ((ikey[j] << 8 * i) >> 24)) & 0xff) return bytes(rmtx) # S盒变换 - 前4位为行号,后4位为列号 def Subbytes(mtx): rmtx = [] for i in mtx: rmtx.append(S_Box[i >> 4][i & 0x0f]) return bytes(rmtx) # 行变换 - 按字节循环移位 def ShiftRows(mtx): rmtx = b'' rmtx += mtx[0:4] + \ mtx[5:8] + mtx[4:5] + \ mtx[10:12] + mtx[8:10] + \ mtx[15:16] + mtx[12:15] return rmtx # 有限域上的乘法 GF(2^8) def GFMul(a, b): p, hi_bit_set = 0, 0 for counter in range(8): if b & 1 != 0: p ^= a hi_bit_set = a & 0x80 a = (a << 1) & 0xff if hi_bit_set != 0: a ^= 0x1b # x^8 + x^4 + x^3 + x + 1 b >>= 1 return p & 0xff # 列变换 def MixColumns(mtx): rmtx = [0] * 16 for i in range(4): arr = [] for j in range(4): arr.append(mtx[i + j * 4]) rmtx[i] = GFMul(0x02, arr[0]) ^ GFMul(0x03, arr[1]) ^ arr[2] ^ arr[3] rmtx[i + 4] = arr[0] ^ GFMul(0x02, arr[1]) ^ GFMul(0x03, arr[2]) ^ arr[3] rmtx[i + 8] = arr[0] ^ arr[1] ^ GFMul(0x02, arr[2]) ^ GFMul(0x03, arr[3]) rmtx[i + 12] = GFMul(0x03, arr[0]) ^ arr[1] ^ arr[2] ^ GFMul(0x02, arr[3]) return bytes(rmtx) # 解密过程 # 逆行变换 - 按字节循环移位 def InvShiftRows(mtx): rmtx = b'' rmtx += mtx[0:4] + \ mtx[7:8] + mtx[4:7] + \ mtx[10:12] + mtx[8:10] + \ mtx[13:16] + mtx[12:13] return rmtx # 逆S盒变换 - 前4位为行号,后4位为列号 def InvSubbytes(mtx): rmtx = [] for i in mtx: rmtx.append(Inv_S_Box[i >> 4][i & 0x0f]) return bytes(rmtx) # 逆列变换 def InvMixColumns(mtx): rmtx = [0] * 16 for i in range(4): arr = [] for j in range(4): arr.append(mtx[i + j * 4]) rmtx[i] = GFMul(0x0e, arr[0]) ^ GFMul(0x0b, arr[1]) ^ GFMul(0x0d, arr[2]) ^ GFMul(0x09, arr[3]) rmtx[i + 4] = GFMul(0x09, arr[0]) ^ GFMul(0x0e, arr[1]) ^ GFMul(0x0b, arr[2]) ^ GFMul(0x0d, arr[3]) rmtx[i + 8] = GFMul(0x0d, arr[0]) ^ GFMul(0x09, arr[1]) ^ GFMul(0x0e, arr[2]) ^ GFMul(0x0b, arr[3]) rmtx[i + 12] = GFMul(0x0b, arr[0]) ^ GFMul(0x0d, arr[1]) ^ GFMul(0x09, arr[2]) ^ GFMul(0x0e, arr[3]) return bytes(rmtx) class AES(object): # AES-128 # Nk密钥长度(双字),Nb分组大小(双字),Nr轮数 # 128 4 4 10 # 192 6 4 12 # 256 8 4 14 # 128 4*4*8=128bits 4*4*8=128bits 10轮 def __init__(self, K: bytes): self.Nk, self.Nb, self.Nr = 4, 4, 10 # 通过密钥K生成实例,密钥K位数不足补零 self.K = K[:self.Nk * 4] while len(self.K) < self.Nk * 4: self.K += b'\x00' self.W = self.KeyExpansion() def Encrypt(self, m: bytes) -> bytes: # 加密 # 明文不足16*8bits补零 while len(m) % 16 != 0: m += b'\x00' c = b'' for i in range(len(m) // 16): # 对每一个16*8bits的块进行循环 mtx = m[i * 16:i * 16 + 16] ikey = self.W[:4] mtx = AddRoundKey(mtx, ikey) for round in range(1, self.Nr): mtx = Subbytes(mtx) mtx = ShiftRows(mtx) mtx = MixColumns(mtx) ikey = self.W[4*round:4*round+4] mtx = AddRoundKey(mtx, ikey) mtx = Subbytes(mtx) mtx = ShiftRows(mtx) ikey = self.W[4 * self.Nr:] mtx = AddRoundKey(mtx, ikey) c += mtx return c def Decrypt(self, m: bytes) -> bytes: # 解密 c = b'' for i in range(len(m) // 16): mtx = m[i * 16:i * 16 + 16] ikey = self.W[self.Nr * 4:] mtx = AddRoundKey(mtx, ikey) for round in range(self.Nr-1, 0, -1): mtx = InvShiftRows(mtx) mtx = InvSubbytes(mtx) ikey = self.W[4 * round:4 * round + 4] mtx = AddRoundKey(mtx, ikey) mtx = InvMixColumns(mtx) mtx = InvShiftRows(mtx) mtx = InvSubbytes(mtx) ikey = self.W[:4] mtx = AddRoundKey(mtx, ikey) c += mtx return c def KeyExpansion(self): # 密钥扩展函数,密钥 K 扩展生成 Nb(Nr+1)个字 4*4*8bits=128bits -> 4*(10+1)*32bits=1408bits w = [] for i in range(self.Nk): temp = 0 for j in range(4): temp = (temp << 8) | self.K[4 * i + j] w.append(temp) for i in range(self.Nk, self.Nb * (self.Nr + 1)): temp = w[i - 1] if i % self.Nk == 0: temp = SubWord(RotWord(temp)) ^ Rcon[i // self.Nk - 1] elif self.Nk > 6 and i % self.Nk == 4: temp = SubWord(temp) w.append(w[i - self.Nk] ^ temp) return w def getK(self) -> bytes: # 返回密钥K return self.K def generateK(self) -> bytes: # 随机产生密钥K pass def print_bytes_hex(m): for i in m: print(hex(i)[2:].rjust(2, '0'), end='') print() if __name__ == '__main__': # AES-128 # 密钥不足128bits 添零,多余128bits 截取前128bits # messages 不足128bits的倍数 补零 m = b'https://blog.vhcffh.com' key = b'123456' a = AES(key) cc = a.Encrypt(m) mm = a.Decrypt(cc) print("明文:", end='') print(m) print("密钥:", end='') print(a.K) print("密文:", end='') print_bytes_hex(cc) # 以bytes输出 print("解密:", end='') print(mm) ``` ## 参考 1. [aes 加密 python 源码](https://github.com/FreyZhang007/python-encrypt/blob/93090b37a92005781c01bf21921c632e35f50254/aes.py) 2. [Advanced Encryption Standard](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) 3. [DES 加密算法的 C++实现](https://songlee24.github.io/2014/12/06/des-encrypt/) --- url: /blog/2022/python-implements-DES-encryption.md --- # python 实现 DES 加密 ## DES 算法原理 DES 算法是一种最通用的对称密钥算法,因为算法本身是公开的,所以其安全性取决于密钥的安全性。 基于密钥的算法通常有两类:对称算法和公开密钥算法。 对称算法的对称性体现在加密密钥能够从解密密钥推算出来,反之亦然。 在大多数对称算法中,加解密的密钥是相同的,DES 就是这样。 可见,对称密钥算法的加解密密钥都是保密的。 而公开密钥算法的密钥有两个(公钥和私钥),公钥是公开的,私钥是保密的。 下面是 DES 加密算法的整体流程图: ![DES](./imgs/des.png) 从上面的流程图可以看出,DES 加密主要由四个部分完成: 1. 初始置换 IP 2. 子密钥 Ki 的获取 3. 密码函数 f 4. 尾置换 FP 其中,第二部分和第三部分是 DES 算法的核心。 注意:DES 解密算法与加密算法完全相同,只需要将子密钥的使用顺序反过来就行了。 下面针对这四个步骤分别讲一下大致思路。 ### 1.初始置换 IP 这一部分很简单,IP(initial permutation)是一个 8x8 的置换表: ```python IP = (58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7) ``` 根据表中的规定,将输入的 64 位明文重新进行排序,即将第 58 位放到第 1 位,第 50 位放到第 2 位……以此类推。 初始置换以后得到的是一个 64 位的输出。 ### 2.子密钥 Ki 的获取 下面是获取子密钥 Ki 的流程图: ![Ki](./imgs/Ki.png) * 用户输出的密钥是 64 位的,根据密钥置换表 PC-1,将 64 位变成 56 位密钥。(去掉了奇偶校验位) * 将 PC-1 置换得到的 56 位密钥,分为前 28 位 C0 和后 28 位 D0,分别对它们进行循环左移,C0 左移得到 C1,D0 左移得到 D1。 * 将 C1 和 D1 合并成 56 位,然后通过 PC-2 表进行压缩置换,得到当前这一轮的 48 位子密钥 K1 。 * 然后对 C1 和 D1 进行左移和压缩置换,获取下一轮的子密钥……一共进行 16 轮,得到 16 个 48 位的子密钥。 PC-1 和 PC-2 如下: ```python PC_1L = (57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36) PC_1R = (63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4) PC_2 = (14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32) ``` ### 3.密码函数 f 下面是密码函数 f(R, K)的流程图: ![f(R,K)](./imgs/f.png) 密码函数 f(R, K)接受两个输入:32 位的数据和 48 位的子密钥。然后: * 通过表 E 进行扩展置换,将输入的 32 位数据扩展为 48 位 * 将扩展后的 48 位数据与 48 位的子密钥进行异或运算 * 将异或得到的 48 位数据分成 8 个 6 位的块,每一个块通过对应的一个 S 表产生一个 4 位的输出。其中,每个 S 表都是 4 行 16 列。具体的置换过程如下:把 6 位输入中的第 1 位和第 6 位取出来行成一个两位的二进制数 x ,作为 Si 表中的行数(0~3);把 6 位输入的中间 4 位构成另外一个二进制数 y,作为 Si 表的列数(0~15);查出 Si 表中 x 行 y 列所对应的整数,将该整数转换为一个 4 位的二进制数 * 把通过 S 表置换得到的 8 个 4 位连在一起,形成一个 32 位的数据。然后将该 32 位数据通过表 P 进行置换(称为 P-置换),置换后得到一个仍然是 32 位的结果数据,这就是 f(R, K)函数的输出 这部分用到了扩展置换表 E,8 个 S 表以及 P-置换表: ```python E = (32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1) P = (16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25) S = [[[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7], [0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8], [4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0], [15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13]], [[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10], [3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5], [0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15], [13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9]], [[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8], [13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1], [13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7], [1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12]], [[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15], [13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9], [10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4], [3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14]], [[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9], [14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6], [4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14], [11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3]], [[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11], [10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8], [9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6], [4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13]], [[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1], [13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6], [1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2], [6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12]], [[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7], [1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2], [7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8], [2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11]]] ``` ### 4.尾置换 FP 合并 L16 和 R16 得到一个 64 位的数据,再经过尾置换后得到的就是 64 位的密文。 注意:要将 L16 和 R16 合并成 R16L16(即左右互换)。 尾置换表 FP 如下: ```python FP = (40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25) ``` ## python 实现 ```python IP = (58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7) FP = (40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25) E = (32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1) P = (16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25) S = [[[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7], [0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8], [4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0], [15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13]], [[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10], [3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5], [0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15], [13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9]], [[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8], [13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1], [13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7], [1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12]], [[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15], [13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9], [10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4], [3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14]], [[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9], [14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6], [4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14], [11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3]], [[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11], [10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8], [9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6], [4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13]], [[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1], [13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6], [1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2], [6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12]], [[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7], [1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2], [7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8], [2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11]]] PC_1L = (57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36) PC_1R = (63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4) PC_2 = (14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32) def Permute(block, b_len, PP): # 通过置换矩阵PP对block进行置换,b_len是块长度(位,bit) res = 0 for i in PP: res = res << 1 # 第i-1的位置 即倒数第 res |= (block >> (b_len - i)) & 0x01 return res def bytesToblocks(m): # 字节序列转换位blocks b'\x00\x01\x02\x03\xff\xff\xff\xff' -> [0x00010203,0xffffffff] while len(m) % 8 != 0: m += b'\x00' blocks = [] for i in range(len(m) // 4): blocks.append((m[4 * i] << 24) | (m[4 * i + 1] << 16) | (m[4 * i + 2] << 8) | (m[4 * i + 3])) return blocks def blocksTobytes(blocks): # blocks转换为字节序列 [0x00010203,0xffffffff] -> b'\x00\x01\x02\x03\xff\xff\xff\xff' res = b'' for i in blocks: res += i.to_bytes(4, byteorder="big") return res L = lambda x, n: ((x << n) | (x >> (28 - n))) & 0x0fffffff # 对一个28bits的数x进行循环左移n位 class DES(object): def F(self, block, subKeyid): """ :param block: 32bits :param subKeyid: :return: res: 32bits """ temp = Permute(block, 32, E) ^ self.subKs[subKeyid] res = 0 for i in range(8): res = res << 4 yxxxxy = (temp >> 6 * (7 - i)) & 0x3f xxxx = (yxxxxy & 0x1f) >> 1 yy = ((yxxxxy >> 5) << 1) | (yxxxxy & 0x01) res |= S[i][yy][xxxx] res = Permute(res, 32, P) return res & 0xffffffff def __init__(self, K: bytes): # 通过密钥K生成实例 self.K = K self.K_blocks = bytesToblocks(K)[:2] # 生成字密钥self.subKs[16] self.subKs = None self.generate_subKs() def Encrypt(self, m: bytes) -> bytes: # 加密 blocks = bytesToblocks(m) cblocks = [] for i in range(len(blocks) // 2): # 每个64bits的高32bits,低32bits high, low = blocks[2 * i], blocks[2 * i + 1] # 第一步:初始置换IP temp = Permute((high << 32) | low, 64, IP) & 0xffffffffffffffff # 第二步:获取 Li 和 Ri high, low = temp >> 32, temp & 0xffffffff # 第三步:共16轮迭代 for j in range(16): high, low = low, (high ^ self.F(low, j)) # 第四步:合并L16和R16,注意合并为 R16L16 high, low = low, high # 第五步:末尾置换FP temp = Permute((high << 32) | low, 64, FP) & 0xffffffffffffffff high, low = temp >> 32, temp & 0xffffffff cblocks.append(high) cblocks.append(low) return blocksTobytes(cblocks) def Decrypt(self, e: bytes) -> bytes: # 解密 blocks = bytesToblocks(e) cblocks = [] for i in range(len(blocks) // 2): # 每个64bits的高32bits,低32bits high, low = blocks[2 * i], blocks[2 * i + 1] # 第一步:初始置换IP temp = Permute((high << 32) | low, 64, IP) & 0xffffffffffffffff # 第二步:获取 Li 和 Ri high, low = temp >> 32, temp & 0xffffffff # 第三步:共16轮迭代, 子密钥逆序 for j in range(16): high, low = low, (high ^ self.F(low, 15 - j)) # 第四步:合并L16和R16,注意合并为 R16L16 high, low = low, high # 第五步:末尾置换FP temp = Permute((high << 32) | low, 64, FP) & 0xffffffffffffffff high, low = temp >> 32, temp & 0xffffffff cblocks.append(high) cblocks.append(low) return blocksTobytes(cblocks) def generate_subKs(self): # print(bin((self.K_blocks[0] << 32) | self.K_blocks[1])[2:].rjust(32, '0')) C = Permute((self.K_blocks[0] << 32) | self.K_blocks[1], 64, PC_1L) & 0x0fffffff D = Permute((self.K_blocks[0] << 32) | self.K_blocks[1], 64, PC_1R) & 0x0fffffff # print(bin(C)[2:].rjust(28, '0')) # print(bin(D)[2:].rjust(28, '0')) self.subKs = [] for i in range(16): if i in (0, 1, 8, 15): C, D = L(C, 1), L(D, 1) else: C, D = L(C, 2), L(D, 2) self.subKs.append(Permute((C << 28) | D, 56, PC_2)) # print(hex(self.subKs[i])) def getK(self) -> bytes: # 返回密钥K return self.K def generateK(self) -> bytes: # 随机产生密钥K pass def print_bytes_hex(m): for i in m: print(hex(i)[2:].rjust(2, '0'), end='') print() if __name__ == '__main__': # 密钥不足64bits 添零,多于64bits 使用前64bits # messages 不足64bits的倍数 补零 m = b'https://blog.vhcffh.com' # 明文 k = b'123456' # 密钥 a = DES(k) cc = a.Encrypt(m) mm = a.Decrypt(cc) print("明文:", end='') print(m) print("密钥:", end='') print(a.K) print("密文:", end='') print_bytes_hex(cc) # 以bytes输出 print("解密:", end='') print(mm) ``` ## 参考 1. [des 加密 python 源码](https://github.com/FreyZhang007/python-encrypt/blob/93090b37a92005781c01bf21921c632e35f50254/des.py) 2. [DES 算法实例详解](http://www.hankcs.com/security/des-algorithm-illustrated.html) 3. [DES 加密算法的 C++实现](https://songlee24.github.io/2014/12/06/des-encrypt/) --- url: /blog/2022/wp-665.md --- # docker 镜像 alpine 无法联网 alpine docker 镜像无法联网出现如下错误: ``` ERROR: https://mirrors.ustc.edu.cn/alpine/v3.15/main: temporary error (try again later) WARNING: Ignoring https://mirrors.ustc.edu.cn/alpine/v3.15/main: No such file or directory 3069277072:error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed:ssl/statem/statem_clnt.c:1914: ``` 基础的 docker 镜像是~ ~python:3.6-alpine~ ~ **stack overflow 上看到一个解答** [docker can't build because of alpine error - Stack Overflow](https://stackoverflow.com/questions/53710206/docker-cant-build-because-of-alpine-error) 1. 使用~ ~`docker build . --network local `进行构建--错误仍在 2. 添加~ ~8.8.8.8~ ~到文件` /etc/resolv.conf `并重启 docker--错误仍在 **思路:启动一个基础 docker 容器,并进入查找错误原因** 1. 启动并进入容器\ `docker run -it python:3.6-alpine /bin/sh ` 2. 执行 Dockerfile 文件里的命令\ apk 安装及更新时出错\ 使用~ ~ping~ ~命令测试网络连通性,出错~ ~ping: clock\_gettime(MONOTONIC) failed~ ~\ 使用~ ~nc~ ~命令连接外部服务器,可以连接 ip,不能连接域名\ 猜想应该是域名解析的问题 **对 alpine 不熟,换一个其他的基础容器** 使用镜像~ ~python:3.6~ ~,启动镜像后使用 pip 时报错 ``` Fatal Python error: Py_Initialize: can't initialize time PermissionError: [Errno 1] Operation not permitted Current thread 0xb6fda010 (most recent call first): Aborted (core dumped) # pip Fatal Python error: Py_Initialize: can't initialize time PermissionError: [Errno 1] Operation not permitted Current thread 0xb6f12010 (most recent call first): Aborted (core dumped) ``` 比较了一下两个 python 镜像的大小 ``` python 3.6 a3bba401d788 2 weeks ago 696MB python 3.6-alpine dc8c4f7b64b7 2 weeks ago 34.8MB ``` 还是用~ ~alpine~ ~版本的吧 **域名解析问题** 搜到[这里](https://www.sudops.com/kubernetes-alpine-image-resolve-ext-dns.html),说要将~ ~/etc/resolv.conf~ ~文件中的~ ~ndots~ ~注释掉 进入容器查看~ ~/etc/resolv.conf~ ~文件,并没有字符~ ~ndots~ ~ 搜索 ping 的错误~ ~ping: clock\_gettime(MONOTONIC) failed~ ~ 找到了[这里](https://docs.linuxserver.io/faq#libseccomp),有三种解决方法 1. 安装一个软件包\ `wget http://ftp.us.debian.org/debian/pool/main/libs/libseccomp/libseccomp2_2.4.4-1~bpo10+1_armhf.deb && sudo dpkg -i libseccomp2_2.4.4-1~bpo10+1_armhf.deb`\ 下载时发现 2.4.4 版本已经没了,于是下载 2.5.1 版本\ 安装后仍然时间还不对,安装前是 1970 年,安装后变成了 2071 年 2. 第二个方法一样,不过是添加源安装软件 3. 方法三要更新树莓派的系统 方法 2 和方法 3 需要添加源或更新系统,不想搞太多源,也懒得重新安装 又搜了一下,看到[这里](https://gitlab.alpinelinux.org/alpine/aports/-/issues/12091),有人提示添加~ ~--security-opt seccomp=unconfined~ ~ 测试一下 ``` ❯ docker run -it --rm --security-opt seccomp=unconfined python:3.6-alpine ping baidu.com PING baidu.com (220.181.38.148): 56 data bytes 64 bytes from 220.181.38.148: seq=0 ttl=48 time=45.827 ms 64 bytes from 220.181.38.148: seq=1 ttl=48 time=47.090 ms ``` 网络可以 ping 通了,应该可行 但是使用~ ~docker build~ ~时,出现了问题 ``` ❯ docker build . --security-opt seccomp=unconfined Error response from daemon: The daemon on this platform does not support setting security options on build ``` 系统无法使用此选项 **从其它系统构建镜像后导出** docker 镜像保存: \~ ~docker save 0d33 > proxy\_pool.tar~ ~ docker 镜像导入: \~ ~docker load < proxy\_pool.tar~ ~ 两个机器架构不同 按照[这里](https://blog.vhcffh.com/667.html)进行跨平台构建即可 --- url: /blog/2022/wp-667.md --- # docker 交叉编译 1. 查看存在的builder:~ ~`docker buildx ls` 2. 创建一个新的builder:`docker buildx create --name mybuilder` 3. 使用新的builder:`docker buildx use mybuilder && docker buildx inspect --bootstrap`\ `docker buildx inspect --bootstrap`命令出错:\ ``Error:error getting credentials - err: exit status 1, out: `Failed to execute child process “dbus-launch” (No such file or directory)``\ 根据[这里](https://github.com/moby/moby/issues/34175)提示,安装了两个软件包:\ `apt install dbus-x11`\ `apt install gnupg2 pass` 4. 构建镜像:`docker buildx build --platform linux/arm64/v7 -t name:proxy_pool .`\ 平台可以选择`linux/amd64`,`linux/arm64`,`linux/arm/v7`等 ## **参考** 1. [Leverage multi-CPU architecture support | Docker Documentation](https://docs.docker.com/desktop/multi-arch/) --- url: /blog/2022/wp-675.md --- # WSL2联网问题 使用wsl做开发有一段时间了,最近突然遇到在wsl中无法联网,查找原因发现是因为无法解析域名。 查看文件`/etc/resolv.conf`,里面解释文件是wsl启动时生成的,将文件里的`namespace`更改为8.8.8.8后,可以正常访问网络,但是重启之后,文件`/etc/resolv.conf`恢复成原来的了。 经过一番查找,按如下方法修改后,文件/etc/resolv.conf不在会改变。 ``` sudo rm /etc/resolv.conf sudo bash -c 'echo "nameserver 8.8.8.8" > /etc/resolv.conf' sudo bash -c 'echo "[network]" > /etc/wsl.conf' sudo bash -c 'echo "generateResolvConf = false" >> /etc/wsl.conf' sudo chattr +i /etc/resolv.conf ``` ## 参考 1. [WSL 2 keeps overwriting resolv.conf · Issue #5420 · microsoft/WSL (github.com)](https://github.com/microsoft/WSL/issues/5420) 2. [Custom resolv.conf being replaced by generated file, even with comment removed. · Issue #1908 · microsoft/WSL (github.com)](https://github.com/microsoft/WSL/issues/1908#issuecomment-641396913) 3. [WSL 中的高级设置配置 | Microsoft Docs](https://docs.microsoft.com/zh-cn/windows/wsl/wsl-config) --- url: /blog/2022/wp-677.md --- # nginx ssl配置问题(curl 60错误) 通过[acme.sh](https://github.com/acmesh-official/acme.sh)工具给网站配置了ssl,按照流程配置成功后,使用浏览器可以正常访问,但使用curl进行访问时,出现如下错误: ``` curl: (60) SSL certificate problem: unable to get local issuer certificate More details here: https://curl.haxx.se/docs/sslcerts.html curl failed to verify the legitimacy of the server and therefore could not establish a secure connection to it. To learn more about this situation and how to fix it, please visit the web page mentioned above. ``` 由于浏览器能够访问成功,就主观认为是系统curl和系统证书的问题。 找到了[这里](https://stackoverflow.com/questions/24611640/curl-60-ssl-certificate-problem-unable-to-get-local-issuer-certificate),尝试了各种方法都不行。 最终找到这个[网站](https://whatsmychaincert.com/)对自己网站的ssl进行测试,结果是: ``` ****.com is misconfigured. This is the chain it should be using. ``` 于是怀疑是自己nginx配置有问题,查看网站给出的证书文件(chain),比自己配置的多了一部分。 网站上的有两个`CERTIFICATE`段,而acme.sh生成的只有一个。直接从`whatsmychaincert`网站下载fallchain证书。 在服务器上重新配置,并重启nginx,curl就可以访问了。 对比nginx和apache的配置: ``` acme.sh --install-cert -d example.com \ --cert-file /path/to/certfile/in/apache/cert.pem \ --key-file /path/to/keyfile/in/apache/key.pem \ --fullchain-file /path/to/fullchain/certfile/apache/fullchain.pem \ --reloadcmd "service apache2 force-reload" acme.sh --install-cert -d example.com \ --key-file /path/to/keyfile/in/nginx/key.pem \ --fullchain-file /path/to/fullchain/nginx/cert.pem \ --reloadcmd "service nginx force-reload" ``` 可以看到apache多配置了一个 --cert-file 参数,而nginx只配置了 \--fullchain-file ,仔细看了一下nginx的证书安装命令,原来是安装了cert文件,忘记安装fullchain文件了。 大意了。。。 明明参数是`--fullchain-file`为啥文件名要是`cert.pem` --- url: /blog/2022/wp-691.md --- # C语言红黑树实现 ## 红黑树的结构 类型定义包括两个部分:一个root节点和多个树节点 ### **root节点** root节点保存红黑树的树根节点 ``` typedef struct rb_tree() { Node* node; } RBRoot; ``` ### **树节点** 树节点用于保存红黑树节点中的信息包括 * 节点的颜色~ ~color~ ~ * 节点存储的有效信息~ ~key~ ~ * 以及节点的左孩子节点~ ~left~ ~,右孩子节点~ ~right~ ~和父节点~ ~parent~ ~ ``` typedef struct RBTreeNode { unsigned char color; Type key; struct RBTreeNode* left; struct RBtreeNode* right; struct RBtreeNode* parent; } Node, *RBTree; ``` 主要有六个函数用于实现红黑树:红黑树的**创建**与**销毁**,**左旋**,**右旋**,**插入节点**,**删除节点** ## **创建** 分配一个root节点 ``` RBRoot* create_rbtree() { RBRoot* root = (RBRoot*) malloc( sizeof( RBRoot ) ); root->node = NULL; return root; } ``` ## **销毁** 销毁主要分四个步骤: 1. 销毁左子树 2. 销毁右子树 3. 释放树根节点空间 4. 释放root节点空间 ``` static void rbtree_destroy( RBTree tree ) { if ( tree == NULL ) return; if ( tree->left != NULL ) rbtree_destroy( tree->left ); if ( tree->right != NULL ) rbtree_destroy( tree->right ); free( tree ); } void destroy_rbtree( RBRoot* root ) { if ( root != NULL ) rbtree_destroy( root->node ); free( root ); } ``` 为了实现在红黑树的插入与删除后,并且保持红黑树的性质不变,需要用到下面两个对树的基本操作左旋和右旋 ## **左旋**
左旋操作 对节点b进行左旋操作,进行左旋后减少了左子树的深度,增大了右子树的深度 ``` static void rbtree_left_rotate( RBRoot* root, Node* x ) { Node* y = x->right; x->right = y->left; if ( y->left != NULL ) y->left->parent = x; y->parent = x->parent; if ( x->parent == NULL ) { root->node = y; } else { if ( x->parent->left == x ) x->parent->left = y; else x->parent->right = y; } y->left = x; x->parent = y; } ``` ## **右旋**
右旋操作 右旋操作与左旋操作相反,上图对节点a进行右旋操作,减少了右子树的深度,增大了左子树的深度 ``` static void rbtree_right_rotate( RBRoot* root, Node* y ) { Node* x = y->left; y->left = x->right; if ( x->right != NULL ) x->right->parent = y; x->parent = y->parent; if ( y->parent == NULL ) { root->node = x; } else { if ( y == y->parent->right ) y->parent->right = x; else y->parent->left = x; } x->right = y; y->parent = x; } ``` ## **插入节点** 红黑树的插入分为两步:首先将待插入节点标记为红色并安装二叉查找树的方法插入;第二步对红黑树进行调整,使其保持红黑树的特性。根据不同的情况需要做出不同的调整: 1. 情况一:新节点N位于树的根上,没有父节点 1. 直接将其标记为黑色即可 2. 情况二:新节点N的父节点P是黑色 1. 直接插入,无需修正 3. 情况三:父节点P与叔父节点U都为红色 1. 将父节点P与叔父节点U标记为黑色 2. 将祖父节点G标记为红色 3. 将祖父节点G作为新插入节点重新进行修正(重新执行修正) 4. 情况四:父节点P为红色,叔父节点U不为红色(新节点与父节点不同侧) * 父节点P为祖父节点G的左子节点**且**新节点N为父节点P的右子节点 1. 对节点N进行左旋操作 2. 对节点N的左子树(左旋操作前的父节点P)执行情况5 * 父节点P为祖父节点G的右子节点**且**新节点N为父节点P的左子节点 a. 对节点N进行右旋操作 b. 对节点N的右子树(右旋操作前的父节点P)执行情况5 5. 情况五:父节点P为红色,叔父节点U不为红色 1. 将父节点P标记为黑色 2. 将祖父节点G标记为红色 3. 父节点P为祖父节点G的右子节点**且**新节点N为父节点P的右子节点 1. 对父节点P进行右旋操作 4. 父节点P为祖父节点G的左子节点**且**新节点N为父节点P的左子节点 1. 对父节点P进行左旋操作 ``` static void rbtree_insert_fixup( RBRoot* root, Node* node ) { Node *parent, *gparent; while ( ( parent = rb_parent( node ) ) && rb_is_red( parent ) ) { gparent = rb_parent( parent ); if ( parent == gparent->left ) { { Node* uncle = gparent->right; if ( uncle && rb_is_red( uncle ) ) { rb_set_black( uncle ); rb_set_black( parent ); rb_set_red( gparent ); node = gparent; continue; } } if ( parent->right == node ) { Node* tmp; rbtree_left_rotate( root, parent ); tmp = parent; parent = node; node = tmp; } rb_set_black( parent ); rb_set_red( gparent ); rbtree_right_rotate( root, gparent ); } else { { Node* uncle = gparent->left; if ( uncle && rb_is_red( uncle ) ) { rb_set_black( uncle ); rb_set_black( parent ); rb_set_red( gparent ); node = gparent; continue; } } if ( parent->left == node ) { Node* tmp; rbtree_right_rotate( root, parent ); tmp = parent; parent = node; node = tmp; } rb_set_black( parent ); rb_set_red( gparent ); rbtree_left_rotate( root, gparent ); } } rb_set_black( root->node ); } static void rbtree_insert( RBRoot* root, Node* node ) { Node* y = NULL; Node* x = root->node; while ( x != NULL ) { y = x; if ( node->key < x->key ) x = x->left; else x = x->right; } rb_parent( node ) = y; if ( y != NULL ) { if ( node->key < y->key ) y->left = node; else y->right = node; } else { root->node = node; } node->color = RED; rbtree_insert_fixup( root, node ); } int insert_rbtree( RBRoot* root, Type key ) { Node* node; if ( search( root->node, key ) != NULL ) return -1; if ( ( node = create_rbtree_node( key, NULL, NULL, NULL ) ) == NULL ) return -1; rbtree_insert( root, node ); return 0; } ``` ## **删除节点** 节点的删除也分为两个步骤:**节点的删除**与**删除后的修正** **节点的删除**分为三种情况: 1. 节点没有子树,直接删除 2. 节点只有一个子树,用子节点替代此节点,在子节点上进行修正 3. 节点有两个子树,用右子树最小值节点替代此节点,在新节点上进行修正 对于**删除后的修正**,如果删除节点是红色,则无需修正,否则需要修正,分多种情况: 1. 情况一:N是新的根 1. 无需修正 2. 情况二:兄弟节点S是红色 1. 对父节点P进行左旋,交换N的父节点P与祖父节点G的颜色 2. 进入情况三 3. 情况三:N的父节点G,兄弟节点S和S的儿子都是黑色 1. 将兄弟节点S设置为红色 2. 对父节点P从**情况一**开始重新修正 4. 情况四:N的父节点G为红色,兄弟节点S和S的儿子都是黑色 1. 交换N的兄弟和父亲的颜色 5. 情况五:N的父节点G为红色,兄弟节点S为红色,S的右儿子为黑色,N为其父节点的左儿子 1. 在S上做右旋 2. 交换S和它的父节点颜色 3. 进入情况六 6. 情况六:S是黑色,S的右儿子是红色,N为其父节点的左儿子 1. 在N的父节点P上做左旋 2. 交换N和父节点的颜色 3. 将S的右子树设置为黑色 ``` static void rbtree_delete_fixup( RBRoot* root, Node* node, Node* parent ) { Node* other; while ( ( !node || rb_is_black( node ) ) && node != root->node ) { if ( parent->left == node ) { other = parent->right; if ( rb_is_red( other ) ) { rb_set_black( other ); rb_set_red( parent ); rbtree_left_rotate( root, parent ); other = parent->right; } if ( ( !other->left || rb_is_black( other->left ) ) && ( !other->right || rb_is_black( other->right ) ) ) { rb_set_red( other ); node = parent; parent = rb_parent( node ); } else { if ( !other->right || rb_is_black( other->right ) ) { rb_set_black( other->left ); rb_set_red( other ); rbtree_right_rotate( root, other ); other = parent->right; } rb_set_color( other, rb_color( parent ) ); rb_set_black( parent ); rb_set_black( other->right ); rbtree_left_rotate( root, parent ); node = root->node; break; } } else { other = parent->left; if ( rb_is_red( other ) ) { rb_set_black( other ); rb_set_red( parent ); rbtree_right_rotate( root, parent ); other = parent->left; } if ( ( !other->left || rb_is_black( other->left ) ) && ( !other->right || rb_is_black( other->right ) ) ) { rb_set_red( other ); node = parent; parent = rb_parent( node ); } else { if ( !other->left || rb_is_black( other->left ) ) { rb_set_black( other->right ); rb_set_red( other ); rbtree_left_rotate( root, other ); other = parent->left; } rb_set_color( other, rb_color( parent ) ); rb_set_black( parent ); rb_set_black( other->left ); rbtree_right_rotate( root, parent ); node = root->node; break; } } } if ( node ) rb_set_black( node ); } void rbtree_delete( RBRoot* root, Node* node ) { Node *child, *parent; int color; if ( ( node->left != NULL ) && ( node->right != NULL ) ) { Node* replace = node; replace = replace->right; while ( replace->left != NULL ) { replace = replace->left; } if ( rb_parent( node ) ) { if ( rb_parent( node )->left == node ) { rb_parent( node )->left = replace; } else { rb_parent( node )->right = replace; } } else { root->node = replace; } child = replace->right; parent = rb_parent( replace ); color = rb_color( replace ); if ( parent == node ) { parent = replace; } else { if ( child ) rb_set_parent( child, parent ); parent->left = child; replace->right = node->right; rb_set_parent( node->right, replace ); } replace->parent = node->parent; replace->color = node->color; replace->left = node->left; node->left->parent = replace; if ( color == BLACK ) rbtree_delete_fixup( root, child, parent ); free( node ); return; } if ( node->left != NULL ) { child = node->left; } else { child = node->right; } parent = node->parent; color = node->color; if ( child ) child->parent = parent; if ( parent ) { if ( parent->left == node ) { parent->left = child; } else { parent->right = child; } } else { root->node = child; } if ( color == BLACK ) rbtree_delete_fixup( root, child, parent ); free( node ); } void delete_rbtree( RBRoot* root, Type key ) { Node *z, *node; if ( ( z = search( root->node, key ) ) != NULL ) rbtree_delete( root, z ); } ``` ## 参考 1. [linux/rbtree.h at 5bfc75d92efd494db37f5c4c173d3639d4772966 · torvalds/linux (github.com)](https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/tools/include/linux/rbtree.h) 2. [linux/rbtree.c at 5bfc75d92efd494db37f5c4c173d3639d4772966 · torvalds/linux (github.com)](https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/lib/rbtree.c) --- url: /blog/2022/wp-697.md --- # 将Rust程序编译成WebAssembly ## 环境搭建 1. 从[这里](https://www.rust-lang.org/tools/install)安装Rust工具链,rustup(Rust版本管理),rustc(Rust编译器),cargo(Rust包管理) 2. 安装[wasm-pack](https://rustwasm.github.io/wasm-pack/installer/),用于构建、测试发布Rust生成的WebAssembly 3. 安装cargo-generate,用于从模板快速启动一个Rsut项目,`cargo install cargo-generate` 4. 安装[npm](https://www.npmjs.com/),用于运行JavaScript bundler和开发服务器 ## 实现Hello World 1.克隆基础模板项目 ``` cargo generate --git https://github.com/rustwasm/wasm-pack-template ``` 2.添加一个新的rust编译器 ``` rustup target add wasm32-unknown-unknown ``` 3.编译 ``` wasm-pack build ``` ## 启动Web服务器 1.初始化web项目 ``` npm init wasm-app www ``` 2.进入目录www,初始化依赖 ``` npm install ``` 3.在www项目中加载本地依赖wasm-app包 ``` { // ... "dependencies": { // Add this three lines block! "wasm-app": "file:../pkg" }, "devDependencies": { //... } } ``` 4.运行`npm install`在www项目中安装wasm-app依赖 5.修改文件`wasm-game-of-life/www/index.js`导入生成的库 6.启动服务器 ``` npm run start ``` --- url: /blog/2022/wp-698.md --- # struct timespec转化为struct tm C语言中遇到了三种时间类型: 1. time\_t 2. struct timespec 3. struct tm 这里总结一下它们之间的相互转化 time\_t 和 struct timespec time\_t 只存储秒而struct timespec存储了纳秒,直接将秒转化即可: ``` time_t time = (time_t)ut_tv.tv_sec; ``` time\_t 和 struct tm 1. [**mktime**](https://www.cplusplus.com/reference/ctime/mktime/) 将tm转化为time\_t 2. [**localtime**](https://www.cplusplus.com/reference/ctime/localtime/) 将time\_t转化为tm(本地时区) 3. [**gmtime**](https://www.cplusplus.com/reference/ctime/gmtime/) 将time\_t转化为tm(UTC时区) timespec 和 tm 可以通过time\_t 间接转换 ### 参考 1. [](http://ostack.cn/qa/?qa=1049564/)[c - Convert timeval to time\_t - Stack Overflow](https://stackoverflow.com/questions/9252849/convert-timeval-to-time-t/9252913) 2. [struct tm - C++ Reference (cplusplus.com)](https://www.cplusplus.com/reference/ctime/tm/) --- url: /blog/2022/wp-707.md --- # Rust框架Yew支持CSS Yew主框架并未开始支持CSS,但在[这里](https://github.com/yewstack/yew/issues/533)统计了关于CSS支持的一些建议。 为了支持CSS,最直接的方法是在html!宏中直接添加class和id属性,然后在静态的index.html导入一个实现的CSS文件。其次可以使用一些其它的库来支持CSS。目前有5个库实现了Yew的CSS支持。 ## yew [![](https://gh-card.dev/repos/dancespiele/yew_styles.svg)](https://github.com/dancespiele/yew_styles) styles是一个完全不依赖js的Yew样式库,但官网的教程中要用npm来进行安装,其支持的样式及布局可以参考[这里](https://yewstyles.spielrs.tech/layouts)。此项目的目的是为Yew提供一个无需任何js依赖的样式框架,并实现一个类似于flexbox的布局系统。最近的更新在3个月前。 [![](https://gh-card.dev/repos/dungeonfog/yew-mdc.svg)](https://github.com/dungeonfog/yew-mdc) yew-mdc是一个为Yew框架实现的[Material Design](https://material.io/design)组件。最近更新在16个月前。 [![](https://gh-card.dev/repos/AlephAlpha/muicss-yew.svg)](https://github.com/AlephAlpha/muicss-yew) MUICSS-yew 是基于 [MUI](https://www.muicss.com/) 的CSS框架,[这里](https://alephalpha.github.io/muicss-yew/)是它支持的样式布局。最近更新是11个月前。 [![](https://gh-card.dev/repos/yewstack/yewtify.svg)](https://github.com/yewstack/yewtify) [Vuetify](https://vuetifyjs.com/en/)组件的Yew框架的实现。两年前更新的。 [![](https://gh-card.dev/repos/futursolo/stylist-rs.svg)](https://github.com/futursolo/stylist-rs) stylist-rs通过css!和style!等宏直接将css写到rust中,两个月前更新。 另外[这里](https://github.com/jetli/awesome-yew#component-libraries)总结了yew的一些项目和组件等。 --- url: /blog/2022/wp-709.md --- # Rust框架Yew的使用 ## 环境搭建 Yew框架也是将rust代码编译成WebAssembly,添加target ``` rustup target add wasm32-unknown-unknown ``` 安装`trunk`,他与[wasm-pack](https://rustwasm.github.io/wasm-pack/installer/)类似,是将rust编译成wasm的重要工具。 ``` cargo install trunk ``` ## 实现Hello World 1.创建项目,用cargo创建一个新的project ``` cargo new yew-app ``` 2\. 添加依赖,在Cargo.toml中添加yew依赖 ```toml [dependencies] yew = { git = "https://github.com/yewstack/yew/" } ``` 3.实现Hello World ```rust use yew::prelude::*; #[function_component(App)] fn app() -> Html { html! {

{ "Hello World" }

} } fn main() { yew::start_app::(); } ``` 4.在项目根目录下创建index.html ```html ``` ## 启动Web应用 ``` trunk serve ``` --- url: /blog/2022/wp-717.md --- # 联想小新pro 13 windows11 bug记录(2022) ## 人脸识别无法正常工作 具体现象:windows Hello 无法录入人脸,一直卡在请确保你的面孔剧中。 解决方法:在设备管理器中卸载IR Camera(勾选删除此设备的驱动程序)和Camera,重启之后就可以录入人脸了。 ## Window+X导致资源管理器重启 [微软 Win11 预览版 22563 出现重大 Bug:Win + X 导致资源管理器崩溃,解决方案出炉 - IT之家 (ithome.com)](https://www.ithome.com/0/604/913.htm)这里是关于此bug的具体详情及处理方案 更新后bug已解决 --- url: /blog/2022/wp-721.md --- # Yew框架实现秒表 ## 实现计数器 用Yew框架实现一个web定时器,首先Interval实现一个简单的计数器,每10ms发送Msg UpdateTime并组件的update方法中调用tick方法,更新时间。 ```rust use gloo::timers::callback::Interval; use yew::{html, Component, Context, Html}; pub enum Msg { UpdateTime, } pub struct MyTimer { timer_min: u32, timer_sec: u32, timer_csec: u32, timer_handle: Option, } impl MyTimer { fn tick(&mut self) { self.timer_csec += 1; if self.timer_csec >= 100{ self.timer_csec = 0; self.timer_sec += 1; if self.timer_sec == 60 { self.timer_sec = 0; self.timer_min += 1; } } } } impl Component for MyTimer { type Message = Msg; type Properties = (); fn create(ctx: &Context) -> Self { let timer_handle = { let link = ctx.link().clone(); Interval::new(10, move || link.send_message(Msg::UpdateTime)) }; let timer_handle = Some(timer_handle); Self { timer_min: 0, timer_sec: 0, timer_csec: 0, timer_handle, } } fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { match msg { Msg::UpdateTime => { self.tick(); true } } } fn view(&self, _ctx: &Context) -> Html { html! { <>
{ format!("{:0>2}:{:0>2}:{:0>2}",&self.timer_min,&self.timer_sec,&self.timer_csec) }
} } } ``` ## 实现秒表功能 秒表需要实现**开始**、**停止**、**继续**、**计次**、**重置**五个功能。因为开始和继续都是初始化定时器,因此可以用同一个Msg表示,在`Msg`中添加这4个消息。 ```rust pub enum Msg { StartTime, StopTime, CountTime, ResetTime, UpdateTime, } ``` ### 开始 默认秒表应该处于停止状态,点击开始按钮,发送`StartTime`消息后,开始计数。因此需要将`Interval`的初始化放到`update`方法中。在`create`方法中用`None`初始化`timer_handle`,在`update`中接收到`StartTime`消息后进行初始化。 ```rust match msg { Msg::StartTime => { let timer_handle = { let link = ctx.link().clone(); Interval::new(10, move || link.send_message(Msg::UpdateTime)) }; self.timer_handle = Some(timer_handle); true } // ... } ``` ### 停止 停止秒表直接将timer\_handle重新赋值为None即可,这样就删除了定时器。 ```rust Msg::StopTime=>{ self.timer_handle = None; true } ``` ### 继续 继续与开始的逻辑相同。 ### 计次 计次需要一个变量来保存当前的时间,因此在结构体中增加一个可变数组用于保存当前时间。 ```rust Msg::CountTime => { self.message.push( format!("{:0>2}:{:0>2}:{:0>2}",&self.timer_min,&self.timer_sec,&self.timer_csec) } ); true } ``` ### 重置 重置需要将所有计时器归零,并清空message中的计数。 ```rust Msg::ResetTime=>{ self.timer_csec = 0; self.timer_sec = 0; self.timer_min = 0; self.message.clear(); true } ``` 到这里,秒表的基本功基本完全实现了,接下来将update方法中的代码整理到结构体为结构体的方法,添加一些变量来记录秒表的状态,另外添加一些css来优化界面。 ## 代码整理 将代码逻辑进行一些整理优化 ```rust use gloo::timers::callback::Interval; use yew::{html, Component, Context, Html}; pub enum Msg { StartTime, StopTime, CountTime, ResetTime, UpdateTime, } pub struct MyTimer { timer_csec: u32, last_timer_csec: u32, timer_handle: Option, message: Vec<(u32, u32)>, state: u8, } impl MyTimer { fn new(_ctx: &Context) -> Self { Self { timer_csec: 0, last_timer_csec: 0, timer_handle: None, message: vec![], state: 0, } } fn start_time(&mut self, ctx: &Context) -> bool { if self.state == 0 || self.state == 2 { let timer_handle = { let link = ctx.link().clone(); Interval::new(10, move || link.send_message(Msg::UpdateTime)) }; self.timer_handle = Some(timer_handle); self.state = 1; } true } fn stop_time(&mut self) -> bool { if self.state == 1 { self.timer_handle = None; self.state = 2; } true } fn conut_time(&mut self) -> bool { if self.state == 1 { self.message .push((self.timer_csec, self.timer_csec - self.last_timer_csec)); self.last_timer_csec = self.timer_csec; } true } fn reset_time(&mut self) -> bool { if self.state == 2 { self.timer_csec = 0; self.last_timer_csec = 0; self.message.clear(); self.state = 0; } true } fn tick(&mut self) -> bool { self.timer_csec += 1; true } } impl Component for MyTimer { type Message = Msg; type Properties = (); fn create(ctx: &Context) -> Self { MyTimer::new(ctx) } fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { match msg { Msg::StartTime => self.start_time(ctx), Msg::StopTime => self.stop_time(), Msg::CountTime => self.conut_time(), Msg::ResetTime => self.reset_time(), Msg::UpdateTime => self.tick(), } } fn view(&self, ctx: &Context) -> Html { let mut button1 = html! { }; let mut button2 = html! { }; if self.state == 1 { button1 = html! { }; button2 = html! { }; } else if self.state == 2 { button1 = html! { }; button2 = html! { }; } html! { <>
{ format!("{:0>2}:{:0>2}.{:0>2}",&self.timer_csec/6000,(&self.timer_csec%6000)/100,&self.timer_csec%100) }
{ button1 } { button2 }
{ for self.message.iter().map(|m| html!{
  • {format!("{:#?}",m)}
  • })}
    } } } ``` --- url: /blog/2022/wp-734.md --- # 从CNF到DPLL算法 从三个问题出发: 1. 什么是CNF? 2. 如何将逻辑表达式转化为CNF? 3. 什么是DPLL算法? ## 合取范式(CNF,conjunctive normal form) CNF是指一系列逻辑表达式的合取,每一个子逻辑表达式都为下列类型之一: * 为原子表达式(例如:$ p $,$ \lnot p$) * 为原子表达式的逻辑或(逻辑析取)(例如:$ p \lor q $,$ p \lor \lnot q $) * 为原子表达式(例如:$ p $,$ \lnot p $) * 为原子表达式的逻辑或(逻辑析取)(例如:$ p \lor q $,$ p \lor \lnot q $) ## 转化为CNF 将普通的逻辑表达式转化为CNF需要按一下四个步骤对现有表达式进行转换: 1. 用$ (\alpha \rightarrow \beta) \land (\beta \rightarrow \alpha) $替换$ \alpha \leftrightarrow \beta$ 2. 用$ \lnot \alpha \lor \beta$替换$ \alpha \rightarrow \beta $ 3. 替换 1. 用$ \lnot \alpha \land \lnot \beta $替换$ \lnot (\alpha \lor \beta) $ 2. 用$ \lnot \alpha \lor \lnot \beta $替换$ \lnot (\alpha \land \beta) $ 3. 用$ \alpha$替换$ \lnot \lnot \alpha $ 4. 用$ (\alpha \lor \beta) \land (\alpha \lor \gamma) $替换$ \alpha \lor (\beta \land \gamma) $ ### 例子 下面利用上面的步骤将一个逻辑表达式转化为CNF ```math \begin{align*} & \qquad(p \Leftrightarrow q)\Rightarrow \lnot r\ \cr \rightarrow & \qquad ((p\Rightarrow q)\land (q\Rightarrow p)) \Rightarrow \lnot r &(1)\cr \rightarrow &\qquad \lnot ((\lnot p \lor q)\land (\lnot q\lor p))\lor \lnot r &(2)\cr \rightarrow &\qquad (\lnot(\lnot p \lor q)\lor \lnot(\lnot q\lor p))\lor \lnot r &(3.1)\cr \rightarrow &\qquad ((\lnot \lnot p\land \lnot q)\lor(\lnot \lnot q\land \lnot p)) \lor \lnot r &(3.2)\cr \rightarrow &\qquad ((p\land \lnot q)\lor(q\land \lnot p)) \lor \lnot r &(3.3)\cr \rightarrow &\qquad (p\lor q\lor \lnot r)\land(p\lor \lnot p\lor \lnot r) \land(\lnot q\lor q\lor \lnot r)\land(\lnot q\lor\lnot p\lor\lnot r) &(4) \end{align*} ``` ## DPLL算法 **DPLL**(Davis-Putnam-Logemann-Loveland)**算法**,是一种完备的、以[回溯](https://zh.wikipedia.org/wiki/%E5%9B%9E%E6%BA%AF)为基础的[算法](https://zh.wikipedia.org/wiki/%E7%AE%97%E6%B3%95),用于解决在[合取范式](https://zh.wikipedia.org/wiki/%E5%90%88%E5%8F%96%E8%8C%83%E5%BC%8F)(CNF)中[命题逻辑](https://zh.wikipedia.org/wiki/%E5%91%BD%E9%A2%98%E9%80%BB%E8%BE%91)的[布尔可满足性问题](https://zh.wikipedia.org/wiki/%E5%B8%83%E7%88%BE%E5%8F%AF%E6%BB%BF%E8%B6%B3%E6%80%A7%E5%95%8F%E9%A1%8C);也就是解决CNF-SAT问题。 ### SAT问题 布尔可满足性问题(**Boolean satisfiability problem**;\*\*SAT \*\*)属于[决定性问题](https://zh.wikipedia.org/wiki/%E6%B1%BA%E5%AE%9A%E6%80%A7%E5%95%8F%E9%A1%8C),也是第一个被证明属于[NP完全](https://zh.wikipedia.org/wiki/NP%E5%AE%8C%E5%85%A8)的问题。此问题在[计算机科学](https://zh.wikipedia.org/wiki/%E9%9B%BB%E8%85%A6%E7%A7%91%E5%AD%B8)上许多的领域皆相当重要,包括[计算机科学基础理论](https://zh.wikipedia.org/w/index.php?title=%E9%9B%BB%E8%85%A6%E7%A7%91%E5%AD%B8%E5%9F%BA%E7%A4%8E%E7%90%86%E8%AB%96\&action=edit\&redlink=1)、[算法](https://zh.wikipedia.org/wiki/%E6%BC%94%E7%AE%97%E6%B3%95)、[人工智能](https://zh.wikipedia.org/wiki/%E4%BA%BA%E5%B7%A5%E6%99%BA%E6%85%A7)、[硬件设计](https://zh.wikipedia.org/w/index.php?title=%E7%A1%AC%E9%AB%94%E8%A8%AD%E8%A8%88\&action=edit\&redlink=1)等等。 SAT问题,也叫作Boolean satisfiability problem(布尔可满足性问题),属于[决定性问题](https://zh.wikipedia.org/wiki/%E6%B1%BA%E5%AE%9A%E6%80%A7%E5%95%8F%E9%A1%8C)。具体指给定一组布尔表达式,指定其中每个布尔变量的取值,使得布尔表达式的值为TRUE。 ### 算法概述 SAT问题是无法再多项式时间复杂度内解决的,DPLL算法也不例外。 DPLL算法是一种搜索算法,思想与DFS十分相似,也可以说DPLL是DFS在SAT问题上的一种实现,具体实现方法就是,算法总公式中会选择一个变量,将其赋值为True,化简赋值后的表达式,如果简化的公式是可能满足的(递归),那么原公式也是可满足的;否则就将变量复制为False,重新执行一遍递归判定,若不满足,那么原公式便是不可满足的。 ```txt dpll(CS,B) { % CS==set of clauses. B=Bindings of atoms to truth values loop { if (empty(CS)) return B; if (emptyClause in CS) return Fail; if (easyCaseIn(CS,B)) [CS,B] = easyCase(CS,B); } % No easy cases CSCopy = copy(CS); BCopy=Copy(B); P = choose an unbound atom; [CSCopy, BCopy] = propagate(CSCopy, BCopy, P, True); answer = dpll(CSCopy,BCopy); if (answer != Fail) return answer; [CS, B] = propagate(CS, B, P, False); return dpll(CS,B) } ``` --- url: /blog/2022/wp-756.md --- # 树莓派4B wlan0消失 1. wlan0 没有启动,使用`sudo ifconfig wlan0 up`启用即可 2. wlan0被blocked,使用`sudo rfkill unblock all`解锁即可 --- url: /blog/2022/wp-760.md --- # 树莓派4B运行docker错误 树莓派4B安装raspberrypi系统后,用自带的源安装docker,在涉及到libseccomp这个库时会报错 ```bash ------------------------------------- _ () | | ___ _ __ | | / __| | | / \ | | \__ \ | | | () | |_| |___/ |_| \__/ Brought to you by linuxserver.io ------------------------------------- To support LSIO projects visit: https://www.linuxserver.io/donate/ ------------------------------------- GID/UID ------------------------------------- User uid: 1000 User gid: 1000 ------------------------------------- @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Your DockerHost is running an outdated version of libseccomp To fix this, please visit https://docs.linuxserver.io/faq#libseccomp Apps will not behave correctly without this @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ``` 具体原因是由于libseccomp2库的一些bug,已经修复但没有被推送到所有仓库,在[这里](https://docs.linuxserver.io/faq#libseccomp)有一些解释。也介绍了一些措施去修复。 尝试后并没有用,可能是某些步骤错了。 最后通过通过docker官方脚本重新安装解决了 ```bash sudo apt remove docker.io sudo apt autoremove sudo apt update sudo apt upgrade curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh systemctl start docker ``` docker官方下载的安装脚本`get-docker.sh`会给apt添加一个新的源,并从docker的官方源重新安装docker的最新版本。 ## 参考 1. [FAQ - LinuxServer.io](https://docs.linuxserver.io/faq#libseccomp) --- url: /blog/2022/wp-765.md --- # 一些不常用C语言关键字 ## volatile 该关键字是一个类型修饰符,提醒编译器它后面所定义的变量随时有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据 ## typedef `typedef int myint;` 声明类型:`int a;`等价于`myint a;` `typedef void (*fun)(void);` 声明函数指针:`void (*p)(void);`等价于`fun p;` ## register register将数据存储在寄存器中 `register type var_name __asm(reg)` 声明一个命名寄存器的变量,reg为寄存器名 ## attribute `__attribute__ ((attribute-list))` 编译器优化配置 设置函数属性,类型属性,变量属性 ## inline 将被调用函数以内联的方式嵌入到调用函数内 --- url: /blog/2022/树莓派文件系统只读.md --- # 树莓派文件系统只读 ### 问题原因 新烧录树莓派镜像后,文件系统变为只读 ### 解决方法 需要进入单用户模式,使用`fsck`命令对文件系统进行修复 具体操作步骤 1. 进入单用户模式 ```bash # 切换到root用户 sudo su root # 进入单用户模式 init 1 # 可以通过此命令查看当前系统运行模式 runlevel ``` 2. 使用`fsck`命令对文件系统进行修复, 主要修复根目录所在分区 ```bash # 查看当前文件硬盘 lsblk # sda 8:0 0 465.8G 0 disk # └─sda1 8:1 0 465.8G 0 part /mnt/disk1 # mmcblk0 179:0 0 14.8G 0 disk # ├─mmcblk0p1 179:1 0 213.6M 0 part /boot # └─mmcblk0p2 179:2 0 14.6G 0 part / # 卸载需要修复的分区 umount /dev/mmcblk0p2 # 对文件系统修复,一路确认即可 fsck.ext4 -y /dev/mmcblk0p2 ``` 3. 重启系统 重启后尽量在重启一次 如果仍然只读,可尝试重新再修复一次 --- url: /blog/2023/ATT-Intel-diff.md --- # AT\&T 汇编与 Intel 汇编区别 1. 前缀不同 在 Intel 的语法中,寄存器和和立即数都没有前缀。 但是在 AT\&T 中,寄存器前需要加上"%",而立即数前需要加上"$"。 在 Intel 的语法中,十六进制和二进制立即数后缀分别是"h"和"b",而在 AT\&T 中,十六进制立即数前需要加上"0x"。 | Intel 语法 | AT\&T 语法 | | -------------- | ----------------- | | mov eax,8 | movl $8,%eax | | mov ebx,0ffffh | movl $0xffff,%ebx | | int 80h | int $0x80 | 2. 操作数方向不同 Intel 与 AT\&T 操作数的方向正好相反。 在 Intel 语法中,第一个操作数是目的操作数,第二个操作数源操作数。 而在 AT\&T 中,第一个数是源操作数,第二个数是目的操作数。 | Intel 语法 | AT\&T 语法 | | ------------- | ---------------- | | mov eax,\[ecx] | movl (%ecx),%eax | 3. 内存单元操作数不同 在 Intel 的语法中,基寄存器用"\[]"括起来,而在 AT\&T 中,用"()"括起来。 | Intel 语法 | AT\&T 语法 | | --------------- | ----------------- | | mov eax,\[ebx+5] | movl 5(%ebx),%eax | 4. 寻址方式不同 Intel 的指令格式是 segreg:\[base+index\*scale+disp],而 AT\&T 的格式是%segreg:disp(base,index,scale)。 其中 index/scale/disp/segreg 全部是可选的,完全可以简化掉。 如果没有指定 scale 而指定了 index,则 scale 的缺省值为 1。 当立即数用在 scale/disp 中时,不应当在其前冠以"$"前缀。 | Intel 语法 | AT\&T 语法 | | ------------------------- | ------------------------------ | | mov eax,\[ebx+20h] | movl 0x20(%ebx),%eax | | add eax,\[ebx+ecx\*2h | addl (%ebx,%ecx,0x2),%eax | | lea eax,\[ebx+ecx] | leal (%ebx,%ecx),%eax | | sub eax,\[ebx+ecx\*4h-20h] | subl -0x20(%ebx,%ecx,0x4),%eax | 5. 后缀不同 在 AT\&T 的操作码后面有一个后缀,其含义就是指出操作码的大小。 "l"表示长整数(32 位),"w"表示字(16 位),"b"表示字节(8 位)。 而在 Intel 的语法中,则要在内存单元操作数的前面加上 byte ptr, word ptr 和 dword ptr。 | Intel 语法 | AT\&T 语法 | | ----------------------- | ---------------- | | mov al,bl | movb %bl,%al | | mov ax,bx | movw %bx,%ax | | mov eax,ebx | movl %ebx,%eax | | mov eax,dword ptr \[ebx] | movl (%ebx),%eax | --- url: /blog/2023/Everything-I-wish-I-knew-when-learning-C.md --- # C 语言必备知识 学习 C 语言相当困难。不是因为 C 语言难学,而是因为用 C 编程需要知道很多其它类型的知识。例如 * C 语言没有消除平台或操作系统差异的环境;你也需要了解你的平台。 * 有许多 C 语言编译器选项和构建工具,即使是运行一个简单的程序也要做出许多决定。 * 有一些与 CPU、操作系统、一般的编译代码有关的重要概念 * 与其他语言相比,C 语言的使用方式多种多样,所以集中的"社区"或风格要少得多。 ## 有用的资源 * [TutorialsPoint C](https://www.tutorialspoint.com/cprogramming/index.htm):基础的介绍 * [awesome-c](https://github.com/oz123/awesome-c):一些有用的库和工具 * [cppreference](https://en.cppreference.com/w/c):C 语言和标准库的技术参考 ## 可以学习的好项目 学习一些小的,只包含 C 语言的项目是很有用的: * [Bloopsaphone](https://github.com/whymirror/bloopsaphone/blob/tree/master/c):一个用于合成声音的 Ruby 库,它的核心是一个小型的 C 模块。有少量的概念和良好的结构。 * [Simple Dynamic Strings (sds)](https://github.com/antirez/sds):一个很好的例子,只有一个.c 和.h 文件,就能实现复杂的资源管理。 * [Brogue CE](https://github.com/tmewett/BrogueCE):一个 roguelinke 的视频游戏,很大单并不完美,有 30k+行,贡献它可以很好的磨砺你的 C 语言。 * [stb single-file libraries](https://github.com/nothings/stb):这些是中小型的模块,旨在高度便携,包括针对嵌入式设备和游戏机。 ## 编译,链接,头文件和符号表 这是一些关于编译 C 语言的一些基础知识,这对理解其它事情很有帮助。 C 代码是写在.c 源文件中的。每个源文件都被编译成一个.o 对象文件,它就像是.c 文件中编译后的功能代码的容器。它们是不可执行的。对象文件内部有一个符号表,是该文件中定义的全局函数和变量的名称。 ```sh # compile to objects cc -c thing.c -o thing.o cc -c stuff.c -o stuff.o ``` 源文件是完全独立的,可以并行地编译成对象。 为了跨文件使用函数和变量,我们使用头文件(.h)。这些只是以特定方式使用的普通 C 源文件。记得上面说过,对象文件只包含全局函数和变量的名称,没有类型、宏,甚至函数参数。为了跨文件使用符号,我们需要指定所有需要使用它们的额外信息。我们把这些"声明"放在自己的.h 文件中,这样其他的.c 文件就可以通过`#include`引用它们了。 为了避免重复,一个.c 文件通常不会定义自己的类型/宏等,而只是包括自己或其所属模块/组件的头文件。 **把头文件看作是一个 API 的规范**,它可以在任何数量的源文件中实现。你甚至可以为不同的平台或目的,编写同一头文件的不同实现。 当编译一个仅被声明(例如被包含的头文件)而未被定义的符号的引用时,对象文件将标记这个符号是缺失的,需要被实现。 将一个或多个对象连接在一起的最后工作,匹配所有的符号引用,是由编译器的 "链接器 "组件完成的。链接器输出完整的可执行文件或共享库。 ```sh # link objects to executable cc thing.o stuff.o -o gizmo ``` 总之,我们在 C 语言中不像其他语言那样"包含"其他源文件。我们只包括声明,然后代码被链接器匹配起来。 ## 不要使用这些函数 C 语言已经很老了,并试图做到高度向后兼容。因此,它有一些应该被避免的功能。 * `atoi()`,`atol()`等类似函数;发生错误时,它们返回 0,但是 0 也是一个有效的值。更好的是使用`strtoi()`等函数。 * `gets()`是不安全的,因为它没有给出目标缓冲区的界限。最好使用`fgets()`。 ## 数组不是值 重要的是要意识到 C 语言作为一种语言,只处理已知大小的数据块。你也许可以把 C 语言概括为 "复制已知大小的值的语言"。 我可以在程序中传递一个整数或一个结构,从函数中返回它们,等等,并将它们视为适当的对象,因为 C 知道它们的大小,因此可以编译代码来复制它们的全部数据。 我不能用数组来做这个。当我在一个函数中声明一个`int[5]`类型的变量时,实际上我并没有得到一个`int[5]`类型的值;我得到的是一个`int*`值,其中有 5 个 int 被分配到它那里。由于这只是一个指针,应当由程序员而不是语言管理复制它后面的数据并保持其有效性。 然而,结构体内的数组被视为值,并与结构体一起被完全复制。 (从技术上讲,已知大小的数组类型是真实的类型,而不仅仅是指针;例如,sizeof 会告诉你整个数组的大小。但是你不能把它们当作自带的值)。 ## 重要的编译选项 编译器有这么多的选项,而默认的选项并不是很好。下面是你可能需要的绝对必要的标志。(它们是以 GCC/Clang 风格给出的;其他编译器的语法可能有所不同)。 * `-O2`:为发布版本的构建优化代码 * `-g -Og`:用于调试构建;为调试器启用额外信息,并为调试进行优化。 * `-Wall`:启用所有警告;也可以通过`-Wno-...`禁用指定警告。 * `-Werror`:将所有警告转换为错误;确保至少启用`-Werror=implicit`,确保调用未声明函数时报错。 * `-DNAME`和`-DNAME=value`:生命一个宏。 * `-fsanitize=address,undefined`:用于调试构建;使能两个 sanitizer,它在整个编译后的代码中注入额外的检查以发现错误。详见[GCC 编译选项](https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html)。 * `-std=...`:选择一个标准。在大多数情况下,你可以省略它,使用你的编译器的默认值(通常是最新的标准)。那些对可移植性有特殊关注的人可以使用`-std=c89`来保持"经典"C。 参考[Option Summary (Using the GNU Compiler Collection (GCC))](https://gcc.gnu.org/onlinedocs/gcc/Option-Summary.html),了解更多编译选项。 ## 三种类型的内存以及何时使用 * **自动存储**是存储局部变量的地方。当一个函数被调用时,将为其创建一个新的自动存储区域,并在其返回时删除。只有返回值被保留;它被复制到调用它的函数的自动存储中。这意味着返回一个指向局部变量的指针是不安全的,因为底层数据会被悄悄地删除。自动存储通常被称为**堆栈**。 * **分配的存储空间**是使用`malloc()`的得到的。它一直存活到被`free()`的时候,所以可以被传递到任何地方,包括向上传递到调用函数。它通常被称为**堆**。 * **静态存储**在程序的生命周期内有效。它是在进程开始时分配的。全局变量被存储在这里。 如果你想从一个函数中 "返回 "内存,你不必使用 malloc/分配的存储;你可以传递一个指向本地数据的指针: ```c void getData(int *data) { data[0] = 1; data[1] = 4; data[2] = 9; } void main() { int data[3]; getData(data); printf("%d\n", data[1]); } ``` ## 命名习惯 C 语言不支持命名空间。如果你要做一个公共库,或者希望一个"模块"有一个名字,你需要选择一个前缀来添加到所有公共 API 的名字中: * 函数 * 类型 * 枚举值 * 宏 此外,你应该总是为每个枚举包括一些不同的前缀,这样你就知道这个值属于哪个枚举类型: ```c enum color { COLOR_RED, COLOR_BLUE, ... } ``` 关于名字没有真正的标准,例如,`snake_case`或者`cameCase`。选择并保持一致,我所知道的最接近约定的东西是,有些人把类型命名为`my_type_t`,因为许多标准的 C 类型都是这样的(`ptrdiff_t`,`int32_t`, 等等)。 ## static 在一个函数或文件内变量上使用,`static`使其成为文件本地的。它不会被导出为一个符号,供其他源文件使用。 `static`也可以用在局部变量上,这使得变量在调用该函数时持续存在。你可以认为这就像一个全局变量,它只在一个函数的范围内。这对于计算和存储数据以便在随后的调用中重复使用是很有用的;但请记住,这也有全局/共享状态,需要在多线程或递归调用中注意他们的使用。 (它看起来有多重含义,因为在全局作用域中它似乎减少了变量的作用域,但在函数作用域中它又增加了变量的作用域)。实际上,在这两种情况下,它所做的是使它们与文件相联系。 ## 结构体方法参数 如果你在 C 语言之前学过一种更有特点的语言,你可能会发现很难想象如何转化这些知识。这里有一个类似于面向对象编程的常用方法:"结构体方法"。你写的函数接收指向结构的指针,以改变它们或获得属性: ```c typedef struct { int x; int y; } vec2; void vec_add(vec2 *u, const vec2 *v) { u->x += v->x; u->y += v->y; } int vec_dot(const vec2 *u, const vec2 *v) { return u->x * v->x + u->y * v->y; } ``` 你不能扩展结构或做任何真正类似于 OO 的事情,但这是一个有用的模式,可以用来参考。 ## const 将一个`T`类型的变量或参数声明为`const T`,大致上意味着该变量不能被修改。这意味着它不能被赋值,如果 T 是一个指针或数组类型,它也不能被改变。 将类型为`T`的变量等于类型为`const T`的变量是允许的,反过来是不允许的。 一个好的习惯是默认将函数的指针参数声明为常数,只有在需要修改它们时才省略。 ## 平台与标准 APIs 当你通过`#include`引用头文件时,一般无法确定它们依赖什么。通常来自以下情况: * 标准 C 库,缩写为"stdlib"。例如:`stdio.h`,`stdlib.h`,`error.h` * 这是语言规范的一部分,应该由所有符合要求的平台和编译器来实现。非常安全的依赖。 * * POSIX,一套标准的操作系统 APIs。例如:`unistd.h`,`sys/time.h` * 通常由 Linux,macOS,BSDs 实现。 * windows 默认情况下不可用。可以通过[MinGW](https://sourceforge.net/p/mingw-w64/mingw-w64/ci/master/tree/mingw-w64-headers/crt/)进行支持,对于更完整的支持,需要[Cygwin](https://cygwin.com/)库。 * 非标准的操作系统接口 * Linux 特殊 APIs,可以通知[man 文档](https://www.man7.org/linux/man-pages/index.html)第三章了解。 * [Windows Win32](https://docs.microsoft.com/en-us/windows/win32/api/) * [Mac's OS APIs](https://developer.apple.com/documentation/technologies) * 安装在标准位置的一些第三方库。 通过一个平台中立的头文件与你的特定平台代码对接可能是一个好主意,这样它就可以用不同的方式来实现。很多流行的 C 语言库基本上都是统一的、精心设计的对特定平台功能的抽象。 ## 整型 整数在 C 语言中是经常被抱怨的,编写正确的代码需要一些小心: ### Sizes [所有的整型有一个默认的最小 size](https://en.cppreference.com/w/c/language/arithmetic_types).在常见的平台上,有些比它们的最小 size 更大,例如 int,尽管最小 size 是 16 位,但在 Windows、macOS 和 Linux 上是 32 位。在编写可移植代码时,你必须假设整数永远不能超过其最小 size。 如果你想精确控制整数大小,你可以使用`stdint.h`中的标准类型,如`int32_t`、`uint64_t`等。还有`_least_t`和`_fast_t`类型。例如: `uint_fast8_t`、`uint_least8_t`。 * `_least_t`:表示至少保存多少字节。 * `_fast_t`:表示让编译其选择一个可以容纳多少字节并且操作最快。 需要在任何地方使用这些明确的类型吗?必须承认我对这个问题很纠结,但我越想越觉得你应该这样做--没有什么坏处。你真正不应该这样做的唯一原因是在制作 API 时必须使用缺少`stdint.h`的老式 C89 编译器。使用`int16_fast_t`或其他类型而不是`int`,不会更糟,只会更清楚。 ### 算数和优化 [C 语言中的算术受制于许多奇怪的规则](https://en.cppreference.com/w/c/language/operator_arithmetic),这些规则可能会产生意想不到的或无法实现的结果。[整数提升](https://en.cppreference.com/w/c/language/conversion#Integer_promotions)尤其需要注意。 可以参考[Nayuki’s summary of C integer rules](https://www.nayuki.io/page/summary-of-c-cpp-integer-rules)。 ### 无符号 char 所有其他的整数类型都默认为有符号,但 char 可以是有符号或无符号的,这取决于平台的情况。因此,它只有在用于字符串时才是可移植的;如果你想要一个小的/最小的 8 位的数字,也要指定符号。 ## 宏与 const 变量 要定义简单的常数,你有两个选择: ```c static const int my_constant = 5; // or #define MY_CONSTANT 5 ``` 区别在于,前者是一个真正的变量,后者是一个复制粘贴的内联表达式。 * 与变量不同,你可以在需要"常量表达式"的情况下使用宏,比如数组长度或开关语句的情况。 * 与宏不同,你可以得到一个变量的指针。 让常量真正成为"常量表达式"是非常有用的,因此它们通常应该被定义为宏。变量更适合于更大或更复杂的值,如结构体实例。 如果你的常数是一个整数,你有第三个更好的选择,即枚举: ```c enum { MY_CONSTANT = 5 } ``` 这在 C 语言中定义了一个常量表达式,而不是在预处理程序中,所以它可以更容易被调试器等看到。 在 C23 中,你可以给一个枚举类型指定明确的基础类型: ```c enum : size_t { BUFFER_LENGTH = 1024 } ``` ## 宏与内联函数 宏可以有参数,然后可以扩展为 C 代码。 相对于函数的优势: * 这些代码被直接粘贴在周围的代码中,而不是编译函数调用指令。这可以使代码更快,因为函数调用有一些开销。 * 它们可以是类型通用的。例如,`x + y`是任何数字类型的有效语法。如果我们把它变成一个函数,我们就必须把它们声明为参数,并事先选择它们的类型,即大小和符号性,这将使它只能在某些情况下使用。 缺点 * 参数的重复使用。假设我们有一个宏`MY_MACRO(x)`。如果 x 在定义中被多次使用,那么表达式 x 将被多次出现,因为它被简单地复制和粘贴。而函数的参数的表达式被会被计算一次,然后传入函数。 * 它们可能容易出错,因为它们是在源码级别工作。一般来说,多使用括号是个好主意,总是对整个宏定义本身和任何参数使用括号,这样表达式就不会在无意中合并。 ```c // Instead of: #define MY_MACRO(x) x+x // Do: #define MY_MACRO(x) ((x)+(x)) ``` 除非你需要类型通用,否则你可以通过将一个函数定义为静态内联来获得两方面的好处。内联向编译器提供了一个提示,即函数中的代码应该直接编译到使用它的地方,而不是被调用。你可以把静态内联函数放在头文件中,就像宏一样,没有任何问题。 此外,从 C11 开始,你可以使用一个特殊的宏`_Generic`为不同类型的函数提供重载: ```c #define sin(X) _Generic((X), \ long double: sinl, \ default: sin, \ float: sinf \ )(X) ``` ## 参考 1. [Everything I wish I knew when learning C - Tom Mewett (tmewett.com)\[原文\]](https://tmewett.com/c-tips/#essential-compiler-flags) --- url: /blog/2023/Linux-uart.md --- # Linux 串口挂载失败 Ubuntu 插上 CH340 的 usb 转串口后,使用`lsmod`查看 ch341 驱动正常加载,`lsusb`也可以看到 usb 已经正常加载,但是没有`/dev/ttyUSB0` 查看系统 log ``` kernel: usb 1-3: new full-speed USB device number 5 using ohci-pci kernel: usb 1-3: New USB device found, idVendor=1a86, idProduct=7523, bcdDevice= 2.64 kernel: usb 1-3: New USB device strings: Mfr=0, Product=2, SerialNumber=0 kernel: usb 1-3: Product: USB Serial kernel: ch341 1-3:1.0: ch341-uart converter detected kernel: usb 1-3: ch341-uart converter now attached to ttyUSB0 mtp-probe[54666]: checking bus 1, device 5: "/sys/devices/pci0000:00/0000:00:06.0/usb1/1-3" mtp-probe[54666]: bus: 1, device: 5 was not an MTP device snapd[766]: udevmon.go:149: udev event error: Unable to parse uevent, err: cannot parse libudev event: invalid env data systemd[1]: Starting Braille Device Support... systemd-udevd[54665]: ttyUSB0: Conflicting device node '/dev/ttyUSB0' found, link to '/dev/ttyUSB0' will not be created. snapd[766]: hotplug.go:200: hotplug device add event ignored, enable experimental.hotplug brltty[54669]: BRLTTY 6.4 rev BRLTTY-6.4 [https://brltty.app/] brltty[54669]: BRLTTY 6.4 rev BRLTTY-6.4 [https://brltty.app/] brltty[54669]: executing as the invoking user: root brltty[54669]: brltty: executing as the invoking user: root mtp-probe[54678]: checking bus 1, device 5: "/sys/devices/pci0000:00/0000:00:06.0/usb1/1-3" mtp-probe[54678]: bus: 1, device: 5 was not an MTP device snapd[766]: udevmon.go:149: udev event error: Unable to parse uevent, err: cannot parse libudev event: invalid env data brltty[54669]: BrlAPI Server: release 0.8.3 brltty[54669]: brltty: BrlAPI Server: release 0.8.3 systemd[1]: Started Braille Device Support. brltty[54669]: Linux Screen Driver: brltty[54669]: brltty: Linux Screen Driver: kernel: input: BRLTTY 6.4 Linux Screen Driver Keyboard as /devices/virtual/input/input10 brltty[54669]: USB configuration set error 16: 设备或资源忙 brltty[54669]: brltty: USB configuration set error 16: 设备或资源忙 kernel: usb 1-3: usbfs: interface 0 claimed by ch341 while 'brltty' sets config #1 systemd-logind[768]: Watching system buttons on /dev/input/event8 (BRLTTY 6.4 Linux Screen Driver Keyboard) brltty[54669]: USB interface in use: 0 (ch341) brltty[54669]: brltty: USB interface in use: 0 (ch341) ModemManager[964]: [base-manager] port ttyUSB0 released by device '/sys/devices/pci0000:00/0000:00:06.0/usb1/1-3' kernel: ch341-uart ttyUSB0: ch341-uart converter now disconnected from ttyUSB0 kernel: ch341 1-3:1.0: device disconnected ModemManager[964]: [base-manager] couldn't check support for device '/sys/devices/pci0000:00/0000:00:06.0/usb1/1-3': Operation was cancelled brltty[54669]: NoSpeech Speech Driver: brltty[54669]: brltty: NoSpeech Speech Driver: ``` 像是程序 brltty 占用了 usb,卸载 brltty 后恢复正常 ## 参考 1. [Unable to connect to /dev/ttyUSB0 \[Solved\] | MySensors Forum](https://forum.mysensors.org/topic/8871/unable-to-connect-to-dev-ttyusb0-solved) 2. [drivers - /dev/ttyUSB0 not present in Ubuntu 22.04 - Ask Ubuntu](https://askubuntu.com/questions/1403705/dev-ttyusb0-not-present-in-ubuntu-22-04) --- url: /blog/2023/WSL2-bridge-network-and-using-mDNS.md --- # WSL2 启用桥接网络并开启 mDNS 1. WSL2 启用桥接网络 首先在`Hyper-V管理器`中创建新的虚拟交换机(假设名字为 eth\_switch),并选择外部网络和要桥接道德物理网卡。 编辑 window 主目录下.wslconfig 文件,添加两行配置 ``` #C:\Users\\.wslconfig [wsl2] #... networkingMode=bridged vmSwitch=eth_switch ``` 重启 WSL2 后,eth0 将和物理网卡一样获取一个独立 ip 2. 启用 avahi-daemon.service 服务实现 mDNS 广播 ``` # 安装 sudo apt-get install avahi-daemon # 启动 systemctl start avahi-daemon.service # 配置自动启动 systemctl enable avahi-daemon.service ``` 配置 WSL2 计算机名 修改文件`/etc/wsl.conf` ``` [network] hostname = wsl ``` 重启之后便可以在局域网内通过计算机名 wsl 访问 wsl 了 ``` PS C:\Users\fly92> ping wsl 正在 Ping wsl.local [192.168.10.164] 具有 32 字节的数据: 来自 192.168.10.164 的回复: 字节=32 时间<1ms TTL=64 来自 192.168.10.164 的回复: 字节=32 时间<1ms TTL=64 来自 192.168.10.164 的回复: 字节=32 时间<1ms TTL=64 来自 192.168.10.164 的回复: 字节=32 时间<1ms TTL=64 192.168.10.164 的 Ping 统计信息: 数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失), 往返行程的估计时间(以毫秒为单位): 最短 = 0ms,最长 = 0ms,平均 = 0ms ``` ### 参考 1. [WSL 中的高级设置配置](https://learn.microsoft.com/zh-cn/windows/wsl/wsl-config) --- url: /blog/2023/nmcli-config-wifi.md --- # nmcli 配置 wifi ## 连接 wifi ### 扫描 wifi ```bash sudo nmcli dev wifi list ``` ### 连接新的 wifi ```bash sudo nmcli dev wifi connect NETWORK-SSID password "NETWORK-PASSWORD" ``` ### 查看保存的 wifi 信息 ```bash nmcli con show ``` ### 连接保存在 wifi ```bash nmcli con up NETWORK-SSID ``` ### 断开 wifi ```bash nmcli con down NETWORK-SSID ``` ## 创建 wifi 热点 ### 创建热点 ```bash nmcli connection add type wifi ifname wlan0 con-name local-ap autoconnect yes ssid WIFI-SSID mode ap ``` ### 设置密码 ```bash nmcli connection modify local-ap 802-11-wireless.mode ap 802-11-wireless-security.key-mgmt wpa-psk ipv4.method shared 802-11-wireless-security.psk "PASSWORD" ``` ### 启动热点 ```bash nmcli connection up local-ap ``` ### 配置 dhcp ```bash # vim /etc/dhcp/dhcpd.conf option domain-name-servers 8.8.8.8; subnet 192.168.44.0 netmask 255.255.255.0 { # 目标网段 range 192.168.44.128 192.168.44.200; # 具体的IP option routers 192.168.44.1; # 网关地址 option domain-name-servers 114.114.114.114; # dns解析 } ``` ### 启动 dhcp 服务 ```bash systemctl restart dhcpd ``` --- url: /blog/2023/snr-evm.md --- # SNR 与 EVM 的关系 ## 关系 当 SNR 较大(大于 10dB)时,**SNR=-20lg(EVM)**,其中 SNR 的单位为 dB,EVM 为正实数。 如果 EVM 用 dB 表示,可以简化为**SNR = -2\*EVM\_dB**。(此式在 SNR 大于 4dB 时,误差较小,不超过 1dB) 下面依次介绍 log、dB、dBm、evm、snr 的概念,再说明此结论的推导过程。 ## 对数公式 如果 a^b=N(a>0,且 a≠1,则 b 叫做以 a 为底 N 的对数,公式如下,其中 a 叫做底数,N 叫做真数, b 叫对数。 ![对数](./imgs/index.png) 通常我们将以 10 为底的对数叫常用对数,以 e 为底的对数叫自然对数。 常用对数:$ lg(b) = log\_{10}(b) $ (10 为底数)。 自然对数:$ ln(b) = log\_{e}(b) $ (e 为对数)。 对数的基本性质中,下面的结论经常用到: ```math \begin{array}{l} log_{a}M^n = nlog_{a}M \cr lg10 = 1 \cr lg2 \approx 0.3 \cr lg1 = 0 \end{array} ``` ## 信号强度 日常中通常使用功率来衡量一个电器做功的快慢,如一个 10W 的电灯泡,10W 功率就是电灯泡消耗能量做功的快慢。 在天线收发系统里,同样也需要消耗电能来转换为电磁波的能量进行传输。 但是电磁波的能量衰减非常快,例如一个 100mW 的能量源,传输一段距离后很快就能衰减成 1mW、0.1mW、0.01mW 甚至更小。 对于这种呈几何数量级的衰减,使用功率来衡量会给计数带来不便,因此引用新的概念:dB 和 dBm。 ### dB 介绍 dB 是一个表征相对值的值,纯粹的比值,只表示**两个量的相对大小关系,没有单位**。公式为 dB=10lg(A/B),**一般作为(SNR)信噪比、损耗的单位**。 当考虑甲的功率相比于乙功率大或小多少个 dB 时,按下面的计算公式:10log(甲功率/乙功率)。 例如 A 的功率为 100mW,B 的功率为 10mW,则 10lg(100 / 10) = 10dB,说明 A 比 B 大 10dB。 > 例:如何理解:+3 dB 表示增大为两倍,-3 dB 表示下降为 1/2。 > 如果 A 的功率开始为 100mW,经过衰减变成了 50mw,则 10lg(50 / 100) = 10lg(1/ 2) = -10lg(2)= -3dB。 > 如果 A 的功率开始为 100mW,经过放大变成了 200mw,则 10lg(200 / 100) = 10lg(2) = 3dB。 ### dBm 介绍 dBm 是一个表示功率绝对值(可以认为是以 1mW 功率为基准的一个比值),计算公式为:10log(功率值/1mw)。 记住一个口诀: 加 3 乘 2, 加 10 乘 10; 减 3 除 2,减 10 除 10 (+3 dB,表示功率增加为 2 倍;+10 dB,表示功率增加为 10 倍。-3 dB,表示功率减小为 1/2;-10 dB,表示功率减小为 1/10)。 > 例:功率 P 为 1mw,折算为 dBm 后为 0dBm。 > 40W 的功率,按 dBm 单位进行折算后的值应为: > 10log(40W/1mw)=10log(40000)= 46dBm。 按口决: 40W = 1w \_ (( 10 \_ 10 \_ 10)W \_ 10 \_ 2 \_ 2) => 10 + 10 + 10 + 10 + 3 + 3 = 46dBm > 那么 44dBm 是多少 W? (25W) 需要注意的是,对于功率的增益,我们用 10lg(Po/Pi),而对于电压和电流的增益,要用 20lg(Vo/Vi)、20lg(Io/Ii)。 原因是 这个 2 来源于电功率转换公式的平方上: ```math P = UI = I^2R = \frac {U^2} {R} ``` ## SNR 和 EVM ### SNR 介绍 信噪比 SNR(Signal-to-noise Ratio),指的是系统中信号与噪声的比。公式为 SNR = 10lg(Ps / Pn),其中: * SNR:信噪比,单位是 dB。 * Ps:信号的有效功率。 * Pn:噪声的有效功率。 ```math SNR(dB) = 10 *lg \frac {\int s(t)^2 \mathrm{d}t} {\int n(t)^2 \mathrm{d}t} = 10 * lg \frac {\int s(t)^2 \mathrm{d}t} {\int (x(t)-s(t))^2 \mathrm{d}t}\tag{1} ``` 式中$ x(t) $ 为矢量 reference signal,$ s(t) $ 为矢量输入信号。 ### EVM 介绍 ![EVM](./imgs/EVM.png) QAM 调制信号通常用其 EVM 来衡量信号质量,EVM 是英文 Error Vector Magnitude 缩写,意为误差向量幅度。 误差矢量 Error Vector 的幅度与参考信号 Reference Signal 幅度的比值。通常测量的 EVM 为其 RMS 值(Root Mean Square 均方根),计算公式如: ```math EVM=\sqrt {\frac {\int (x(t)-s(t))^2 \mathrm{d}t} {\int x(t)^2 \mathrm{d}t}}\tag{2} ``` 式中$ x(t) $ 为矢量 reference signal,$ s(t) $ 为矢量输入信号。 大多时候,EVM 用 dB 表示: ```math EVM_{dB} = 10*lg{EVM} ``` ### SNR 与 EVM 关系 从(1)式 和(2)式可以看出:当矢量输入信号$ s(t) $越接近于矢量reference signal $ x(t) $,即$ s(t) $可以用$ x(t) $代替,推导如下: ```math \begin{array}{l} SNR(dB) = 10 * lg \frac {\int s(t)^2 \mathrm{d}t} {\int (x(t)-s(t))^2 \mathrm{d}t} \cr \approx 10 * lg \frac {\int x(t)^2 \mathrm{d}t} {\int (x(t)-s(t))^2 \mathrm{d}t} \cr = -1 * 10 * lg \frac {\int (x(t)-s(t))^2 \mathrm{d}t} {\int x(t)^2 \mathrm{d}t} \cr = -10 * lg({EVM}^2) \cr =-2 * 10 * lg(EVM) =-2EVM_{dB} \end{array} ``` --- url: /blog/2024/arm-cortex-m3.md --- # ARM Cortex-M3 基础 ## 性能简介 ### 高性能 * 许多指令都是**单周期**的——包括乘法相关指令。并且从整体性能上, Cortex-M3 比得过绝大多数其它的架构。 * **指令总线和数据总线被分开**,取值和访内可以并行不悖 * Thumb-2 的到来**告别了状态切换**的旧世代,再也不需要花时间来切换于 32 位 ARM 状态和 16 位 Thumb 状态之间了。这简化了软件开发和代码维护,使产品面市更快。 * Thumb-2 指令集为编程带来了更多的灵活性。许多数据操作现在能用更短的代码搞定,这意味着 Cortex-M3 的**代码密度更高**,也就对存储器的需求更少。 * 取指都按 32 位处理。同一周期最多可以取出两条指令,留下了更多的带宽给数据传输。 * Cortex-M3 的设计允许单片机高频运行(现代半导体制造技术能保证 100MHz 以上的速度)。即使在相同的速度下运行, CM3 的每指令周期数(CPI)也更低,于是同样的 MHz 下可以做更多的工作;另一方面,也使同一个应用在 CM3 上需要更低的主频。 ### 先进的中断处理功能 * 内建的嵌套向量中断控制器支持多达 **240 条外部中断输入**。向量化的中断功能剧烈地缩短了中断延迟,因为不再需要软件去判断中断源。中断的嵌套也是在硬件水平上实现的,不需要软件代码来实现。 * Cortex-M3 在进入异常服务例程时,**自动压栈了 R0-R3, R12, LR, PSR 和 PC**,并且在返回时自动弹出它们,这多清爽!既加速了中断的响应,也再不需要汇编语言代码了。 * NVIC 支持对每一路中断设置不同的优先级,使得中断管理极富弹性。最粗线条的实现也至少要支持 8 级优先级,而且还能动态地被修改。 * 优化中断响应还有两招,它们分别是“咬尾中断机制”和“晚到中断机制”。 * 有些需要较多周期才能执行完的指令,是可以被中断-继续的——就好比它们是一串指令一样。这些指令包括加载多个寄存器(LDM),存储多个寄存器(STM),多个寄存器参与的 PUSH,以及多个寄存器参与的 POP。 * 除非系统被彻底地锁定, NMI(不可屏蔽中断)会在收到请求的第一时间予以响应。对于很多安全-关键(safety-critical)的应用, NMI 都是必不可少的。 ### 低功耗 * Cortex-M3 需要的逻辑门数少,所以先天就适合低功耗要求的应用(功率低于 0.19mW/MHz)。 * 在内核水平上支持节能模式(SLEEPING 和 SLEEPDEEP 位)。通过使用“等待中断指令(WFI)”和“等待事件指令(WFE)”,内核可以进入睡眠模式,并且以不同的方式唤醒。另外,模块的时钟是尽可能地分开供应的,所以在睡眠时可以把 CM3 的大多数“官能团”给停掉。 * CM3 的设计是全静态的、同步的、可综合的。任何低功耗的或是标准的半导体工艺均可放心饮用。 ### 系统特性 * 系统支持“位寻址带”操作(8051 位寻址机制的“威力大幅加强版”),字节不变的大端模式,并且支持非对齐的数据访问。 * 拥有先进的 fault 处理机制,支持多种类型的异常和 faults,使故障诊断更容易。 * 通过引入 banked 堆栈指针机制,把系统程序使用的堆栈和用户程序使用的堆栈划清界线。如果再配上可选的 MPU,处理器就能彻底满足对软件健壮性和可靠性有严格要求的应用。 ### 调试支持 * 在支持传统的 JTAG 基础上,还支持更新更好的串行线调试接口。 * 基于 CoreSight 调试解决方案,使得处理器哪怕是在运行时,也能访问处理器状态和存储器内容。 * 内建了对多达 6 个断点和 4 个数据观察点的支持。 * 可以选配一个 ETM,用于指令跟踪。数据的跟踪可以使用 DWT * 在调试方面还加入了以下的新特性,包括 fault 状态寄存器,新的 fault 异常,以及闪存修补 (patch)操作,使得调试大幅简化。 * 可选 ITM 模块,测试代码可以通过它输出调试信息,而且“拎包即可入住”般地方便使用。 ## 基于 Cortex-M3 的芯片设计 Cortex-M3 处理器内核是芯片的中央处理单元,完整的 MCU 还需要很多其他组件,例如存储,外设,IO 等。芯片设计商得到 CM3 核的授权后,就会把 CM3 用到自己的芯片中,做一些定制化的设计,所以不同的厂商有不同的配置,想了解具体型号的处理器需要查阅厂家提供的文档,比如 stm32,nxp,ti,Freescale。基于 ARM 低成本和高效的处理器设计方案, 得到授权的厂商生产了多种多样的的处理器、 单片机以及片上系统(SoC)。这种商业模式就是所谓的“知识产权授权”。 ![ARM](https://www.vhcffh.com/api/v2/objects/icon/c4qhkpqz7o9jy2312h.png) ### ARM 发展历史 ![ARM处理器架构进化史](https://www.vhcffh.com/api/v2/objects/icon/bqfspufqm1jicbx63o.png) ARMv7 架构的闪亮登场。在这个版本中,内核架构首次从单一款式变成 3 种款式。 * ARM-A:设计用于高性能的“开放应用平台” * ARM-R:用于高端的嵌入式系统,尤其是那些带有实时要求的 * ARM-M:用于深度嵌入的,单片机风格的系统中 | 处理器名字 | 架构版本号 | 存储器管理特性 | 其它特性 | | :------------- | :--------- | :------------------- | :----------- | | ARM7TDMI | v4T | | | | ARM7TDMI-S | v4T | | | | ARM7EJ-S | v5E | | DSP,Jazelle | | ARM920T | v4T | MMU | | | ARM922T | v4T | MMU | | | ARM926EJ-S | v5E | MMU | DSP,Jazelle | | ARM946E-S | v5E | MPU | DSP | | ARM966E-S | v5E | | DSP | | ARM968E-S | v5E | | DMA,DSP | | ARM966HS | v5E | MPU(可选) | DSP | | ARM1020E | v5E | MMU | DSP | | ARM1022E | v5E | MMU | DSP | | ARM1026EJ-S | v5E | MMU 或 MPU | DSP, Jazelle | | ARM1136J(F)-S | v6 | MMU | DSP, Jazelle | | ARM1176JZ(F)-S | v6 | MMU+TrustZone | DSP, Jazelle | | ARM11 MPCore | v6 | MMU+多处理器缓存支持 | DSP | | ARM1156T2(F)-S | v6 | MPU | DSP | | Cortex-M3 | v7-M | MPU(可选) | NVIC | | Cortex-R4 | v7-R | MPU | DSP | | Cortex-R4F | v7-R | MPU | DSP+浮点运算 | | Cortex-A8 | v7-A | MMU+TrustZone | DSP, Jazelle | 1. **DSP(Digital Signal Processor)**:数字信号处理是一种专用的处理器,用于执行数字信号处理任务,如音频处理、图像处理、无线通信等。 2. **MMU(内存管理单元)**:用于管理程序对内存的访问,主要负责将**逻辑地址**(由 CPU 生成)转换为**物理地址**(内存中实际存储的地址),并提供一些额外的功能,例如**内存保护**、**虚拟内存**和**缓存控制**等。 3. **Jazelle**:是 ARM 处理器的硬件 Java 加速器 4. **DMA(Direct Memory Access)**:直接内存访问是一种技术,用于实现数据在外设和内存之间的直接传输,而无需 CPU 的干预。 5. **MPU**:可以把 MPU 认为是 MMU 的功能子集,它只支持分区保护,不支持具有“定位决定性”的虚拟内存机制 6. **TrustZone**:是 ARM 公司提供的一种硬件支持的安全解决方案,旨在提高处理器级别的系统安全性。TrustZone 技术通过将处理器划分为两个独立的安全域(Secure World 和 Normal World)来实现安全隔离。Secure World 是一个受保护的执行环境,用于运行安全敏感的代码和数据,而 Normal World 则是普通的执行环境,用于运行一般的应用程序和操作系统。 ### M3 架构图 Cortex-M3 是一个 32 位处理器内核。内部的数据路径是 32 位的,寄存器是 32 位的,存储器接口也是 32 位的。 CM3 采用了哈佛结构,拥有独立的指令总线和数据总线,可以让取指与数据访问并行不悖。这样一来数据访问不再占用指令总线,从而提升了性能。Both 小端模式和大端模式都是支持的。 ![Cortex-M3 的一个简化视图](https://www.vhcffh.com/api/v2/objects/icon/cevhrmixr8ojmhs4ym.png) ## 寄存器组 Cortex-M3 处理器拥有 R0-R15 的寄存器组。其中 R13 作为堆栈指针 SP。 SP 有两个,但在同一时刻只能有一个可以看到,这也就是所谓的“banked”寄存器。复位后,寄存器默认值不确定。 ![寄存器组](https://www.vhcffh.com/api/v2/objects/icon/lge9tdl21sklhbezf3.png) ### R0-R12:通用寄存器 R0-R12 都是 32 位通用寄存器,用于数据操作。但是注意:绝大多数 16 位 Thumb 指令只能访问 R0-R7,而 32 位 Thumb-2 指令可以访问所有寄存器。 ### Banked R13: 两个堆栈指针 Cortex-M3 拥有两个堆栈指针,然而它们是 banked,因此任一时刻只能使用其中的一个。 * 主堆栈指针(MSP):复位后缺省使用的堆栈指针,用于操作系统内核以及异常处理例程(包括中断服务例程) * 进程堆栈指针(PSP):由用户的应用程序代码使用。 堆栈指针的最低两位永远是 0,这意味着堆栈总是 4 字节对齐的。 > 在 ARM 编程领域中,凡是打断程序顺序执行的事件,都被称为异常(exception)。除了外部中断外,当有指令执行了“非法操作”,或者访问被禁的内存区间,因各种错误产生的 fault,以及不可屏蔽中断发生时,都会打断程序的执行,这些情况统称为异常。在不严格的上下文中,异常与中断也可以混用。另外,程序代码也可以主动请求进入异常状态的(常用于系统调用)。 ### R14:连接寄存器 当呼叫一个子程序时,由 R14 存储返回地址 > 不像大多数其它处理器,ARM 为了减少访问内存的次数(访问内存的操作往往要 3 个以上指令周期,带 MMU 和 cache 的就更加不确定了),把返回地址直接存储在寄存器中。这样足以使很多只有 1 级子程序调用的代码无需访问内存(堆栈内存),从而提高了子程序调用的效率。如果多于 1 级,则需要把前一级的 R14 值压到堆栈里。在 ARM 上编程时,应尽量只使用寄存器保存中间结果,迫不得以时才访问内存。在 RISC 处理器中,为了强调访内操作越过了处理器的界线,并且带来了对性能的不利影响,给它取了一个专业的术语:溅出。 ### R15:程序计数寄存器 指向当前的程序地址。如果修改它的值,就能改变程序的执行流。 ### 特殊功能寄存器 Cortex-M3 还在内核水平上搭载了若干特殊功能寄存器,包括 * 程序状态字寄存器组(PSRs) * 中断屏蔽寄存器组(PRIMASK, FAULTMASK, BASEPRI) * 控制寄存器(CONTROL) | 寄存器 | 功能 | | :-------- | :-------------------------------------------------------------------------------------- | | xPSR | 记录 ALU 标志(0 标志,进位标志,负数标志,溢出标志),执行状态,以及当前正服务的中断号 | | PRIMASK | 除能所有的中断——当然了,不可屏蔽中断(NMI)才不甩它呢。 | | FAULTMASK | 除能所有的 fault——NMI 依然不受影响,而且被除能的 faults 会“上访”。 | | BASEPRI | 除能所有优先级不高于某个具体数值的中断。 | | CONTROL | 定义特权状态,并且决定使用哪一个堆栈指针 | ## 操作模式和特权级别 特权级和用户级。这可以提供一种存储器访问的保护机制,使得普通的用户程序代码不能意外地,甚至是恶意地执行涉及到要害的操作。处理器支持两种特权级,这也是一个基本的安全模型。在 CM3 运行主应用程序时(线程模式),既可以使用特权级,也可以使用用户级;但是异常服务例程必须在特权级下执行。复位后,处理器默认进入线程模式,特权极访问。在特权级下,程序可以访问所有范围的存储器并且可以执行所有指令。 ![合法的操作模式转换图](https://www.vhcffh.com/api/v2/objects/icon/05lgxd5kytc4v4tgl9.png) 在特权级下的程序可以为所欲为,但也可能会把自己给玩进去——切换到用户级。一旦进入用户级,再想回来就得走“法律程序”了——用户级的程序不能简简单单地试图改写 CONTROL 寄存器就回到特权级,它必须先“申诉”:执行一条系统调用指令(SVC)。这会触发 SVC 异常,然后由异常服务例程(通常是操作系统的一部分)接管,如果批准了进入,则异常服务例程修改 CONTROL 寄存器,才能在用户级的线程模式下重新进入特权级。 事实上,从用户级到特权级的唯一途径就是异常:如果在程序执行过程中触发了一个异常,处理器总是先切换入特权级,并且在异常服务例程执行完毕退出时,返回先前的状态。 ## 嵌套向量中断控制器 Cortex-M3 在内核水平上搭载了一颗中断控制器——嵌套向量中断控制器 NVIC(Nested Vectored Interrupt Controller)。它与内核有很深的“亲密接触”——与内核是紧耦合的。 NVIC 提供如下的功能: * 可嵌套中断支持 * 向量中断支持 * 动态优先级调整支持 * 中断延迟大大缩短 * 中断可屏蔽 ### 可嵌套中断支持 可嵌套中断支持的作用范围很广,覆盖了所有的外部中断和绝大多数系统异常。外在表现是,这些异常都可以被赋予不同的优先级。当前优先级被存储在 xPSR 的专用字段中。当一个异常发生时,硬件会自动比较该异常的优先级是否比当前的异常优先级更高。如果发现来了更高优先级的异常,处理器就会中断当前的中断服务例程(或者是普通程序),而服务新来的异常——即立即抢占。 ### 向量中断支持 当开始响应一个中断后,CM3 会自动定位一张向量表,并且根据中断号从表中找出 ISR 的入口地址,然后跳转过去执行。不需要像以前的 ARM 那样,由软件来分辨到底是哪个中断发生了,也无需半导体厂商提供私有的中断控制器来完成这种工作。这么一来,中断延迟时间大为缩短。 ### 动态优先级调整支持 软件可以在运行时期更改中断的优先级。如果在某 ISR 中修改了自己所对应中断的优先级,而且这个中断又有新的实例处于悬起中(pending),也不会自己打断自己,从而没有重入(reentry)。所谓的重入,就是指某段子程序还没有执行完,就因为中断或者是多任务操作系统的调度原因,导致该子程序在一个新的寄存器上下文中被执行(请不要把重入与递归混淆,它们有本质的区别)。这种情况常常会闹出乱子,因此有“可重入性”的研究。 ### 中断延迟大大缩短 Cortex-M3 为了缩短中断延迟,引入了好几个新特性。包括自动的现场保护和恢复,以及其它的措施,用于缩短中断嵌套时的 ISR 间延迟。详情请见后面关于“咬尾中断”和“晚到中断”的讲述。 > **咬尾中断**:当处理器在响应某异常时,如果又发生其它异常,但它们优先级不够高,则被阻塞——这个我们已经知道。那么在当前的异常执行返回后,系统处理悬起的异常时,倘若还是先 POP,然后又把 POP 出来的内容 PUSH 回去,这不成了砸锅炼铁再铸锅,白白浪费 CPU 时间吗,可知还有多少紧急的事件悬而未决呀!正因此,CM3 不会傻乎乎地 POP 这些寄存器,而是继续使用上一个异常已经 PUSH 好的成果,消灭了这种铺张浪费。这么一来,看上去好像后一个异常把前一个的尾巴咬掉了,前前后后只执行了一次入栈/出栈操作。 > **晚到中断**:CM3 的中断处理还有另一个机制,它强调了优先级的作用,这就是“晚到的异常处理”。当 CM3 对某异常的响应序列还处在早期:入栈的阶段,尚未执行其服务例程时,如果此时收到了高优先级异常的请求,则本次入栈就成了为高优先级中断所做的了——入栈后,将执行高优先级异常的服务例程。 ### 中断可屏蔽 既可以屏蔽优先级低于某个阈值的中断/异常(设置 BASEPRI 寄存器),也可以全体封杀(设置 PRIMASK 和 FAULTMASK 寄存器)。这是为了让时间关键(time-critical)的任务能在死线(deadline,或曰最后期限)到来前完成,而不被干扰。 ## 存储器映射 总体来说,Cortex-M3 支持 4GB 存储空间: ![存储器映射](https://www.vhcffh.com/api/v2/objects/icon/ifi375vwfgf82nyfgq.png) 从图中可见,不像其它的 ARM 架构,它们的存储器映射由半导体厂家说了算,Cortex-M3 预先定义好了“粗线条的”存储器映射。通过把片上外设的寄存器映射到外设区,就可以简单地以访问内存的方式来访问这些外设的寄存器,从而控制外设的工作。结果,片上外设可以使用 C 语言来操作。这种预定义的映射关系,也使得对访问速度可以做高度的优化,而且对于片上系统的设计而言更易集成。 Cortex-M3 的内部拥有一个总线基础设施,专用于优化对这种存储器结构的使用。在此之上,CM3 甚至还允许这些区域之间“越权使用”。比如说,数据存储器也可以被放到代码区,而且代码也能够在外部 RAM 区中执行(但是会变慢不少)。 处于最高地址的系统级存储区,是 CM3 用于藏“私房钱”的——包括中断控制器、MPU 以及各种调试组件。所有这些设备均使用固定的地址。通过把基础设施的地址定死,就至少在内核水平上,为应用程序的移植扫清了障碍。 ## 总线接口 Cortex-M3 内部有若干个总线接口,以使 CM3 能同时取址和访内(访问内存),它们是: * 指令存储区总线(两条) * 系统总线 * 私有外设总线 有两条**代码存储区总线**负责对代码存储区的访问,分别是**I-Code 总线和 D-Code**总线。前者用于取指,后者用于查表等操作,它们按最佳执行速度进行优化。 **系统总线**用于访问内存和外设,覆盖的区域包括 **SRAM**,片上外设,片外 **RAM**,片外扩展设备,以及系统级存储区的部分空间。 **私有外设总线**负责一部分私有外设的访问,主要就是访问调试组件。它们也在系统级存储区。 ## 存储器保护单元(MPU) Cortex-M3 有一个可选的存储器保护单元。配上它之后,就可以对特权级访问和用户级访问分别施加不同的访问限制。当检测到犯规(violated)时,MPU 就会产生一个 fault 异常,可以由 fault 异常的服务例程来分析该错误,并且在可能时改正它。 MPU 有很多玩法。最常见的就是由操作系统使用 MPU,以使特权级代码的数据,包括操作系统本身的数据不被其它用户程序弄坏。MPU 在保护内存时是按区(region)管理的。它可以把某些内存 region 设置成只读,从而避免了那里的内容意外 被更改;还可以在多任务系统中把不同任务之间的数据区隔离。一句话,它会使嵌入式系统变得更加健壮,更加可靠。 ## 中断和异常 ARMv7-M 开创了一个全新的异常模型,CM3 采用了它。请你一定要划清界线:这种异常模型跟传统 ARM 处理器使用的完全是两码事。新的异常模型“使能”了非常高效的异常处理。它支持 16-4-1=11 种系统异常(保留了 4+1 个档位),外加 240 个外部中断输入。在 CM3 中取消了 FIQ 的概念(v7 前的 ARM 都有这个 FIQ,快中断请求),这是因为有了更新更好的机制——中断优先级管理以及嵌套中断支持,它们被纳入 CM3 的中断管理逻辑中。因此,支持嵌套中断的系统就更容易实现 FIQ。 CM3 的所有中断机制都由 NVIC 实现。除了支持 240 条中断之外,NVIC 还支持 16-4-1=11 个内部异常源,可以实现 fault 管理机制。结果,CM3 就有了 256 个预定义的异常类型,如表所示。 | 编号 | 类型 | 优先级 | 简介 | | :--- | :---------------- | :--------- | :--------------------------------------------------- | | 0 | NA | NA | 没有异常在运行 | | 1 | 复位 | -3(最高) | 复位 | | 2 | NMI | -2 | 不可屏蔽中断(来自外部 NMI 输入脚) | | 3 | 硬(hard) fault | -1 | 所有被除能的 fault,都将“上访”成硬 fault | | 4 | MemManage fault | 可编程 | 存储器管理 fault,MPU 访问犯规以及访问非法位置 | | 5 | 总线 fault | 可编程 | 总线错误(预取流产(Abort)或数据流产) | | 6 | 用法(usage) Fault | 可编程 | 由于程序错误导致的异常 | | 7-10 | 保留 | NA | NA | | 11 | SVCall | 可编程 | 系统服务调用 | | 12 | 调试监视器 | 可编程 | 调试监视器(断点,数据观察点,或者是外部调试请求 | | 13 | 保留 | NA | NA | | 14 | PendSV | 可编程 | 为系统设备而设的“可悬挂请求”(pendable request) | | 15 | SysTick | 可编程 | 系统滴答定时器(也就是周期性溢出的时基定时器——译注) | | 16 | IRQ #0 | 可编程 | 外中断#0 | | 17 | IRQ #1 | 可编程 | 外中断#1 | | … | … | … | … | | 255 | IRQ #239 | 可编程 | 外中断#239 | 虽然 CM3 是支持 240 个外中断的,但具体使用了多少个是由芯片生产商决定。CM3 还有一个 NMI(不可屏蔽中断)输入脚。当它被置为有效(assert)时,NMI 服务例程会无条件地执行。 ## 调试支持 Cortex-M3 在内核水平上搭载了若干种调试相关的特性。最主要的就是程序执行控制,包括**停机(halting)**、**单步执行(stepping)**、**指令断点**、**数据观察点**、**寄存器**和**存储器访问**、**性能速写(profiling)** 以及各种跟踪机制。 Cortex-M3 的调试系统基于 ARM 最新的 CoreSight 架构。不同于以往的 ARM 处理器,内核本身不再含有 JTAG 接口。取而代之的,是 CPU 提供称为**调试访问接口(DAP)的总线接口。通过这个总线接口,可以访问芯片的**寄存器,也可以访问**系统存储器**,甚至是在内核运行的时候访问!对此总线接口的使用,是由一个调试端口(DP)设备完成的。 调试端口 DPs 不属于 CM3 内核,但它们是在芯片的内部实现的。目前可用的 DPs 包括 **SWJ-DP**(既支持传统的 JTAG 调试,也支持新的串行线调试协议),另一个 **SW-DP** 则去掉了对 JTAG 的支持。另外,也可以使用 ARM CoreSignt 产品家族的 **JTAG-DP** 模块。这下就有 3 个 DPs 可以选了,芯片制造商可以从中选择一个,以提供具体的调试接口(通常都是选 SWJ-DP)。 此外, CM3 还能挂载一个所谓的 **嵌入式跟踪宏单元(ETM)** 。 ETM 可以不断地发出跟踪信息,这些信息通过一个被称为 **跟踪端口接口单元(TPIU)** 的模块而送到内核的外部,再在芯片外面使用一个**跟踪信息分析仪**,就可以把 TIPU 输出的**已执行指令信息**捕捉到,并且送给调试主机——也就是 PC。所有这些调试组件都可以由 DAP 总线接口来控制, CM3 内核提供 DAP 接口。此外,运行中的程序也能控制它们。所有的跟踪信息都能通过 TPIU 来访问到。 ## 参考资料 1. [ARM documentation](https://developer.arm.com/documentation) 2. [Cortex-M3 权威指南.Joseph Yiu.著.宋岩.译](https://d1.amobbs.com/bbs_upload782111/files_11/ourdev_418859.pdf) --- url: /blog/2024/coc-clangd.md --- # Coc-Clangd 配置 使用 vim 进行 c 项目开发时,使用 Coc-Clangd 作为语言服务器实现自动补全。当对项目结构进行调整后(.h 和.c 文件分离到了不同的目录),Coc-Clangd 需要一定的配置才能找到相关的.h 文件,否则会出现错误: ``` '*.h' file not found [clang: pp_file_not_found] ``` 具体需要在项目根目录生成`compile_commands.json`文件或者`compile_flags.txt`文件: 1. 当使用 cMake 时,可以运行`cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1`生成`compile_commands.json`。 2. 当使用 make 时,需要安装[Bear](https://github.com/rizsotto/Bear)(apt 等包管理即可安装)。运行`make clean && bear make`即可生成`compile_commands.json`。 3. 对于其它情况(只使用 clang,没有构建工具和 make)时。可以创建`compile_flags.txt`文件[参考 2](https://releases.llvm.org/8.0.0/tools/clang/tools/extra/docs/clangd/Installation.html#compile-flags-txt)进行相关配置。 ## 参考 1. [ProgrammAbel (u/ProgrammAbel) - Reddit](https://www.reddit.com/r/vim/comments/hrfm1q/cocvim_cocclangd_setup_help/) 2. [Getting started with clangd --- Extra Clang Tools 8 documentation (llvm.org)](https://releases.llvm.org/8.0.0/tools/clang/tools/extra/docs/clangd/Installation.html) --- url: /blog/2024/dnspod-acme.md --- # 腾讯云自动申请域名证书 ## 安装 acme.sh ```bash curl https://get.acme.sh | sh -s email=my@example.com ``` 会将 acme.sh 下载到 `~/.acme.sh/` 中,并且创建一个定时任务: ```bash # crontab -l 16 2 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null ``` ## 申请证书 通过配置 DNS 的方式可以申请泛域名 在 dnspod 控制台: 我的账号 -> API 密钥 -> DNSPod Token -> 创建密钥 配置环境变量,配置创建的密钥 ```bash export DP_Id="**********" export DP_Key="****************" ``` 申请证书,大概 1 分钟左右,脚本会通过 DNSPod 添加 DNS TXT 记录(申请完成后会自动删除),用于验证域名 并且配置的密钥,会保存起来,用于自动更新 ```bash acme.sh --issue --dns dns_dp -d example.com -d *.example.com # 更新证书的命令 # acme.sh --renew --dns dns_dp -d example.com -d *.example.com # 查看证书的命令 # acme.sh --list ``` ## 安装证书 acme.sh 会将证书放到指定路径,并重启 nginx ```bash acme.sh --install-cert -d vhcffh.com \ --key-file /etc/pki/nginx/private/example.key \ --fullchain-file /etc/pki/nginx/example.crt \ --reloadcmd "service nginx force-reload" ``` 在 nginx 中配置 ```bash ssl_certificate "/etc/pki/nginx/example.crt"; ssl_certificate_key "/etc/pki/nginx/private/example.key"; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_ciphers PROFILE=SYSTEM; ssl_prefer_server_ciphers on; ``` --- url: /blog/2024/freertos-tasks.md --- # FreeRTOS 的任务 > 使用 RTOS 的实时应用程序可以被构建为一组独立的任务。每个任务在自己的上下文中执行,不依赖于系统内的其他任务或 RTOS 调度器本身。在任何时间点,应用程序中只能执行一个任务,实时 RTOS 调度器负责决定所要执行的任务。因此, RTOS 调度器可以在应用程序执行时重复启停每个任务(将任务调入或调出)。由于任务不了解 RTOS 调度器活动,因此实时 RTOS 调度器负责确保任务调入时的处理器上下文(寄存器值、堆栈内容等)与任务调出时的处理器上下文完全相同。为实现这一点,每个任务都分配有自己的堆栈。当任务调出时,执行上下文被保存到该任务的堆栈中,以便以后再调入相同的任务时可以准确地恢复其执行上下文。 简而言之, RTOS 任务的调入与调出就是对**处理器上下文的保存与恢复**。任务调度主要是三个关键词:**固定优先级**,**抢占式**,**时间片轮询**。 ## 任务状态 * 运行 当任务实际执行时,它被称为处于运行状态。任务当前正在使用处理器。如果运行 RTOS 的处理器只有一个内核,那么在任何给定时间内都只能有一个任务处于运行状态。 * 准备就绪 准备就绪任务指那些能够执行(它们不处于阻塞或挂起状态),但目前没有执行的任务,因为同等或更高优先级的不同任务已经处于运行状态。 * 阻塞 如果任务当前正在等待时间或外部事件,则该任务被认为处于阻塞状态。 例如,如果一个任务调用`vTaskDelay()`,它将被阻塞(被置于阻塞状态),直到延迟结束-一个时间事件。 任务也可以通过阻塞来等待**队列**、**信号量**、**事件组**、**通知**等事件。处于阻塞状态的任务通常有一个"超时"期, 超时后任务将被超时,并被解除阻塞,即使该任务所等待的事件没有发生。 阻塞状态下的任务不使用任何处理时间,不能被选择进入运行状态。 * 挂起 与"阻塞"状态下的任务一样,"挂起"状态下的任务不能被选择进入运行状态,但处于挂起状态的任务没有超时。相反,任务只有在分别通过 `vTaskSuspend()`和`xTaskResume()`API 调用明确命令时才会进入或退出挂起状态。 ## 任务优先级 优先级分配从`0`到`configMAX_PRIORITIES - 1`。 数字越小优先级越低。 多个任务可以公用同一个优先级。 ## 任务调度 ### 单核(默认调度) FreeRTOS 默认使用**固定优先级**的**抢占式**调度策略,对同等优先级的任务执行**时间片轮询**调度: * **固定优先级**:指调度器不会永久更改任务的优先级,尽管它可能会因优先级继承而暂时提高任务的优先级。 * **抢占式**:调度器始终运行优先级最高且可运行的 RTOS 任务,无论任务何时能够运行。 * **时间片轮询**:调度器会在每个 tick 中断( RTOS 用来衡量时间的周期性中断)上在同等优先级任务之间进行切换, tick 中断之间的时间构成一个时间片。 \*\*[优先级继承](https://www.cnblogs.com/mickey-double/p/14366803.html)\*\*是为了防止优先级反转现象采取的一种优化机制。 如低优先级任务获取了高优先级任务需要的资源,会提升低优先级任务的优先级。 \*\*[优先级反转](https://zhuanlan.zhihu.com/p/32175772)\*\*低优先级任务占据了高优先级任务的资源(信号量等),出现系统一直运行中等优先级任务的一种不合理现象。 * `configUSE_PREEMPTION = 0`:关闭抢占 * `configUSE_TIME_SLICING = 0`:关闭时间片切换 ### 非对称多核(AMP 调度) > 每个核心各自运行自己的 FreeRTOS,互不影响。 使用 FreeRTOS 的非对称多处理 (AMP) 是指多核设备的每个核心都单独运行自己的 FreeRTOS 实例。这些 核心并不都需要具有相同架构,但如果 FreeRTOS 实例之间需要进行通信,则需要共享一些内存。 每个核心都会运行自己的 FreeRTOS 实例, 因此任何给定核心上的调度算法与上文的单核系统调度算法完全相同 。您可以使用流缓冲区或消息缓冲区作为核间通信原语, 这样一来,一个核心上的任务可以进入“阻塞”状态, 以等待另一个核心发来的数据或事件。 ### 对称多核(SMP 调度) > 由 FreeRTOS 来调度,将不同任务分配到不同的核心。 使用 FreeRTOS 的对称多处理 (SMP) 是指 一个 FreeRTOS 实例可以跨多个处理器核心调度 RTOS 任务。由于只有一个 FreeRTOS 实例在运行,一次只能使用 FreeRTOS 的一个端口, 因此每个核心必须具有相同的处理器架构并共用相同的内存空间。 FreeRTOS SMP 调度策略使用与单核调度策略相同的算法,但与单核和 AMP 场景不同的是,SMP 在任何给定时间都会导致多个任务处于运行状态 (每个核心上都有一个运行状态的任务)。这意味着,只有缺乏可运行的高优先级任务时,才会运行低优先级任务的假设不再成立。要想了解其中的原因,请考虑一下,若起初只有一个高优先级任务和两个中等优先级任务处于”就绪“状态,SMP 调度器会如何选择在双核微控制器上运行的任务。调度器需要选择两个任务,每个核心对应一个任务。首先,高优先级任务是指可运行的最高优先级任务,因此会选择将它用于第一个核心。这样就剩下了两个中等优先级的任务作为可运行的最高优先级任务,因此会将它们用于第二个核心。结果是高优先级和中等优先级的任务同时运行。 --- url: /blog/2024/freshrss-reset-password.md --- # FreshRSS 重置密码 FreshRSS 的用户密码没有存储在数据库中 存储在配置文件 `/data/users//config.php` 中 ```php '$2a$09$pXJyqN3SxOOGw7YB.0P1M.CCDtEYPhd1by5aVBQSJw5Yvr9mYXIDa', ... ``` `passwordHash`是通过`password_hash`函数生成的,不可逆 使用`python`模块`bcrypt`生成一个新的密码,替换旧的密码 ```python import bcrypt bcrypt.hashpw(b'12345678',bcrypt.gensalt()) # b'$2b$12$g5tE6HYOqJyea.TbA9jibeem9TItCDItYjqehIefQEpqomr/X0VTq' ``` 因为 salt 不同,每次生成的 hash 值也不一样 替换后,用密码 `12345678` 即可登录,登陆后不要忘了配置新的密码 --- url: /blog/2024/git-multi-user.md --- # Git配置多个用户 只需要将下面的配置放入到`~/.gitconfig`等文件 ```python # ~/.gitconfig [user] name = korbin email = korbin.zhang@outlook.com [includeIf "gitdif:~/works/"] path = ~/.gitconfig-works [includeIf "gitdif:~/codes/"] path = ~/.gitconfig-codes ``` ```python # ~/.gitconfig-works [user] name = Works email = works@outlook.com ``` ```python # ~/.gitconfig-codes [user] name = Codes email = codes@outlook.com ``` --- url: /blog/2025/C++-lvalue-rvalue-reference.md --- 在 C++ 的世界里,“引用”是一个既基础又强大的概念。然而,随着 C++11 的到来,引用的家族迎来了新成员——右值引用,这让许多开发者感到困惑。正确理解左值引用和右值引用,以及它们在函数参数传递中的应用,是编写高效、现代 C++ 代码的关键。 本文将系统地梳理 C++ 中的引用,帮助你彻底搞懂它们之间的区别与联系。 ## 核心概念:什么是引用? 在 C++ 中,**引用(Reference)** 可以看作是一个变量的 **别名(Alias)**。它不是一个新变量,也不占用新的内存空间(在底层实现上,它通常是一个指针常量,但我们从语言层面理解它就是个别名)。一旦引用被初始化为一个变量,它就终生绑定到这个变量上,不能再改为其他变量的引用。 对引用的任何操作,都等同于对它所绑定的原始变量进行操作。 ## 左值(Lvalue)与右值(Rvalue) 在深入了解两种引用之前,我们必须先掌握左值和右值的概念。 * **左值 (Lvalue - Locator Value)**: 指的是那些在内存中有固定地址、可以被“定位”到的表达式。简单来说,**可以出现在赋值符号 `=` 左边的就是左值**。 * 例如:变量名 (`int x = 10;` 这里的 `x` 就是左值)、数组元素、函数返回的引用等。 * 特点:有持久的内存地址,不是临时的。 * **右值 (Rvalue - Read Value)**: 指的是那些只能被“读取”的值,通常是临时的、没有固定内存地址的表达式。简单来说,**只能出现在赋值符号 `=` 右边的就是右值**。 * 例如:字面量 (`10`, `'a'`)、临时对象(`x + y` 的计算结果)、函数返回的值(非引用)等。 * 特点:是临时的,表达式结束后就会被销毁。 ```cpp int x = 10; // x 是左值, 10 是右值 int y = 20; // y 是左值, 20 是右值 int z = x + y; // z 是左值, (x + y) 的计算结果是右值 ``` ## 左值引用 (Lvalue Reference, `&`) 这是我们传统意义上最常说的“引用”,使用 `&` 符号声明。 **特点与规则:** 1. **只能绑定到左值**。 2. 它的主要作用是作为别名,避免大对象的拷贝。 **示例:** ```cpp int a = 100; int& ref_a = a; // 正确:左值引用 ref_a 绑定到左值 a ref_a = 200; // 修改 ref_a 就是修改 a,此刻 a 的值也变成了 200 int& ref_b = 10; // 编译错误!不能将左值引用绑定到右值 (字面量 10) ``` ### 特例:`const` 左值引用 一个 `const` 的左值引用**既可以绑定到左值,也可以绑定到右值**。当它绑定到右值时,C++ 会创建一个临时的、无名的左值对象,并将该引用绑定到这个临时对象上。 ```cpp const int& ref_c = a; // 正确:const 左值引用绑定到左值 a const int& ref_d = 10; // 正确:const 左值引用绑定到右值 10 const int& ref_e = a + 5;// 正确:const 左值引用绑定到右值 (a+5 的结果) ``` 这个特性使得 `const T&` 成为函数参数传递中非常重要的类型,因为它既能接收左值也能接收右值,同时保证了安全性。 ## 右值引用 (Rvalue Reference, `&&`) 这是 C++11 引入的新特性,使用 `&&` 符号声明。它的出现主要是为了解决两个问题:**移动语义 (Move Semantics)** 和 **完美转发 (Perfect Forwarding)**。 **特点与规则:** 1. **只能绑定到右值**。 2. 它的核心作用是“窃取”或“移动”右值(临时对象)的资源,从而避免不必要的深拷贝,大幅提升性能。 **示例:** ```cpp int&& rref_a = 10; // 正确:右值引用 rref_a 绑定到右值 10 int&& rref_b = a + 5; // 正确:右值引用 rref_b 绑定到右值 (a+5 的结果) int a = 100; int&& rref_c = a; // 错误!不能将右值引用绑定到左值 a ``` ### `std::move` 的作用 `std::move` 是一个非常有用的函数,它可以**无条件地将一个左值强制转换为右值引用**。这并不意味着它“移动”了任何东西,它只是做了一个类型转换,告诉编译器:“你可以把这个对象当成一个临时对象来处理了”。 ```cpp #include // 需要包含 头文件 int a = 100; int&& rref_d = std::move(a); // 正确:通过 std::move 将左值 a 转换为右值引用 rref_d = 300; // 修改 rref_d 仍然是修改 a,因为它们指向同一块内存 // 此刻 a 的值也变成了 300 ``` **注意**:被 `std::move` 之后,原来的变量 `a` 就进入了一个“未指定但有效”的状态。你不应该再对它的值做任何假设,它的资源可能已经被“窃取”了。 ## 作为函数参数 这是理解引用最有价值的场景之一。 ### 值传递 (Pass by Value) * **工作方式**: 函数会创建参数的一个**完整副本**。函数内部对参数的任何修改都**不会**影响到函数外部的原始变量。 * **优点**: 安全,函数不会意外修改外部状态。 * **缺点**: 对于大对象(如 `std::vector`),创建副本的开销非常大,严重影响性能。 ```cpp void process_by_value(int val) { val = 99; // 只修改了副本 val,不影响外部 } ``` ### 左值引用传递 (Pass by Lvalue Reference) * **工作方式**: 函数接收的是原始变量的**别名**,没有创建任何副本。函数内部对参数的修改**会直接**影响到外部的原始变量。 * **优点**: 效率高,避免了拷贝开销;能修改外部变量。 * **缺点**: 可能会意外修改原始数据。如果不想修改,应该使用 `const`。 ```cpp // 允许修改 void process_by_ref(int& ref) { ref = 99; // 直接修改了外部变量 } // 推荐:不允许修改,但同样高效 void process_by_const_ref(const std::string& s) { // s = "new value"; // 编译错误! std::cout << s << std::endl; // 只是读取,非常高效 } ``` **最佳实践**: 当你传递一个大对象且不希望在函数内修改它时,**总是使用 `const T&`**。 ### 右值引用传递 (Pass by Rvalue Reference) * **工作方式**: 这个函数重载**专门接收右值(临时对象)**。它通常用于实现**移动构造函数**和**移动赋值运算符**。 * **目的**: 当我们知道传入的是一个临时对象时,我们不需要拷贝它的数据,只需要“窃取”它的内部资源(如指向堆内存的指针),然后将临时对象置为空壳。这就是**移动语义**。 **示例:一个简单的字符串类** ```cpp #include #include #include class MyString { private: char* data; size_t len; public: // 构造函数:从 C 风格字符串 MyString(const char* p = nullptr) { if (p) { len = strlen(p); data = new char[len + 1]; strcpy(data, p); std::cout << "Constructor called for: " << data << std::endl; } else { len = 0; data = new char[1]; *data = '\0'; std::cout << "Default Constructor called" << std::endl; } } // 析构函数 ~MyString() { if (data) { std::cout << "Destructor called for: " << (len > 0 ? data : "empty/moved-from string") << std::endl; } delete[] data; } // 拷贝构造函数 (深拷贝) MyString(const MyString& other) { std::cout << "Copy Constructor called (deep copy from: " << other.data << ")\n"; len = other.len; data = new char[len + 1]; strcpy(data, other.data); } // 移动构造函数 (资源窃取) MyString(MyString&& other) noexcept { std::cout << "Move Constructor called (stealing from: " << (other.data ? other.data : "empty") << ")\n"; // 窃取资源 data = other.data; len = other.len; // 将临时对象置为空壳 other.data = nullptr; other.len = 0; } // 拷贝赋值运算符 MyString& operator=(const MyString& other) { std::cout << "Copy Assignment Operator called\n"; if (this != &other) { delete[] data; len = other.len; data = new char[len + 1]; strcpy(data, other.data); } return *this; } // 移动赋值运算符 MyString& operator=(MyString&& other) noexcept { std::cout << "Move Assignment Operator called\n"; if (this != &other) { delete[] data; data = other.data; len = other.len; other.data = nullptr; other.len = 0; } return *this; } void print(const std::string& name) const { std::cout << name << " content: \"" << (data ? data : "null") << "\"\n"; } }; MyString create_string() { return MyString("temporary"); // 返回一个临时对象 (右值) } int main() { std::cout << "--- 1. Initial construction ---\n"; MyString s1("hello"); // 调用构造函数 // 预期输出: Constructor called for: hello std::cout << "\n--- 2. Copy construction ---\n"; MyString s2 = s1; // s1 是左值,调用拷贝构造函数 // 预期输出: Copy Constructor called (deep copy from: hello) std::cout << "\n--- 3. Move construction (usually optimized away by RVO) ---\n"; MyString s3 = create_string(); // create_string() 返回右值,理论上调用移动构造,但常被 RVO 优化 // 预期输出: Constructor called for: temporary (RVO 生效) std::cout << "\n--- 4. Force move construction ---\n"; MyString s4 = std::move(s1); // std::move(s1) 将 s1 转换为右值,明确调用移动构造函数 // 预期输出: Move Constructor called (stealing from: hello) std::cout << "\n--- After move ---\n"; s1.print("s1"); // s1 的资源已被移走 // 预期输出: s1 content: "null" s4.print("s4"); // 预期输出: s4 content: "hello" std::cout << "\n--- End of main (Destructors will be called) ---\n"; // 析构函数按栈顺序逆序调用:s4, s3, s2, s1 // 预期输出: // Destructor called for: hello // Destructor called for: temporary // Destructor called for: hello // Destructor called for: empty/moved-from string return 0; } ``` 在上面的例子中,`s4` 的创建就得益于移动语义,它没有进行昂贵的内存分配和拷贝,只是交换了指针,效率极高。 > **关于返回值优化 (RVO)** > > 你可能会注意到,在编译运行时,第 3 步(`MyString s3 = create_string();`)并没有像预期那样打印出 "Move Constructor called"。这是因为现代 C++ 编译器会进行一种非常普遍的优化,叫做 **返回值优化 (Return Value Optimization, RVO)**。 > > 编译器会识别出 `create_string` 函数返回的是一个临时对象,并且这个对象会立即用于构造 `s3`。因此,编译器会“跳过”创建临时对象的步骤,直接在 `s3` 的内存空间上构造这个对象,从而避免了一次不必要的移动(或拷贝)操作。这是 C++ 标准允许甚至鼓励的行为,可以极大地提升性能。 > > 所以,即使你看不到移动构造函数的调用,也要理解这背后是移动语义和 RVO 共同作用的结果。第 4 步中使用 `std::move` 的例子则能更明确地展示移动构造函数的调用。 ## 总结 | 特性 | 左值引用 (`&`) | 右值引用 (`&&`) | | :--------------- | :----------------------------------------- | :--------------------------------- | | **声明符号** | `&` | `&&` | | **绑定对象** | 只能绑定到左值 | 只能绑定到右值 | | **`const` 特例** | `const T&` 可以绑定到右值 | - | | **主要用途** | 1. 作为变量别名
    2. 作为函数参数避免拷贝 | 1. 实现移动语义
    2. 实现完美转发 | 掌握左值和右值引用的区别是迈向现代 C++ 开发的重要一步。它不仅能让你写出性能更优的代码,也是理解标准库中许多高级功能(如智能指针、`std::vector` 的 `emplace_back`)的基础。 --- url: /blog/2025/c-variable-args.md --- `va_list`是 C 语言专门用来解决可变参数问题的工具,于 C99 标准引入, `va_list` 类型允许函数接受可变数量的参数,这在编写需要处理不定数量参数的函数时非常有用。`va_list` 类型是在 `stdarg.h` 头文件中定义的,它允许函数处理可变数量的参数。下面我们将详细介绍 `va_list` 的用法以及实际应用示例。 ## va\_list的用法 `va_list` 是一个指向参数列表的指针,它允许函数处理不定数量的参数。`va_list` 类型通常与 `va_start`、`va_arg` 和 `va_end`一起使用。 * `va_list`:初始化 `va_list`类型的变量,使其指向参数列表的起始位置。 * `va_arg`:获取参数列表中的下一个参数,并将指针移动到下一个参数。 * `va_end`:清理 `va_list` 类型的变量。 ## 应用举例 ### 计算总和 ```cpp #include #include int sum(int count, ...) { int sum = 0; va_list ap; va_start(ap, count); while (count--) sum += va_arg(ap, int); va_end(ap); return sum; } int main() { printf("sum(5,1,2,3,4,5):%d\n", sum(5, 1, 2, 3, 4, 5)); printf("sum(3,10,20,30):%d\n", sum(3, 10, 20, 30)); } ``` 在上面的例子中, `sum` 函数通过固定参数 `count` 指定后续可变参数的数量,使用 `va_start` 初始化参数列表,在循环中用 `va_arg` 依次按 `int` 类型取出每个参数并累加计算结果。类似可以用于计算最大值、最小值、平均数等。编译输出结果如下: ```bash ❯ gcc main.c && ./a.out sum(5,1,2,3,4,5):15 sum(3,10,20,30):60 ``` ### 日志格式化 ```cpp void log_debug(char *format, ...) { char buf[100]; va_list ap; va_start(ap, format); vsprintf(buf, format, ap); va_end(ap); extern void uart_write(char *buf, int len); uart_write(buf, strlen(buf)); } int main() { int number = 10; log_debug("%s %c %d %02X\n", "test", 'C', number, number); } ``` 上面的例子,配合 `vsprintf`,在单片机开发中,可以方便的进行日志输出打印。 ## 参考 1. [标准库头文件 stdarg.h - cppreference.cn - C++参考手册](https://cppreference.cn/w/c/header/stdarg) 2. [有关 va\_list 的一些笔记 | bachzart's Blog](https://bachzart.github.io/2025/08/26/%E6%9C%89%E5%85%B3-va-list-%E7%9A%84%E4%B8%80%E4%BA%9B%E7%AC%94%E8%AE%B0/) --- url: /blog/2025/can-base.md --- # CAN 基础 ## 拓扑结构 ![CAN 拓扑结构](./imgs/can-topology.png) CAN 总线有两个 ISO 国际标准:ISO11898 和 ISO11519。 * ISO11898 定义了通信速率为 125 Kbps ~ 1 Mbps 的高速 CAN 通信标准,属于闭环总线,总线长度 ≤ 40 米。 * ISO11519 定义了通信速率为 10 ~ 125 Kbps 的低速 CAN 通信标准,属于开环总线,总线长度可达 1000 米。 * ISO16845 定义了认证需要的测试用例 * 在同一条总线上,所有节点的通信速度必须相同;如果两条不同通信速度的总线上的节点想要实现信息交互,必须通过网关或者中继器转发信息。 ## 信号表示 ![CAN 信号表示](./imgs/can-signal.png) ## 通信特点 ### 多主多从结构 * CAN 总线上的所有节点没有主从之分,在总线空闲状态,任意节点都可以向总线上发送消息 * 当总线上出现连续的 11 位隐形电平,那么总线就处于空闲状态 * 最先向总线发送消息的节点获得总线的发送权,当多个节点同时向总线发送消息时,所发送消息的优先级高的那个节点获得总线的发送权 * 依赖于**硬件**的验收滤波技术,CAN 总线可以实现一对一,一对多以及广播的数据传输方式。 ### 非破坏性位仲裁机制 当多个节点同时向总线发送消息时,对各个消息的标识符(即 ID 号)进行逐位仲裁,如果某个节点发送的消息仲裁获胜,那么这个节点将获取总线的发送权,仲裁失败的节点则立即停止发送并转变为监听(接收)状态。 这种仲裁机制既不会造成已发送数据的延迟,也不会破坏已经发送的数据。 ### 报文过滤 CAN 总线中没有地址的概念,CAN 总线是通过报文 ID 来实现收发数据的。每个节点上都会有一个验收滤波 ID 表,其位于 CAN 节点的验收滤波器中,如果总线上的报文的 ID 号在某个节点的验收滤波 ID 表中,那么这一帧报文就能通过该节点验收滤波器的验收,该节点就会接收这一帧报文。 ### 远程数据请求 某个节点 Node\_A 可以通过发送`遥控帧`到总线上的方式,请求某个节点 Node\_B 来发送由该遥控帧所指定的报文。 ### 出错处理 * 所有的节点都可以检测出错误 * 检测出错误的节点会立即通知总线上其它所有的节点 * 正在发送消息的节点,如果检测到错误,会立即停止当前的发送,同时不断地重复发送此消息,直到该消息发送成功为止 ### 故障封闭 节点能够判断错误的类型,判断是暂时性的数据错误(如噪声干扰)还是持续性的数据错误(如节点内部故障),如果判断是严重的持续性错误,那么节点就会切断自己与总线的联系,从而避免影响总线上其他节点的正常工作。 ### 位填充 ![CAN 位填充原则](./imgs/can-bit-fill.png) CAN 协议中规定,当相同极性的电平持续五位时,则添加一个极性相反的位。 ## 网络分层架构 ![CAN 网络层次结构](./imgs/can-net-stack.jpeg) ## 帧结构 ### 数据帧和遥控帧 ![CAN 数据帧](./imgs/can-data-frame.png) ![CAN 遥控帧](./imgs/can-remote-frame.png) * RTR(Remote Transmission Request) 位保证了数据帧的优先级高于遥控帧 * SRR(Substitutes Remote Requests) 位保证了标准数据帧的优先级高于扩展数据帧 * IDE(Identifier Extension) 位保证了标准遥控帧的优先级高于扩展遥控帧 * DLC(Data Length Code) 位指示了数据段中的字节数,对于遥控帧而言,DLC 表示该遥控帧对应的数据帧的数据段的字节数 * 数据段从 MSB 开始输出 * CRC 校验序列(15bit)的计算范围包括:SOF,仲裁段,控制段和数据段 * ACK 包括 ACK 槽和 ACK 分界符: * 发送节点发出的报文中 ACK 槽为`隐性1` * 接收节点在接收到正确的报文之后会在 ACK 槽发送`显性0`,通知发送节点正常接收结束 * EOF(End Of Frame) 表示该帧报文的结束,由 7 个隐性位构成 ### 错误帧 在 CAN 总线通信中,一共有五种错误,分别是:位错误、ACK 错误、填充错误、CRC 错误、格式错误。 ![CAN 错误帧](./imgs/can-error-frame.png) * 主动错误标志:6 个连续的显性位 0 * 被动错误标志:6 个连续的隐性位 1 * 错误分界符:8 个连续的隐性位 1 ### 过载帧 ![CAN 过载帧](./imgs/can-overload-frame.png) * 接受单元会发从此帧来通知总线自己还没有做好接收准备 ### 帧间隔 ![CAN 帧间隔](./imgs/can-inter-frame.png) * 数据帧和遥控帧可通过插入帧间隔将本帧与前面的任何帧(数据帧、遥控帧、错误帧、过载帧)分开,过载帧和错误帧前不能插入帧间隔 ## 错误通知 ### 节点错误状态 按照 CAN 协议的规定,CAN 总线上的节点始终处于以下三种状态之一: * 主动错误状态 * 可以正常通信 * 在检测出错误时,发出主动错误标志 * 被动错误状态 * 可以正常通信 * 在检测出错误时,发出被动错误标志 * 总线关闭状态 * 节点不能收发报文 * 在满足一定条件的时候,再次进入到主动错误状态 ### 错误状态的转换 在 CAN 节点内,有两个计数器:发送错误计数器(TEC)和接收错误计数器(REC)。TEC 和 REC 计数值的变化,是根据下表的规定来进行的 ![TEC/REC](./imgs/can-tec-rec.png) CAN 节点错误状态的转换,就是基于这两个计数器来进行的 ![错误状态转换](./imgs/can-error-state.png) ### 错误帧的发送 ![错误帧的发送](./imgs/can-error-recovery.png) 1. 发送节点 Node\_A 发送一个显性位,但是却从总线上听到一个隐形位,于是 Node\_A 节点就会检测到一个位错误 2. Node\_A 检测到位错误之后,立即在下一位开始发送主动错误帧:6 个连续显性位的主动错误标志+8 个连续隐性位的错误界定符 3. 对应 Node\_A 发出的主动错误标志,总线上电平为 6 个连续显性位 4. 接收节点 Node\_B 和 Node\_C 从总线上听到连续 6 个显性位,那么就会检测到一个**填充错误**,于是这两个节点都会发送主动错误帧 5. 对应 Node\_B 和 Node\_C 发出的主动错误标志,总线电平又有 6 个连续显性电平,对应 Node\_B 和 Node\_C 发出的错误界定符,总线电平有 8 个连续的隐性电平 6. 在间歇域之后,Node\_A 节点重新发送刚刚出错的报文 ## Socket CAN ### 命令行工具 #### ip 命令 ```bash ip link set can0 type can help ``` ##### 设置 CAN 设备的波特率 ```bash ip link set can0 type can bitrate 500000 ip link set can0 type can bitrate 500000 dbitrate 2000000 fd on ip link set can0 type can bitrate 500000 sample-point 0.875 ``` ##### 启动/关闭 CAN 设备 ```bash ip link set can0 up ip link set can0 down ``` ##### 设置 CAN 设备的模式 ```bash ip link set can0 type can loopback on ip link set can0 type can listen-only on ``` ##### 查看详细的配置信息 ```bash ip -details link show can0 ``` #### can-utils 程序 ##### candump ```bash candump can0,0x123:0x7FF # 仅显示can0上收到的ID为0x123的消息 ``` ##### cansend ```bash cansend can0 123#1122334455667788 # 发送一个ID为0x123的报文 ``` ##### cangen ```bash cangen can0 -g 0x123 -I 1000 -L 8 -D 0x1122334455667788 # 每1000ms发送一个ID为0x123的报文 ``` ##### cansniffer ```bash cansniffer can0 # 抓取can0上的所有报文,可以过滤掉数据不变的帧 ``` ## python-can ### 安装 ```bash pip install python-can ``` ### 使用 ```python # import the library import can # create a bus instance # many other interfaces are supported as well (see documentation) bus = can.Bus(interface='socketcan', channel='vcan0', receive_own_messages=True) # send a message message = can.Message(arbitration_id=123, is_extended_id=True, data=[0x11, 0x22, 0x33]) bus.send(message, timeout=0.2) # iterate over received messages for msg in bus: print(f"{msg.arbitration_id:X}: {msg.data}") # or use an asynchronous notifier notifier = can.Notifier(bus, [can.Logger("recorded.log"), can.Printer()]) ``` --- url: /blog/2025/cpp-bit-manipulation-comprehensive-guide.md --- 位操作是程序员的“超能力”之一。它允许我们深入到数据的最底层,直接操控二进制位,从而实现高效的算法、紧凑的数据存储和底层的硬件交互。C++ 语言提供了一套丰富且不断进化的工具集来支持位操作,从经典的位运算符到 C++20 引入的高性能函数,应有尽有。 本文将系统性地介绍 C++ 中所有与位操作相关的函数和工具,分为四个类别,并提供清晰的示例,帮助你彻底掌握它们。 ## 基础核心:位运算符 这是所有位操作的基石。它们是 C++ 内置的运算符,直接对整数类型进行操作。 | 运算符 | 名称 | 描述 | | :----- | :------------- | :----------------------------------------------------------------------------------------- | | `&` | 按位与 (AND) | 两个操作数对应位都为 1 时,结果位才为 1。 | | `\|` | 按位或 (OR) | 两个操作数对应位只要有 1 个为 1,结果位就为 1。 | | `^` | 按位异或 (XOR) | 两个操作数对应位不同时,结果位为 1,相同时为 0。 | | `~` | 按位取反 (NOT) | 翻转操作数的每一位(0 变 1,1 变 0)。 | | `<<` | 左移 | 将操作数的所有位向左移动指定的位数,右侧补 0。 | | `>>` | 右移 | 将操作数的所有位向右移动指定的位数,左侧补位(对于无符号数补 0,对于有符号数取决于实现)。 | **示例** ```cpp #include #include // 用于二进制可视化 #include int main() { unsigned char a = 0b01010101; // 85 unsigned char b = 0b11110000; // 240 std::cout << "a = " << std::bitset<8>(a) << std::endl; // 输出: a = 01010101 std::cout << "b = " << std::bitset<8>(b) << std::endl; // 输出: b = 11110000 std::cout << "-------------------------" << std::endl; std::cout << "a & b = " << std::bitset<8>(a & b) << std::endl; // 输出: a & b = 01010000 std::cout << "a | b = " << std::bitset<8>(a | b) << std::endl; // 输出: a | b = 11110101 std::cout << "a ^ b = " << std::bitset<8>(a ^ b) << std::endl; // 输出: a ^ b = 10100101 std::cout << "~a = " << std::bitset<8>(~a) << std::endl; // 输出: ~a = 10101010 std::cout << "a << 2= " << std::bitset<8>(a << 2) << std::endl; // 输出: a << 2= 01010100 std::cout << "b >> 2= " << std::bitset<8>(b >> 2) << std::endl; // 输出: b >> 2= 00111100 } ``` ## 函数式编程的优雅:`` (C++14) 为了让位运算能与 STL 算法(如 `std::accumulate`)无缝协作,C++14 在 `` 头文件中引入了一组函数对象。 * `std::bit_and`: 执行按位与 (`&`)。 * `std::bit_or`: 执行按位或 (`|`)。 * `std::bit_xor`: 执行按位异或 (`^`)。 * `std::bit_not`: 执行按位取反 (`~`)。 它们的主要优势在于作为“可调用对象”传递,使代码更具表现力。 **示例:计算向量中所有元素的异或和** ```cpp #include #include #include // For std::accumulate #include // For std::bit_xor int main() { std::vector nums = {1, 2, 3, 4, 5}; // 01, 10, 11, 100, 101 // 计算 ((((0^1)^2)^3)^4)^5 int xor_sum = std::accumulate( nums.begin(), nums.end(), 0, // 初始值 std::bit_xor() // 操作函数 ); std::cout << "XOR sum of the vector is: " << xor_sum << std::endl; // 输出: XOR sum of the vector is: 1 } ``` ## 现代 C++的高性能工具箱:`` (C++20) C++20 带来了革命性的 `` 头文件,提供了一组高效的底层位操作函数,它们通常能直接映射到现代 CPU 的硬件指令,性能极高。 * `std::popcount(n)`: 计算 `n` 中值为 1 的位的数量("population count")。 * `std::has_single_bit(n)`: 检查 `n` 是否是 2 的幂(即是否只有一个位是 1)。 * `std::countl_zero(n)` / `std::countr_zero(n)`: 分别计算从左(高位)或从右(低位)开始的连续 0 的数量。 * `std::bit_ceil(n)` / `std::bit_floor(n)`: 分别计算大于等于 `n` の最小的 2 的幂和小于等于 `n` 的最大的 2 的幂。 * `std::rotl(n, s)` / `std::rotr(n, s)`: 将 `n` 循环左移或右移 `s` 位。 **示例** ```cpp #include #include // 需要 C++20 #include int main() { unsigned int num = 42; // 二进制: ...00101010 std::cout << "Number: " << num << std::endl; // 输出: Number: 42 std::cout << "Popcount (number of set bits): " << std::popcount(num) << std::endl; // 输出: Popcount (number of set bits): 3 std::cout << "Is power of two? " << std::boolalpha << std::has_single_bit(num) << std::endl; // 输出: Is power of two? false std::cout << "Ceiling to power of two: " << std::bit_ceil(num) << std::endl; // 输出: Ceiling to power of two: 64 std::cout << "Floor to power of two: " << std::bit_floor(num) << std::endl; // 输出: Floor to power of two: 32 // 对于一个 8 位数 unsigned char byte = 0b00110100; // 52 std::cout << "\nByte: " << std::bitset<8>(byte) << std::endl; // 输出: Byte: 00110100 std::cout << "Count leading zeros: " << std::countl_zero(byte) << std::endl; // 输出: Count leading zeros: 2 std::cout << "Count trailing zeros: " << std::countr_zero(byte) << std::endl; // 输出: Count trailing zeros: 2 std::cout << "Rotate left by 2: " << std::bitset<8>(std::rotl(byte, 2)) << std::endl; // 输出: Rotate left by 2: 11010000 } ``` ## 面向对象的位容器:`` `std::bitset` 是一个类模板,用于管理一个固定大小的位序列。它像一个 `bool` 数组,但为位操作进行了空间和性能优化,并提供了丰富的成员函数。 * **构造**: 可以从整数、字符串等创建。 * **访问与修改**: `[]`, `set()`, `reset()`, `flip()`。 * **查询**: `count()`, `size()`, `any()`, `none()`, `all()`。 * **转换**: `to_string()`, `to_ulong()`, `to_ullong()`。 **示例** ```cpp #include #include #include int main() { // 创建一个 8 位的 bitset std::bitset<8> flags("01010011"); std::cout << "Initial flags: " << flags << std::endl; // 输出: Initial flags: 01010011 std::cout << "Number of active flags: " << flags.count() << std::endl; // 输出: Number of active flags: 4 // 检查特定标志位 if (flags[2]) { // 第 2 位 (从右到左) std::cout << "Flag 2 is active." << std::endl; // 输出: Flag 2 is active. } // 修改标志位 flags.flip(7); // 翻转最高位 flags.set(0); // 激活最低位 std::cout << "Modified flags: " << flags << std::endl; // 输出: Modified flags: 11010011 // 转换为整数 std::cout << "Integer value: " << flags.to_ulong() << std::endl; // 输出: Integer value: 211 } ``` ## 总结 | 功能 (Feature) | 来源 (Source) | 核心用途 | C++ 版本 | | :----------------- | :------------- | :----------------------------------- | :------- | | **位运算符** | C++ 语言内置 | 基础、直接的位运算 | C++98 | | **位运算函数对象** | `` | 与 STL 算法结合,实现函数式风格 | C++14 | | **高性能位函数** | `` | 高性能、访问底层 CPU 指令 | C++20 | | **位序列容器** | `` | 管理固定大小的位集合,如标志位、掩码 | C++98 | **如何选择?** * **日常简单操作**: 直接使用**位运算符** (`&`, `|`, `^` 等)。 * **需要与 STL 算法集成**: 使用 `` 中的**函数对象**。 * **追求极致性能或需要高级位操作** (如 `popcount`, 循环移位): 优先使用 C++20 的 `` 库。 * **需要管理一组状态标志或构建位掩码**: `std::bitset` 是最清晰、最安全的选择。 通过掌握这些工具,你可以在 C++ 中编写出更高效、更优雅、更底层的代码。希望这篇指南能成为你探索位操作世界的得力助手! --- url: /blog/2025/cpp-numeric.md --- C++ 标准库的 `` 头文件是进行数值计算的宝库。它不仅仅包含几个简单的函数,而是提供了一整套强大、高效且灵活的算法,用于处理序列数据。这些算法能够帮助我们写出更简洁、更具表现力且性能更高的代码,尤其是在现代 C++ (C++17 及以后) 中,它们中的许多都支持并行执行。 本文将全面介绍 `` 头文件中的核心算法,分为三大类: 1. **累积与归约 (Reduction)**:将一个序列合并成单个值。 2. **扫描与前缀和 (Scan)**:计算序列的中间累积结果。 3. **数值生成与数学工具**:用于初始化序列和基础数学计算。 ## 累积与归约 (Reduction) 这类算法的目标是将一个序列中的所有元素通过某个二元操作(如加法、乘法)合并成一个单一的值。 ### `std::accumulate` (C++98) 这是最经典、最基础的累积算法。它严格按照从左到右的顺序进行计算。 * **功能**:计算一个序列 `[first, last)` 中所有元素的总和,从一个初始值 `init` 开始。 * **特点**:保证严格的顺序执行,可以通过自定义操作实现求积、字符串拼接等。 ```cpp #include #include #include #include std::vector nums = {1, 2, 3, 4, 5}; // 计算总和 int sum = std::accumulate(nums.begin(), nums.end(), 0); // 15 // 计算乘积 int product = std::accumulate(nums.begin(), nums.end(), 1, std::multiplies()); // 120 // 拼接字符串 std::vector words = {"Hello", " ", "World"}; std::string sentence = std::accumulate(words.begin(), words.end(), std::string("")); // "Hello World" ``` ### `std::reduce` (C++17) `std::reduce` 是 `std::accumulate` 的现代化、可并行化版本。 * **功能**:与 `accumulate` 类似,但**不保证计算顺序**。 * **核心区别**: 1. **并行性**:允许乱序执行,因此可以利用 `std::execution::par` 等执行策略进行并行计算,从而大幅提升性能。 2. **操作要求**:由于乱序执行,提供的二元操作**必须满足结合律和交换律**(如加法、乘法),否则结果未定义。 3. **默认初始值**:有一个不需要 `init` 参数的版本,它会使用元素类型的默认构造函数(例如 `int{}` 即 `0`)作为初始值。 ```cpp #include #include #include std::vector big_vec(1'000'000, 1); // 使用并行策略计算总和 long parallel_sum = std::reduce(std::execution::par, big_vec.begin(), big_vec.end(), 0L); ``` > **何时选择?** > > * 需要保证顺序(如减法),或在 C++17 之前的代码中,使用 `std::accumulate`。 > * 追求性能,且操作允许乱序(如加法),现代 C++ 代码首选 `std::reduce`。 ### `std::inner_product` (C++98) * **功能**:计算两个序列的内积(点积)。它将两个序列的对应元素相乘,然后将所有乘积累加起来。 * **灵活性**:可以自定义累积操作和元素配对操作。 ```cpp std::vector v1 = {1, 2, 3}; std::vector v2 = {4, 5, 6}; // 计算内积: (1*4 + 2*5 + 3*6) int dot_product = std::inner_product(v1.begin(), v1.end(), v2.begin(), 0); // 32 ``` ### `std::transform_reduce` (C++17) 这是最强大的归约算法,完美体现了 "map-reduce" 思想。 * **功能**: 1. **单序列**:先对每个元素应用一个转换操作(map),然后再对结果进行归约(reduce)。 2. **双序列**:先对两个序列的对应元素应用一个二元操作(transform),然后再对结果进行归约(reduce)。 * **特点**:可并行化,且将转换和归约合二为一,避免了创建中间容器。 ```cpp std::vector nums = {1, 2, 3, 4}; // 计算平方和 (1*1 + 2*2 + 3*3 + 4*4) int sum_of_squares = std::transform_reduce( nums.begin(), nums.end(), 0, // 初始值 std::plus<>(), // Reduce 操作: 相加 [](int x) { return x * x; } // Transform 操作: 平方 ); // 30 ``` ## 扫描与前缀和 (Scan) 扫描算法不会将序列归约为单个值,而是生成一个包含所有中间累积结果的新序列。 ### `std::partial_sum` (C++98) * **功能**:计算序列的“部分和”,即 `inclusive_scan` 的早期版本。结果中的第 `i` 个元素是原序列前 `i+1` 个元素的和。 ```cpp std::vector data = {1, 2, 3, 4, 5}; std::vector result(5); std::partial_sum(data.begin(), data.end(), result.begin()); // result: {1, 3, 6, 10, 15} ``` ### `std::inclusive_scan` & `std::exclusive_scan` (C++17) 这两个是现代化的、可并行的扫描算法。 * **`inclusive_scan`**:包含当前元素的扫描,行为与 `partial_sum` 相同。 * **`exclusive_scan`**:不包含当前元素的扫描,结果中的第 `i` 个元素是原序列前 `i` 个元素的和。 ```cpp std::vector data = {1, 2, 3, 4, 5}; std::vector incl_res(5), excl_res(5); // 包含当前项的前缀和 std::inclusive_scan(data.begin(), data.end(), incl_res.begin()); // incl_res: {1, 3, 6, 10, 15} // 不包含当前项的前缀和 std::exclusive_scan(data.begin(), data.end(), excl_res.begin(), 0); // excl_res: {0, 1, 3, 6, 10} ``` ### `std::adjacent_difference` (C++98) * **功能**:可以看作是 `partial_sum` 的逆运算,计算序列中每两个相邻元素之间的差。 ```cpp std::vector data = {1, 3, 6, 10, 15}; std::vector result(5); std::adjacent_difference(data.begin(), data.end(), result.begin()); // result: {1, 2, 3, 4, 5} (恢复了原始序列) ``` ## 数值生成与数学工具 ### `std::iota` (C++11) * **功能**:用一个从指定值开始连续递增的序列来填充一个范围。非常适合快速初始化。 ```cpp std::vector nums(5); std::iota(nums.begin(), nums.end(), 10); // 填充 10, 11, 12, 13, 14 ``` ### `std::gcd` & `std::lcm` (C++17) * **功能**:计算两个整数的最大公约数(Greatest Common Divisor)和最小公倍数(Least Common Multiple)。 ```cpp int a = 12, b = 18; int common_divisor = std::gcd(a, b); // 6 int common_multiple = std::lcm(a, b); // 36 ``` ## 总结 `` 头文件为 C++ 开发者提供了一套声明式、高效且功能强大的工具。通过使用这些算法,我们可以: * **提高代码可读性**:算法的名称(如 `accumulate`, `transform_reduce`)清晰地表达了代码的意图。 * **提升代码性能**:C++17 引入的并行版本算法可以充分利用现代多核 CPU 的计算能力。 * **减少错误**:标准库算法经过了充分的测试和优化,比手写的循环更可靠。 下次当你需要对一个序列进行数值计算时,不妨先查阅一下 `` 头文件,很可能已经有一个完美的算法在等着你了。 ## 参考 1. https://cppreference.cn/w/cpp/numeric 2. https://cplusplus.com/reference/numeric/ --- url: /blog/2025/cpp-stl-containers-overview.md --- C++ 标准模板库(STL)是 C++ 语言的基石之一,它提供了一套性能卓越、经过严格测试的通用算法和数据结构。其中,容器是 STL 最核心的部分,它们是用于管理和存储对象集合的类模板。正确地选择容器,不仅能让代码更简洁、更安全,还能显著提升程序的性能。 本文将全面介绍 C++ 中常见的各类容器,分析它们的内部实现、优缺点以及最佳适用场景,帮助你今后在项目开发中做出明智的选择。 C++ 容器主要可以分为三类:**顺序容器**、**关联容器**和**无序关联容器**。 ## 顺序容器 (Sequence Containers) 顺序容器中的元素按照严格的线性顺序排列。你可以精确地控制每个元素的位置。 ### `std::vector` * **特点**:动态数组。所有元素在内存中**连续存储**。 * **优点**: * **访问最快**:通过索引 `[]` 或 `.at()` 进行随机访问的时间复杂度为 O(1),并且由于内存连续,能很好地利用 CPU 缓存。 * **尾部操作高效**:在尾部添加/删除元素(`.push_back()`, `.pop_back()`)的平均时间复杂度为 O(1)。 * **缺点**: * **中间插入/删除慢**:在容器开头或中间插入/删除元素的时间复杂度为 O(n),因为它需要移动之后的所有元素。 * **可能触发重分配**:当 `vector` 容量不足时,它会重新分配一块更大的内存,并将所有旧元素拷贝过去,这可能是一个耗时的操作。 * **适用场景**:需要快速随机访问,且主要在尾部进行添加/删除操作的场景。**这是最常用、最应该被首选的容器。** ### `std::deque` (Double-Ended Queue) * **特点**:双端队列。其内部实现并非一整块连续内存,而是由多个连续的内存块(chunks)链接而成。 * **优点**: * **两端操作高效**:支持在头部和尾部快速添加/删除元素,时间复杂度均为 O(1)。 * **支持随机访问**:虽然其随机访问性能略逊于 `vector`(需要两次指针解引用),但时间复杂度仍为 O(1)。 * **缺点**: * **中间插入/删除慢**:与 `vector` 类似,中间操作的时间复杂度为 O(n)。 * **内存不连续**:缓存命中率低于 `vector`,遍历速度可能稍慢。 * **适用场景**:需要频繁地在容器的两端进行插入和删除操作的场景,例如实现一个既能先进先出又能后进先出的队列。 ### `std::list` * **特点**:双向链表。每个元素都存储着指向前一个和后一个元素的指针。 * **优点**:在**任何位置插入/删除元素都非常快**(O(1)),前提是你已经拥有指向该位置的迭代器。 * **缺点**: * **不支持随机访问**:无法通过索引(如 `list[i]`)访问元素。要访问第 n 个元素,必须从头或尾开始遍历 n 次,时间复杂度为 O(n)。 * **内存开销大**:每个元素都需要额外的空间来存储前后指针。 * **适用场景**:需要对集合进行大量、不规律的插入和删除操作,而对随机访问没有要求的场景。 ### `std::array` (C++11) * **特点**:固定大小的数组,是对 C 风格数组的现代封装。大小在编译时确定,不可改变。 * **优点**:性能与 C 风格数组完全相同,但提供了更安全、更方便的接口(如获取大小、迭代器支持、边界检查等)。 * **缺点**:大小固定,缺乏灵活性。 * **适用场景**:当你明确知道需要一个在编译期就确定大小的数组时。 ## 关联容器 (Associative Containers) 关联容器根据**键 (key)** 来高效地排序和存储元素。它们非常适合用于快速查找。 ### `std::map` * **特点**:存储**键-值对 (key-value pairs)**,其中键是唯一的。内部通常由**红黑树**实现,元素自动按键的升序排序。 * **优点**:可以根据键快速查找、插入和删除元素,平均时间复杂度为 O(log n)。 * **适用场景**:需要存储键值对,并且要求元素能**自动排序**的场景。 ### `std::set` * **特点**:只存储**唯一的键**。和 `map` 一样,内部也是由红黑树实现,元素自动排序。 * **优点**:快速查找、插入和删除(O(log n))。常用于检查一个元素是否存在于一个集合中。 * **适用场景**:需要存储不重复的元素,并要求它们保持有序。 > `std::multimap` 和 `std::multiset` 分别是 `map` 和 `set` 的多键版本,它们允许存储重复的键。 ## 无序关联容器 (Unordered Associative Containers) 这类容器在 C++11 中引入,它们也存储键或键值对,但基于**哈希表 (Hash Table)** 实现,不保证元素有序,换来的是更快的平均性能。 ### `std::unordered_map` * **特点**:存储键-值对,键是唯一的。通过哈希函数将键映射到桶 (bucket) 中以实现快速访问。 * **优点**:提供了**最快的平均查找、插入和删除速度**,平均时间复杂度为 O(1)。 * **缺点**: * 元素是无序的。 * 在最坏情况下(所有元素哈希到同一个桶中),性能会退化到 O(n)。 * 需要为哈希表本身和键的哈希值消耗额外内存。 * **适用场景**:追求极致查找性能,且不关心元素顺序的场景。**这是 `std::map` 的常用高性能替代品。** ### `std::unordered_set` * **特点**:只存储唯一的键,基于哈希表实现。 * **优点**:最快的平均查找、插入和删除速度(O(1))。 * **适用场景**:需要以最高性能判断一个元素是否存在于集合中,且不关心顺序。 ## 总结与选择建议 为了方便选择,下表总结了各类容器的特点: | 需求 | 推荐容器 | 备选方案 | 时间复杂度 (平均) | | :--- | :--- | :--- | :--- | | **快速随机访问** | `std::vector` | `std::deque`, `std::array` | O(1) | | **大量中间插入/删除** | `std::list` | `std::forward_list` | O(1) | | **快速两端插入/删除** | `std::deque` | `std::list` | O(1) | | **键值对存储 (有序)** | `std::map` | `std::multimap` | O(log n) | | **键值对存储 (最快查找)** | `std::unordered_map` | `std::unordered_multimap` | O(1) | | **存储唯一元素 (有序)** | `std::set` | `std::multiset` | O(log n) | | **存储唯一元素 (最快查找)** | `std::unordered_set` | `std::unordered_multiset` | O(1) | **通用建议**: 1. 如果不确定用什么,**`std::vector` 通常是最好的第一选择**。它的性能优秀且用途广泛。 2. 如果需要按键查找,并且不要求排序,**`std::unordered_map` 是 `std::map` 的高性能替代品**。 3. 只有在需要频繁在容器中间进行插入/删除时,才考虑使用 `std::list`。 --- url: /blog/2025/foundation-model.md --- # 大模型参数和内存解析 在讨论大模型时,我们通常会见到类似 “175B” 或 “125M” 的描述。 * **B** 表示 **billion**(十亿) * **M** 表示 **million**(百万) ## 每个参数的内存占用 模型中参数的数据类型直接影响每个参数的内存大小,常见的数据类型包括: | 数据类型 | 每个参数占用内存 | 说明 | | ---------------- | ---------------- | ------------------------------------------ | | float32 | 4 字节 | 全精度训练常用,但占用内存较大 | | float16/bfloat16 | 2 字节 | 半精度训练/推理,兼顾速度与精度 | | int8 | 1 字节 | 量化推理,内存和计算资源大大降低 | | int4 | 0.5 字节 | 边缘设备或极限量化场景,进一步降低内存开销 | 举例: 对于一个 7B 模型来说: * **float32**:约 28 GB * **float16**:约 14 GB * **int8**:约 7 GB * **int4**:约 3.5 GB ## 模型案例 ### GPT-3 * 参数规模从 125M 到 175B 不等 * 原始训练时可能使用 float32,但推理常用 float16 或经过 int8 量化 * 例如:GPT-3 175B 模型在推理时常采用 float16 来降低内存需求 ### LLaMA-2 * 提供 7B、13B 和 70B 等多个版本 * 原始训练一般采用 float16 或 bfloat16 * 推理阶段支持 float16,同时有 int8 或 int4 的量化版本 ### DeepSeek 系列 * 包括 DeepSeek-LLM 和 DeepSeek-Coder 等 * DeepSeek-LLM 常见有 7B 与 67B 版本,原始精度使用 float16/bfloat16,推理时可选 int8/int4 * DeepSeek-Coder 针对代码任务,提供 1.3B、6.7B、33B 不同版本,均支持灵活的数据类型选择 --- url: /blog/2025/freertos-lock.md --- # FreeRTOS中的锁 在FreeRTOS中,锁(locks)主要用于在多任务环境中同步访问共享资源,防止多个任务同时访问资源引发冲突。 FreeRTOS提供了几种不同的锁机制,每种锁都有其特定的使用场景和作用。 ## 互斥锁(Mutexes) 互斥锁用于任务间的互斥访问,通常用来保护共享资源,确保同一时间只有一个任务能够访问资源,避免数据竞争。 在FreeRTOS中,互斥锁是二值信号量的一个扩展,但它增加了[优先级继承机制](https://www.cnblogs.com/mickey-double/p/14366803.html),用于避免优先级反转(priority inversion)的问题。 通常用于保护共享资源(例如硬件设备、全局变量等)。 ```c #include "FreeRTOS.h" #include "semphr.h" SemaphoreHandle_t xMutex; void vTask1(void *pvParameters) { while (1) { // 获取互斥锁 if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) { // 访问共享资源 printf("Task 1 is accessing shared resource\n"); printf("Task 1 is run...\n"); vTaskDelay(pdMS_TO_TICKS(500)); printf("Task 1 is run end\n"); // 释放互斥锁 xSemaphoreGive(xMutex); } } vTaskDelete(NULL); } void vTask2(void *pvParameters) { while (1) { // 获取互斥锁 if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) { // 访问共享资源 printf("Task 2 is accessing shared resource\n"); printf("Task 2 is run...\n"); vTaskDelay(pdMS_TO_TICKS(500)); printf("Task 2 is run end\n"); // 释放互斥锁 xSemaphoreGive(xMutex); } } vTaskDelete(NULL); } int main() { // 创建互斥锁 xMutex = xSemaphoreCreateMutex(); if (xMutex != NULL) { // 创建两个任务 xTaskCreate(vTask1, "Task1", 1000, NULL, 1, NULL); xTaskCreate(vTask2, "Task2", 1000, NULL, 1, NULL); // 启动调度器 vTaskStartScheduler(); } else { // 创建互斥锁失败 printf("Failed to create mutex\n"); } return 0; } ``` ## 二值信号量(Binary Semaphores) 二值信号量(xSemaphoreCreateBinary,xSemaphoreTake,xSemaphoreGive)本质上是一种简单的锁机制,可以用来同步任务或中断与任务之间的通信,控制任务间的同步。 二值信号量只有两个状态:“已获取”或“未获取”。 但二值信号量**不支持优先级继承机制**,适用于比较简单的同步需求。 ## 计数信号量(Counting Semaphores) 计数信号量可以用来控制多个任务访问共享资源的数量,通常用于限制资源的使用(例如限制同时可访问的设备数量)。 计数信号量可以有一个计数值,任务通过获取和释放信号量来减少或增加计数值。 通常用于控制对有限数量的资源(例如多个设备或缓冲区)的访问。 ## 事件组(Event Groups) 事件组用于任务之间的复杂同步和状态管理。一个事件组由多个二进制标志位组成,任务可以等待这些标志位的变化。 事件组允许多个任务等待不同的标志位或者一个组合条件(例如:多个条件满足才继续执行)。 比较适用于需要多个任务同步或触发条件的场景。 通常用于任务间的复杂同步(例如多个任务的状态变化)。 --- url: /blog/2025/freertos-memory-management.md --- FreeRTOS 将内存管理与内核分离,通过统一的 `pvPortMalloc()` 和 `vPortFree()` 接口实现多种可选的堆管理算法(heap\_1 ~ heap\_5),以适配不同嵌入式场景。 **核心原则**: * **动态分配**:运行时按需申请/释放内存,灵活但需控制碎片化与实时性。 * **静态分配**:编译期固定内存,可靠但利用率低。 * **线程安全**:FreeRTOS 内存 API 会在分配/释放时挂起调度器,避免并发冲突。 **五种内存管理实现**(位于 *Source/Portable/MemMang*,工程中只能选择一个): 1. **heap\_1** 仅支持分配,不支持释放,执行时间固定,无碎片。 适合启动时**一次性创建对象且不删除**的系统。 2. **heap\_2** 支持释放,但不合并相邻空闲块,易产生碎片。 适合分配/释放**大小固定的对象**。 3. **heap\_3** 封装 malloc/free,保证线程安全。 **依赖编译器堆设置**,非确定性,可能增大代码体积。 4. **heap\_4** 最常用,支持释放并合并相邻空闲块,减少碎片。 适合**随机大小分配/释放**的应用。 5. **heap\_5** 基于 heap\_4,支持**多个不连续内存区域**(如片内 RAM + 外部 SDRAM)。 需先调用 `vPortDefineHeapRegions()` 初始化区域。 **典型使用示例**(heap\_4): ```c #include "FreeRTOS.h" #include "task.h" #include void *ptr = NULL; void mem_test(void) { size_t free_before = xPortGetFreeHeapSize(); printf("申请前剩余内存: %u 字节\n", free_before); ptr = pvPortMalloc(1024); if (ptr) { printf("申请成功, 地址: %p\n", ptr); sprintf((char*)ptr, "Tick=%u", xTaskGetTickCount()); } size_t free_after = xPortGetFreeHeapSize(); printf("申请后剩余内存: %u 字节\n", free_after); vPortFree(ptr); printf("释放后剩余内存: %u 字节\n", xPortGetFreeHeapSize()); } ``` **要点**: * 使用 `xPortGetFreeHeapSize()` 监控剩余堆空间。 * 对 heap\_5,需先定义内存区域数组并调用 `vPortDefineHeapRegions()`。 * 对实时性要求高的系统,建议选用 **heap\_1** 或静态分配,避免碎片化和不确定延迟。 --- url: /blog/2025/freertos-task-scheduling.md --- FreeRTOS 作为一个轻量级的实时操作系统(RTOS),其核心功能之一就是任务调度。它允许多个任务“同时”运行,为嵌入式系统提供了并发处理能力。要真正理解 FreeRTOS 是如何工作的,就必须深入其任务调度和上下文切换的底层细节。 本文将详细解析 FreeRTOS 的任务调度过程,特别是任务切换时堆栈(Stack)和关键寄存器(SP, PC 等)的变化情况。 ## 任务状态 在任何时刻,一个任务都必然处于以下几种状态之一: * **运行态 (Running)**: 任务当前正在 CPU 上执行。在单核处理器上,永远只有一个任务处于运行态。 * **就绪态 (Ready)**: 任务已经准备好,可以运行,但因为有更高优先级的任务正在运行,所以它在等待 CPU。 * **阻塞态 (Blocked)**: 任务正在等待某个外部事件(例如 `vTaskDelay` 延时、等待信号量、队列数据等),在此期间它不会被调度。 * **挂起态 (Suspended)**: 任务被显式地“暂停”,除非被显式地恢复,否则调度器会完全忽略它。 调度器的核心职责,就是在所有处于“就绪态”的任务中,选择优先级最高的那个,让它进入“运行态”。 ## FreeRTOS 调度器原理 FreeRTOS 采用的是**基于优先级**的**抢占式调度**算法。 * **基于优先级**: 每个任务都被赋予一个优先级。调度器总是确保当前运行的是所有就绪态任务中优先级最高的那个。 * **抢占式**: 如果一个高优先级的任务变为就绪态(例如,一个中断服务程序唤醒了它),而当前正在运行的是一个低优先级的任务,调度器会立即暂停(抢占)低优先级任务,让高优先级任务运行。 这种机制的切换点被称为**上下文切换 (Context Switch)**。 ## 上下文切换 (Context Switch) 详解 上下文切换是 FreeRTOS 实现多任务并发的魔法核心。它指的是保存当前运行任务的 CPU 状态(上下文),然后加载即将运行任务的 CPU 状态的过程。 **任务的上下文主要包括:** 1. **CPU 寄存器**: 通用寄存器(在 ARM Cortex-M 中如 R0-R12)、程序计数器 (PC)、链接寄存器 (LR)、程序状态寄存器 (PSR) 等。 2. **任务堆栈指针 (SP)**: 指向该任务私有堆栈的栈顶。 每个任务都有一个独立的**任务控制块 (Task Control Block, TCB)**,它是一个数据结构,用于存储任务的所有信息,其中最重要的就是任务的堆栈指针 `pxTopOfStack`。 ### 切换过程拆解 假设当前正在运行 `Task_Low` (低优先级),此时一个中断发生,使得 `Task_High` (高优先级) 从阻塞态变为了就绪态。当中断服务程序(ISR)完成时,调度器将被触发,执行一次上下文切换。 ```mermaid graph TD subgraph "Task Low 运行中" A[Task Low 正在执行] end subgraph "中断发生" B{中断发生
    如 SysTick} C[ISR 使 Task High 进入就绪态] D{调度器决定切换
    因 Task High 优先级更高} end subgraph "上下文切换: 保存 Task Low" E[硬件自动保存核心寄存器
    PC, LR, PSR 到 Task Low 堆栈] F[软件 PendSV 保存剩余寄存器
    R4-R11 到 Task Low 堆栈] G[保存 Task Low 的新堆栈指针 SP
    到其 TCB] end subgraph "上下文切换: 恢复 Task High" H[从 Task High 的 TCB 加载 SP 到 CPU] I[软件 PendSV 从 Task High 堆栈
    恢复寄存器 R4-R11] J[硬件从 Task High 堆栈
    恢复核心寄存器 PC, LR, PSR] end subgraph "Task High 运行中" K[Task High 开始/恢复执行] end A --> B B --> C C --> D D --> E E --> F F --> G G --> H H --> I I --> J J --> K ``` *上下文切换流程图* #### 第 1 步:保存 `Task_Low` 的上下文 当中断发生时,CPU 硬件会自动将一部分核心寄存器(如 PC, PSR, LR 等)压入 `Task_Low` 的堆栈。在 FreeRTOS 的切换机制(通常在 `PendSV_Handler` 中实现)里,会继续执行以下操作: 1. **软件压栈**: 将 CPU 中剩余的通用寄存器(R4-R11 等,具体取决于架构)也压入 `Task_Low` 的堆栈。 2. **保存 SP**: 将当前堆栈指针 SP 的值,保存到 `Task_Low` 的 TCB 中的 `pxTopOfStack` 成员里。 至此,`Task_Low` 的所有“记忆”(即它运行到哪里,各个寄存器的值是什么)都被完整地保存在了它自己的堆栈中。 #### 第 2 步:选择下一个要运行的任务 调度器会查看就绪任务列表,发现 `Task_High` 是当前优先级最高的就绪任务,因此决定下一个运行它。 #### 第 3 步:恢复 `Task_High` 的上下文 1. **加载 SP**: 从 `Task_High` 的 TCB 中,读取 `pxTopOfStack` 的值,并将其加载到 CPU 的 SP 寄存器中。现在,SP 指向了 `Task_High` 的堆栈顶。 2. **软件弹栈**: 从 `Task_High` 的堆栈中,将之前保存的 R4-R11 等通用寄存器依次弹出,恢复到 CPU 的相应寄存器中。 3. **硬件弹栈与返回**: 当 `PendSV_Handler` 退出时,CPU 硬件会自动从 `Task_High` 的堆栈中弹出之前保存的 PC, PSR, LR 等寄存器。 当 PC 寄存器被恢复后,CPU 的下一条指令就会从 `Task_High` 上次被中断的地方继续执行。至此,一次完整的上下文切换完成。 ### 堆栈与寄存器变化图解 让我们更直观地看看这个过程。 **1. 切换前:** * CPU 的 SP 寄存器指向 `Task_Low` 的堆栈。 * CPU 的 PC 寄存器指向 `Task_Low` 正在执行的代码。 ```mermaid graph TD subgraph "CPU" SP["SP Register"] PC["PC Register"] end subgraph "Memory" TaskLowStack["Task_Low Stack
    ...
    Local Vars"] TaskHighStack["Task_High Stack
    ...
    Saved Context"] end SP -- "points to" --> TaskLowStack PC -- "points to" --> TaskLowCode[Task_Low Code] style TaskLowStack fill:#cde4ff ``` **2. 切换中 (保存 `Task_Low`):** * `Task_Low` 的所有寄存器被压入其堆栈。 * `Task_Low` 的 TCB 更新:`tcb_low.pxTopOfStack = SP`。 ```mermaid graph TD subgraph "CPU" SP["SP Register"] end subgraph "Memory" TaskLowStack["Task_Low Stack
    Saved R4-R11
    Saved PC, LR...
    ...
    Local Vars"] TaskLowTCB["Task_Low TCB
    pxTopOfStack"] end SP -- "points to new top" --> TaskLowStack TaskLowTCB -- "pxTopOfStack = SP" --> SP style TaskLowStack fill:#cde4ff ``` **3. 切换中 (恢复 `Task_High`):** * SP 更新:`SP = tcb_high.pxTopOfStack`。 * `Task_High` 的寄存器从其堆栈中弹出到 CPU。 ```mermaid graph TD subgraph "CPU" SP["SP Register"] end subgraph "Memory" TaskHighStack["Task_High Stack
    ...
    Saved Context"] TaskHighTCB["Task_High TCB
    pxTopOfStack"] end SP -- "points to" --> TaskHighStack SP -- "SP = pxTopOfStack" --> TaskHighTCB style TaskHighStack fill:#d5fada ``` **4. 切换后:** * CPU 的 SP 寄存器指向 `Task_High` 的堆栈。 * CPU 的 PC 寄存器指向 `Task_High` 的代码。`Task_High` 开始运行。 ## 关键寄存器的作用 * **SP (Stack Pointer)**: 任务切换的“定位器”。它的值在 TCB 和 CPU 之间来回传递,确保了每个任务都能找到自己独立的堆栈空间。 * **PC (Program Counter)**: 任务执行的“指令指针”。保存和恢复 PC 是实现任务断点续传的关键,使得任务看起来像是连续执行的。 * **LR (Link Register)**: 函数调用的“返回地址”。每个任务都有自己的调用栈,LR 必须作为上下文的一部分被保存,否则函数调用关系会错乱。 * **PSR (Program Status Register)**: 任务的“状态标志”。包含了条件码(零、负、进位等),控制着条件分支的执行,必须被精确恢复。 ## 总结 FreeRTOS 的任务调度是一个高效且精巧的机制。它通过为每个任务维护一个独立的堆栈和 TCB,在上下文切换时,利用 `PendSV` 这个特殊设计的低优先级中断,快速地保存和恢复 CPU 寄存器,从而实现了任务之间的无缝切换。 理解这一底层过程,不仅能帮助我们更好地使用 FreeRTOS,还能在遇到多任务相关的疑难- 杂症时,提供更深入的调试思路。 --- url: /blog/2025/gcc-build-options.md --- # gcc 编译选项总结 ## 有用的编译选项 | 编译选项 | 说明 | | :-------------------------------: | :------------------------------------------------------- | | `-save-temps` | 保存生成的预处理 `.i`、编译`.s`、汇编 `.o`生成的临时文件 | | `-dM` | 生成预定义的宏信息 | | `-###` | 打印(不执行)gcc 调用的各个子命令 | | `-v` | 可以打印出 gcc 搜索头文件的路径和顺序 | | `-Q --help=optimizers` | 打印优化选项 | | `-fdiagnostics-color` | 通过定义环境变量`GCC_COLORS`来彩色打印诊断信息 | | `-print-file-name` | 显示出 gcc 究竟会连接哪个 libc 库 | | `-P` | 去掉预处理时生成的行号标记 | | `-DMACRO` | 命令行中预定义一个宏 | | `-UMACRO` | 命令行中取消一个宏 | | `-Wa,option` | 将选项`option`传递给汇编器 | | `-Wa,-L` | 在目标文件中保留局部符号 | | `-fverbose-asm` | 生成带有详细信息的汇编文件 | | `-fsanitize=address` | 生成能检查内存访问错误的可执行文件 | | `-fsanitize=thread -fPIE -pie` | 生成能检查数据竞争的可执行文件 | | `-Wl,option` | 将选项`option`传递给连接器 | | `-Wl,-Map=o.map` | 让连接器生成内存映射文件 | | `__attribute__ ((__used__))` | 禁止函数被优化掉 | | `__attribute__ ((always_inline))` | 强制函数永远以 inline 的形式调用 | | `-fsyntax-only` | 只做语法检查,不进行实际的编译输出 | | `-Wall` | 打开警告信息 | | `-x` | 指定语言类型 | | `-fpack-struct=2` | 改变结构体成员的字节对齐 | ## 常用的系统库 | 库名称 | GCC 链接命令 | 作用 | | :------------------ | :--------------- | :----------------------- | | math (数学库) | `-lm` | 三角函数、对数、指数等 | | pthread (多线程库) | `-lpthread` | POSIX 线程 | | dl (动态链接库加载) | `-ldl` | `dlopen` / `dlsym` 等 | | rt (实时库) | `-lrt` | 高精度定时器、实时功能 | | zlib (压缩库) | `-lz` | 压缩与解压缩 | | ssl/crypto | `-lssl -lcrypto` | OpenSSL 安全加密库 | | curl | `-lcurl` | 网络库,支持 HTTP/FTP 等 | ## 常用的第三方库 | 库名称 | GCC 链接方式 | 说明 | | :------- | :-------------------------------------- | :------------------- | | OpenCV | `-lopencv_core -lopencv_imgproc ...` | 图像处理与计算机视觉 | | FFmpeg | `-lavcodec -lavformat -lavutil ...` | 音视频编解码 | | SQLite | `-lsqlite3` | 轻量级数据库 | | Boost | `-lboost_system -lboost_filesystem ...` | 通用 C++ 库 | | protobuf | `-lprotobuf` | Google 协议缓冲区 | | json-c | `-ljson-c` | C 语言 JSON 解析库 | | yaml-cpp | `-lyaml-cpp` | C++ YAML 解析库 | ## 参考 1. [100 个 gcc 小技巧](https://github.com/hellogcc/100-gcc-tips) --- url: /blog/2025/git-linux-build.md --- # Git下载构建Linux 在本文中,我将介绍如何使用 Git 从官方仓库下载最新版 Linux 内核源码,然后进行配置、编译构建并安装。无论你是开发者、系统管理员还是 Linux 爱好者,这篇文章都能为你提供详细的步骤。 ## 源代码下载 Linux 内核源码托管在 [kernel.org](https://www.kernel.org/) 的 Git 仓库中。 Linux 的最新版本是 mainline 。 进入[官网](https://www.kernel.org/),点击 mainline 行的browse。 点击summary,获取最新 linux 版本的 Git 地址。 打开终端,执行以下命令开始克隆内核源码(仓库体积较大,可能需要较长时间): ```bash git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git ``` > **提示**:克隆完成后,所有历史提交记录都会下载到本地。如果只想获取最新的代码,可以使用 `--depth` 参数来进行浅克隆 > > ```bash > git clone --depth=1 https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git > ``` 进入下载好的源码目录: ```bash cd linux ``` ## 配置内核源码 在开始编译之前,需要对内核进行配置。可以选择图形界面配置或基于已有配置文件的方式进行设置。 ### 图形界面配置 确保安装了必要的依赖(`libncurses5-dev` 或 `libncurses-dev`),然后运行: ```bash make menuconfig ``` ### 使用现有配置文件 你可以复制当前系统正在使用的内核配置文件,然后更新配置: ```bash cp /boot/config-$(uname -r) .config make oldconfig ``` 在配置过程中,根据需要启用或禁用内核选项。大多数用户保留默认配置即可。 ### 生成arm64配置 ```bash make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig ``` ## 编译与构建内核 配置完成后,就可以编译内核。编译过程需要一些时间,这取决于你的硬件性能和选择的配置选项。 使用以下命令启动编译,`-j$(nproc)` 参数会自动利用所有 CPU 核心,加速编译过程: ```bash make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j6 ``` 编译结束后,你会在源码目录下看到编译生成的各类目标文件和模块。 ## 运行内核 使用命令运行编译后的内核 ```bash qemu-system-aarch64 -machine virt -cpu cortex-a53 -nographic -smp 1 -m 2048 -kernel arch/arm64/boot/Image ``` 可以看到启动成功,但是没有挂载上文件系统,就`kernel panic` ```bash ❯ qemu-system-aarch64 -machine virt -cpu cortex-a53 -nographic -smp 1 -m 2048 -kernel arch/arm64/boot/Image [ 0.000000] Booting Linux on physical CPU 0x0000000000 [0x410fd034] [ 0.000000] Linux version 6.14.0-rc5-00013-g99fa936e8e4f (frey@wsl) (aarch64-linux-gnu-gcc (GCC) 14.2.0, GNU ld (GNU Binutils) 2.43) #2 SMP PREEMPT Mon Mar 10 22:25:13 CST 2025 [ 0.000000] KASLR enabled [ 0.000000] random: crng init done [ 0.000000] Machine model: linux,dummy-virt [ 0.000000] efi: UEFI not found. [ 0.000000] OF: reserved mem: Reserved memory: No reserved-memory node in the DT [ 0.000000] NUMA: Faking a node at [mem 0x0000000040000000-0x00000000bfffffff] [ 0.000000] NODE_DATA(0) allocated [mem 0xbfbfb980-0xbfbfdfbf] [ 0.000000] Zone ranges: [ 0.000000] DMA [mem 0x0000000040000000-0x00000000bfffffff] [ 0.000000] DMA32 empty [ 0.000000] Normal empty [ 0.000000] Movable zone start for each node [ 0.000000] Early memory node ranges [ 0.000000] node 0: [mem 0x0000000040000000-0x00000000bfffffff] [ 0.000000] Initmem setup node 0 [mem 0x0000000040000000-0x00000000bfffffff] [ 0.000000] cma: Reserved 32 MiB at 0x00000000bba00000 on node -1 [ 0.000000] psci: probing for conduit method from DT. [ 0.000000] psci: PSCIv1.1 detected in firmware. [ 0.000000] psci: Using standard PSCI v0.2 function IDs [ 0.000000] psci: Trusted OS migration not required [ 0.000000] psci: SMC Calling Convention v1.0 [ 0.000000] percpu: Embedded 25 pages/cpu s61592 r8192 d32616 u102400 [ 0.000000] Detected VIPT I-cache on CPU0 [ 0.000000] CPU features: kernel page table isolation forced ON by KASLR [ 0.000000] CPU features: detected: Kernel page table isolation (KPTI) [ 0.000000] CPU features: detected: ARM erratum 845719 [ 0.000000] alternatives: applying boot alternatives [ 0.000000] Kernel command line: [ 0.000000] printk: log buffer data + meta data: 131072 + 458752 = 589824 bytes [ 0.000000] Dentry cache hash table entries: 262144 (order: 9, 2097152 bytes, linear) [ 0.000000] Inode-cache hash table entries: 131072 (order: 8, 1048576 bytes, linear) [ 0.000000] Fallback order for Node 0: 0 [ 0.000000] Built 1 zonelists, mobility grouping on. Total pages: 524288 [ 0.000000] Policy zone: DMA [ 0.000000] mem auto-init: stack:all(zero), heap alloc:off, heap free:off [ 0.000000] software IO TLB: SWIOTLB bounce buffer size adjusted to 2MB [ 0.000000] software IO TLB: area num 1. [ 0.000000] software IO TLB: mapped [mem 0x00000000bb600000-0x00000000bb800000] (2MB) [ 0.000000] SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1 [ 0.000000] rcu: Preemptible hierarchical RCU implementation. [ 0.000000] rcu: RCU event tracing is enabled. [ 0.000000] rcu: RCU restricting CPUs from NR_CPUS=512 to nr_cpu_ids=1. [ 0.000000] Trampoline variant of Tasks RCU enabled. [ 0.000000] Tracing variant of Tasks RCU enabled. [ 0.000000] rcu: RCU calculated value of scheduler-enlistment delay is 25 jiffies. [ 0.000000] rcu: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=1 [ 0.000000] RCU Tasks: Setting shift to 0 and lim to 1 rcu_task_cb_adjust=1 rcu_task_cpu_ids=1. [ 0.000000] RCU Tasks Trace: Setting shift to 0 and lim to 1 rcu_task_cb_adjust=1 rcu_task_cpu_ids=1. [ 0.000000] NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0 [ 0.000000] Root IRQ handler: gic_handle_irq [ 0.000000] GICv2m: range[mem 0x08020000-0x08020fff], SPI[80:143] [ 0.000000] rcu: srcu_init: Setting srcu_struct sizes based on contention. [ 0.000000] arch_timer: cp15 timer(s) running at 62.50MHz (virt). [ 0.000000] clocksource: arch_sys_counter: mask: 0x1ffffffffffffff max_cycles: 0x1cd42e208c, max_idle_ns: 881590405314 ns [ 0.000061] sched_clock: 57 bits at 63MHz, resolution 16ns, wraps every 4398046511096ns [ 0.010953] Console: colour dummy device 80x25 [ 0.011631] printk: legacy console [tty0] enabled [ 0.016729] Calibrating delay loop (skipped), value calculated using timer frequency.. 125.00 BogoMIPS (lpj=250000) [ 0.016954] pid_max: default: 32768 minimum: 301 [ 0.018494] LSM: initializing lsm=capability [ 0.021314] Mount-cache hash table entries: 4096 (order: 3, 32768 bytes, linear) [ 0.021419] Mountpoint-cache hash table entries: 4096 (order: 3, 32768 bytes, linear) [ 0.052601] cacheinfo: Unable to detect cache hierarchy for CPU 0 [ 0.073827] rcu: Hierarchical SRCU implementation. [ 0.073945] rcu: Max phase no-delay instances is 1000. [ 0.082111] EFI services will not be available. [ 0.082833] smp: Bringing up secondary CPUs ... [ 0.084629] smp: Brought up 1 node, 1 CPU [ 0.084720] SMP: Total of 1 processors activated. [ 0.084770] CPU: All CPU(s) started at EL1 [ 0.085034] CPU features: detected: 32-bit EL0 Support [ 0.085072] CPU features: detected: 32-bit EL1 Support [ 0.085161] CPU features: detected: CRC32 instructions [ 0.099130] alternatives: applying system-wide alternatives [ 0.123449] Memory: 1978696K/2097152K available (17728K kernel code, 5150K rwdata, 12208K rodata, 3072K init, 744K bss, 82340K reserved, 32768K cma-reserved) [ 0.152982] devtmpfs: initialized [ 0.178128] clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns [ 0.178750] futex hash table entries: 256 (order: 2, 16384 bytes, linear) [ 0.182446] 2G module region forced by RANDOMIZE_MODULE_REGION_FULL [ 0.182559] 0 pages in range for non-PLT usage [ 0.182617] 514496 pages in range for PLT usage [ 0.186387] pinctrl core: initialized pinctrl subsystem [ 0.196332] DMI not present or invalid. [ 0.219290] NET: Registered PF_NETLINK/PF_ROUTE protocol family [ 0.230164] DMA: preallocated 256 KiB GFP_KERNEL pool for atomic allocations [ 0.231152] DMA: preallocated 256 KiB GFP_KERNEL|GFP_DMA pool for atomic allocations [ 0.232221] DMA: preallocated 256 KiB GFP_KERNEL|GFP_DMA32 pool for atomic allocations [ 0.232649] audit: initializing netlink subsys (disabled) [ 0.237149] audit: type=2000 audit(0.208:1): state=initialized audit_enabled=0 res=1 [ 0.242673] thermal_sys: Registered thermal governor 'step_wise' [ 0.242743] thermal_sys: Registered thermal governor 'power_allocator' [ 0.243272] cpuidle: using governor menu [ 0.244689] hw-breakpoint: found 6 breakpoint and 4 watchpoint registers. [ 0.245377] ASID allocator initialised with 32768 entries [ 0.252209] Serial: AMBA PL011 UART driver [ 0.316980] 9000000.pl011: ttyAMA0 at MMIO 0x9000000 (irq = 13, base_baud = 0) is a PL011 rev1 [ 0.318820] printk: legacy console [ttyAMA0] enabled [ 0.407534] HugeTLB: registered 1.00 GiB page size, pre-allocated 0 pages [ 0.408045] HugeTLB: 0 KiB vmemmap can be freed for a 1.00 GiB page [ 0.408352] HugeTLB: registered 32.0 MiB page size, pre-allocated 0 pages [ 0.408691] HugeTLB: 0 KiB vmemmap can be freed for a 32.0 MiB page [ 0.409266] HugeTLB: registered 2.00 MiB page size, pre-allocated 0 pages [ 0.410754] HugeTLB: 0 KiB vmemmap can be freed for a 2.00 MiB page [ 0.411163] HugeTLB: registered 64.0 KiB page size, pre-allocated 0 pages [ 0.411589] HugeTLB: 0 KiB vmemmap can be freed for a 64.0 KiB page [ 0.422530] ACPI: Interpreter disabled. [ 0.429665] iommu: Default domain type: Translated [ 0.429974] iommu: DMA domain TLB invalidation policy: strict mode [ 0.432015] SCSI subsystem initialized [ 0.434719] usbcore: registered new interface driver usbfs [ 0.435215] usbcore: registered new interface driver hub [ 0.435988] usbcore: registered new device driver usb [ 0.438373] pps_core: LinuxPPS API ver. 1 registered [ 0.438699] pps_core: Software ver. 5.3.6 - Copyright 2005-2007 Rodolfo Giometti [ 0.439186] PTP clock support registered [ 0.439959] EDAC MC: Ver: 3.0.0 [ 0.441633] scmi_core: SCMI protocol bus registered [ 0.451134] FPGA manager framework [ 0.452112] Advanced Linux Sound Architecture Driver Initialized. [ 0.466163] vgaarb: loaded [ 0.468516] clocksource: Switched to clocksource arch_sys_counter [ 0.473167] VFS: Disk quotas dquot_6.6.0 [ 0.473598] VFS: Dquot-cache hash table entries: 512 (order 0, 4096 bytes) [ 0.475572] pnp: PnP ACPI: disabled [ 0.498996] NET: Registered PF_INET protocol family [ 0.500966] IP idents hash table entries: 32768 (order: 6, 262144 bytes, linear) [ 0.508950] tcp_listen_portaddr_hash hash table entries: 1024 (order: 2, 16384 bytes, linear) [ 0.509902] Table-perturb hash table entries: 65536 (order: 6, 262144 bytes, linear) [ 0.510513] TCP established hash table entries: 16384 (order: 5, 131072 bytes, linear) [ 0.511574] TCP bind hash table entries: 16384 (order: 7, 524288 bytes, linear) [ 0.513749] TCP: Hash tables configured (established 16384 bind 16384) [ 0.515960] UDP hash table entries: 1024 (order: 4, 65536 bytes, linear) [ 0.517155] UDP-Lite hash table entries: 1024 (order: 4, 65536 bytes, linear) [ 0.518956] NET: Registered PF_UNIX/PF_LOCAL protocol family [ 0.533079] RPC: Registered named UNIX socket transport module. [ 0.533441] RPC: Registered udp transport module. [ 0.533611] RPC: Registered tcp transport module. [ 0.533846] RPC: Registered tcp-with-tls transport module. [ 0.534094] RPC: Registered tcp NFSv4.1 backchannel transport module. [ 0.534610] PCI: CLS 0 bytes, default 64 [ 0.536824] kvm [1]: HYP mode not available [ 0.546413] Initialise system trusted keyrings [ 0.548140] workingset: timestamp_bits=42 max_order=19 bucket_order=0 [ 0.551637] squashfs: version 4.0 (2009/01/31) Phillip Lougher [ 0.554195] NFS: Registering the id_resolver key type [ 0.554994] Key type id_resolver registered [ 0.555354] Key type id_legacy registered [ 0.555904] nfs4filelayout_init: NFSv4 File Layout Driver Registering... [ 0.557397] nfs4flexfilelayout_init: NFSv4 Flexfile Layout Driver Registering... [ 0.558893] 9p: Installing v9fs 9p2000 file system support [ 0.626086] Key type asymmetric registered [ 0.626427] Asymmetric key parser 'x509' registered [ 0.627092] Block layer SCSI generic (bsg) driver version 0.4 loaded (major 245) [ 0.627747] io scheduler mq-deadline registered [ 0.628482] io scheduler kyber registered [ 0.629287] io scheduler bfq registered [ 0.655255] pl061_gpio 9030000.pl061: PL061 GPIO chip registered [ 0.659715] ledtrig-cpu: registered to indicate activity on CPUs [ 0.664721] pci-host-generic 4010000000.pcie: host bridge /pcie@10000000 ranges: [ 0.665863] pci-host-generic 4010000000.pcie: IO 0x003eff0000..0x003effffff -> 0x0000000000 [ 0.667013] pci-host-generic 4010000000.pcie: MEM 0x0010000000..0x003efeffff -> 0x0010000000 [ 0.667599] pci-host-generic 4010000000.pcie: MEM 0x8000000000..0xffffffffff -> 0x8000000000 [ 0.669366] pci-host-generic 4010000000.pcie: Memory resource size exceeds max for 32 bits [ 0.671053] pci-host-generic 4010000000.pcie: ECAM at [mem 0x4010000000-0x401fffffff] for [bus 00-ff] [ 0.673373] pci-host-generic 4010000000.pcie: PCI host bridge to bus 0000:00 [ 0.674164] pci_bus 0000:00: root bus resource [bus 00-ff] [ 0.674582] pci_bus 0000:00: root bus resource [io 0x0000-0xffff] [ 0.674966] pci_bus 0000:00: root bus resource [mem 0x10000000-0x3efeffff] [ 0.675380] pci_bus 0000:00: root bus resource [mem 0x8000000000-0xffffffffff] [ 0.677876] pci 0000:00:00.0: [1b36:0008] type 00 class 0x060000 conventional PCI endpoint [ 0.684199] pci 0000:00:01.0: [1af4:1000] type 00 class 0x020000 conventional PCI endpoint [ 0.685457] pci 0000:00:01.0: BAR 0 [io 0x0000-0x001f] [ 0.685782] pci 0000:00:01.0: BAR 1 [mem 0x00000000-0x00000fff] [ 0.686634] pci 0000:00:01.0: BAR 4 [mem 0x00000000-0x00003fff 64bit pref] [ 0.687649] pci 0000:00:01.0: ROM [mem 0x00000000-0x0003ffff pref] [ 0.690778] pci 0000:00:01.0: ROM [mem 0x10000000-0x1003ffff pref]: assigned [ 0.691667] pci 0000:00:01.0: BAR 4 [mem 0x8000000000-0x8000003fff 64bit pref]: assigned [ 0.692282] pci 0000:00:01.0: BAR 1 [mem 0x10040000-0x10040fff]: assigned [ 0.693232] pci 0000:00:01.0: BAR 0 [io 0x1000-0x101f]: assigned [ 0.693941] pci_bus 0000:00: resource 4 [io 0x0000-0xffff] [ 0.694504] pci_bus 0000:00: resource 5 [mem 0x10000000-0x3efeffff] [ 0.695383] pci_bus 0000:00: resource 6 [mem 0x8000000000-0xffffffffff] [ 0.758592] virtio-pci 0000:00:01.0: enabling device (0000 -> 0003) [ 0.776913] Serial: 8250/16550 driver, 4 ports, IRQ sharing enabled [ 0.786524] msm_serial: driver initialized [ 0.787566] SuperH (H)SCI(F) driver initialized [ 0.788711] STM32 USART driver initialized [ 0.817222] loop: module loaded [ 0.819956] megasas: 07.727.03.00-rc1 [ 0.825879] physmap-flash 0.flash: physmap platform flash device: [mem 0x00000000-0x03ffffff] [ 0.828468] 0.flash: Found 2 x16 devices at 0x0 in 32-bit bank. Manufacturer ID 0x000000 Chip ID 0x000000 [ 0.829623] Intel/Sharp Extended Query Table at 0x0031 [ 0.831260] Using buffer write method [ 0.832620] physmap-flash 0.flash: physmap platform flash device: [mem 0x04000000-0x07ffffff] [ 0.834380] 0.flash: Found 2 x16 devices at 0x0 in 32-bit bank. Manufacturer ID 0x000000 Chip ID 0x000000 [ 0.835111] Intel/Sharp Extended Query Table at 0x0031 [ 0.836804] Using buffer write method [ 0.837322] Concatenating MTD devices: [ 0.837526] (0): "0.flash" [ 0.837692] (1): "0.flash" [ 0.837952] into device "0.flash" [ 0.865829] tun: Universal TUN/TAP device driver, 1.6 [ 0.883830] thunder_xcv, ver 1.0 [ 0.884214] thunder_bgx, ver 1.0 [ 0.884944] nicpf, ver 1.0 [ 0.887822] hns3: Hisilicon Ethernet Network Driver for Hip08 Family - version [ 0.888112] hns3: Copyright (c) 2017 Huawei Corporation. [ 0.888873] hclge is initializing [ 0.889266] e1000: Intel(R) PRO/1000 Network Driver [ 0.889509] e1000: Copyright (c) 1999-2006 Intel Corporation. [ 0.890006] e1000e: Intel(R) PRO/1000 Network Driver [ 0.890429] e1000e: Copyright(c) 1999 - 2015 Intel Corporation. [ 0.891030] igb: Intel(R) Gigabit Ethernet Network Driver [ 0.891574] igb: Copyright (c) 2007-2014 Intel Corporation. [ 0.892397] igbvf: Intel(R) Gigabit Virtual Function Network Driver [ 0.893067] igbvf: Copyright (c) 2009 - 2012 Intel Corporation. [ 0.894384] sky2: driver version 1.30 [ 0.899286] VFIO - User Level meta-driver version: 0.3 [ 0.908820] usbcore: registered new interface driver usb-storage [ 0.920923] rtc-pl031 9010000.pl031: registered as rtc0 [ 0.921877] rtc-pl031 9010000.pl031: setting system clock to 2025-03-11T12:15:46 UTC (1741695346) [ 0.924773] i2c_dev: i2c /dev entries driver [ 0.941260] sdhci: Secure Digital Host Controller Interface driver [ 0.941587] sdhci: Copyright(c) Pierre Ossman [ 0.943342] Synopsys Designware Multimedia Card Interface Driver [ 0.946224] sdhci-pltfm: SDHCI platform and OF driver helper [ 0.955270] usbcore: registered new interface driver usbhid [ 0.955680] usbhid: USB HID core driver [ 0.966204] hw perfevents: enabled with armv8_pmuv3 PMU driver, 7 (0,8000003f) counters available [ 0.981580] NET: Registered PF_PACKET protocol family [ 0.983268] 9pnet: Installing 9P2000 support [ 0.983923] Key type dns_resolver registered [ 1.006902] registered taskstats version 1 [ 1.009045] Loading compiled-in X.509 certificates [ 1.028728] Demotion targets for Node 0: null [ 1.039448] input: gpio-keys as /devices/platform/gpio-keys/input/input0 [ 1.049892] clk: Disabling unused clocks [ 1.050371] PM: genpd: Disabling unused power domains [ 1.050995] ALSA device list: [ 1.051202] No soundcards found. [ 1.060111] /dev/root: Can't open blockdev [ 1.061434] VFS: Cannot open root device "" or unknown-block(0,0): error -6 [ 1.062042] Please append a correct "root=" boot option; here are the available partitions: [ 1.062782] 1f00 131072 mtdblock0 [ 1.062885] (driver?) [ 1.063616] List of all bdev filesystems: [ 1.064152] ext3 [ 1.064179] ext2 [ 1.064706] ext4 [ 1.064930] squashfs [ 1.065135] vfat [ 1.065504] [ 1.066044] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) [ 1.067317] CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted 6.14.0-rc5-00013-g99fa936e8e4f #2 [ 1.067962] Hardware name: linux,dummy-virt (DT) [ 1.069456] Call trace: [ 1.069861] show_stack+0x18/0x24 (C) [ 1.070978] dump_stack_lvl+0x60/0x80 [ 1.071317] dump_stack+0x18/0x24 [ 1.071691] panic+0x168/0x360 [ 1.072154] mount_root_generic+0x1f8/0x2b0 [ 1.072603] mount_root+0x208/0x248 [ 1.072918] prepare_namespace+0x1d8/0x240 [ 1.073664] kernel_init_freeable+0x254/0x278 [ 1.074329] kernel_init+0x20/0x140 [ 1.074665] ret_from_fork+0x10/0x20 [ 1.075455] Kernel Offset: 0x518503000000 from 0xffff800080000000 [ 1.076354] PHYS_OFFSET: 0xfff0f0e3c0000000 [ 1.076753] CPU features: 0x000,00000900,00800000,0200421b [ 1.077146] Memory Limit: none [ 1.078023] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]--- ``` ## 添加根文件系统 ### 配置内核支持 勾选上RAM block device support,也即CONFIG\_BLK\_DEV\_RAM=y ```txt Device Drivers ---> [*] Block devices ---> RAM block device support (16) Default number of RAM disks (4096) Default RAM disk size (kbytes) (NEW) ``` 重新编译 ```bash make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j6 ``` ### 制作根文件系统 创建一个文件夹`initramfs`作为系统根目录,然后创建一个可执行程序\`init.c ```c // initramfs/init.c #include #include int main() { printf("hello world!\n"); int count = 0; while (1) { printf("count: %d\n", count++); sleep(1); } return 0; } ``` 编程生成`init`启动文件 ```bash aarch64-linux-gnu-gcc init.c -o init -static ``` 使用cpio打包文件系统 ```bash find . | cpio -o -H newc | gzip > initramfs.cpio.gz ``` ## 运行带有跟文件系统的内核 ```bash qemu-system-aarch64 -machine virt -cpu cortex-a53 -nographic -smp 1 -m 2048 -kernel arch/arm64/boot/Image -append "root=/dev/ram0 rootfstype=ramfs rw init=/init" -initrd initramfs.cpio.gz ``` 按下`Ctrl + a`键,然后按下`x`键推出qemu ```txt ... [ 2.001303] Key type dns_resolver registered [ 2.045796] registered taskstats version 1 [ 2.049218] Loading compiled-in X.509 certificates [ 2.079850] Demotion targets for Node 0: null [ 2.098945] input: gpio-keys as /devices/platform/gpio-keys/input/input0 [ 2.116717] clk: Disabling unused clocks [ 2.117571] PM: genpd: Disabling unused power domains [ 2.118599] ALSA device list: [ 2.120375] No soundcards found. [ 2.192567] Freeing unused kernel memory: 3072K [ 2.194741] Run /init as init process hello world! count: 0 count: 1 count: 2 count: 3 count: 4 count: 5 QEMU: Terminated ``` ### qemu参数解释 * `-machine virt `:指定`qemu`模拟的设备,这里就是指一个通用的`armv8`架构的芯片 * `-cpu cortex-a53 `:指定具体的核心 * `-nographic`:表示启动时没有图形界面 * `-smp 1 `:设置该设备只有1个核 * `-m 2048 `:表示设置该设备有`2048MB`的内存 * `-kernel arch/arm64/boot/Image`:指定用于启动的内核文件 * `-initrd initramfs.cpio.gz`:表示指定使用这个文件作为`ramfs`的内容,也即我们自己生成的根文件系统 * `-append "root=/dev/ram0 rootfstype=ramfs rw init=/init`:表示了传递给内核的参数 * `root` 表示根文件系统的设备为 `/dev/ram0`, 除此之外制定了文件系统的类型是 `ramfs` * `init` 表示系统启动的第一个进程名称,内核会从文件系统中加载该进程去执行 --- url: /blog/2025/hugging-face-model.md --- # Hugging Face 模型简介 下面是 Hugging Face 上各种**模型格式对应的加载方式和推理方法**,本文按文件类型分类说明,并给出具体代码示例: * PyTorch * Safetensors * TensorFlow / Keras * Flax / JAX * ONNX * TensorFlow.js * GGUF(llama.cpp) * 分词器(Tokenizer) ## ✅ 1. PyTorch (`.bin`, `.pt`, `.pth`) ### 使用 Hugging Face Transformers: ```python from transformers import AutoModel, AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("model_dir/") model = AutoModel.from_pretrained("model_dir/") inputs = tokenizer("Hello world", return_tensors="pt") outputs = model(**inputs) ``` > `model_dir/` 目录中需要包含:`pytorch_model.bin` + `config.json` + tokenizer 文件 ## ✅ 2. Safetensors (`.safetensors`) 等价于 PyTorch 权重,但更安全。Transformers 会自动识别,无需特殊处理。 ```python from transformers import AutoModel model = AutoModel.from_pretrained("model_dir/") ``` ### 纯 PyTorch 加载: ```python from safetensors.torch import load_file state_dict = load_file("model.safetensors") model = MyModel() model.load_state_dict(state_dict) ``` ## ✅ 3. TensorFlow / Keras (`.h5`, `.pb`, `.ckpt`) ### Keras `.h5`: ```python from tensorflow.keras.models import load_model model = load_model("model.h5") result = model.predict(input_data) ``` ### SavedModel (`saved_model.pb`) ```python import tensorflow as tf model = tf.saved_model.load("saved_model_directory/") result = model(input_tensor) ``` ## ✅ 4. Flax / JAX (`.msgpack`) 需要使用 `transformers` 的 Flax 模型接口: ```python from transformers import FlaxAutoModel model = FlaxAutoModel.from_pretrained("model_dir/") ``` ## ✅ 5. ONNX (`.onnx`) 使用 `onnxruntime` 推理: ```python import onnxruntime import numpy as np session = onnxruntime.InferenceSession("model.onnx") inputs = {"input_ids": np.array([[101, 102]])} outputs = session.run(None, inputs) ``` ## ✅ 6. TensorFlow.js (`model.json` + `.bin`) 在 **浏览器中运行**: ```html ``` ## ✅ 7. GGUF (`.gguf`) 用于 **llama.cpp** 项目中的大语言模型: ### CLI 推理: ```bash ./main -m model.gguf -p "Hello, how are you?" ``` 或使用 [llama-cpp-python](https://github.com/abetlen/llama-cpp-python): ```python from llama_cpp import Llama llm = Llama(model_path="model.gguf") output = llm("Hello world", max_tokens=50) ``` ## ✅ 8. 分词器(Tokenizer) Hugging Face 的 `transformers` 和 `tokenizers` 均可自动加载: ```python from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("model_dir/") inputs = tokenizer("文本", return_tensors="pt") ``` 支持: * `tokenizer.json` * `vocab.txt` * `merges.txt` * `sentencepiece.model` 等 ## 🧠 小结 | 文件类型 | 加载方法 | | -------------- | --------------------------------------------- | | `.bin` | `transformers.AutoModel.from_pretrained()` | | `.safetensors` | 同上(自动识别),或用 `safetensors` 手动加载 | | `.h5` | `tf.keras.models.load_model()` | | `.pb` | `tf.saved_model.load()` | | `.onnx` | `onnxruntime.InferenceSession()` | | `model.json` | 浏览器中用 `tf.loadGraphModel()` | | `.gguf` | llama.cpp 或 llama-cpp-python | | 分词器相关文件 | `AutoTokenizer.from_pretrained()` | --- url: /blog/2025/keras-base.md --- # Keras 基础 ::: tip 参考文档 [Keras 官网文档](https://keras.io) ::: ## 安装 ```bash # 安装 Tensorflow pip install tensorflow --user -i https://pypi.tuna.tsinghua.edu.cn/simple # 安装 Keras pip install keras --user -i https://pypi.tuna.tsinghua.edu.cn/simple ``` ## 线性回归 ### 示例程序 ```python import numpy as np import matplotlib.pyplot as plt from keras.src.models import Sequential # Keras 中的顺序模型 from keras.src.layers import Dense # Keras 中的全连接层 from keras.src.layers import Input # 构造训练样本 x_train = np.random.rand(100) # 一维数据,样本量 100,服从均一分布 noise = np.random.normal(0, 0.01, x_train.shape) # 噪声数据,服从高斯分布(正态分布) y_train = x_train * 0.1 + 0.2 + noise # 编译模型 model = Sequential() # 顺序模型 model.add(Input(shape=(1,))) # 输入数据 1 维 model.add(Dense(units=1)) # 全连接层,输出数据 1 维, model.compile(optimizer="sgd", loss="mse") # 优化算法:随机梯度下降,损失函数:均方差 # 训练样本 for step in range(5000): cost = model.train_on_batch(x_train, y_train) if step % 500 == 0: print("cost: ", cost) # 查看训练得到的权重和偏置 w, b = model.layers[0].get_weights() print("W= ", w, "b= ", b) # 展示拟合结果 y_pred = model.predict(x_train) plt.scatter(x_train, y_train) plt.plot(x_train, y_pred, "r-", lw=3) plt.show() ``` ### 结果展示 ![linear-regression](./imgs/keras-1.png) ## 非线性回归 ### 示例程序 ```python import numpy as np import matplotlib.pyplot as plt from keras.src.models import Sequential # Keras 中的顺序模型 from keras.src.layers import Input, Dense, Activation # Keras 中的全连接层和激活函数 from keras.src.optimizers import SGD # Keras 中的 SGD 优化器 # 构造训练样本 x_train = np.linspace(-0.5, 0.5, 200) # 一维数据,样本量 200,等差线性分布 noise = np.random.normal(0, 0.02, x_train.shape) # 噪声数据,服从高斯分布(正态分布) y_train = np.square(x_train) + noise # 编译模型 model = Sequential() # 顺序模型 model.add(Input(shape=(1,))) model.add(Dense(units=5)) # 全连接层,输出数据 10 维, 输入数据 1 维 model.add(Activation("tanh")) # tanh 激活函数 model.add(Dense(units=1)) # 全连接层,输出数据 1 维, 输入数据 10 维 model.add(Activation("tanh")) # tanh 激活函数 sgd = SGD(learning_rate=0.2) # 设置随机梯度下降算法的学习率 model.compile(optimizer=sgd, loss="mse") # 优化算法:随机梯度下降,损失函数:均方差 # 训练样本 for step in range(5000): cost = model.train_on_batch(x_train, y_train) if step % 500 == 0: print("cost: ", cost) # 展示拟合结果 y_pred = model.predict(x_train) plt.scatter(x_train, y_train) plt.plot(x_train, y_pred, "r-", lw=3) plt.show() ``` ### 结果展示 ![Non-linear-regression](./imgs/keras-2.png) ## MNIST 数据分类 ### 示例程序 ```python from keras.src.datasets import mnist from tensorflow.python.keras.utils import np_utils from keras.src.models import Sequential from keras.src.layers import Input, Dense, Dropout from keras.src.optimizers import SGD # 载入数据集(第一次运行会联网下载) (x_train, y_train), (x_test, y_test) = mnist.load_data() print("x_train: ", x_train.shape, "y_train: ", y_train.shape) print("x_test: ", x_test.shape, "y_test: ", y_test.shape) x_train = x_train.reshape(x_train.shape[0], -1) / 255.0 # 归一化 x_test = x_test.reshape(x_test.shape[0], -1) / 255.0 # 标签值转为 one-hot 码 y_train = np_utils.to_categorical(y_train, num_classes=10) y_test = np_utils.to_categorical(y_test, num_classes=10) # 编译模型 model = Sequential() # 顺序模型 model.add(Input(shape=(x_train.shape[1],))) # 全连接层(隐藏层),输出数据 200 维,输入数据维度由训练数据本身决定, 使用 tanh 激活函数 model.add(Dense(units=196, input_dim=x_train.shape[1], activation="relu")) # 全连接层(隐藏层),输出数据 100 维,输入数据由前一层决定, 使用 tanh 激活函数 model.add(Dense(units=98, activation="relu")) model.add(Dropout(0.4)) # 让 20% 的神经元不工作(防止过拟合的一种手段) # 全连接层(输出层),输出数据 10 维(共 10 种手写数字),输入数据由前一层决定, 使用 softmax 激活函数 model.add(Dense(units=10, activation="softmax")) sgd = SGD(learning_rate=0.1) # 设置随机梯度下降算法的学习率 model.compile( optimizer=sgd, loss="categorical_crossentropy", metrics=["accuracy"]) # 优化算法:随机梯度下降,损失函数:交叉熵 # 训练样本 model.fit(x_train, y_train, batch_size=32, epochs=10) # 评估模型 loss, accuracy = model.evaluate(x_test, y_test) print("loss= ", loss, "accuracy= ", accuracy) ``` --- url: /blog/2025/linux-busybox-run.md --- # qemu 运行 busybox 在[上篇文章](/blog/2025/git-linux-build.md)中,通过编译linux内核,编写简单的C程序,实现基于内核运行程序。本篇文章实现在linux内核上运行busybox。 ## 下载 busybox 在[busybox官网](https://git.busybox.net/busybox/)可以获取到最新的busybox源码,通过git下载。 本文编译v1.37.0。 ```bash git clone git://git.busybox.net/busybox ``` ## 配置构建 生成默认配置 ```bash make defconfig ``` 通过 `menuconfig` 配置static编译 ```bash Busybox Settings ---> Build Options ---> [*] Build BusyBox as a static binary (no shared libs) (NEW) ``` ```bash make menuconfig ``` ```bash ❯ make menuconfig *** Unable to find the ncurses libraries or the *** required header files. *** 'make menuconfig' requires the ncurses libraries. *** *** Install ncurses (ncurses-devel) and try again. *** make[2]: *** [/home/frey/workspace/git/os/busybox/scripts/kconfig/lxdialog/Makefile:15: scripts/kconfig/lxdialog/dochecklxdialog] Error 1 make[1]: *** [/home/frey/workspace/git/os/busybox/scripts/kconfig/Makefile:14: menuconfig] Error 2 make: *** [Makefile:444: menuconfig] Error 2 ``` 在wsl2运行archlinux编译时,遇到下面检测`ncurses-devel`出现的错误,但是已经安装了`ncurses`,通过gcc可以编译包含``头文件的程序。 在文件`check-lxdialog.sh`中直接返回0,表示成功即可进行配置。 ```bash --- a/scripts/kconfig/lxdialog/check-lxdialog.sh +++ b/scripts/kconfig/lxdialog/check-lxdialog.sh @@ -44,11 +44,13 @@ tmp=.lxdialog.tmp trap "rm -f $tmp" 0 1 2 3 15 # Check if we can link to ncurses +# 直接返回0成功 check() { $cc -x c - -o $tmp 2>/dev/null <<'EOF' #include CURSES_LOC main() {} EOF +exit 0 ``` 编译 ```bash make -j6 ``` 遇到一些报错 ```bash networking/tc.c: In function 'cbq_print_opt': networking/tc.c:236:27: error: 'TCA_CBQ_MAX' undeclared (first use in this function); did you mean 'TCA_CBS_MAX'? 236 | struct rtattr *tb[TCA_CBQ_MAX+1]; | ^~~~~~~~~~~ | TCA_CBS_MAX networking/tc.c:236:27: note: each undeclared identifier is reported only once for each function it appears in networking/tc.c:249:16: error: 'TCA_CBQ_RATE' undeclared (first use in this function); did you mean 'TCA_TBF_RATE64'? 249 | if (tb[TCA_CBQ_RATE]) { | ^~~~~~~~~~~~ | TCA_TBF_RATE64 ``` 内核不再支持CBQ(Class-Based Queuing,一种流量控制和调度算法),编辑`.config`文件,禁用`CONFIG_TC`可消除错误。 [参考](https://www.reddit.com/r/linuxquestions/comments/1cizfpo/id_like_some_help_with_this_youtube_guide/) ```bash libbb/hash_md5_sha.c:1316:35: error: 'sha1_process_block64_shaNI' undeclared (first use in this function); did you mean 'sha1_process_block64'? 1316 | || ctx->process_block == sha1_process_block64_shaNI | ^~~~~~~~~~~~~~~~~~~~~~~~~~ | sha1_process_block64 ``` busybox的bug,arm架构没有`sha1_process_block64_shaNI`。[参考](https://lists.busybox.net/pipermail/busybox/2024-September/090944.html) ```bash diff --git a/libbb/hash_md5_sha.c b/libbb/hash_md5_sha.c index 57a801459..75a61c32c 100644 --- a/libbb/hash_md5_sha.c +++ b/libbb/hash_md5_sha.c @@ -1313,7 +1313,9 @@ unsigned FAST_FUNC sha1_end(sha1_ctx_t *ctx, void *resbuf) hash_size = 8; if (ctx->process_block == sha1_process_block64 #if ENABLE_SHA1_HWACCEL +# if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) || ctx->process_block == sha1_process_block64_shaNI +# endif #endif ) { hash_size = 5; ``` 编译成功后,执行install,在`_install`目录生成boxbusy文件 ```bash make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j6 install ``` ## 打包根文件系统 ``` cd _install ln -s bin/busybox init find . | cpio -o -H newc | gzip > initramfs.cpio.gz ``` ## 运行 ```bash qemu-system-aarch64 -machine virt -cpu cortex-a53 -nographic -smp 1 -m 2048 -kernel arch/arm64/boot/Image -append "root=/dev/ram0 rootfstype=ramfs rw init=/init" -initrd ../busybox/_install/initramfs.cpio.gz ``` ``` [ 1.117193] 9pnet: Installing 9P2000 support [ 1.117799] Key type dns_resolver registered [ 1.139603] registered taskstats version 1 [ 1.141234] Loading compiled-in X.509 certificates [ 1.157385] Demotion targets for Node 0: null [ 1.168037] input: gpio-keys as /devices/platform/gpio-keys/input/input0 [ 1.177580] clk: Disabling unused clocks [ 1.178038] PM: genpd: Disabling unused power domains [ 1.178810] ALSA device list: [ 1.179113] No soundcards found. [ 1.222812] Freeing unused kernel memory: 3072K [ 1.223923] Run /init as init process can't run '/etc/init.d/rcS': No such file or directory can't open /dev/tty2: No such file or directory can't open /dev/tty3: No such file or directory can't open /dev/tty4: No such file or directory can't open /dev/tty2: No such file or directory ls can't open /dev/tty3: No such file or directory can't open /dev/tty4: No such file or directory ls bin init linuxrc sbin dev initramfs.cpio.gz root usr can't open /dev/tty2: No such file or directory ~ # ls bin init linuxrc sbin dev initramfs.cpio.gz root usr can't open /dev/tty3: No such file or directory can't open /dev/tty4: No such file or directory ``` 对于`can't open /dev/tty4: No such file or directory`打包前创建tty终端即可 ```bash mknod dev/tty2 c 4 2 mknod dev/tty3 c 4 2 mknod dev/tty4 c 4 2 find . | cpio -o -H newc | gzip > initramfs.cpio.gz ``` --- url: /blog/2025/linux-lock.md --- # Linux中的各种锁及其基本原理 通过本文将了解到如下内容: 1. Linux系统的并行性特征 2. 互斥和同步机制 3. Linux中常用锁的基本特性 4. 互斥锁和条件变量 ## Linux的并行性特征 Linux作为典型的多用户、多任务、抢占式内核调度的操作系统,为了提高并行处理能力,无论在内核层面还是在用户层面都需要特殊的机制来确保任务的正确性和系统的稳定运行,就如同一个国家需要各种法律条款来约束每个公民的行为,才能有条不紊地运转。 在内核层面涉及到各种软硬件中断、进线程睡眠、抢占式内核调度、多处理器SMP架构等,因此内核在完成自己工作的时候一直在处理这些资源抢占的冲突问题。 在用户层面的进程,虽然Linux作为虚地址模式操作系统,为每个进程开辟了独立的虚拟地址空间,伪独占式拥有资源,但是仍然存在很多场景不得不产生多个进程共享资源的问题,来完成进程间的通信。 在线程层面,线程作为进程的一部分,进程内的多个线程只拥有自己的独立堆栈等少量结构,大部分的资源还是线程共享,因此多线程的资源占用冲突比进程更加明显,所以多线程编程的线程安全问题是个重难点。 综上可知,无论在kernel还是user space都必须有一些机制来确保对于资源共享问题的解决,然后这个机制就是接下来要说的:同步和互斥。 ## 同步和互斥机制 ### 基本概念 同步和互斥的概念有时候很容易混淆,可以简单地认为同步是更加宏观角度的一种说法,互斥是冲突解决的细节方法。 所谓同步就是调度者让任务按照约定的合理的顺序进行,但是当任务之间出现资源竞争,也就是竞态冲突时,使用互斥的规则强制约束允许数量的任务占用资源,从而解决各个竞争状态,实现任务的合理运行。 同步和互斥密不可分。 简单来说: * 同步与互斥机制是用于控制多个任务对某些特定资源的访问策略 * 同步是控制多个任务按照一定的规则或顺序访问某些共享资源 * 互斥是控制某些共享资源在任意时刻只能允许规定数量的任务访问 ### 角色分类 整个协调流程涉及的角色本质上只有三类: * 不可独占的共享资源 * 多个使用者 * 调度者 调度者需要为多个运行任务制定访问使用规则来实现稳定运行,这个调度者可以是内核、可以是应用程序,具体场景具体分析。 ### 重要术语 要很好地理解同步和互斥,就必须得搞清楚几个重要术语: * 竞争冒险(race hazard)或竞态条件(race condition) 最早听说这个术语是在模电数电的课程上,门电路出现竞态条件造成错误的结果,在计算机里面就是多个使用者同时操作共享的变量造成结果的不确定。 * 临界区 临界区域critical section是指多使用者可能同时共同操作的那部分代码,比如自加自减操作,多个线程处理时就需要对自加自减进行保护,这段代码就是临界区域。 ## Linux中常用的锁 在说锁之前还需要知道几个东西:信号量和条件变量。 这两个东西和锁有一定的联系和区别,在不同的场合单独使用或者配合实现来说实现安全的并发,至于网上很多说互斥锁是一种信号量的特例,对于这种特例理解不了也罢。 信号量和互斥锁的场景不一样,信号量主要是资源数量的管理(池化),实际用的频率远不如互斥锁,文字游戏着实无趣,实用主义至上,掌握高频工具的特点正确使用即可,大可不必过于学术派。 在使用锁时需要明确几个问题: * 锁的所有权问题 谁加锁 谁解锁 解铃还须系铃人 * 锁的作用就是对临界区资源的读写操作的安全限制 * 锁是否可以被多个使用者占用(互不影响的使用者对资源的占用) * 占用资源的加锁者的释放问题(锁持有的超时问题) * 等待资源的待加锁者的等待问题(如何通知到其他等着资源的使用者) * 多个临界区资源锁的循环问题(死锁场景) 带着问题明确想要达到的目的,我们同样可以根据自己的需求设计锁,Linux现有的锁如果从上面几个问题的角度去理解,就非常容易了。 ### 自旋锁spinlock 自旋锁的主要特征是使用者在想要获得临界区执行权限时,如果临界区已经被加锁,那么自旋锁并不会阻塞睡眠,等待系统来主动唤醒,而是原地忙轮询资源是否被释放加锁,自旋就是自我旋转,这个名字还是很形象的。 自旋锁有它的优点就是避免了系统的唤醒,自己来执行轮询,如果在临界区的资源代码非常短且是原子的,那么使用起来是非常方便的,避免了各种上下文切换,开销非常小,因此在内核的一些数据结构中自旋锁被广泛的使用。 ### 互斥锁mutex 使用者使用互斥锁时在访问共享资源之前对进行加锁操作,在访问完成之后进行解锁操作,谁加锁谁释放,其他使用者没有释放权限。 加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进程解锁。 区别于自旋锁,互斥锁无法获取锁时将阻塞睡眠,需要系统来唤醒,可以看出来自旋转自己原地旋转来确定锁被释放了,互斥锁由系统来唤醒,但是现实并不是那么美好的,因为很多业务逻辑系统是不知道的,仍然需要业务线程执行while来轮询是否可以重新加锁。 考虑这种情况:解锁时有多个线程阻塞,那么所有该锁上的线程都被编程就绪状态,第一个变为就绪状态的线程又执行加锁操作,那么其他的线程又会进入等待,对其他线程而言就是虚假唤醒。 在这种方式下,只有一个线程能够访问被互斥锁保护的资源。 ### 读写锁rwlock 读写锁也叫共享互斥锁:读模式共享和写模式互斥,本质上这种非常合理,因为在数据没有被写的前提下,多个使用者读取时完全不需要加锁的。 读写锁有读加锁状态、写加锁状态和不加锁状态三种状态,当读写锁在写加锁模式下,任何试图对这个锁进行加锁的线程都会被阻塞,直到写进程对其解锁。 读优先的读写锁:读写锁rwlock默认的也是读优先,也就是:当读写锁在读加锁模式先,任何线程都可以对其进行读加锁操作,但是所有试图进行写加锁操作的线程都会被阻塞,直到所有的读线程都解锁,因此读写锁很适合读次数远远大于写的情况。 这种情况需要考虑写饥饿问题,也就是大量的读一直轮不到写,因此需要设置公平的读写策略。 在一次面试中曾经问到实现一个写优先级的读写锁,感兴趣的可以想想如何实现。 ### RCU锁 RCU锁是读写锁的扩展版本,简单来说就是支持多读多写同时加锁,多读没什么好说的,但是对于多写同时加锁,还是存在一些技术挑战的。 RCU锁翻译为Read Copy Update Lock,读-拷贝-更新 锁。 Copy拷贝:写者在访问临界区时,写者将先拷贝一个临界区副本,然后对副本进行修改;Update更新:RCU机制将在在适当时机使用一个回调函数把指向原来临界区的指针重新指向新的被修改的临界区,锁机制中的垃圾收集器负责回调函数的调用。 更新时机:没有CPU再去操作这段被RCU保护的临界区后,这段临界区即可回收了,此时回调函数即被调用。 从实现逻辑来看,RCU锁在多个写者之间的同步开销还是比较大的,涉及到多份数据拷贝,回调函数等,因此这种锁机制的使用范围比较窄,适用于读多写少的情况,如网络路由表的查询更新、设备状态表更新等,在业务开发中使用不是很多。 ### 可重入锁和不可重入锁 * 递归锁recursive mutex 可重入锁(reentrant mutex) * 非递归锁non-recursive mutex 不可重入锁(non-reentrant mutex) Windows下的Mutex和Critical Section是可递归的。 Linux下的`pthread_mutex_t`锁默认是非递归的。 在Linux中可以显式设置`PTHREAD_MUTEX_RECURSIVE`属性,将`pthread_mutex_t`设为递归锁避免这种场景。 同一个线程可以多次获取同一个递归锁,不会产生死锁。 而如果一个线程多次获取同一个非递归锁,则会产生死锁。 如下代码对于非递归锁的死锁示例: ```c MutexLock mutex; void funca() { mutex.lock(); do_sth(); mutex.unlock(); } void funcb() { mutex.lock() funca(); mutex.unlock(); } ``` 代码中testb使用了mutex并且调用testa,但是testa中也调用了相同的mutext,这种场景下如果mutex是非递归的就会出现死锁。 ### 条件变量condition variables 条件变量是用来等待线程而不是上锁的,通常和互斥锁一起使用。 互斥锁的一个明显的特点就是某些业务场景中无法借助系统来唤醒,仍然需要业务代码使用while来判断,这样效率本质上比较低。 而条件变量通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,所以互斥锁和条件变量通常一起使用,来让条件变量异步唤醒阻塞的线程。 条件变量和互斥锁的典型使用就是生产者和消费者模型,这个模型非常经典,也在面试中经常被问到,示例代码: ```c #include #include #define MAX 5 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t notfull = PTHREAD_COND_INITIALIZER; //是否队满 pthread_cond_t notempty = PTHREAD_COND_INITIALIZER; //是否队空 int top = 0; int bottom = 0; void* produce(void* arg) { int i; for ( i = 0; i < MAX*2; i++) { pthread_mutex_lock(&mutex); while ((top+1)%MAX == bottom) { printf("full! producer is waiting\n"); //等待队不满 pthread_cond_wait(notfull, &mutex); } top = (top+1) % MAX; //发出队非空的消息 pthread_cond_signal(notempty); pthread_mutex_unlock(&mutex); } return (void*)1; } void* consume(void* arg) { int i; for ( i = 0; i < MAX*2; i++) { pthread_mutex_lock(&mutex); while ( top%MAX == bottom) { printf("empty! consumer is waiting\n"); //等待队不空 pthread_cond_wait(notempty, &mutex); } bottom = (bottom+1) % MAX; //发出队不满的消息 pthread_cond_signal(notfull); pthread_mutex_unlock(&mutex); } return (void*)2; } int main(int argc, char *argv[]) { pthread_t thid1; pthread_t thid2; pthread_t thid3; pthread_t thid4; int ret1; int ret2; int ret3; int ret4; pthread_create(&thid1, NULL, produce, NULL); pthread_create(&thid2, NULL, consume, NULL); pthread_create(&thid3, NULL, produce, NULL); pthread_create(&thid4, NULL, consume, NULL); pthread_join(thid1, (void**)&ret1); pthread_join(thid2, (void**)&ret2); pthread_join(thid3, (void**)&ret3); pthread_join(thid4, (void**)&ret4); return 0; } ``` 其中`pthread_cond_wait`的使用是个需要注意的地方:`pthread_cond_wait()`是先将互斥锁解开,并陷入阻塞,直到`pthread_signal()`发出信号后`pthread_cond_wait()`再加上锁,然后退出。 --- url: /blog/2025/linux-terminal-tips.md --- 总结一下在Linux终端中进行命令输入、编辑的一些技巧 | 命令 | 说明 | | -------------------- | --------------------------------------------- | | `Tab` | 自动补全 | | `Up/Ctrl+p` | 上一条命令 | | `Down/Ctrl+n` | 下一条命令 | | `Left/Ctrl+b` | 光标左移(后退) | | `Right/Ctrl+f` | 光标右移(前进) | | `Ctrl+r` | 按字符串寻找历史命令 | | `Ctrl+c` | 中断正在执行的程序 | | `Ctrl+l` | 清屏 | | `Ctrl+a/e` | 移动光标到命令行首/行尾 | | `Ctrl+Left/Right` | 光标左移/右移一个单词 | | `Ctrl+k` | 删除光标之后所有字符 | | `Ctrl+u` | 清空当前键入的命令 | | `Ctrl+w` | 删除光标前的单词 | | `Ctrl+y` | 粘贴 `Ctrl+w` 或 `Ctrl+k` 删除的内容 | | `Ctrl+d` | 删除当前光标所在字符 (在没有字符时会关闭终端) | | `Backspace/Ctrl+h` | 删除光标的前一个字符 | | `Ctrl+x+e` | 使用编辑器编辑当前命令 | ## 参考 1. [Linux命令大全搜索工具](https://jaywcjlove.github.io/linux-command) *(jaywcjlove.github.io)* 2. [Linux命令大全(手册)](https://www.linuxcool.com/) *(linuxcool.com)* 3. [MAN手册 - 中文](https://manpages.debian.org/buster/manpages-zh/index.html) *(debian.org)* 4. [Linux 命令行速查表](https://www.cheat-sheet.cn/post/linux-command-line-cheat-sheet/) *(cheat-sheet.cn)* --- url: /blog/2025/mmu-mpu-difference.md --- MPU(Memory Protection Unit,内存保护单元)和 MMU(Memory Management Unit,内存管理单元)是现代计算机系统中用于内存管理的两种硬件设备。尽管它们在基本功能上有相似之处,但在复杂性和功能上存在显著差异。 ## MMU 的功能和特点 MMU 是一种更为先进的内存管理设备,主要用于虚拟内存和内存保护操作。它能够将虚拟地址转换为物理地址,并提供缓存控制、总线仲裁和银行切换等高级功能。这些特性使得 MMU 适用于运行高端操作系统(如 Windows、Linux 和 Android)的处理器,这些处理器通常被称为应用处理器。 MMU 的主要功能包括: * 虚拟内存管理:将虚拟地址映射到物理地址。 * 内存保护:防止未经授权的内存访问。 * 缓存控制:管理缓存的使用。 * 总线仲裁:协调多个设备对总线的访问。 * Bank切换:在不同的内存银行之间切换。 :::tip **Bank**理解为一组独立的存储单元。在计算机系统中,为了管理或扩展内存,硬件设计者会将物理内存划分为多个独立的区域(Bank)。这些区域可以有自己的特点和访问方式。 ::: :::tip **Bank切换**是一种用于扩展可用内存或管理内存资源的技术。当CPU的地址总线宽度有限,无法直接访问大容量内存时,或者出于低功耗等考虑,可以通过切换不同的Bank来让CPU访问到不同的内存区域。 ::: ## MPU 的功能和特点 MPU 通常是 CPU 的一部分,主要用于内存保护。与 MMU 不同,MPU 不支持虚拟内存管理和其他高级功能,因此它的复杂性和成本较低。MPU 适用于简单的系统,特别是在低功耗处理器中。 MPU 的主要功能包括: * 内存保护:防止未经授权的内存访问。 * 低复杂性和低成本:适用于不需要虚拟内存管理的简单系统。 ## 对比 | 关键 | MMU | MPU | | :------- | :--------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------ | | 定义 | MMU是指内存管理单元。 | MPU指的是内存保护单元。 | | 主要操作 | MMU用于多个与内存相关的操作,如虚拟内存、虚拟地址到物理地址的转换和内存保护。 | MPU只用于内存保护。 | | 功能 | MMU也可以执行MPU的功能。 | MPU不能被用来执行MMU的功能。 | | 使用方法 | 运行高端操作系统如Windows、Linux和Android的处理器使用MMU。这样的处理器被称为应用处理器。 | 运行RTOS操作的处理器如ThreadX使用MPU。这样的处理器被称为基带处理器,在运行3G/4G协议的地方很有用。 | | bank切换 | MMU能够进行Bank切换。 | MPU不能进行Bank切换。 | | 缓存控制 | MMU可以执行高速缓存控制操作。 | MPU不能执行高速缓存控制。 | | 先进性 | MMU是更先进的硬件。 | MPU是相对不太先进的硬件。 | | 开销 | MMU使用更多的开销。 | MPU使用的开销较少。 | --- url: /blog/2025/nginx-log-statistics.md --- 记录如何通过命令行对Nginx日志进行简单分析,日志格式为: ```bash log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; ``` 1. 统计所有请求的总数(PV) ```bash awk '{print $1}' /var/log/nginx/access.log | wc -l ``` 2. 统计独立IP的访问数量(UV) ```bash awk '{print $1}' /var/log/nginx/access.log | sort | uniq | wc -l ``` 3. 按IP访问次数排序,显示前100个 ```bash awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -n 100 ``` 4. 查看访问最频繁的页面(前50名) ```bash awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -n 50 ``` 5. 排除静态资源(如JS、CSS)后,查看最频繁页面 ```bash grep -vE '.js|.css|.ico' /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -n 50 ``` 6. 统计每秒请求数,显示Top 100时间点 ```bash awk '{print $4}' /var/log/nginx/access.log | cut -c 14-21 | sort | uniq -c | sort -rn | head -n 100 ``` 7. 列出传输时间超过3秒的页面(前20条) ```bash awk '($NF > 3){print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20 ``` --- url: /blog/2025/nodejs-error-03000086.md --- # nodejs opensslErrorStack 错误 报错信息如下 ```bash ❯ npm run build > script-server@1.18.0 build > vue-cli-service build Browserslist: caniuse-lite is outdated. Please run: npx update-browserslist-db@latest Why you should do it regularly: https://github.com/browserslist/update-db#readme ⠹ Building for production...Error: error:0308010C:digital envelope routines::unsupported at new Hash (node:internal/crypto/hash:69:19) at Object.createHash (node:crypto:133:10) at module.exports (/home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/util/createHash.js:135:53) at NormalModule._initBuildHash (/home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/NormalModule.js:417:16) at handleParseError (/home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/NormalModule.js:471:10) at /home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/NormalModule.js:503:5 at /home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/NormalModule.js:358:12 at /home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:373:3 at iterateNormalLoaders (/home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:214:10) at iterateNormalLoaders (/home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:221:10) at /home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:236:3 at runSyncOrAsync (/home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:130:11) at iterateNormalLoaders (/home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:232:2) at Array. (/home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:205:4) at Storage.finished (/home/frey/workspace/project/script-manager/web-src/node_modules/webpack/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:55:16) at /home/frey/workspace/project/script-manager/web-src/node_modules/webpack/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:91:9 Error: error:0308010C:digital envelope routines::unsupported at new Hash (node:internal/crypto/hash:69:19) at Object.createHash (node:crypto:133:10) at module.exports (/home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/util/createHash.js:135:53) at NormalModule._initBuildHash (/home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/NormalModule.js:417:16) at handleParseError (/home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/NormalModule.js:471:10) at /home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/NormalModule.js:503:5 at /home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/NormalModule.js:358:12 at /home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:373:3 at iterateNormalLoaders (/home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:214:10) at iterateNormalLoaders (/home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:221:10) at /home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:236:3 at runSyncOrAsync (/home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:130:11) at iterateNormalLoaders (/home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:232:2) at Array. (/home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:205:4) at Storage.finished (/home/frey/workspace/project/script-manager/web-src/node_modules/webpack/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:55:16) at /home/frey/workspace/project/script-manager/web-src/node_modules/webpack/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:91:9 Error: error:0308010C:digital envelope routines::unsupported at new Hash (node:internal/crypto/hash:69:19) at Object.createHash (node:crypto:133:10) at module.exports (/home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/util/createHash.js:135:53) at NormalModule._initBuildHash (/home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/NormalModule.js:417:16) at handleParseError (/home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/NormalModule.js:471:10) at /home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/NormalModule.js:503:5 at /home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/NormalModule.js:358:12 at /home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:373:3 at iterateNormalLoaders (/home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:214:10) at iterateNormalLoaders (/home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:221:10) at /home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:236:3 at runSyncOrAsync (/home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:130:11) at iterateNormalLoaders (/home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:232:2) at Array. (/home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:205:4) at Storage.finished (/home/frey/workspace/project/script-manager/web-src/node_modules/webpack/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:55:16) at /home/frey/workspace/project/script-manager/web-src/node_modules/webpack/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:91:9 ⠙ Building for production...Browserslist: caniuse-lite is outdated. Please run: npx update-browserslist-db@latest Why you should do it regularly: https://github.com/browserslist/update-db#readme Browserslist: caniuse-lite is outdated. Please run: npx update-browserslist-db@latest Why you should do it regularly: https://github.com/browserslist/update-db#readme Browserslist: caniuse-lite is outdated. Please run: npx update-browserslist-db@latest Why you should do it regularly: https://github.com/browserslist/update-db#readme ⠦ Building for production.../home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:114 throw e; ^ Error: error:0308010C:digital envelope routines::unsupported at new Hash (node:internal/crypto/hash:69:19) at Object.createHash (node:crypto:133:10) at module.exports (/home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/util/createHash.js:135:53) at NormalModule._initBuildHash (/home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/NormalModule.js:417:16) at handleParseError (/home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/NormalModule.js:471:10) at /home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/NormalModule.js:503:5 at /home/frey/workspace/project/script-manager/web-src/node_modules/webpack/lib/NormalModule.js:358:12 at /home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:373:3 at iterateNormalLoaders (/home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:214:10) at iterateNormalLoaders (/home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:221:10) at /home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:236:3 at context.callback (/home/frey/workspace/project/script-manager/web-src/node_modules/loader-runner/lib/LoaderRunner.js:111:13) at /home/frey/workspace/project/script-manager/web-src/node_modules/cache-loader/dist/index.js:147:7 at /home/frey/workspace/project/script-manager/web-src/node_modules/graceful-fs/graceful-fs.js:61:14 at FSReqCallback.oncomplete (node:fs:192:23) { opensslErrorStack: [ 'error:03000086:digital envelope routines::initialization error' ], library: 'digital envelope routines', reason: 'unsupported', code: 'ERR_OSSL_EVP_UNSUPPORTED' } Node.js v18.20.5 ``` ## 主要原因 这通常是由于 Nodejs 版本与 OpenSSL 3.0 (默认禁用了某些加密功能) 不兼容导致的,这个错误通常出现在 Node.js 17 版本及更高版本中。 ## 解决方法一 设置环境变量,告诉 Node.js 使用 OpenSSL 3.0 的传统加密功能。 **在 Windows 上(CMD)** ```cmd set NODE_OPTIONS=--openssl-legacy-provider npm run build ``` **在 Windows 上(PowerShell)** ```powershell $env:NODE_OPTIONS="--openssl-legacy-provider" npm run build ``` **在 macOs 或 Linux 上** ```bash export NODE_OPTIONS=--openssl-legacy-provider npm run build ``` ## 解决方法二 修改`package.json`, 运行命令前设置环境变量 ```json { "scripts": { "serve": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service serve", "build": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build" } } ``` ## 解决方法三 将 Nodejs 降级到 v16.x 版本 --- url: /blog/2025/pyright-config.md --- # pyright 配置 1. 设置额外的包搜索路径 在项目根目录创建文件`pyrightconfig.json` ```json { "extraPaths": ["./src", "./libs", "../shared"] } ``` "extraPahts" 告诉 pyright 去哪里找你的模块,不在默认路径的模块可以通过这里指定 --- url: /blog/2025/ssh-warning.md --- # ssh连接时出现警告 升级最新版本的ssh(10.0)后,连接服务器时会出现警告: ```bash ❯ ssh test ** WARNING: connection is not using a post-quantum key exchange algorithm. ** This session may be vulnerable to "store now, decrypt later" attacks. ** The server may need to be upgraded. See https://openssh.com/pq.html ``` 主要原因是,ssh最新版本新增并修改了默认的加密密钥协商算法,以应对量子计算机的共计。 > OpenSSH 支持多种加密密钥协商算法 被认为对量子计算机的攻击是安全的。 我们建议所有SSH连接都使用这些算法。 > > OpenSSH 提供了后量子密钥协议(KexAlgorithms) 自 9.0 版本(2022 年 4 月)起默认使用,最初通过 sntrup761x25519-sha512 算法实现。最近,在OpenSSH 9.9中, 我们新增了第二个后量子密钥协议 mlkem768x25519-sha256,并在 OpenSSH 10.0(2025 年 4 月)中将其定为新的默认方案。 ## 背景 量子计算机(QC)是一种能够执行计算的设备 信息编码为量子态。这样的装置可以很快解决 这些问题对现有“经典”计算机来说是难以解决的。 支撑多种密码算法的数学原理 这些都是量子计算机被认为能够解决的问题之一 有效解决。这意味着,足够强大的量子计算机 (即“密码学相关”量子计算机)能够破解 他们。受影响最大的是用于密钥协商和数字化的密码学 签名,这两者在SSH中都起着重要作用。 幸运的是,有足够强大的量子计算机能够破解密码学 还没有被发明出来。关于密码学相关时的估计 量子计算机将会到来,基于该领域的进展速度, 时间范围在5到20年之间,许多观察者预计他们会到来 2030年代中期。 SSH连接的全部隐私都依赖于加密密钥协议。 如果攻击者能够破坏密钥协议,那么他们就能解密 和 查看整个会话。攻击者不必在现实中进行此攻击 时间;它们可能先收集加密的SSH会话,之后再解密 一旦他们获得了量子计算机的访问权限。 这被称为“先存储,后解密”攻击(也称为 “先收获,后解密”)。 幸运的是,改进的“后量子”密码算法已被开发出来 这些问题依赖于不同的底层数学问题,这些问题被理解为 不能被量子计算机攻击。 OpenSSH 支持后量子密钥协议,以防止“立即存储, “解密后”攻击已经持续多年,从那时起它就成为默认设置 OpenSSH-9.0,于2022年发布。 ## 处理方式 1. 升级服务器ssh版本 2. 或在本机的ssh\_config中禁用警告 ```bash Match host unsafe.example.com WarnWeakCrypto no ``` --- url: /blog/2025/wsl-network-error-0x8007054f.md --- 当 WSL 报错 `createinstance/createvm/configurenetworking/0x8007054f` 并提示无法配置网络时,通常是由于网络服务或系统配置问题导致的。以下是解决此问题的步骤。 ```bash wsl: 出现了内部错误。 错误代码: createinstance/createvm/configurenetworking/0x8007054f wsl: 无法配置网络 (networkingmode mirrored),回退到 networkingmode none。 ``` ## 解决方法 ### 重启 HNS 服务 在管理员模式下运行 PowerShell,重启 Host Network Service (HNS) 服务。 ```powershell wsl --shutdown net stop hns net start hns ``` ### 重置 WSL 网络配置 如果问题仍然存在,可以尝试重置网络堆栈。 ```powershell netsh winsock reset netsh int ip reset ``` 执行后需要重启电脑。 ### 完全关闭并重启 WSL 关闭所有 WSL 实例并重新启动服务。 ```powershell wsl --shutdown wsl -d <你的发行版名称> ``` ### 检查 Hyper-V 和相关功能 确保 Windows 功能中的 Hyper-V 和 Virtual Machine Platform 已启用。如果未启用,可以通过以下步骤操作: 1. 打开“控制面板” > “程序” > “启用或关闭 Windows 功能”。 2. 勾选 Hyper-V 和 虚拟机平台,然后重启系统。 --- url: /blog/2026/C++11-new-feature.md --- C++11 是 C++ 程序设计语言标准的一个重要版本,于 2011 年由 ISO 正式发布,取代了原有的 C++98/03 标准。C++11 在核心语法、STL 标准模板库等方面增加了众多新功能,例如新增 `nullptr`、`auto`、`decltype` 关键字,引入 Lambda 表达式、`std::array`、`std::forward_list`、`std::tuple` 等容器,以及右值引用与移动语义等。下面将对这些新特性逐一总结。 ## nullptr `nullptr` 出现的目的是为了替代 `NULL`。 在某种意义上来说,传统 C++ 会把 `NULL`、0 视为同一种东西,这取决于编译器如何定义 `NULL`,有些编译器会将 `NULL` 定义为 `((void\*)0)`,有些则会直接将其定义为 0。 C++ 不允许直接将 `void *` 隐式转换到其他类型,但如果 NULL 被定义为 `((void*)0)`,那么当编译 `char *ch = NULL;` 时,NULL 只好被定义为 0。 而这依然会产生问题,将导致了 C++ 中重载特性会发生混乱,考虑: ```c++ void foo(char *); void foo(int); ``` 对于这两个函数来说,如果 NULL 被定义为了 0,那么 `foo(NULL);` 这个语句将会去调用 `foo(int)`,从而导致行为违反直觉。 为了解决这个问题,C++11 引入了 nullptr 关键字,专门用来区分空指针、0。 nullptr 的类型为 `nullptr_t`,能够隐式的转换为任何指针或成员指针的类型,也能和他们进行相等或者不等的比较。 **当需要使用 NULL 时候,养成直接使用 nullptr的习惯。** ## 类型推导 C++11 引入了 auto 和 decltype 这两个关键字实现了类型推导,让编译器来操心变量的类型。 ### auto关键字 auto 在很早以前就已经进入了 C++,但是他始终作为一个存储类型的指示符存在,与 register 并存。在传统 C++ 中,如果一个变量没有声明为 register 变量,将自动被视为一个 auto 变量。而随着 register 被弃用,对 auto 的语义变更也就非常自然了:可以让编译器自动分析初始值来判断变量所属的类型。当然,使用 auto 必须确定初始值。 使用 auto 进行类型推导的一个最为常见而且显著的例子就是迭代器。在以前我们需要这样来书写一个迭代器: ```c++ for(vector::const_iterator itr = vec.cbegin(); itr != vec.cend(); ++itr) ``` 而有了 auto 之后可以: ```c++ // 由于 cbegin() 将返回 vector::const_iterator // 所以 itr 也应该是 vector::const_iterator 类型 for(auto itr = vec.cbegin(); itr != vec.cend(); ++itr) { /* ... */ } ``` 一些其他的常见用法: ```c++ auto i = 5; // i 被推导为 int auto arr = new auto(10) // arr 被推导为 int * auto a = make_shared("hello world"); // a 被推导为 shared_ptr ``` :::note 注意:auto 不能用于函数传参,因此下面的做法是无法通过编译的(考虑重载的问题,我们应该使用模板)。此外,auto 也不能用于推导数组类型。 ::: ```c++ int add(auto x, auto y); // 错误:auto 不能用于函数参数 ``` ```c++ #include int main() { auto i = 5; int arr[10] = {0}; auto auto_arr = arr; // 合法,auto_arr 被推导为 int* auto auto_arr2[10] = arr; // 错误:auto 不能用于数组声明 return 0; } ``` ### decltype关键字 decltype 关键字用于在编译时推导表达式的类型,而不会实际计算表达式的值。它的用法和 `sizeof` 很相似: ```c++ decltype(表达式) ``` 例如: ```c++ auto x = 1; auto y = 2; decltype(x + y) z; // z 的类型被推导为 int ``` 变量 x 和 y 的类型都为 int,让编译器通过推断括号里的表达式来判断 z 的类型。我们很容易得出 z 的类型也为 int。 ### 拖尾返回类型、auto 与 decltype 配合 你可能会思考,auto 能不能用于推导函数的返回类型。考虑这样一个例子加法函数的例子,在传统 C++ 中我们必须这么写(T 和 U 可能是不同类型): ```c++ template R add(T x, U y) { return x + y; } ``` 这样的代码其实变得很丑陋,因为程序员在使用这个模板函数的时候,必须明确指出返回类型。但事实上我们并不知道 add() 这个函数会做什么样的操作,获得一个什么样的返回类型。 在 C++11 中这个问题得到解决。虽然你可能马上回反应出来使用 decltype 推导 x+y 的类型,写出这样的代码: ```c++ decltype(x + y) add(T x, U y); // 错误:x 和 y 尚未定义 ``` 但事实上这样的写法并不能通过编译。这是因为在编译器读到 decltype(x+y) 时,x 和 y 尚未被定义。为了解决这个问题,C++11 还引入了一个叫做**拖尾返回类型**(trailing return type),利用 auto 关键字将返回类型后置: ```c++ template auto add(T x, U y) -> decltype(x + y) { return x + y; } ``` 从 C++14 开始是可以直接让普通函数具备返回值推导,因此下面的写法变得合法: ```c++ template auto add(T x, U y) { return x + y; } ``` ## 区间迭代 - 基于范围的 for 循环 C++11 引入了基于范围的迭代写法,我们拥有了能够写出像 Python 一样简洁的循环语句。 最常用的 std::vector 遍历将从原来的样子: ```c++ std::vector arr(5, 100); for(std::vector::iterator i = arr.begin(); i != arr.end(); ++i) { std::cout << *i << std::endl; } ``` 变得非常的简单: ```c++ for(auto i : arr) { std::cout << i << std::endl; } ``` ## 初始化列表 在传统 C++ 中,不同的对象有着不同的初始化方法,例如普通数组、POD (plain old data,没有构造、析构和虚函数的类或结构体)类型都可以使用 {} 进行初始化,也就是我们所说的初始化列表。而对于类对象的初始化,要么需要通过拷贝构造函数、要么就需要使用 () 进行。这些不同方法都针对各自对象。在传统C++中可以使用初始化列表如下: ```c++ // 普通数组 int i_arr[3] = { 1, 2, 3 }; // POD类型:结构体 struct A { int x; struct B { int i; int j; } b; } a = { 1, { 2, 3 } }; // 拷贝初始化(copy-initialization) int i = 0; class Foo { public: Foo(int) {} Foo(const Foo &); } foo = 123; // 需要拷贝构造函数 // 直接初始化:使用 () 进行 int j(0); Foo bar(123); ``` 为了统一初始化方式,并且让初始化行为具有确定的效果,C++11 中提出了列表初始化(List-initialization)的概念。 C++11 首先把初始化列表的概念绑定到了类型上,并将其称之为 `initializer_list`,允许构造函数或其他函数像参数一样使用初始化列表,这就为类对象的初始化与普通数组和 POD 的初始化方法提供了统一的桥梁。可以拿个类模板 `initializer_list` 作为构造函数的参数,则初始化列表就智能用于构造该函数。值得注意的是列表中的元素必须是同一种类型或者可以转化为同一种类型。 【实例】通过初始化列表初始化对象。 ```c++ class Foo { public: Foo(int) {} private: Foo(const Foo &); }; int main(void) { // 调用Foo(int)构造函数初始化(c++98编译通过 c++11编译通过) Foo a1(123); // 报错:Foo的拷贝构造函数声明为私有的,该语句属于拷贝初始化,语义上需要拷贝构造函数参与,因此私有拷贝构造会导致编译错误(即使实际可能被优化掉) Foo a2 = 123; // 列表初始化(c++98编译失败 c++11编译通过) Foo a3 = { 123 }; // 列表初始化(c++98编译失败 c++11编译通过) Foo a4 { 123 }; // 列表初始化(C++98 编译通过,C++11 编译通过) int a5 = { 3 }; // 列表初始化(C++98 编译失败,C++11 编译通过) int a6 { 3 }; return 0; } ``` 在上例中,a3、a4 使用了新的初始化方式来初始化对象,效果如同 a1 的直接初始化。a5、a6 则是基本数据类型的列表初始化方式。可以看到,它们的形式都是统一的。 这里需要注意的是,a3 虽然使用了等于号,但它仍然是列表初始化,语义上需要拷贝构造函数。但在实际编译器(如 g++)中,由于 copy elision 优化,可能不会报错。 a4 和 a6 的写法,是 C++98/03 所不具备的。在 C++11 中,可以直接在变量名后面跟上初始化列表,来进行对象的初始化。 同时列表初始化方法也适用于用new操作等圆括号进行初始化的地方,如下: ```c++ int* a = new int { 123 }; double b = double { 12.12 }; int* arr = new int[3] { 1, 2, 3 }; ``` 让人惊奇的是在 C++11 中可以使用列表初始化方法对堆中分配的内存的数组进行初始化,而在 C++98/03 中是不能这样做的。 ## 模板增强 ### 外部模板 传统 C++ 中,模板只有在使用时才会被编译器实例化。只要在每个编译单元(文件)中编译的代码中遇到了被完整定义的模板,都会实例化。这就产生了重复实例化而导致的编译时间的增加。并且,我们**没有办法通知编译器不要触发模板实例化**。 C++11 引入了外部模板,扩充了原来的强制编译器在特定位置实例化模板的语法,使得**能够显式的告诉编译器何时进行模板的实例化**: ```c++ template class std::vector; // 强行实例化 extern template class std::vector; // 不在该编译文件中实例化模板 ``` ### 尖括号 “>” 在传统 C++ 的编译器中,>> 一律被当做右移运算符来进行处理。但实际上我们很容易就写出了嵌套模板的代码: ```c++ std::vector> wow; ``` 这在传统C++编译器下是不能够被编译的,而 C++11 开始,连续的右尖括号将变得合法,并且能够顺利通过编译。 ### 类型别名模板 在传统 C++ 中,typedef 可以为类型定义一个新的名称,但是却没有办法为模板定义一个新的名称。因为,模板不是类型。例如: ```c++ template class SuckType { public: T a; U b; SuckType() : a(value) , b(value) {} }; template typedef SuckType, U, 1> NewType; // 不合法 ``` C++11 使用 using 引入了下面这种形式的写法,并且同时支持对传统 typedef 相同的功效: ```c++ template using NewType = SuckType; // 合法 ``` ### 默认模板参数 我们可能定义了一个加法函数: ```c++ template auto add(T x, U y) -> decltype(x+y) { return x+y } ``` 但在使用时发现,要使用 add,就必须每次都指定其模板参数的类型。 在 C++11 中提供了一种便利,可以指定模板的默认参数: ```c++ template auto add(T x, U y) -> decltype(x + y) { return x + y; } ``` ## 构造函数 ### 委托构造 C++11 引入了委托构造的概念,这使得构造函数可以在同一个类中一个构造函数调用另一个构造函数,从而达到简化代码的目的: ```c++ class Base { public: int value1; int value2; Base() { value1 = 1; } Base(int value) : Base() { // 委托 Base() 构造函数 value2 = 2; } }; ``` ### 继承构造 在继承体系中,如果派生类想要使用基类的构造函数,需要在构造函数中显式声明。 假若基类拥有为数众多的不同版本的构造函数,这样,在派生类中得写很多对应的“透传”构造函数。如下: ```c++ struct A { A(int i) {} A(double d,int i) {} A(float f,int i,const char* c) {} // ...等等系列的构造函数版本 }; struct B:A { B(int i):A(i) {} B(double d,int i):A(d,i) {} B(float f,int i,const char* c):A(f,i,c) {} // ......等等好多个和基类构造函数对应的构造函数 }; ``` C++11的继承构造: ```c++ struct A { A(int i) {} A(double d,int i){} A(float f,int i,const char* c){} // ...等等系列的构造函数版本 }; struct B:A { using A::A; // 关于基类各构造函数的继承一句话搞定 // ...... }; ``` 如果一个继承构造函数不被相关的代码使用,编译器不会为之产生真正的函数代码,这样比透传基类各种构造函数更加节省目标代码空间。 ## Lambda表达式 C++11 新标准新增的一项重要功能就是 lambda 表达式,所谓 Lambda 表达式实际上就是提供了一个类似匿名函数的特性,而匿名函数则是在需要一个函数,但是又不想费力去命名一个函数的情况下去使用的。 Lambda 的组成结构与函数很相似,它拥有一个返回类型,一个形参列表,一个函数体。Lambda 也可以定义在函数内部。它的组成结构如下: ```c++ [capture list] (parameter list) option -> return type { function body} ``` * capture list 表示捕获列表,也就是 lambda 所在函数中的局部变量的列表,如果没有,则这个列表为空。 * \[] 不捕获任何变量。 * \[&] 捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。 * \[=] 捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。注意值捕获的前提是变量可以拷贝,且被捕获的变量在 lambda 表达式被创建时拷贝,而非调用时才拷贝。如果希望 lambda 表达式在调用时能即时访问外部变量,我们应当使用引用方式捕获。 ```c++ int a = 0; auto f = [=] { return a; }; a += 1; cout << f() << endl; // 输出0 int a = 0; auto f = [&a] { return a; }; a += 1; cout << f() < add_x(int x) { return [&](int a) { return x + a; }; } ``` 上面函数返回了一个 lambda 表达式,参数 x 仅是一个临时变量,函数 `add_x` 调用后就被销毁了,但是返回的 lambda 表达式却引用了该变量,当调用这个表达式时,引用的是一个垃圾值,会产生没有意义的结果。上面这种情况,使用默认传值方式可以避免悬挂引用问题。 lambda 表达式可以赋值给对应类型的函数指针。但是使用函数指针并不是那么方便。所以 STL 定义在 < functional > 头文件提供了一个多态的函数对象封装 std::function,其类似于函数指针。它可以绑定任何类函数对象,只要参数与返回类型相同。如下面的返回一个 bool 且接收两个 int 的函数包装器: ```c++ std::function wrapper = [](int x, int y) { return x < y; }; ``` lambda 表达式一个更重要的应用是其可以用于函数的参数,通过这种方式可以实现回调函数。 最常用的是在 STL 算法中,比如你要统计一个数组中满足特定条件的元素数量,通过 lambda 表达式给出条件,传递给 `count_if` 函数: ```c++ int value = 3; vector v {1, 3, 5, 2, 6, 10}; int count = std::count_if(v.begin(), v.end(), [value](int x) { return x > value; }); ``` 就可以返回 vector 中值大于 3 的元素数量。 当需要遍历容器并对每个元素进行操作时: ```c++ std::vector v = { 1, 2, 3, 4, 5, 6 }; int even_count = 0; for_each(v.begin(), v.end(), [&even_count](int val){ if(!(val & 1)){ ++ even_count; } }); std::cout << "The number of even is " << even_count << std::endl; ``` 大部分 STL 算法,可以非常灵活地搭配 lambda 表达式来实现想要的效果。 ## 新增容器 ### `std::array` std::array 保存在栈内存中,相比堆内存中的 std::vector,我们能够灵活的访问这里面的元素,从而获得更高的性能。 std::array 会在编译时创建一个固定大小的数组,std::array 不能够被隐式的转换成指针,使用 std::array只需指定其类型和大小即可: ```c++ std::array arr= {1,2,3,4}; int len = 4; std::array arr = {1,2,3,4}; // 非法, 数组大小参数必须是常量表达式1234 ``` 当我们开始用上了 std::array 时,难免会遇到要将其兼容 C 风格的接口,这里有三种做法: ```c++ void foo(int *p, int len) { return; } std::array arr = {1,2,3,4}; // C 风格接口传参 // foo(arr, arr.size()); // 非法, 无法隐式转换 foo(&arr[0], arr.size()); foo(arr.data(), arr.size()); // 使用 `std::sort` std::sort(arr.begin(), arr.end()); ``` ### `std::forward_list` `std::forward_list` 是一个列表容器,使用方法和 `std::list` 基本类似。 和 `std::list` 的双向链表的实现不同,`std::forward_list` 使用单向链表进行实现,提供了 O(1) 复杂度的元素插入,不支持快速随机访问(这也是链表的特点),也是标准库容器中唯一一个不提供 `size()` 方法的容器。当不需要双向迭代时,具有比 `std::list`更高的空间利用率。 ### 无序容器 C++11 引入了两组无序容器: `std::unordered_set/std::unordered_multiset` 和 `std::unordered_map/std::unordered_multimap`。 无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 O(1)。 ### 元组 std::tuple 元组的使用有三个核心的函数: * `std::make_tuple`: 构造元组 * `std::get`: 获得元组某个位置的值 * `std::tie`: 元组拆包 ```c++ #include #include auto get_student(int id) { // 返回类型被推断为 std::tuple if (id == 0) return std::make_tuple(3.8, 'A', "张三"); if (id == 1) return std::make_tuple(2.9, 'C', "李四"); if (id == 2) return std::make_tuple(1.7, 'D', "王五"); return std::make_tuple(0.0, 'D', "null"); // 如果只写 0 会出现推断错误, 编译失败 } int main() { auto student = get_student(0); std::cout << "ID: 0, " << "GPA: " << std::get<0>(student) << ", " << "成绩: " << std::get<1>(student) << ", " << "姓名: " << std::get<2>(student) << '\n'; double gpa; char grade; std::string name; // 元组进行拆包 std::tie(gpa, grade, name) = get_student(1); std::cout << "ID: 1, " << "GPA: " << gpa << ", " << "成绩: " << grade << ", " << "姓名: " << name << '\n'; } ``` 合并两个元组,可以通过 `std::tuple_cat` 来实现。 ```c++ auto new_tuple = std::tuple_cat(get_student(1), std::move(t)); ``` ## 正则表达式 C++11 提供了正则表达式库,用于操作 std::string 对象,对模式 std::regex (本质是 `std::basic_regex`)进行初始化,通过 `std::regex_match` 进行匹配,从而产生 std::smatch (本质是 `std::match_results` 对象)。 我们通过一个简单的例子来简单介绍这个库的使用。考虑下面的正则表达式: \[a-z]+.txt: 在这个正则表达式中, \[a-z] 表示匹配一个小写字母, + 可以使前面的表达式匹配多次,因此 \[a-z]+ 能够匹配一个及以上小写字母组成的字符串。在正则表达式中一个 . 表示匹配任意字符,而 . 转义后则表示匹配字符 . ,最后的 txt 表示严格匹配 txt 这三个字母。因此这个正则表达式的所要匹配的内容就是文件名为纯小写字母的文本文件。 `std::regex_match`用于匹配字符串和正则表达式,有很多不同的重载形式。最简单的一个形式就是传入std::string以及一个std::regex进行匹配,当匹配成功时,会返回 true,否则返回 false。例如: ```c++ #include #include #include int main() { std::string fnames[] = {"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"}; // 在 C++ 中 `\` 会被作为字符串内的转义符,为使 `\.` 作为正则表达式传递进去生效,需要对 `\` 进行二次转义,从而有 `\\.` std::regex txt_regex("[a-z]+\\.txt"); for (const auto &fname: fnames) std::cout << fname << ": " << std::regex_match(fname, txt_regex) << std::endl; } ``` 另一种常用的形式就是依次传入std::string/std::smatch/std::regex三个参数,其中 std::smatch 的本质其实是 `std::match_results`,在标准库中, std::smatch 被定义为了 `std::match_results`,也就是一个子串迭代器类型的 `match_results`。使用 std::smatch 可以方便的对匹配的结果进行获取,例如: ```c++ std::regex base_regex("([a-z]+)\\.txt"); std::smatch base_match; for(const auto &fname: fnames) { if (std::regex_match(fname, base_match, base_regex)) { // sub_match 的第一个元素匹配整个字符串 // sub_match 的第二个元素匹配了第一个括号表达式 if (base_match.size() == 2) { std::string base = base_match[1].str(); std::cout << "sub-match[0]: " << base_match[0].str() << std::endl; std::cout << fname << " sub-match[1]: " << base << std::endl; } } } ``` 以上两个代码段的输出结果为: ```txt foo.txt: 1 bar.txt: 1 test: 0 a0.txt: 0 AAA.txt: 0 sub-match[0]: foo.txt foo.txt sub-match[1]: foo sub-match[0]: bar.txt bar.txt sub-match[1]: bar ``` ## 语言级线程支持 ```c++ std::thread std::mutex/std::unique_lock std::future/std::packaged_task std::condition_variable ``` 代码编译需要使用 -pthread 选项。 ## 右值引用和move语义 c++11 引入了右值引用和移动语义,为了避免无谓的复制,提高程序性能。 ### 左值、右值 C++ 中所有的值都必然属于左值、右值二者之一。左值是指表达式结束后依然存在的持久化对象,右值是指表达式结束时就不再存在的临时对象。所有的具名变量或者对象都是左值,而右值不具名。很难得到左值和右值的真正定义,但是有一个可以区分左值和右值的便捷方法:看能不能对表达式取地址,如果能,则为左值,否则为右值。 ```c++ int a = 5; // a是左值, 5是右值 a = b; // a、b 都是左值,只不过将 b 可以当做右值使用 ``` ### 左值引用、右值引用 C++98 的引用很常见了,就是给变量取了个别名,在 C++11 中,因为增加了右值引用(rvalue reference)的概念,所以 C++98 中的引用都称为了左值引用(lvalue reference)。 ```c++ // C++98 int a = 10; int& refA = a; // refA 是 a 的别名,修改 refA 就是修改 a // a 是左值,&refA 是左值引用 int& b = 1; // 编译错误!1 是右值,不能使用左值引用 ``` C++11 中的右值引用使用 `&&` 表示: ```c++ int&& a = 1; // 合法,将不具名(匿名)变量取了个别名 int b = 1; int&& c = b; // 编译错误!不能将一个左值绑定到一个右值引用 ``` ```c++ class A { public: int a; }; A getTemp() { return A(); } A && a = getTemp(); //getTemp()的返回值是右值(临时变量) ``` getTemp()返回的右值本来在表达式语句结束后,其生命也就该终结了(因为是临时变量),而通过右值引用,该右值又重获新生,其生命期将与右值引用类型变量a的生命期一样,只要a还活着,该右值临时变量将会一直存活下去。实际上就是给那个临时变量取了个名字。 :::note 注意:这里a的类型是右值引用类型(int &&),但是如果从左值和右值的角度区分它,它实际上是个左值。因为可以对它取地址,而且它还有名字,是一个已经命名的右值。 ::: 所以,左值引用只能绑定左值,右值引用只能绑定右值,如果绑定的不对,编译就会失败。但是,常量左值引用却是个奇葩,它可以算是一个“万能”的引用类型,它可以绑定非常量左值、常量左值、右值,而且在绑定右值的时候,常量左值引用还可以像右值引用一样将右值的生命期延长,缺点是,只能读不能改。 ```c++ const int & a = 1; // 常量左值引用绑定 右值, 不会报错 class A { public: int a; }; A getTemp() { return A(); } const A & a = getTemp(); // 不会报错 而 A& a 会报错 ``` ### 加入右值引用的目的 个人认为右值引用的目的主要是为了是减少内存拷贝,优化性能。比如下面的代码: ```c++ String Fun() { String str = "hello world"; return str; } ``` str 为临时对象,然后调用 String 的拷贝构造函数,将临时对象的值赋值给 String,这种拷贝是完全没有必要的,如果堆内存很大,那么这个拷贝构造的代价会很大,带来了额外的性能损耗。 为了避免临时对象的拷贝构造,我们可以使用移动语义来实现: ```c++ MyString& operator=(MyString&& other) { cout << "MyString& operator=(const MyString&& other)" << endl; if (this != &other) { m_nLen = other.m_nLen; m_pData = other.m_pData; other.m_pData = NULL; } return *this; } ``` 我们没有深度拷贝堆内存中的数据,而是仅仅复制了指针,并把源对象 other 的指针置空。事实上,我们“偷取”了属于源对象 other 的内存数据。由于源对象 other 是一个右值,不会再被使用,因此客户并不会觉察到源对象被改变了。在这里,我们并没有真正的复制,所以我们把这个构造函数叫做“转移构造函数”(move constructor),他的工作就是把资源从一个对象转移到另一个对象,而不是复制他们,这样就能避免内存拷贝带来的额外性能消耗。 ## std::bind 函数 C++11 引入了新的标准库函数 bind,std::bind 函数定义在头文件 functional 中,是一个函数模板,它就像一个函数适配器,接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。 std::bind 将可调用对象与其参数一起进行绑定,绑定后的结果可以使用 std::function 保存。std::bind 主要有以下两个作用: 将可调用对象和其参数绑定成一个仿函数; 只绑定部分参数,减少可调用对象传入的参数。 std::bind 函数有两种函数原型,定义如下: ```c++ template /* unspecified */ bind (Fn&& f, Args&&... args); template /* unspecified */ bind (Fn&& f, Args&&... args); ``` 参数说明: * f:一个可调用对象(可以是函数对象、函数指针、函数引用、成员函数指针、数据成员指针),它的参数将被绑定到 args 上。 * args:绑定参数列表,参数会被值或占位符(std::placeholders)替换,其长度必须与 f 接收的参数个数一致。 ### std::bind绑定普通函数 ```c++ double my_divide (double x, double y) {return x/y;} auto fn_half = std::bind (my_divide,_1,2); std::cout << fn_half(10) << '\n'; // 5 ``` * bind 的第一个参数是函数名,普通函数做实参时,会隐式转换成函数指针。因此`std::bind (my_divide,_1,2)`等价于`std::bind (&my_divide,_1,2)`; * `_1`表示占位符,位于 `` 中:`std::placeholders::_1`; ### std::bind绑定一个成员函数 ```c++ struct Foo { void print_sum(int n1, int n2) { std::cout << n1+n2 << '\n'; } int data = 10; }; int main() { Foo foo; auto f = std::bind(&Foo::print_sum, &foo, 95, std::placeholders::_1); f(5); // 100 } ``` bind 绑定类成员函数时,第一个参数表示对象的成员函数的指针,第二个参数表示对象的地址。 必须显示的指定 `&Foo::print_sum`,因为编译器不会将对象的成员函数隐式转换成函数指针,所以必须在 `Foo::print_sum` 前添加 &; 使用对象成员函数的指针时,必须要知道该指针属于哪个对象,因此第二个参数为对象的地址 \&foo; ## std :: function 类模板 类模板 std :: function 是一个通用的多态函数包装器。 std :: function 的实例可以存储,复制和调用任何可调用的目标 :包括函数,lambda 表达式,绑定表达式或其他函数对象,以及指向成员函数和指向数据成员的指针。当 std::function 对象未包裹任何实际的可调用元素,调用该 std::function 对象将抛出 `std::bad_function_call` 异常。 原型如下: ```c++ template< class R, class... Args > class function; ``` 例如:`std::function func`;则 function 类的实例 func 可以指向返回值为 int 型,有两个形参都为 int 型的函数。 ```c++ #include #include int f(int a, int b) { return a + b; } int main() { std::function func = f; std::cout << func(1, 2) << std::endl; // 3 return 0; } ``` 我们再来先来看看以前 C 和 C++98 的用法: ```c++ #include // C 风格函数 int c_func(int a, int b) { return a + b; } // 函数对象 struct functor { public: int operator() (int a, int b) { return a + b; } }; int main() { typedef int (*Func)(int, int); Func func = c_func; std::cout << func(1, 2) << std::endl; // 3 functor ft; std::cout << ft(1, 2) << std::endl; // 3 return 0; } ``` 从上面我们可以看出,使用 C++11 的 function 类调用函数方便多了。 --- url: /blog/2026/STM32-flash-ram-cache.md --- 在进行 STM32 嵌入式开发时,理解存储架构(Flash、SRAM、CCM RAM)及其访问特性是性能优化的基础。本文以 STM32F407 为例,总结其存储特性。 ## 地址空间映射 STM32F407 拥有 4GB 的线性地址空间,其内部存储器的分配如下表所示: | 类型 | 起始地址 | 总线连接 | 空间大小 | 特点 | | :-------------- | :----------- | :------------ | :--------- | :----------------------------------------------- | | **Flash** | `0x08000000` | I-Bus / D-Bus | Up to 1 MB | 非易失性存储,存放程序代码和常量数据。 | | **SRAM1** | `0x20000000` | System Bus | 112 KB | 主系统 RAM,支持 DMA 访问。 | | **SRAM2** | `0x2001C000` | System Bus | 16 KB | 辅助 RAM,常用于特定外设缓存。 | | **CCM RAM** | `0x10000000` | D-Bus | 64 KB | **核心耦合存储器**。速度最快,但**不支持 DMA**。 | | **Backup SRAM** | `0x40024000` | AHB Bus | 4 KB | 低功耗备份 RAM,Vbat 供电时数据不丢失。 | ## Flash 等待周期与 ART 加速器 STM32F407 的主频最高可达 168MHz,而内部 Flash 的访问频率通常较低(约 30MHz)。为了匹配 CPU 的高速处理能力,意法半导体引入了 **ART 加速器 (Adaptive Real-Time Accelerator)**。 ### 1. 等待周期 (Wait States) 在不同的工作频率下,必须配置正确的等待周期(Latency): | HCLK 频率 (Vdd=3.3V) | 等待周期 (WS) | | :------------------- | :------------ | | 0 < HCLK ≤ 30 MHz | 0 WS | | 30 < HCLK ≤ 60 MHz | 1 WS | | 60 < HCLK ≤ 90 MHz | 2 WS | | 150 < HCLK ≤ 168 MHz | **5 WS** | ### 2. ART 加速器原理 ART 加速器包含: * **128位预取队列**:每次从 Flash 读取 128 位数据(4 条指令)。 * **指令缓存 (I-Cache)**:64 行 128 位缓存。 * **数据缓存 (D-Cache)**:8 行 128 位缓存。 **启用建议**:在初始化系统时,务必开启加速器。 ```c FLASH->ACR |= FLASH_ACR_ICEN | FLASH_ACR_DCEN | FLASH_ACR_PRFTEN | FLASH_ACR_LATENCY_5WS; ``` ## CCM RAM:被忽视的性能利器 **CCM (Core Coupled Memory)** 直接连接到 CPU 的 D-Bus,访问延迟极低,非常适合存放: * 任务栈 (Stack) * 频繁调用的全局变量 * 关键算法代码 (需从 Flash 拷贝至此运行) **注意陷阱**:CCM RAM **不经过总线矩阵**,因此 **DMA 控制器无法直接访问 CCM RAM**。如果你将 DMA 缓冲区设置在 CCM 中,DMA 传输会直接失败。 ## Flash 读写注意事项 ### 1. 扇区结构 (Sector Layout) STM32F407 的 Flash 扇区大小不均匀(非对齐): * **Sector 0-3**: 16 KB * **Sector 4**: 64 KB * **Sector 5-11**: 128 KB 这种设计导致在将 Flash 当作 EEPROM 使用时,如果需要擦除 Sector 11,必须一次性擦除 128KB 数据,极其耗时且影响寿命。 :::warning 如果有变量存储 CCM RAM ,在进行 Flash 擦除/写入时,必须禁用 ART 数据缓存才能正常写入,否则写入时会返回`FLASH_ERROR_PROGRAM`错误。因为 CCM RAM 和写 Flash 都是通过D-Bus进行访问,而开启数据缓存时,硬件会周期性访问 CCM RAM 进行缓存同步,导致写 Flash 时 D-Bus 总线占用,写入失败。 ::: ### 2. 写入规则 * **先擦后写**:Flash 只能将位从 `1` 变为 `0`。写入前必须执行扇区擦除。 * **对齐要求**:F4 系列通常支持 byte (8-bit), half-word (16-bit), word (32-bit), double word (64-bit) 写入,取决于供电电压。 ## 参考 1. [STM32F4xx Reference Manual (RM0090)](https://www.st.com.cn/resource/en/reference_manual/rm0090-stm32f405415-stm32f407417-stm32f427437-and-stm32f429439-advanced-armbased-32bit-mcus-stmicroelectronics.pdf) 2. [Cortex-M4 Memory Map Guide](https://developer.arm.com/documentation/dui0553/a/the-cortex-m4-processor/memory-model/memory-regions--types-and-attributes) --- url: /blog/2026/linux-mount-smb.md --- # Linux自动挂载硬盘并共享 记录linux下自动挂载硬盘,并通过smb共享到网络中。 ## 自动挂载 1. `blkid`查看 `UUID` ```bash korbin@pi:~ $ blkid /dev/mmcblk0p1: LABEL_FATBOOT="bootfs" LABEL="bootfs" UUID="F587-071F" BLOCK_SIZE="512" TYPE="vfat" PARTUUID="7d559f21-01" /dev/mmcblk0p2: LABEL="rootfs" UUID="d6944274-f2f7-4644-96a4-213c3b367f5c" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="7d559f21-02" /dev/loop0: TYPE="swap" /dev/sda1: UUID="5a9e8d37-1d53-45d1-b724-2695d70477f0" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="075b7ae2-de80-4a87-8c01-f028b9ad8cf0" /dev/zram0: LABEL="zram0" UUID="a7f80131-092f-412c-ae98-dec1724993ed" TYPE="swap" ``` 2. 修改 `/etc/fstab`实现启动自动挂载 ```bash sudo mkdir /mnt/disk1 sudo echo "UUID=5a9e8d37-1d53-45d1-b724-2695d70477f0 /mnt/disk1 ext4 defaults 0 0" >> /etc/fstab ``` ## smb共享 1. 安装 `samba` ```bash sudo apt install samba ``` 2. 配置 `smb`共享 ```bash [disk1] comment = Public Shared Data available = yes valid users = frey path = /mnt/disk1 browseable = yes writeable = yes guest ok = no create mask = 0664 directory mask = 0755 force user = frey ``` 3. 重启 `smb`服务 ```bash sudo systemctl restart smb.service ``` --- url: /blog/2026/ollama-config.md --- 1. 修改监听IP wsl2环境下,ollama安装后默认监听`127.0.0.1`,在docker运行的容器通过`http://host.docker.internal:11434`也无法访问,修改成监听`0.0.0.0`便可以访问了。 通过在服务配置中的增加环境变量`OLLAMA_HOST`进行修改(修改后需要重启服务): ```txt ... [Unit] Description=Ollama Service After=network-online.target [Service] ExecStart=/usr/local/bin/ollama serve User=ollama Group=ollama Restart=always RestartSec=3 Environment="OLLAMA_HOST=0.0.0.0:11434" ... ``` 2. 日志查看 通过`journalctl -u ollama -f`可查看日志信息,环境变量中增加`OLLAMA_DEBUG=1`会输出更详细的日志信息, ## 参考 1. https://docs.ollama.com/ --- url: /blog/index.md --- # 博客 --- url: /book/100-gcc-tips/001-print-predefined-macros.md --- # 打印 gcc 预定义的宏信息 ## 例子 ``` [root@linux:~]$ gcc -dM -E - < /dev/null #define __DBL_MIN_EXP__ (-1021) #define __FLT_MIN__ 1.17549435e-38F #define __CHAR_BIT__ 8 #define __WCHAR_MAX__ 2147483647 #define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_1 1 #define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_2 1 #define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_4 1 #define __DBL_DENORM_MIN__ 4.9406564584124654e-324 #define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_8 1 #define __FLT_EVAL_METHOD__ 0 #define __unix__ 1 #define __x86_64 1 #define __DBL_MIN_10_EXP__ (-307) #define __FINITE_MATH_ONLY__ 0 #define __GNUC_PATCHLEVEL__ 7 ``` ## 技巧 如上所示,使用“`gcc -dM -E - < /dev/null`”命令就可以显示出 gcc 预定义的宏信息。“`-dM`”生成预定义的宏信息,“`-E`”表示预处理操作完成后就停止,不再进行下面的操作。此外,也可以使用这个命令:“`echo | gcc -dM -E -`”。 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html#index-dM-908) ## 贡献者 nanxiao --- url: /book/100-gcc-tips/002-print-commands-only.md --- # 打印 gcc 执行的子命令 ## 例子 ``` $ gcc -### foo.c Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.6/lto-wrapper Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.6.3-1ubuntu5' --with-bugurl=file:///usr/share/doc/gcc-4.6/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.6 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.6 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --enable-objc-gc --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu Thread model: posix gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) COLLECT_GCC_OPTIONS='-mtune=generic' '-march=x86-64' /usr/lib/gcc/x86_64-linux-gnu/4.6/cc1 -quiet -imultilib . -imultiarch x86_64-linux-gnu foo.c -quiet -dumpbase foo.c "-mtune=generic" "-march=x86-64" -auxbase foo -fstack-protector -o /tmp/ccezMraJ.s COLLECT_GCC_OPTIONS='-mtune=generic' '-march=x86-64' as --64 -o /tmp/cc9Ce7IE.o /tmp/ccezMraJ.s COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/4.6/:/usr/lib/gcc/x86_64-linux-gnu/4.6/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.6/:/usr/lib/gcc/x86_64-linux-gnu/ LIBRARY_PATH=/home/xmj/install/cap-llvm-3.4/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/4.6/:/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/home/xmj/install/cap-llvm-3.4/lib/:/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../:/lib/:/usr/lib/ COLLECT_GCC_OPTIONS='-mtune=generic' '-march=x86-64' /usr/lib/gcc/x86_64-linux-gnu/4.6/collect2 "--sysroot=/" --build-id --no-add-needed --as-needed --eh-frame-hdr -m elf_x86_64 "--hash-style=gnu" -dynamic-linker /lib64/ld-linux-x86-64.so.2 -z relro /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/4.6/crtbegin.o -L/home/xmj/install/cap-llvm-3.4/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/4.6 -L/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/home/xmj/install/cap-llvm-3.4/lib -L/usr/lib/gcc/x86_64-linux-gnu/4.6/../../.. /tmp/cc9Ce7IE.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-linux-gnu/4.6/crtend.o /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crtn.o ``` ## 技巧 如上所示,使用`-###`选项可以打印出 gcc 所执行的各个子命令,分别为, cc1: ``` /usr/lib/gcc/x86_64-linux-gnu/4.6/cc1 -quiet -imultilib . -imultiarch x86_64-linux-gnu foo.c -quiet -dumpbase foo.c "-mtune=generic" "-march=x86-64" -auxbase foo -fstack-protector -o /tmp/ccezMraJ.s ``` as: ``` as --64 -o /tmp/cc9Ce7IE.o /tmp/ccezMraJ.s ``` collect2: ``` /usr/lib/gcc/x86_64-linux-gnu/4.6/collect2 "--sysroot=/" --build-id --no-add-needed --as-needed --eh-frame-hdr -m elf_x86_64 "--hash-style=gnu" -dynamic-linker /lib64/ld-linux-x86-64.so.2 -z relro /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/4.6/crtbegin.o -L/home/xmj/install/cap-llvm-3.4/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/4.6 -L/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/home/xmj/install/cap-llvm-3.4/lib -L/usr/lib/gcc/x86_64-linux-gnu/4.6/../../.. /tmp/cc9Ce7IE.o -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/gcc/x86_64-linux-gnu/4.6/crtend.o /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crtn.o ``` 这个跟使用`-v`所显示的内容差不多,区别在于使用`-###`是只打印,不实际执行具体的命令。手册里提到,它的一种用法,就是在脚本里使用这个选项,来获得 gcc 所调用的各个子命令行。 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html#Overall-Options) ## 贡献者 xmj --- url: /book/100-gcc-tips/003-print-level-options.md --- # 打印优化级别的对应选项 ## 例子 ``` $ gcc -Q --help=optimizers The following options control optimizations: -O -Ofast -Os -falign-functions [disabled] -falign-jumps [disabled] -falign-labels [disabled] -falign-loops [disabled] -fasynchronous-unwind-tables [enabled] -fbranch-count-reg [enabled] -fbranch-probabilities [disabled] -fbranch-target-load-optimize [disabled] -fbranch-target-load-optimize2 [disabled] -fbtr-bb-exclusive [disabled] -fcaller-saves [disabled] -fcombine-stack-adjustments [disabled] -fcommon [enabled] -fcompare-elim [disabled] -fconserve-stack [disabled] -fcprop-registers [disabled] -fcrossjumping [disabled] -fcse-follow-jumps [disabled] -fcx-fortran-rules [disabled] -fcx-limited-range [disabled] -fdata-sections [disabled] -fdce [enabled] -fdefer-pop [disabled] -fdelayed-branch [disabled] -fdelete-null-pointer-checks [enabled] -fdevirtualize [disabled] -fdse [enabled] -fearly-inlining [enabled] -fexceptions [disabled] -fexpensive-optimizations [disabled] -ffinite-math-only [disabled] -ffloat-store [disabled] -fforward-propagate [disabled] -fgcse [disabled] -fgcse-after-reload [disabled] -fgcse-las [disabled] -fgcse-lm [enabled] -fgcse-sm [disabled] -fgraphite-identity [disabled] -fguess-branch-probability [disabled] -fhandle-exceptions -fif-conversion [disabled] -fif-conversion2 [disabled] -finline-functions [disabled] -finline-functions-called-once [enabled] -finline-small-functions [disabled] -fipa-cp [disabled] -fipa-cp-clone [disabled] -fipa-matrix-reorg [disabled] -fipa-profile [disabled] -fipa-pta [disabled] -fipa-pure-const [disabled] -fipa-reference [disabled] -fipa-sra [disabled] -fivopts [enabled] -fjump-tables [enabled] -floop-block [disabled] -floop-flatten [disabled] -floop-interchange [disabled] -floop-parallelize-all [disabled] -floop-strip-mine [disabled] -flto-report [disabled] -fltrans [disabled] -fmath-errno [enabled] -fmerge-all-constants [disabled] -fmerge-constants [disabled] -fmodulo-sched [disabled] -fmove-loop-invariants [enabled] -fnon-call-exceptions [disabled] -fnothrow-opt [disabled] -fomit-frame-pointer [disabled] -foptimize-register-move [disabled] -foptimize-sibling-calls [disabled] -fpack-struct [disabled] -fpack-struct= -fpeel-loops [disabled] -fpeephole [enabled] -fpeephole2 [disabled] -fpredictive-commoning [disabled] -fprefetch-loop-arrays [enabled] -freg-struct-return [disabled] -fregmove [disabled] -frename-registers [enabled] -freorder-blocks [disabled] -freorder-blocks-and-partition [disabled] -freorder-functions [disabled] -frerun-cse-after-loop [disabled] -freschedule-modulo-scheduled-loops [disabled] -frounding-math [disabled] -frtti [enabled] -fsched-critical-path-heuristic [enabled] -fsched-dep-count-heuristic [enabled] -fsched-group-heuristic [enabled] -fsched-interblock [enabled] -fsched-last-insn-heuristic [enabled] -fsched-pressure [disabled] -fsched-rank-heuristic [enabled] -fsched-spec [enabled] -fsched-spec-insn-heuristic [enabled] -fsched-spec-load [disabled] -fsched-spec-load-dangerous [disabled] -fsched-stalled-insns [disabled] -fsched-stalled-insns-dep [enabled] -fsched2-use-superblocks [disabled] -fschedule-insns [disabled] -fschedule-insns2 [disabled] -fsection-anchors [disabled] -fsel-sched-pipelining [disabled] -fsel-sched-pipelining-outer-loops [disabled] -fsel-sched-reschedule-pipelined [disabled] -fselective-scheduling [disabled] -fselective-scheduling2 [disabled] -fshort-double [disabled] -fshort-enums [enabled] -fshort-wchar [disabled] -fsignaling-nans [disabled] -fsigned-zeros [enabled] -fsingle-precision-constant [disabled] -fsplit-ivs-in-unroller [enabled] -fsplit-wide-types [disabled] -fstrict-aliasing [disabled] -fstrict-enums [disabled] -fthread-jumps [disabled] -fno-threadsafe-statics [enabled] -ftoplevel-reorder [enabled] -ftrapping-math [enabled] -ftrapv [disabled] -ftree-bit-ccp [disabled] -ftree-builtin-call-dce [disabled] -ftree-ccp [disabled] -ftree-ch [disabled] -ftree-copy-prop [disabled] -ftree-copyrename [disabled] -ftree-cselim [enabled] -ftree-dce [disabled] -ftree-dominator-opts [disabled] -ftree-dse [disabled] -ftree-forwprop [enabled] -ftree-fre [disabled] -ftree-loop-distribute-patterns [disabled] -ftree-loop-distribution [disabled] -ftree-loop-if-convert [enabled] -ftree-loop-if-convert-stores [disabled] -ftree-loop-im [enabled] -ftree-loop-ivcanon [enabled] -ftree-loop-optimize [enabled] -ftree-lrs [disabled] -ftree-phiprop [enabled] -ftree-pre [disabled] -ftree-pta [enabled] -ftree-reassoc [enabled] -ftree-scev-cprop [enabled] -ftree-sink [disabled] -ftree-slp-vectorize [enabled] -ftree-sra [disabled] -ftree-switch-conversion [disabled] -ftree-ter [disabled] -ftree-vect-loop-version [enabled] -ftree-vectorize [disabled] -ftree-vrp [disabled] -funit-at-a-time [enabled] -funroll-all-loops [disabled] -funroll-loops [disabled] -funsafe-loop-optimizations [disabled] -funsafe-math-optimizations [disabled] -funswitch-loops [disabled] -funwind-tables [disabled] -fvar-tracking [enabled] -fvar-tracking-assignments [enabled] -fvar-tracking-assignments-toggle [disabled] -fvar-tracking-uninit [disabled] -fvariable-expansion-in-unroller [disabled] -fvect-cost-model [enabled] -fvpt [disabled] -fweb [enabled] -fwhole-program [disabled] -fwpa [disabled] -fwrapv [disabled] ``` ## 技巧 如上所示,使用`-Q --help=optimizers`选项可以打印出 gcc 的所有优化(相关的)选项,以及缺省情况下它们是否打开。类似的,你也可以查看不同优化级别下,这些优化选项是否打开: ``` $ gcc -Q --help=optimizers -O $ gcc -Q --help=optimizers -O1 $ gcc -Q --help=optimizers -O2 $ gcc -Q --help=optimizers -O3 $ gcc -Q --help=optimizers -Og $ gcc -Q --help=optimizers -Os $ gcc -Q --help=optimizers -Ofast ``` 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html#Overall-Options) ## 贡献者 xmj --- url: /book/100-gcc-tips/004-diagnostics-color.md --- # 打印彩色诊断信息 ## 技巧 这是 gcc-4.9 新增的功能,可以通过定义环境变量`GCC_COLORS`来彩色打印诊断信息。 也可以使用选项`-fdiagnostics-color`来设定。 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Language-Independent-Options.html#Language-Independent-Options) ## 贡献者 xmj --- url: /book/100-gcc-tips/005-print-header-search-dir.md --- # 打印头文件搜索路径 ## 例子 ``` $ gcc -v foo.c ... ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu" ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/4.6/../../../../x86_64-linux-gnu/include" #include "..." search starts here: #include <...> search starts here: /usr/lib/gcc/x86_64-linux-gnu/4.6/include /usr/local/include /usr/lib/gcc/x86_64-linux-gnu/4.6/include-fixed /usr/include/x86_64-linux-gnu /usr/include End of search list. ... ``` ## 技巧 如上所示,使用`-v`选项可以打印出 gcc 搜索头文件的路径和顺序。当然,也可以使用`-###`选项 ## 贡献者 xmj --- url: /book/100-gcc-tips/006-print-file-name.md --- # 打印连接库的具体路径 ## 例子 ``` $ gcc -print-file-name=libc.a /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/libc.a ``` ## 技巧 如上所示,使用`-print-file-name`选项就可以显示出 gcc 究竟会连接哪个 libc 库了。 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Developer-Options.html#index-print-file-name) ## 贡献者 xmj --- url: /book/100-gcc-tips/007-inhibit-linemarkers.md --- # 生成没有行号标记的预处理文件 ## 技巧 有时编译程序会遇到如下类似的错误, ``` In file included from foo.c:15, from a.h:45, b.h:53: error: ... ... ``` 如果错误是由于你所定义的一个很复杂的宏所引起的,你可能会需要先手动编译生成相应的预处理文件,查看下预处理文件中的宏扩展代码。比如,先运行 ``` gcc -E foo.c -o foo.i ``` 来生成 foo.i 预处理文件。然后,还可以尝试手动修改、编译这个预处理文件。 但是,由于生成的预处理文件中含有行号标记(linemarker),所以,运行 ``` gcc -c foo.i -o foo.o ``` 所得到的错误行号信息还是跟最初的一样,如果可以将预处理文件中的行号标记都去掉,似乎会有些帮助。 幸好,gcc 提供了这个选项: > -P > Inhibit generation of linemarkers in the output from the > preprocessor. This might be useful when running the preprocessor on > something that is not C code, and will be sent to a program which > might be confused by the linemarkers. 运行 ``` gcc -E -P foo.c -o foo.i ``` 即可。 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html#Preprocessor-Options) ## 贡献者 xmj --- url: /book/100-gcc-tips/008-define-macro.md --- # 在命令行中预定义宏 ## 例子 ``` #include int main (void) { int i, sum; for (i = 1, sum = 0; i <= 10; i++) { sum += i; #ifdef DEBUG printf ("sum += %d is %d\n", i, sum); #endif } printf ("total sum is %d\n", sum); return 0; } ``` ## 技巧 使用`-D`选项可以在命令行中预定义一个宏,比如: ``` $ gcc -D DEBUG macro.c ``` 中间可以没有空格: ``` $ gcc -DDEBUG macro.c ``` 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html#Preprocessor-Options) ## 贡献者 xmj --- url: /book/100-gcc-tips/009-undefine-macro.md --- # 在命令行中取消宏定义 ## 技巧 类似于`-D`选项,你可以使用`-U`选项在命令行中取消一个宏的定义,比如: ``` $ gcc -U DEBUG macro.c ``` 中间可以没有空格: ``` $ gcc -UDEBUG macro.c ``` 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html#Preprocessor-Options) ## 贡献者 xmj --- url: /book/100-gcc-tips/010-pass-options-to-assembler.md --- # 把选项传给汇编器 ## 例子 ``` #include int main(void) { int i; for (i = 0; i < 10; i++) printf("%d ", i); putchar ('\n'); return 0; } ``` ## 技巧 使用`-Wa,option`可以将选项`option`传递给汇编器。 注意,逗号和选项之间不能有空格。例如: ``` $ gcc -c -Wa,-L foo.c $ objdump -d foo.o foo.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000
    : 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: 48 83 ec 10 sub $0x10,%rsp 8: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp) f: eb 1b jmp 2c <.L2> 0000000000000011 <.L3>: 11: b8 00 00 00 00 mov $0x0,%eax 16: 8b 55 fc mov -0x4(%rbp),%edx 19: 89 d6 mov %edx,%esi 1b: 48 89 c7 mov %rax,%rdi 1e: b8 00 00 00 00 mov $0x0,%eax 23: e8 00 00 00 00 callq 28 <.L3+0x17> 28: 83 45 fc 01 addl $0x1,-0x4(%rbp) 000000000000002c <.L2>: 2c: 83 7d fc 09 cmpl $0x9,-0x4(%rbp) 30: 7e df jle 11 <.L3> 32: bf 0a 00 00 00 mov $0xa,%edi 37: e8 00 00 00 00 callq 3c <.L2+0x10> 3c: b8 00 00 00 00 mov $0x0,%eax 41: c9 leaveq 42: c3 retq ``` 这里的`-L`是汇编器 as 的选项,用于在目标文件中保留局部符号(local symbol)。可以看到,反汇编代码中给出了每个局部符号。 如果此时你使用`oprofile`来统计性能事件,那么获得的结果将不是以函数为单位了,而是以这些符号所划分的代码块为单位。 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Assembler-Options.html#Assembler-Options)和[as 手册](https://sourceware.org/binutils/docs-2.24/as/L.html#L) ## 贡献者 xmj --- url: /book/100-gcc-tips/011-verbose-asm.md --- # 生成有详细信息的汇编文件 ## 例子 ``` #include int main(void) { int i; for (i = 0; i < 10; i++) printf("%d ", i); putchar ('\n'); return 0; } ``` ## 技巧 使用`-fverbose-asm`选项就可以生成带有详细信息的汇编文件: ``` $ gcc -S -fverbose-asm foo.c $ cat foo.s .file "foo.c" # GNU C (Ubuntu/Linaro 4.6.3-1ubuntu5) version 4.6.3 (x86_64-linux-gnu) # compiled by GNU C version 4.6.3, GMP version 5.0.2, MPFR version 3.1.0-p3, MPC version 0.9 # GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072 # options passed: -imultilib . -imultiarch x86_64-linux-gnu foo.c # -mtune=generic -march=x86-64 -fverbose-asm -fstack-protector # options enabled: -fasynchronous-unwind-tables -fauto-inc-dec # -fbranch-count-reg -fcommon -fdelete-null-pointer-checks -fdwarf2-cfi-asm # -fearly-inlining -feliminate-unused-debug-types -ffunction-cse -fgcse-lm # -fident -finline-functions-called-once -fira-share-save-slots # -fira-share-spill-slots -fivopts -fkeep-static-consts # -fleading-underscore -fmath-errno -fmerge-debug-strings # -fmove-loop-invariants -fpeephole -fprefetch-loop-arrays # -freg-struct-return -fsched-critical-path-heuristic # -fsched-dep-count-heuristic -fsched-group-heuristic -fsched-interblock # -fsched-last-insn-heuristic -fsched-rank-heuristic -fsched-spec # -fsched-spec-insn-heuristic -fsched-stalled-insns-dep -fshow-column # -fsigned-zeros -fsplit-ivs-in-unroller -fstack-protector # -fstrict-volatile-bitfields -ftrapping-math -ftree-cselim -ftree-forwprop # -ftree-loop-if-convert -ftree-loop-im -ftree-loop-ivcanon # -ftree-loop-optimize -ftree-parallelize-loops= -ftree-phiprop -ftree-pta # -ftree-reassoc -ftree-scev-cprop -ftree-slp-vectorize # -ftree-vect-loop-version -funit-at-a-time -funwind-tables # -fvect-cost-model -fverbose-asm -fzero-initialized-in-bss # -m128bit-long-double -m64 -m80387 -maccumulate-outgoing-args # -malign-stringops -mfancy-math-387 -mfp-ret-in-387 -mglibc -mieee-fp # -mmmx -mno-sse4 -mpush-args -mred-zone -msse -msse2 -mtls-direct-seg-refs # Compiler executable checksum: 75e879ed14f91af504f4150eadeaa0e6 .section .rodata .LC0: .string "%d " .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp # .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp #, .cfi_def_cfa_register 6 subq $16, %rsp #, movl $0, -4(%rbp) #, i jmp .L2 # .L3: movl $.LC0, %eax #, D.2049 movl -4(%rbp), %edx # i, tmp62 movl %edx, %esi # tmp62, movq %rax, %rdi # D.2049, movl $0, %eax #, call printf # addl $1, -4(%rbp) #, i .L2: cmpl $9, -4(%rbp) #, i jle .L3 #, movl $10, %edi #, call putchar # movl $0, %eax #, D.2050 leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3" .section .note.GNU-stack,"",@progbits ``` 可以看到,在汇编文件中给出了 gcc 所使用的具体选项,以及汇编指令操作数所对应的源程序(或中间代码)中的变量。 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#index-fverbose-asm-2614) ## 贡献者 xmj --- url: /book/100-gcc-tips/012-address-sanitizer.md --- # 利用 Address Sanitizer 工具检查内存访问错误 ## 例子 ``` a.c: #include int main(void) { // your code goes here int a[3] = {0}; a[3] = 1; printf("%d\n", a[3]); return 0; } b.c: #include #include int main(void) { int *p = NULL; p = malloc(10 * sizeof(int)); free(p); *p = 3; return 0; } ``` ## 技巧 gcc 从`4.8`版本起,集成了`Address Sanitizer`工具,可以用来检查内存访问的错误(编译时指定“`-fsanitize=address`”)。以上面`a.c`程序为例: ``` gcc -fsanitize=address -g -o a a.c ``` 执行`a`程序: ``` [root@localhost nan]# ./a ================================================================= ==539==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fff3a152c9c at pc 0x4009b6 bp 0x7fff3a152c60 sp 0x7fff3a152c58 WRITE of size 4 at 0x7fff3a152c9c thread T0 #0 0x4009b5 in main /home/nan/a.c:6 #1 0x34e421ed1c in __libc_start_main (/lib64/libc.so.6+0x34e421ed1c) #2 0x4007b8 (/home/nan/a+0x4007b8) Address 0x7fff3a152c9c is located in stack of thread T0 at offset 44 in frame #0 0x400907 in main /home/nan/a.c:3 This frame has 1 object(s): [32, 44) 'a' <== Memory access at offset 44 overflows this variable HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext (longjmp and C++ exceptions *are* supported) SUMMARY: AddressSanitizer: stack-buffer-overflow /home/nan/a.c:6 main Shadow bytes around the buggy address: 0x100067422540: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100067422550: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100067422560: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100067422570: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x100067422580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 =>0x100067422590: f1 f1 00[04]f4 f4 f3 f3 f3 f3 00 00 00 00 00 00 0x1000674225a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000674225b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000674225c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000674225d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000674225e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Heap right redzone: fb Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack partial redzone: f4 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Contiguous container OOB:fc ASan internal: fe ==539==ABORTING ``` 可以看到,执行程序时检测出了`a`数组的越界访问(`a[3] = 1`)。 再看一下`b`程序: ``` gcc -fsanitize=address -g -o b b.c ``` 执行`b`程序: ``` [root@localhost nan]# ./b ================================================================= ==1951==ERROR: AddressSanitizer: heap-use-after-free on address 0x60400000dfd0 at pc 0x4007f9 bp 0x7fff34277bb0 sp 0x7fff34277ba8 WRITE of size 4 at 0x60400000dfd0 thread T0 #0 0x4007f8 in main /home/nan/b.c:9 #1 0x34e421ed1c in __libc_start_main (/lib64/libc.so.6+0x34e421ed1c) #2 0x400658 (/home/nan/b+0x400658) 0x60400000dfd0 is located 0 bytes inside of 40-byte region [0x60400000dfd0,0x60400000dff8) freed by thread T0 here: #0 0x7fbbb7a7d057 in __interceptor_free /opt/gcc-4.9.2/src/gcc-4.9.2/libsanitizer/asan/asan_malloc_linux.cc:62 #1 0x4007c1 in main /home/nan/b.c:8 #2 0x34e421ed1c in __libc_start_main (/lib64/libc.so.6+0x34e421ed1c) previously allocated by thread T0 here: #0 0x7fbbb7a7d26f in __interceptor_malloc /opt/gcc-4.9.2/src/gcc-4.9.2/libsanitizer/asan/asan_malloc_linux.cc:72 #1 0x4007b1 in main /home/nan/b.c:7 #2 0x34e421ed1c in __libc_start_main (/lib64/libc.so.6+0x34e421ed1c) SUMMARY: AddressSanitizer: heap-use-after-free /home/nan/b.c:9 main Shadow bytes around the buggy address: 0x0c087fff9ba0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9bb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9bc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9bd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9be0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x0c087fff9bf0: fa fa fa fa fa fa fa fa fa fa[fd]fd fd fd fd fa 0x0c087fff9c00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9c10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9c20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9c30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c087fff9c40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Heap right redzone: fb Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack partial redzone: f4 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Contiguous container OOB:fc ASan internal: fe ==1951==ABORTING ``` 执行程序时检测出了访问释放内存的错误(`*p = 3`)。\ 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Debugging-Options.html#index-fsanitize_003daddress-593) ## 贡献者 nanxiao --- url: /book/100-gcc-tips/013-thread-sanitizer.md --- # 利用 Thread Sanitizer 工具检查数据竞争的问题 ## 例子 ``` #include int Global; void *Thread1(void *x) { Global = 42; return x; } int main(void) { pthread_t t; pthread_create(&t, NULL, Thread1, NULL); Global = 43; pthread_join(t, NULL); return Global; } ``` ## 技巧 gcc 从`4.8`版本起,集成了`Address Sanitizer`工具,可以用来检查数据竞争的问题(编译时指定“`-fsanitize=thread -fPIE -pie`”)。以上面程序为例: ``` gcc -fsanitize=thread -fPIE -pie -g -o a a.c -lpthread ``` 执行`a`程序: ``` [root@localhost nan]# ./a ================== WARNING: ThreadSanitizer: data race (pid=14545) Write of size 4 at 0x7f055b4802b0 by thread T1: #0 Thread1 /home/nan/a.c:4 (a+0x000000000a87) Previous write of size 4 at 0x7f055b4802b0 by main thread: #0 main /home/nan/a.c:10 (a+0x000000000ae8) Location is global 'Global' of size 4 at 0x7f055b4802b0 (a+0x0000002012b0) Thread T1 (tid=14547, running) created by main thread at: #0 pthread_create /opt/gcc-4.9.2/src/gcc-4.9.2/libsanitizer/tsan/tsan_interceptors.cc:877 (libtsan.so.0+0x00000004aa83) #1 main /home/nan/a.c:9 (a+0x000000000ad9) SUMMARY: ThreadSanitizer: data race /home/nan/a.c:4 Thread1 ================== ThreadSanitizer: reported 1 warnings ``` 可以看到,执行程序时检测出了对`Global`变量的竞争访问。\ 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Debugging-Options.html#index-fsanitize_003dthread-595) ## 贡献者 nanxiao --- url: /book/100-gcc-tips/014-pass-options-to-linker.md --- # 把选项传给连接器 ## 例子 ``` #include int main (void) { puts ("Hello world!"); return 0; } ``` ## 技巧 使用`-Wl,option`可以将选项`option`传递给连接器。 注意,逗号和选项之间不能有空格。一种常见用法,就是让连接器生成内存映射文件,例如: ``` $ gcc -Wl,-Map=output.map foo.c $ cat output.map Archive member included because of file (symbol) /usr/lib/x86_64-linux-gnu/libc_nonshared.a(elf-init.oS) /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crt1.o (__libc_csu_init) Discarded input sections .note.GNU-stack 0x0000000000000000 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crt1.o .gnu_debuglink 0x0000000000000000 0xc /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crt1.o .note.GNU-stack 0x0000000000000000 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crti.o .gnu_debuglink 0x0000000000000000 0xc /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crti.o .note.GNU-stack 0x0000000000000000 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.6/crtbegin.o .note.GNU-stack 0x0000000000000000 0x0 /tmp/ccBOhdmq.o .note.GNU-stack 0x0000000000000000 0x0 /usr/lib/x86_64-linux-gnu/libc_nonshared.a(elf-init.oS) .note.GNU-stack 0x0000000000000000 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.6/crtend.o .note.GNU-stack 0x0000000000000000 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crtn.o .gnu_debuglink 0x0000000000000000 0xc /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crtn.o Memory map ** file header 0x0000000000400000 0x40 ** segment headers 0x0000000000400040 0x1f8 .interp 0x0000000000400238 0x1c ** fill 0x0000000000400238 0x1c .note.ABI-tag 0x0000000000400254 0x20 .note.ABI-tag 0x0000000000400254 0x20 /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crt1.o .note.gnu.build-id 0x0000000000400274 0x24 ** note header 0x0000000000400274 0x10 ** zero fill 0x0000000000400284 0x14 .dynsym 0x0000000000400298 0x78 ** dynsym 0x0000000000400298 0x78 .dynstr 0x0000000000400310 0x51 ** string table 0x0000000000400310 0x51 .gnu.hash 0x0000000000400368 0x1c ** hash 0x0000000000400368 0x1c .gnu.version 0x0000000000400384 0xa ** versions 0x0000000000400384 0xa .gnu.version_r 0x0000000000400390 0x20 ** version refs 0x0000000000400390 0x20 .rela.dyn 0x00000000004003b0 0x18 ** dynamic relocs 0x00000000004003b0 0x18 .rela.plt 0x00000000004003c8 0x30 ** dynamic relocs 0x00000000004003c8 0x30 .init 0x00000000004003f8 0x18 .init 0x00000000004003f8 0x9 /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crti.o 0x00000000004003f8 _init .init 0x0000000000400401 0x5 /usr/lib/gcc/x86_64-linux-gnu/4.6/crtbegin.o .init 0x0000000000400406 0x5 /usr/lib/gcc/x86_64-linux-gnu/4.6/crtend.o .init 0x000000000040040b 0x5 /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crtn.o .plt 0x0000000000400410 0x30 ** PLT 0x0000000000400410 0x30 .text 0x0000000000400440 0x1d8 .text 0x0000000000400440 0x2c /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crt1.o 0x0000000000400440 _start .text 0x000000000040046c 0x17 /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crti.o ** fill 0x0000000000400483 0xd .text 0x0000000000400490 0x92 /usr/lib/gcc/x86_64-linux-gnu/4.6/crtbegin.o .text 0x0000000000400522 0x15 /tmp/ccBOhdmq.o 0x0000000000400522 main ** fill 0x0000000000400537 0x9 .text 0x0000000000400540 0x92 /usr/lib/x86_64-linux-gnu/libc_nonshared.a(elf-init.oS) 0x0000000000400540 __libc_csu_init 0x00000000004005d0 __libc_csu_fini ** fill 0x00000000004005d2 0xe .text 0x00000000004005e0 0x36 /usr/lib/gcc/x86_64-linux-gnu/4.6/crtend.o ** fill 0x0000000000400616 0x2 .text 0x0000000000400618 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crtn.o .fini 0x0000000000400618 0xe .fini 0x0000000000400618 0x4 /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crti.o 0x0000000000400618 _fini .fini 0x000000000040061c 0x5 /usr/lib/gcc/x86_64-linux-gnu/4.6/crtbegin.o .fini 0x0000000000400621 0x5 /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crtn.o .rodata 0x0000000000400628 0x11 ** merge constants 0x0000000000400628 0x4 .rodata 0x000000000040062c 0xd /tmp/ccBOhdmq.o .eh_frame 0x0000000000400640 0xa4 ** eh_frame 0x0000000000400640 0xa0 .eh_frame 0x00000000004006e0 0x4 /usr/lib/gcc/x86_64-linux-gnu/4.6/crtend.o .eh_frame_hdr 0x00000000004006e4 0x2c ** eh_frame_hdr 0x00000000004006e4 0x2c .ctors 0x0000000000401e28 0x10 .ctors 0x0000000000401e28 0x8 /usr/lib/gcc/x86_64-linux-gnu/4.6/crtbegin.o .ctors 0x0000000000401e30 0x8 /usr/lib/gcc/x86_64-linux-gnu/4.6/crtend.o .dtors 0x0000000000401e38 0x10 .dtors 0x0000000000401e38 0x8 /usr/lib/gcc/x86_64-linux-gnu/4.6/crtbegin.o .dtors 0x0000000000401e40 0x8 /usr/lib/gcc/x86_64-linux-gnu/4.6/crtend.o 0x0000000000401e40 __DTOR_END__ .jcr 0x0000000000401e48 0x8 .jcr 0x0000000000401e48 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.6/crtbegin.o .jcr 0x0000000000401e48 0x8 /usr/lib/gcc/x86_64-linux-gnu/4.6/crtend.o .dynamic 0x0000000000401e50 0x190 ** dynamic 0x0000000000401e50 0x190 .got 0x0000000000401fe0 0x8 ** GOT 0x0000000000401fe0 0x8 .got.plt 0x0000000000401fe8 0x28 ** GOT PLT 0x0000000000401fe8 0x28 ** GOT IRELATIVE PLT 0x0000000000402010 0x0 ** GOT 0x0000000000402010 0x0 .data 0x0000000000402010 0x10 .data 0x0000000000402010 0x4 /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crt1.o 0x0000000000402010 data_start 0x0000000000402010 __data_start .data 0x0000000000402014 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crti.o .data 0x0000000000402018 0x8 /usr/lib/gcc/x86_64-linux-gnu/4.6/crtbegin.o 0x0000000000402018 __dso_handle .data 0x0000000000402020 0x0 /tmp/ccBOhdmq.o .data 0x0000000000402020 0x0 /usr/lib/x86_64-linux-gnu/libc_nonshared.a(elf-init.oS) .data 0x0000000000402020 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.6/crtend.o .data 0x0000000000402020 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crtn.o .bss 0x0000000000402020 0x10 .bss 0x0000000000402020 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crt1.o .bss 0x0000000000402020 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crti.o .bss 0x0000000000402020 0x10 /usr/lib/gcc/x86_64-linux-gnu/4.6/crtbegin.o .bss 0x0000000000402030 0x0 /tmp/ccBOhdmq.o .bss 0x0000000000402030 0x0 /usr/lib/x86_64-linux-gnu/libc_nonshared.a(elf-init.oS) .bss 0x0000000000402030 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.6/crtend.o .bss 0x0000000000402030 0x0 /usr/lib/gcc/x86_64-linux-gnu/4.6/../../../x86_64-linux-gnu/crtn.o .comment 0x0000000000000000 0x2b ** merge strings 0x0000000000000000 0x2b .note.gnu.gold-version 0x0000000000000000 0x1c ** note header 0x0000000000000000 0x10 ** fill 0x0000000000000010 0x9 ** zero fill 0x0000000000000019 0x3 .symtab 0x0000000000000000 0x390 ** symtab 0x0000000000000000 0x390 .strtab 0x0000000000000000 0x1d5 ** string table 0x0000000000000000 0x1d5 .shstrtab 0x0000000000000000 0x115 ** string table 0x0000000000000000 0x115 ``` 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html#Link-Options) ## 贡献者 xmj --- url: /book/100-gcc-tips/015-set-dynamic-linker.md --- # 设置动态连接器 ## 技巧 有人问我,如何通过选项来指定动态连接器,而不使用缺省系统自带的动态连接器。我后来查了下 ld 的手册,有这么一个选项: ``` -Ifile --dynamic-linker=file Set the name of the dynamic linker. This is only meaningful when generating dynamically linked ELF executables. The default dynamic linker is normally correct; don't use this unless you know what you are doing. ``` 看起来,可以通过如下方式来完成: ``` $ gcc foo.c -Wl,-I/home/xmj/tmp/ld-2.15.so $ ldd a.out linux-vdso.so.1 => (0x00007fffce5fe000) /usr/local/lib/libtrash.so (0x00007f1980477000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f19800a3000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f197fe9e000) /home/xmj/tmp/ld-2.15.so => /lib64/ld-linux-x86-64.so.2 (0x00007f1980485000) ``` 注意,tmp 目录下的动态连接器因为也是动态连接的,所以它本身是依赖系统缺省的动态连接器。 详情参见[ld 手册](https://sourceware.org/binutils/docs-2.24/ld/Options.html#Options) ## 贡献者 xmj --- url: /book/100-gcc-tips/016-must-emit-function-code.md --- # 禁止函数被优化掉 ## 例子 ``` #if (GCC_VERSION > 4000) #define DEBUG_FUNCTION __attribute__ ((__used__)) #define DEBUG_VARIABLE __attribute__ ((__used__)) #else #define DEBUG_FUNCTION #define DEBUG_VARIABLE #endif DEBUG_FUNCTION void debug_bb (basic_block bb) { dump_bb (bb, stderr, 0); } ``` ## 技巧 上面的例子是 gcc 的源码。使用 gcc 的扩展功能——函数属性`__attribute__ ((__used__))`,可以指定该函数是有用的,不能被优化掉。 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html#Function-Attributes) ## 贡献者 xmj --- url: /book/100-gcc-tips/017-must-forceinline-function-code.md --- # 强制函数永远以 inline 的形式调用 ## 例子 ``` #if defined(__GNUC__) #define FORCEDINLINE __attribute__((always_inline)) #else #define FORCEDINLINE #endif FORCEDINLINE int add(int a,int b) { return a+b; } ``` ## 技巧 上面的例子是 gcc 的源码。使用 gcc 的扩展功能——函数属性`__attribute__ ((always_inline))`,可以指定该函数永远以 inline 的形式调用 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Inline.html) ## 贡献者 mengke --- url: /book/100-gcc-tips/018-cast-lose-precision.md --- # error: cast from ... to ... loses precision ## 例子 ``` #include class Foo { public: void print() const { std::cout << (int)(this) << "\n"; } }; int main() { class Foo foo; foo.print(); return 0; } ``` ## 技巧 在 g++编译上面的例子,会报如下错误: ``` $ g++ foo.cc foo.cc: In member function ‘void Foo::print() const’: foo.cc:6:28: error: cast from ‘const Foo*’ to ‘int’ loses precision [-fpermissive] ``` 这是一个强制类型转换的错误,你可以修改源代码为: ``` std::cout << (int*)(this) << "\n"; ``` 即可。 如果,你不想(或不能)去修改源程序,只是应为升级了 gcc 而带来了这样的错误,那么也可以使用`-fpermissive`选项,将错误降低为警告: ``` $ g++ foo.cc -fpermissive foo.cc: In member function ‘void Foo::print() const’: foo.cc:6:28: warning: cast from ‘const Foo*’ to ‘int’ loses precision [-fpermissive] ``` 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Dialect-Options.html#index-fpermissive-166) ## 贡献者 xmj --- url: /book/100-gcc-tips/019-warnings-treated-as-errors.md --- # all warnings being treated as errors ## 技巧 在 ubuntu 系统下编译一个程序包,有时会遇到这样的错误: ``` $ make ... cc1: all warnings being treated as errors ``` 这是因为缺省的 CFLAGS 里含有`-Werror`选项,将警告信息升级为错误。当然,一方面这可以让你重视这些可能会带来隐患的警告信息;但,如果你不想修改源码,也可以把这个选项关掉,通过修改 Makefile 或者使用命令行: ``` $ make CFLAGS="... -Wno-error" ``` 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html#Preprocessor-Options) ## 贡献者 xmj --- url: /book/100-gcc-tips/021-syntax-only.md --- # 只做语法检查 ## 例子 ``` $ cat foo.c union u { char c; int i; } $ gcc -fsyntax-only foo.c foo.c:4:1: error: expected identifier or ‘(’ at end of input ``` ## 技巧 如上所示,使用`-fsyntax-only`选项可以只做语法检查,不进行实际的编译输出。 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-fsyntax-only-274) ## 贡献者 xmj --- url: /book/100-gcc-tips/022-save-temps.md --- # 保存临时文件 ## 例子 ``` $ gcc -save-temps a/foo.c $ ls foo.* foo.c foo.i foo.o foo.s $ gcc -save-temps=obj a/foo.c -o a/foo $ ls a foo foo.c foo.i foo.o foo.s ``` ## 技巧 如上所示,使用选项`-save-temps`可以保存 gcc 运行过程中生成的临时文件。这些中间文件的名字是基于源文件而来,并且保存在当前目录下。 如果你在不同目录下有重名的源文件,那么中间文件就会有冲突了。此时,你可以使用`-save-temps=obj`来指定中间文件名基于目标文件而定,并保存在目标文件所在目录下。 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html#Debugging-Options) ## 贡献者 xmj --- url: /book/100-gcc-tips/023-turn-on-warnings.md --- # 打开警告信息 ## 技巧 你的程序编译通过了,但并不意味着已经万事大吉,也许还存在一些不规范的地方,或者一些错误隐患。建议,使用`-Wall`选项打开所有的警告信息,把所有的警告都处理掉。 ``` $ gcc -Wall ... ``` 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html#Preprocessor-Options) ## 贡献者 xmj --- url: /book/100-gcc-tips/024-specify-language.md --- # 指定语言类型 ## 技巧 gcc 是通过文件名后缀来判断源代码语言类型的。 如果你从标准输入把源码传给 gcc,那么就需要通过`-x`选项显式的指定语言类型: ``` $ echo "int x;" | gcc -S -x c - $ cat ./-.s .file "" .comm x,4,4 .ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3" .section .note.GNU-stack,"",@progbits ``` 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html#Overall-Options) ## 贡献者 xmj --- url: /book/100-gcc-tips/025-pack-struct.md --- # 改变结构体成员的字节对齐 ## 例子 ``` #include typedef struct { char a; int b; } ST_A; int main(void) { printf("sizeof(ST_A)=%ld\n",sizeof(ST_A)); } ``` ## 技巧 在上面的程序里,`ST_A`结构体的内存布局默认是这样的:
    Offset 1byte 1byte 1byte 1byte
    0 a 填充字节 填充字节 填充字节
    4 b b b b
    编译执行,结果如下: ``` root@ubuntu:~$ gcc -g -o a a.c root@ubuntu:~$ ./a sizeof(ST_A)=8 ``` 使用 gcc 的"`-fpack-struct[=n]`"选项(“`n`”需要为`2`的倍数)可以改变成员的地址对齐。例如指定“`n=2`”时,将标明结构体成员的最大对齐地址为 2。这样`ST_A`结构体中的成员`b`的地址将不再按照`4`字节对齐,内存布局变为:
    Offset 1byte 1byte 1byte 1byte
    0 a 填充字节 b b
    4 b b
    编译执行,结果如下: ``` root@ubuntu:~$ gcc -g -fpack-struct=2 -o a a.c root@ubuntu:~$ ./a sizeof(ST_A)=6 ``` 当不指定“`n`”时,将没有填充字节,所有成员将一个挨着一个排在一起:
    Offset 1byte 1byte 1byte 1byte
    0 a b b b
    4 b
    编译执行,结果如下: ``` root@ubuntu:~$ gcc -g -fpack-struct -o a a.c root@ubuntu:~$ ./a sizeof(ST_A)=5 ``` 由于这个编译选项会导致 ABI(Application Binary Interface)的改变,所以使用时一定要谨慎。 详情参见[gcc 手册](https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html) ## 贡献者 nanxiao --- url: /book/100-gcc-tips/index.md --- # 一些 gcc 技巧 ## 信息显示 * [打印 gcc 预定义的宏信息](/book/100-gcc-tips/001-print-predefined-macros.md) * [打印 gcc 执行的子命令](/book/100-gcc-tips/002-print-commands-only.md) * [打印优化级别的对应选项](/book/100-gcc-tips/003-print-level-options.md) * [打印彩色诊断信息](/book/100-gcc-tips/004-diagnostics-color.md) * [打印头文件搜索路径](/book/100-gcc-tips/005-print-header-search-dir.md) * [打印连接库的具体路径](/book/100-gcc-tips/006-print-file-name.md) ## 预处理 * [生成没有行号标记的预处理文件](/book/100-gcc-tips/007-inhibit-linemarkers.md) * [在命令行中预定义宏](/book/100-gcc-tips/008-define-macro.md) * [在命令行中取消宏定义](/book/100-gcc-tips/009-undefine-macro.md) ## 汇编 * [把选项传给汇编器](/book/100-gcc-tips/010-pass-options-to-assembler.md) * [生成有详细信息的汇编文件](/book/100-gcc-tips/011-verbose-asm.md) ## 调试 * [利用 Address Sanitizer 工具检查内存访问错误](/book/100-gcc-tips/012-address-sanitizer.md) * [利用 Thread Sanitizer 工具检查数据竞争的问题](/book/100-gcc-tips/013-thread-sanitizer.md) ## 连接 * [把选项传给连接器](/book/100-gcc-tips/014-pass-options-to-linker.md) * [设置动态连接器](/book/100-gcc-tips/015-set-dynamic-linker.md) ## 函数属性 * [禁止函数被优化掉](/book/100-gcc-tips/016-must-emit-function-code.md) * [强制函数 inline](/book/100-gcc-tips/017-must-forceinline-function-code.md) ## 常见错误 * [error: cast from ... to ... loses precision](/book/100-gcc-tips/018-cast-lose-precision.md) * [all warnings being treated as errors](/book/100-gcc-tips/019-warnings-treated-as-errors.md) * \[gdb 无法调试 gcc 编译的程序] ## 其它 * [只做语法检查](/book/100-gcc-tips/021-syntax-only.md) * [保存临时文件](/book/100-gcc-tips/022-save-temps.md) * [打开警告信息](/book/100-gcc-tips/023-turn-on-warnings.md) * [指定语言类型](/book/100-gcc-tips/024-specify-language.md) * [改变结构体成员的字节对齐](/book/100-gcc-tips/025-pack-struct.md) --- url: /book/index.md --- --- url: /index.md --- --- url: /project/asr/index.md --- --- url: /project/assistant/index.md --- --- url: /project/index.md ---