TCP程序设计(二)
在上一次编写的程序中,当客户端连接上服务器端,那么服务器端将会阻塞在读客户端数据那个地方,而此时其他客户端想要连接服务器就不会得到响应,在前面的实验IO编程那块地方,用到过select这个函数。再重新介绍一下I/O处理模型。
1、阻塞I/O模型在所调用的I/O函数没有完成相关 功能,则会使进程挂起,直到相关数据到达才会返回。对管道设备,终端设备和网络进行读写时进程会出现这种情况。
2、非阻塞I/O模型
进程把一个套接口设置成非阻塞是在通 知内核:当所请求的I/O操作非得把本进程投入睡眠才能完成时,不要把本进程投入睡眠,而是返回一个错误。也就是说当数据没有到达时并不等待,而是以一个 错误返回。
3、I/O复用模型
调用select或poll,在这两个 系统调用中的某一个上阻塞,而不是阻塞于真正I/O系统调用。 阻塞于select调用,等待数据报套接口可读。当select返回套接口可读条件时,调 用recevfrom将数据报拷贝到应用缓冲区中。
4、信号驱动I/O模型
首先开启套接口信号驱动I/O功能, 并通过系统调用sigaction安装一个信号处理函数(此系统调用立即返回,进程继续工作,它是非阻塞的)。当数据报准备好被读时,就为该进程生成一个 SIGIO信号。随即可以在信号处理程序中调用recvfrom来读数据报,井通知主循环数据已准备好被处理中。也可以通知主循环,让它来读数据报。
5、异步I/O模型
告知内核启动某个操作,并让内核在整 个操作完成后(包括将数据从内核拷贝到用户自己的缓冲区)通知我们。
现在用到的是I/O复用模型。大家可以把上一次我贴上的代码运行的试一试,当服务器端阻塞时,其他客户端是连不上的。现在将程序修改成如下形似:
先贴出select函数的介绍:
具体用法还算看前面的文件I/0编程里的介绍。
服务器端程序:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
//int socket(int domain, int type, int protocol);
#define PORT 2222
int main(int argc,char *argv[]){
int sockfd,new_sockfd; //套接字描述符
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
char buf[1024];
int res = 0;
int fd_set_usr;
socklen_t num = 0;
fd_set sockfd_set,tmp_sockfd_set; //处理的文件描述符集合
FD_ZERO(&sockfd_set); //集合清0
//创建套接字
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0){
perror("socket error\n");
exit(1);
}
//绑定地址端口
bzero(&server_addr,sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr)) < 0){
perror("bind error\n");
exit(1);
}
printf("bind ok!!!\n");
if(listen(sockfd,10) < 0){
perror("listen error\n");
exit(1);
}
FD_SET(sockfd,&sockfd_set); //将套接字描述符添加到集合之中
/*
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
*/
while(1){
tmp_sockfd_set = sockfd_set;
num = sizeof(struct sockaddr_in);
memset(buf,0,sizeof(buf));
if(select(FD_SETSIZE,&tmp_sockfd_set,NULL,NULL,NULL) < 0){
perror("select error\n");
exit(1);
}
for(fd_set_usr = 0;fd_set_usr < 10;fd_set_usr++){
if(FD_ISSET(fd_set_usr,&tmp_sockfd_set) > 0){
if(fd_set_usr == sockfd){ //服务器连接请求
//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
if((new_sockfd = accept(sockfd,(struct sockaddr*)&client_addr,&num)) < 0){
perror("accept error\n");
exit(1);
}
FD_SET(new_sockfd,&sockfd_set); //添加描述符
printf("new connect ::: %s\n",inet_ntoa(client_addr.sin_addr));
}//服务器端数据处理结束
else{
if((res = recv(new_sockfd,buf,1024,0)) <= 0){
close(fd_set_usr);
FD_CLR(fd_set_usr,&sockfd_set);
printf("client %d has left\n",fd_set_usr);
}
else{
printf("-->%s from %d\n",buf,fd_set_usr);
}
}//客户端描述符处理结束
}//有文件描述符准备好,处理结束
}//for 结束
}//while结束
close(sockfd);
exit(0);
}
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <netdb.h> #include <netinet/in.h> //int socket(int domain, int type, int protocol); #define PORT 2222 int main(int argc,char *argv[]){ int sockfd; //套接字描述符 struct sockaddr_in server_addr; char buf[1024] = "hello world"; struct hostent *host; if(argc != 2){ printf("please enter xxx.xxx.xxx.xxx\n"); exit(1); } //struct hostent *gethostbyname(const char *name); if((host = gethostbyname(argv[1])) == NULL){ perror("gethostbyname error\n"); exit(1); } //创建套接字 if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0){ perror("socket error\n"); exit(1); } //绑定地址端口 /* int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); */ bzero(&server_addr,sizeof(struct sockaddr_in)); server_addr.sin_family = AF_INET; server_addr.sin_addr = *((struct in_addr*)(host->h_addr)); server_addr.sin_port = htons(PORT); if(connect(sockfd,(struct sockaddr*)(&server_addr),sizeof(struct sockaddr_in)) < 0){ perror("connect error\n"); exit(1); } write(sockfd,buf,1024); sleep(30); close(sockfd); exit(0); }
开3个终端做实验,两个客户端都去连接服务器:下面是效果截图
Linux网络编程--UDP程序设计
UDP协议是无连接的,不可靠传输的协议. 服务器与客户端的交互不需要建立连接,没有流量控制的功能。与TCP一样,它也是传输层协议,通信过程中需要IP地址与端口号。使用UDP进行程序设计包括服务器与客户端,下面介绍一下服务器与客户端的通信流程:
服务器流程:
(1)建立服务器套接字描socket
(2)将地址结构绑定到套接字上 bind
(3)数据传输 sendto/recvfrom (不需要连接,所心没有监听)
(4)关闭套接字close
客户端流程:
(1)建立客户端套接字 socket
(2)设置服务器端口
(3)数据传输 sendto/recvfrom
(4)关闭套接字close
可以看出,与TCP通信相比,UDP服务器没有监听端口与等待连接的过程,而客户端没有建立连接的过程.
2. 相关函数
int socket(int domain,int type,int protocol);
type: SOCK_DGRAM 数据报
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int s,void*buf,size_t len,int flags,struct sockaddr* from,socklen_t *fromlen);
参数:
s-套接字描述符
buf-接收数据的缓冲区大小
len-接收数据的缓冲区长度
flags-接收数据的标志
from-客户端或者是服务器的地址
fromlen-客户端或者是服务器的地址长度指针
返回值:
成功返回接收的字节数,发生错误时返回-1.
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int s,const void*buf,size_t len,int flags,const struct sockaddr*to,socklen_t tolen);
参数:
s-套接字描述符
buf-发送缓冲区指针,const类型,由于发送的时候已经把数据写入buf了.
len-发送数据的实际长度
flags-发送数据的标志
to-发送的目的地址结构
tolen-目的地址结构长度
返回值:
成功返回发送的字节数,失败返回-1. 返回0也是合法的表示没有接收到数据。
liklon@liklon-laptop:~$ man socket liklon@liklon-laptop:~$ man bind liklon@liklon-laptop:~$ man recvfrom liklon@liklon-laptop:~$ man inet_aton liklon@liklon-laptop:~$ man gethostbyname liklon@liklon-laptop:~$ man socket liklon@liklon-laptop:~$ man sendto liklon@liklon-laptop:~$
可以通过上面方式查看具体的函数用法。
服务器端程序:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <errno.h> #include <sys/socket.h> #include <netinet/in.h> #define PORT 2222 //int socket(int domain, int type, int protocol); int main(int argc,char *argv[]){ int sockfd; struct sockaddr_in server_addr,client_addr; char buf[1024]; socklen_t res; int real_read; //建立套接字 if((sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0){ perror("socket error\n"); exit(1); } bzero(&server_addr,sizeof(struct sockaddr_in)); server_addr.sin_port = htons(PORT); server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_family = AF_INET; //int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen); if(bind(sockfd,(struct sockaddr*)(&server_addr),sizeof(struct sockaddr_in)) < 0){ perror("bind error\n"); exit(1); } /* ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); */ res = sizeof(struct sockaddr); while(1){ if((real_read = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr*)(&client_addr),&res)) < 0){ perror("recvfrom error\n"); exit(1); } buf[real_read] = '\0'; printf("-->%s\n",buf); }//while1结束 close(sockfd); exit(0); }
服务器端负责接受数据显示。
客户端程序:
#include <string.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #define PORT 2222 int main(){ int sockfd; struct sockaddr_in server_addr; char buf[1024] = "eepw-->liklon\n"; if((sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0){ perror("socket error\n"); exit(1); } bzero(&server_addr,sizeof(struct sockaddr_in)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); server_addr.sin_addr.s_addr = INADDR_ANY; if(sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr*)(&server_addr),sizeof(struct sockaddr_in)) < 0){ perror("sendto error\n"); exit(1); } sleep(30); close(sockfd); exit(0); }
客户端负责发送数据。
下面是运行的效果图:
接楼上
//服务器端代码 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <netinet/in.h> #define PORT 2222 void main(){ int sockfd; struct sockaddr_in server_addr; struct sockaddr_in client_addr; char buf[1024]; int real_read,res; if((sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0){ perror("socket error\n"); exit(1); } fprintf(stderr,"socket ok!-->%d\n",sockfd); //socket OK --> bind bzero(&server_addr,sizeof(struct sockaddr_in)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); server_addr.sin_addr.s_addr = INADDR_ANY; if(bind(sockfd,(struct sockaddr*)(&server_addr),sizeof(struct sockaddr_in)) < 0){ perror("bind error\n"); exit(1); } fprintf(stderr,"bind ok!\n"); //bind ok --> recvfrom while(1){ bzero(buf,sizeof(buf)); res = sizeof(struct sockaddr); if((real_read = recvfrom(sockfd,buf,1024,0,(struct sockaddr*)(&client_addr),&res)) < 0){ perror("recvfrom error\n"); exit(1); } fprintf(stderr,"recvfrom --> %s-->%s\n",inet_ntoa(client_addr.sin_addr),buf); //printf ip and buf }//while close(sockfd); exit(0); }//main end
//客户端代码 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <netinet/in.h> #define PORT 2222 void main(int argc,char *argv[]){ int sockfd; struct hostent *host; struct sockaddr_in server_addr; int res; //struct hostent *gethostbyname(const char *name); if(argc != 3){ fprintf(stderr,"please enter xxx xxx-->\n"); exit(1); } if((host = gethostbyname(argv[1])) == NULL){ perror("gethostbyname error\n"); exit(1); } if((sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0){ perror("socket error\n"); exit(1); } fprintf(stderr,"socket ok!-->%d\n",sockfd); //socket OK --> bind bzero(&server_addr,sizeof(struct sockaddr_in)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); server_addr.sin_addr = *(struct in_addr*)(host->h_addr); if(sendto(sockfd,argv[2],strlen(argv[2]),0,(struct sockaddr*)(&server_addr),sizeof(struct sockaddr_in)) < 0){ perror("sendto error\n"); exit(1); } sleep(30); close(sockfd); exit(0); }//main end
运行结果,开三个终端:
liklon@liklon-laptop:/usr/local/linux_test/udp_test$ ./client 127.0.0.22 eepw socket ok!-->3 liklon@liklon-laptop:/usr/local/linux_test/udp_test$
liklon@liklon-laptop:/usr/local/linux_test/udp_test$ ./client 127.0.0.22 eepw socket ok!-->3 liklon@liklon-laptop:/usr/local/linux_test/udp_test$
liklon@liklon-laptop:/usr/local/linux_test/udp_test$ ./server socket ok!-->3 bind ok! recvfrom --> 127.0.0.1-->liklon recvfrom --> 127.0.0.22-->eepw
(1)装载/存储体系
只有装载和存储指令可以访问内存,这意味着数据处理操作 只能使用寄存器,操作前要从内存装载数据到寄存器,操作后把数据保存回内存。这并不会显著降低执行效率,因为大部分操作需要很多指令来进行所需的运算,而 这些指令都能很快地运行(只需要访问寄存器),不会因外部存储器访问而降低速度。
(2)32位定长指令
所有指令的长度都是32位的,所以处理器只需要一个指令周期就可以从存储器中取得任何指令。而且,指令在内存中都以字对齐方式存储,这样程序计数器(r15)的最低两位总是设置为0。
(3)32位和8位数据
所有ARM处理器都有处理32位字和8位字节的指令,字总是对齐4字节边界的;实现了第四版ARM体系的处理器还有装载和存储半字的指令。
(4)32位地址
第一版和第二版的ARM处理器只有26位的地址空间,更高版本的ARM处理器采用32位地址。第三版和第四版的ARM处理器(不包括4T)具有使用26位地址的向后兼容性。
(5)37个寄存器
30个通用寄存器,其中15个总是可见的
6个状态寄存器,其中在任何时刻有1或者2个是可见的
1个程序计数器
分组寄存器的设置使得处理异常和特权操作时的上下文切换可以迅速进行。
(6)灵活的多数据装载和存储指令
ARM的多数据装载和存储指令可以用单个指令实现某个寄存器分组中任何寄存器组合与存储器间的数据传输。
(7)没有直接往寄存器存入32位值的指令
一般来说,字面值必须从内存载入到寄存器中。然而,很多32位值可以在单个指令中生成(作为立即数使用)。
(8)条件执行
所有指令都可以根据CPSR的状态而有条件地执行,只有设置了S位的指令才能改变CPSR的状态。
(9)强大的桶形移位器
所有数据处理和单数据传输操作的第二个操作数,都可以在操作进行前以一种通用的方式进行移位,从而可以在单个指令内支持(但不限于)地址缩放、乘以一个小常量、构造常量等。
(10)协处理器指令
它提供了一种通用的以用户特定方式扩展ARM体系的方法。
2 汇编模块的结构
下面的例子展示了ARM汇编模块的核心构成:
2.1 AREA伪指令
段(area)是供链接器操作的数据或者代码块。程序由一个或者多个段构成。上面的例子由单个包含代码、标记为只读的段构成。单个代码段是构成程序的最小需求。
2.2 ENTRY伪指令
程序中第一条被执行的指令由ENTRY伪指令标识。程序只能有单个入口点,所以在有多个源代码模块的程序中,只能有一个模块含有ENTRY伪指令。注意当程序含有C代码的时候,入口点一般在C库文件中。
2.3 一般布局
汇编模块中一行代码的格式一般为:
标签 <空白> 指令 <空白> ; 注释
要注意的是,三个部分之间至少由一个空白字符(比如说空格或者制表符)分隔。实际指令从不会从第一列开始,因为指令前必须留有空白,即使没有标签也是。三个部分都是可选的,汇编器接受空白行,这可以让代码看起来更清晰。
------
4 ARM的桶形移位器
ARM核内嵌一个桶形移位器,可以进行各种类型的移位或者循环移位操作。各种类型的ARM指令都可以使用它来在单个指令中完成相对复杂的操作。使用桶形移位器指令的执行时间不会变长,除非是要移动的位数由某个寄存器指定,这时候要多耗费一个执行周期。
桶形移位器可以进行下列类型的操作:
LSL 逻辑左移n位(乘以2的n次方)
ASL 算术左移n位(乘以2的n次方)
LSR 逻辑右移n位(无符号数除以2的n次方)
ASR 算术右移n位,操作数最高位保持不变(即是带符号扩展的移位,有符号数除以2的n次方)
ROR 循环右移n位
RRX 带扩展的循环右移(1位)。这是种33位的循环移位,第33位是PSR的进位标志。
4.2 单数据传输指令
单数据传输指令LDR从内存装载数据到单个寄存器;STR把单个寄存器内的数据保存到内存。这两条指令使用一个基地址寄存器(下面例子中是r0)和一个索引(或者偏移量),索引可以是一个寄存器移动0--31位(5位常量),或者是某个12位常量。
STR r7,[r0],#24 ;后变址(Post-indexed)
LDR r2,[r0],r4,ASR #4 ;后变址(Post-indexed)
STR r3,[r0,r5,LSL #3] ;前变址(Pre-indexed)
LDR r6,[r0,r1,ROR #6]! ;前变址加写回
在前变址(pre-indexed)指令中,计算出偏移量后,加到基地址上,然后使用结果地址进行传输操作。如果选择了写回,传输地址将在传输完成后写回到基地址寄存器。
在后变址(post-indexed)指令中,计算偏移量并加到基地址上,进行数据传输,传输完成后基地址寄存器总是被更新(为数据传输地址)。
4.3 程序状态寄存器传输指令
使用如下形式的MSR指令可以修改程序状态寄存器的N,Z,C,V标志:
MSR cpsr_flag,#expression_r; or spsr_flag in privileged mode
汇编器将试图生成一个8位常量(0--255)的移位值来匹配表达式,表达式的最高4位将被装载到PSR的最高4位,PSR的控制位不受影响。用户模式下只能修改CPSR的这个标志位。
MOV/MVN
可以直接装载一些特定范围的32位值到寄存器中,这些值包括:
(1) 8位常量,即0--255
(2) 8位常量右移偶数位
(3) MVN可以处理(1)(2)中值的按位取反值
如果MOV/MVN指令中给出的立即数常量不在上述范围内,则汇编器会报错。
2. LDR Rd,=字面数值常量
可以装载任何32位值到寄存器中,汇编器先试图用MOV/MVN指令来处理给出的字面数值常量;如果不能处理,再用文字池来处理,即把常量放在文字池中,然后从文字池中取得所需的常量
3. ADR 指令计算给定的PC相关的表达式相对于PC的偏移量,然后试图生成一条指令来进行地址装载,可以处理的地址范围是255字节(非字对齐的地址)或者 1020字节(字对齐的地址,255字)以内。如果给定的表达式表示的地址相对于PC的偏移量不在这个范围内,则汇编器会报错。
4. ADRL 指令计算给定的PC相关的表达式相对于PC的偏移量,然后试图生成两条指令来进行地址装载,可以处理的地址范围是64K字节(非字对齐的地 址,255*255)或者256K字节(字对齐的地址,64K*4)以内。如果给定的表达式表示的地址相对于PC的偏移量不在这个范围内,则汇编器会报 错。
5. LDR Rd,=相对于PC的表达式
指令会计算表达式表示的地址,然后在文字池中放入这个地址值,从文字池中加载地址。注意:汇编后的代码总是从文字池加载地址,不会用相对于PC的偏移量来 表示地址。这样执行时间会较长(需要访问文字池),所以应该尽量使用ADR或者ADRL,只有在不能使用ADR和ADRL时,才使用LDR。
6. 关于调试
发现文档中说的用armsd进行调试的方法,对本章的只有汇编代码的程序不管用,大概是armsd的版本问题。但这一章涉及的程序都可以用ADS 1.2版本中的AXD调试器进行调试,还是图形界面的,比较好用。如果要在调试器中看到源代码,需要的armcc或者armasm的命令行中加入-g参 数,表示需要调试信息。
5 装载常量到寄存器
5.1 为什么装载常量是个问题?
因为所有的ARM指令都是32位长的,而且不把指令流作为数据使用,所以单个指令是没有办法在不从内存加载数据的情况下,把任何32位立即数常量装载到寄存器中的。
虽然数据装载可以把任何32位值装入寄存器中,但还有更直接,也更有效的方法来装载很多常用的常量。
5.2 使用MOV/MVN直接装载
MOV指令可以直接把8位常量(0--255)装载到寄存器中;MVN可以把这些值的按位取反值(0xFFFFFF00到0xFFFFFFFF)装载到寄存器中。
把MOV和MVN与桶形移位器结合使用可以构造更多的常量。可以构造的常量都是8位值循环右移偶数位的结果。例如:
0--255 0--0xFF 不移位
256,260,264,...,1016,1020 0x100--0x3FC之间步进4,循环右移30位
1024,1040,1056,...4080 0x400--0xFF0之间步进16,循环右移28位
等等,以及这些值的按位取反值。可以直接使用指令把这些常量装载到寄存器中:
MOV r0,#0xFF ;r0=255
MOV r0,#0x1,30 ;r0=1020
MOV r0,#0xFF,28 ;r0=4080
然而,把常量转化成这种形式是不太容易的。汇编器会试图进行这种转化,如果转化不能进行,汇编器会报错。
5.3 使用LDR Rd,=字面数常量进行直接装载
汇编器提供不同于MOV和MVN的,不需要数据处理操作的,可构造任何32位数值常量的方法,这就是LDR Rd,=指令。
如果LDR Rd,=指令中给出的常量可以用MOV或者MVN构造,汇编器将使用MOV或者MVN,否则将生成一个带相对PC地址的LDR指令来从文字池中读取所需的 常量。文字池是一块为常量分配的内存。通常,在每个END伪指令后面会有一个文字池。然而在较大的程序中,它却可能是不能访问到的(因为LDR指令中的偏 移量是12位值,只能表示4KB的范围)。这时可以使用LTORG伪指令。
当LDR Rd,=指令要访问文字池中的常量时,汇编器先检查在当前文字池是否可以访问到,池中是否有所需要的常量。如果是,则对已存在的常量进行编址,否则试图把 常量放到下一个文字池中。如果下一个文字池不可访问(因为不存在或者到它的距离超过4KB),则汇编器会报错,这时应该在LDR Rd,=指令后较近的地方放置一个LTORG伪指令。
6 装载地址到寄存器
装载某个地址到寄存器的操作是很常见的,例如装载代码段中的字符串常量或者跳转表的起始地址到寄存器中。然而由于ARM代码是可重定位的,以及可以直接装 载到寄存器中的值是有限的,这时不能使用绝对地址。这时应该用相对于当前PC的偏移量来表示地址,可以直接用当前PC和合适的偏移量来表示,或者可以从文 字池中装载。
6.1 ADR和ADRL伪指令
从效率方面考虑,不需要内存访问的地址装载是很重要的,为此汇编器提供了ADR和ADRL伪指令。ADR和ADRL接受一个PC相关的表达式(同一代码段中的标签)并计算到达指定地方的偏移量。
ADR试图用与LDR Rd,=指令相同的机制来生成单个指令进行地址装载操作。如果指定地址不能在单个指令中构造,汇编器会报错。通常对于非字对齐的地址,偏移量范围是255字节以内;对于字对齐的地址,偏移量范围是1020字节(255字)。
ADRL试图用两条数据处理指令进行地址装载操作。即使可以用单个指令完成操作,第二条冗余的指令也还是会生成。如果不能用两条指令完成操作,汇编器会报 错,这时LDR Rd,=可能是最好的选择。通常对于非字对齐的地址,ADRL可以处理的偏移量范围是64K字节以内;对于字对齐的地址可以处理的范围是256K字节。
6.2 LDR Rd,=相对于PC的表达式
与数常量一样,LDR Rd,=也可以处理相对于PC的表达式,如标签。即使可以用ADD或者SUB来构造所需的地址,也还是会生成LDR指令来装载相对于PC的表达式。
7 跳转表
程序经常需要根据某种条件来在一些操作中选择一个来执行。C语言中常用switch语句来实现这个功能。汇编语言则用跳转表来实现。
8 使用多数据装载/存储指令
8.1 多数据传输与单数据传输
多数据装载和存储指令LDM与STM提供了一种高效的从内存装载内容到多个寄存器,或者把多个寄存器内容存入内存的方法。使用单个多数据装载和存储指令相对于使用多个单数据传输指令的优点是:
更小的代码尺寸
只需要一个寄存器写周期;而相反地,每个单数据装载指令都需要一个寄存器写周期
在没有缓存的ARM处理器中,多数据装载或者存储指令执行时传输第一个字所需时间总是一个非顺序存储器访问周期,但后续传输每个字所需的时间都是一个顺序存储器访问周期(更快)
8.2 寄存器列表
多数据装载或者存储指令中,寄存器r0到r15分别编码为一位,设置这一位表示对应的寄存器参与传输,否则不参与传输。因此可以在单个指令中让任何寄存器子集参与传输。
参与传输的寄存器子集由大括号包围的列表表示,例如:{r1,r4-r6,r8,r10}
8.3 递增/递减,前/后
在寄存器传输中,基地址可以在传输前或者传输后递增或者递减:STMIA r10,{r1,r3-r5,r8}
后缀IA还可以是IB,DA或者DB。这里I表示递增(increment),D表示递减(decrement),A表示后(after),B表示前(before)。
无论是哪种情况,编号最小的寄存器与最低存储器地址间进行传输,编号最大的寄存器与最高存储器地址间进行传输。寄存器出现在列表中的次序是无关紧要的。而 且,ARM总是以递增存储器地址的方式进行顺序存储器访问。所以,递减传输实际上是先进行一个减操作,然后在数据传输中进行地址递增操作。
除非是特别指定,传输完成后基地址寄存器的值是不变的。如果要更新基地址寄存器,必须用!(感叹号)特别指明: LDMDB r11!,{r9,r4-r7}
8.4 栈记法
因为多数据装载和存储指令可以更新基地址寄存器(在栈操作中可以是栈指针),所以它提供了一种用单个指令完成多个寄存器入栈和出栈的方法(出栈用LDM,入栈用STM)。
多数据装载和存储指令可以在多种类型的栈中使用:
递增或递减
栈可以是向上增长的,即从低地址端开始,向高地址端增进,也就是递增栈;或者是向下增长的,从高地址端开始,向低地址端增进,也即是递减栈
空或满
栈指针可以指向栈的最顶端元素(满栈),或者指向栈的下一个可用空间(空栈)
多数据装载和存储指令可以直接实现上述类型的栈。为方便程序员,可以在LDM和STM指令后增加特别的栈指令后缀(可替代递增/递减与前/后后缀)
第二炮 吐槽一下买了龙芯笔记本8089b之后的“悲惨经历”
想一想距离发上一炮以及好几天了...在这期间,团购了一款龙芯8089b的笔记本,主要目的是来体验一下国产CPU以及LINUX系统。
下单后等了两个多星期终于收到了白色的小本。一开机就是红旗linux系统....我也不管了,先开机爽了两把。东搞西搞最后是啥事都没干,,还一不小心把原有的火狐浏览器给卸载掉了。卸载掉浏览器后就坑大了,,,花了4个多小时去装一个浏览器...没有成功,去红旗的官网上下载资料,啥都下载不成功。没办法已经把系统整的乱起八糟了。开始琢磨着换系统....在龙芯吧里看到了个教程是离线安装debian。那就跟着教程开始整呗,,,一开始就让把U盘分区,那我就跟着做,开始U盘分区。没办法,菜鸟注定悲剧,U盘被俺整坏了....最后只能开始动用在论坛用积分换来的U盘。干脆就不分区了,直接上.....结果证明不分区也是可以行的,当时就有抓狂啊。。。
最后终于安装上了debian.......
以为可以松口气了,就想着在上面安装个ARM-LINUX的交叉编译器呗,直接在上面开发OK6410多好呀。菜鸟的想法总是单纯可爱的呀。。将之前在虚拟机中安装的交叉编译工具链直接拖到龙芯电脑上安装,,安装完成后就迫不及待的去编译一个小程序试试,结果就呵呵了....无法执行的二进制文件....以为是没有安装好,就开始在网上下载一些其他的交叉编译工具来安装。都统统失败....盲目的试了一通之后花了2天时间额,哎,觉得好好查查原因。龙芯的CPU架构是MIPS 64位。。我安装的交叉编译工具链是针对于X86架构的,32位的。我也不知道是什么原因,感觉是这里的问题,于是在网上搜索针对于龙芯架构的交叉编译工具,又花了两天,毫无结果...
折腾了一个多星期后,对于在龙芯笔记本上去安装个ARM-linux交叉编译工具链的想法彻底绝望了。但是发现了一点,在龙芯上面使用vi编辑器比较顺手,而我的苦逼海尔电脑上的虚拟机运行起来之后卡的要命。决定在龙芯笔记本上编写代码,在虚拟机中编译....
为了让两个linux系统的电脑互相传文件嘛....第一个想法就是把一台电脑作为ftp服务器....看起来挺简单的,操蛋的是这两台电脑就是不能连接上..具体问题还在分析中。
最后就把希望寄托在了scp指令上...虽然期间遇到了一些麻烦,但是scp指令没让我失望...就在刚才,终于传送成功了
------------吐槽结束啦,之后将遇到的问题以及解决的方法分享出来,希望得到大伙的指点
第三炮 两台linux电脑之间传输文件(一)
在上一楼的吐槽中也了解到现在在琢磨在两台电脑之间传输文件。现在将电脑插上网线虚拟机的网络设置为桥接。龙芯笔记本也是通过网线连接到路由器上。
分别用ifconfig查看ip吧。
虚拟机的IP是:192.168.1.101
龙芯小本的是:192.168.1.105
scp是secure copy的简写,用于在Linux下进行远程拷贝文件的命令,和它类似的命令有cp,不过cp只是在本机进行拷贝不能跨服务器,而且scp传输是加密的。
可能会稍微影响一下速度。当你服务器硬盘变为只读 read only system时,用scp可以帮你把文件移出来。另外,scp还非常不占资源,不会提高多少系统负荷,
在这一点上,rsync就远远不及它了。虽然 rsync比scp会快一点,但当小文件众多的情况下,rsync会导致硬盘I/O非常高,而scp基本不影响系统正常使用。
1.命令格式:
scp [参数] [原路径] [目标路径]
2.命令功能:
scp是 secure copy的缩写, scp是linux系统下基于ssh登陆进行安全的远程文件拷贝命令。linux的scp命令可以在linux服务器之间复制文件和目录。
3.命令参数:
-1 强制scp命令使用协议ssh1
-2 强制scp命令使用协议ssh2
-4 强制scp命令只使用IPv4寻址
-6 强制scp命令只使用IPv6寻址
-B 使用批处理模式(传输过程中不询问传输口令或短语)
-C 允许压缩。(将-C标志传递给ssh,从而打开压缩功能)
-p 保留原文件的修改时间,访问时间和访问权限。
-q 不显示传输进度条。
-r 递归复制整个目录。
-v 详细方式显示输出。scp和ssh(1)会显示出整个过程的调试信息。这些信息用于调试连接,验证和配置问题。
-c cipher 以cipher将数据传输进行加密,这个选项将直接传递给ssh。
-F ssh_config 指定一个替代的ssh配置文件,此参数直接传递给ssh。
-i identity_file 从指定文件中读取传输时使用的密钥文件,此参数直接传递给ssh。
-l limit 限定用户所能使用的带宽,以Kbit/s为单位。
-o ssh_option 如果习惯于使用ssh_config(5)中的参数传递方式,
-P port 注意是大写的P, port是指定数据传输用到的端口号
-S program 指定加密传输时所使用的程序。
试着传输一个文件:
liklon@liklon-laptop:/media/sf_liklon-linux$ scp ./google-chrome-stable_current_i386_35.0.1916.114.deb root@192.168.1.105:/liklon-arm ssh: connect to host 192.168.1.105 port 22: Connection refused lost connection liklon@liklon-laptop:/media/sf_liklon-linux$
发送有错误,,,错误是没有连接上。查资料后解决的方法如下:
1.如果没有安装ssh可以先执行下面两条指令
liklon@liklon-laptop:sudo apt-get install openssh-client liklon@liklon-laptop:sudo apt-get install openssh-server
liklon@liklon-laptop:/media/sf_liklon-linux$ netstat -tl 激活Internet连接 (仅服务器) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 *:ftp *:* LISTEN tcp 0 0 localhost:ipp *:* LISTEN tcp6 0 0 localhost:ipp [::]:* LISTEN liklon@liklon-laptop:/media/sf_liklon-linux$
执行下面的语句
liklon@liklon-laptop:/media/sf_liklon-linux$ /etc/init.d/ssh start
* Starting OpenBSD Secure Shell server sshd Could not load host key: /etc/ssh/ssh_host_rsa_key
Could not load host key: /etc/ssh/ssh_host_dsa_key
[ OK ]
liklon@liklon-laptop:/media/sf_liklon-linux$ sudo /etc/init.d/ssh start
[sudo] password for liklon:
Rather than invoking init scripts through /etc/init.d, use the service(8)
utility, e.g. service ssh start
Since the script you are attempting to invoke has been converted to an
Upstart job, you may also use the start(8) utility, e.g. start ssh
liklon@liklon-laptop:/media/sf_liklon-linux$
可以看出得在root权限下去启动。再来查看
liklon@liklon-laptop:/media/sf_liklon-linux$ netstat -tl 激活Internet连接 (仅服务器) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 *:ftp *:* LISTEN tcp 0 0 *:ssh *:* LISTEN tcp 0 0 localhost:ipp *:* LISTEN tcp6 0 0 [::]:ssh [::]:* LISTEN tcp6 0 0 localhost:ipp [::]:* LISTEN liklon@liklon-laptop:/media/sf_liklon-linux$
liklon@liklon-laptop:/media/sf_liklon-linux$ scp ./google-chrome-stable_current_i386_35.0.1916.114.deb root@192.168.1.105:/liklon-arm The authenticity of host '192.168.1.105 (192.168.1.105)' can't be established. RSA key fingerprint is 30:bc:02:11:cb:e6:ef:c9:92:a9:e3:ef:97:b7:a6:4d. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '192.168.1.105' (RSA) to the list of known hosts. root@192.168.1.105's password: google-chrome-stable_current_i386_35.0.1916.114.deb 100% 45MB 5.6MB/s 00:08 liklon@liklon-laptop:/media/sf_liklon-linux$
回复
有奖活动 | |
---|---|
【有奖活动】分享技术经验,兑换京东卡 | |
话不多说,快进群! | |
请大声喊出:我要开发板! | |
【有奖活动】EEPW网站征稿正在进行时,欢迎踊跃投稿啦 | |
奖!发布技术笔记,技术评测贴换取您心仪的礼品 | |
打赏了!打赏了!打赏了! |