2019-02-21 09:45:21 uffgfbft 阅读数 105
               

嵌入式linux的网络编程(3)--TCP Client程序设计

CSDN2013年度博客之星评选活动开始,本人有幸入围参加评选,如果博客中的文章对你有所帮助,请为 ce123 投上宝贵一票,非常感谢!
投票地址:http://vote.blog.csdn.net/blogstaritem/blogstar2013/ce123

1.概述

 客户端主要需要完成与服务器建立连接,请求数据,应答数据等工作.从代码上来看,客户端程序有很多代码与服务器端程序类似,我们先给出一个简单的客户端源码,随后进行详细的讲解.

  1 /**************************************************************************************/  2 /*简介:TCPClient示例。                                                          */  3 /*************************************************************************************/  4 #include <stdlib.h>    5 #include <stdio.h>   6 #include <errno.h>   7 #include <string.h>   8 #include <netdb.h>   9 #include <sys/types.h>  10 #include <netinet/in.h>  11 #include <sys/socket.h>  12  13 int main(int argc, char *argv[])  14 {  15         int sockfd;  16         char buffer[1024];         17         struct sockaddr_in server_addr;   18         struct hostent *host;      19         int portnumber,nbytes;     20  21         if(argc!=3)  22         {  23                 printf("Usage:%s hostname portnumber\a\n",argv[0]);  24                 exit(1);  25         }  26  27         if((host=gethostbyname(argv[1]))==NULL)  28         {  29                 herror ("Get host name error\n");  30                 exit(1);  31         }  32  33         if((portnumber=atoi(argv[2]))<0)  34         {  35                 printf("Usage:%s hostname portnumber\a\n",argv[0]);  36                 exit(1);  37         }  38  39         /* 客户程序开始建立 sockfd描述符 */  40         if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)  41         {  42                 printf("Socket Error:%s\a\n",strerror(errno));  43                 exit(1);  44         }  45         /* 客户程序填充服务端的资料 */  46         bzero(&server_addr,sizeof(server_addr)); 47         server_addr.sin_family=AF_INET; 48         server_addr.sin_port=htons(portnumber); 49         server_addr.sin_addr=*((struct in_addr *)host->h_addr); 50         /* 客户程序发起连接请求 */ 51         if(connect(sockfd,(struct sockaddr *)(&server_addr),\ 52                                 sizeof(struct sockaddr))==-1) 53         { 54                 printf("Connect Error:%s(%d)\a\n",strerror(errno),errno); 55                 exit(1); 56         } 57         /* 连接成功了 */ 58         if((nbytes=read(sockfd,buffer,1024))==-1) 59         { 60                 printf("Read Error:%s\n",strerror(errno)); 61                 exit(1); 62         } 63         buffer[nbytes]='\0'; 64         printf("I have received:%s\n",buffer); 65  66         /* 结束通讯 */ 67         close(sockfd); 68         exit(0); 69 }
 客户端程序首先连接到服务器端,然后才能进行数据交换,在这之前就必须知道服务器的IP地址和端口号.

2.名字地址转化

 通常,人们在使用过程中都不愿意记忆冗长的IP 地址,尤其到IPv6时,地址长度多达128位,那时就更加不可能一次次记忆那么长的IP地址了.因此,使用主机名将会是很好的选择.在Linux中,同样有一些函数可以实现主机名和地址的转化,最为常见的有gethostbyname,gethostbyaddr,getaddrinfo等,它们都可以实现IPv4和IPv6的地址和主机名之间的转化.其中gethostbyname是将主机名转化为IP地址,gethostbyaddr则是逆操作,是将IP地址转化为主机名,另外getaddrinfo还能实现自动识别IPv4地址和IPv6地址.
 gethostbyname和gethostbyaddr都涉及到一个hostent的结构体,如下所示:

struct hostent{ char *h_name;  /*正式主机名*/ char **h_aliases; /*主机别名*/ int h_addrtype;  /*地址类型*/ int h_length;  /*地址长度*/ char **h_addr_list; /*指向IPv4或IPv6的地址指针数组*/}
 调用该函数后就能返回hostent结构体的相关信息.getaddrinfo函数涉及到一个addrinfo的结构体,如下所示:
struct addrinfo{ int ai_flags;  /*AI_PASSIVE,AI_CANONNAME;*/ int ai_family;  /*地址族*/ int ai_socktype; /*socket类型*/ int ai_protocol; /*协议类型*/ size_t ai_addrlen; /*地址长度*/ char *ai_canoname; /*主机名*/ struct sockaddr *ai_addr;/*socket结构体*/ struct addrinfo *ai_next;/*下一个指针链表*/}
 对hostent结构体而言,addrinfo结构体包含更多的信息.
 gethostbyname函数语法要点如下:


 如果该函数发生错误时,但全局变量errno中不存储错误代码,h_errno中存储的才是错误代码.通过herrno函数访问变量h_errno,该函数的使用与perror的用法一样,如果例子中的29行.

 此外,还可以通过gethostname函数获得本地主机的名字,返回的名字可以用于gethostbyname,该函数的声明如下:

#include <unistd.h>int gethostname(char *hostname, size_t size);
hostname: 一个指向将要存放主机名的缓冲区指针;
size:缓冲区的长度.

 getaddrinfo函数的语法要点如下:

2013-12-19 11:51:32 ce123 阅读数 6090

嵌入式linux的网络编程(3)--TCP Client程序设计

CSDN2013年度博客之星评选活动开始,本人有幸入围参加评选,如果博客中的文章对你有所帮助,请为 ce123 投上宝贵一票,非常感谢!
投票地址:http://vote.blog.csdn.net/blogstaritem/blogstar2013/ce123

1.概述

客户端主要需要完成与服务器建立连接,请求数据,应答数据等工作.从代码上来看,客户端程序有很多代码与服务器端程序类似,我们先给出一个简单的客户端源码,随后进行详细的讲解.

  1 /**************************************************************************************/
  2 /*简介:TCPClient示例。                                                          */
  3 /*************************************************************************************/
  4 #include <stdlib.h>  
  5 #include <stdio.h> 
  6 #include <errno.h> 
  7 #include <string.h> 
  8 #include <netdb.h> 
  9 #include <sys/types.h> 
 10 #include <netinet/in.h> 
 11 #include <sys/socket.h> 
 12 
 13 int main(int argc, char *argv[]) 
 14 { 
 15         int sockfd; 
 16         char buffer[1024];        
 17         struct sockaddr_in server_addr;  
 18         struct hostent *host;     
 19         int portnumber,nbytes;    
 20 
 21         if(argc!=3) 
 22         { 
 23                 printf("Usage:%s hostname portnumber\a\n",argv[0]); 
 24                 exit(1); 
 25         } 
 26 
 27         if((host=gethostbyname(argv[1]))==NULL) 
 28         { 
 29                 herror ("Get host name error\n"); 
 30                 exit(1); 
 31         } 
 32 
 33         if((portnumber=atoi(argv[2]))<0) 
 34         { 
 35                 printf("Usage:%s hostname portnumber\a\n",argv[0]); 
 36                 exit(1); 
 37         } 
 38 
 39         /* 客户程序开始建立 sockfd描述符 */ 
 40         if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) 
 41         { 
 42                 printf("Socket Error:%s\a\n",strerror(errno)); 
 43                 exit(1); 
 44         } 
 45         /* 客户程序填充服务端的资料 */ 
 46         bzero(&server_addr,sizeof(server_addr));
 47         server_addr.sin_family=AF_INET;
 48         server_addr.sin_port=htons(portnumber);
 49         server_addr.sin_addr=*((struct in_addr *)host->h_addr);
 50         /* 客户程序发起连接请求 */
 51         if(connect(sockfd,(struct sockaddr *)(&server_addr),\
 52                                 sizeof(struct sockaddr))==-1)
 53         {
 54                 printf("Connect Error:%s(%d)\a\n",strerror(errno),errno);
 55                 exit(1);
 56         }
 57         /* 连接成功了 */
 58         if((nbytes=read(sockfd,buffer,1024))==-1)
 59         {
 60                 printf("Read Error:%s\n",strerror(errno));
 61                 exit(1);
 62         }
 63         buffer[nbytes]='\0';
 64         printf("I have received:%s\n",buffer);
 65 
 66         /* 结束通讯 */
 67         close(sockfd);
 68         exit(0);
 69 }
客户端程序首先连接到服务器端,然后才能进行数据交换,在这之前就必须知道服务器的IP地址和端口号.

2.名字地址转化

通常,人们在使用过程中都不愿意记忆冗长的IP 地址,尤其到IPv6时,地址长度多达128位,那时就更加不可能一次次记忆那么长的IP地址了.因此,使用主机名将会是很好的选择.在Linux中,同样有一些函数可以实现主机名和地址的转化,最为常见的有gethostbyname,gethostbyaddr,getaddrinfo等,它们都可以实现IPv4和IPv6的地址和主机名之间的转化.其中gethostbyname是将主机名转化为IP地址,gethostbyaddr则是逆操作,是将IP地址转化为主机名,另外getaddrinfo还能实现自动识别IPv4地址和IPv6地址.
gethostbyname和gethostbyaddr都涉及到一个hostent的结构体,如下所示:

struct hostent
{
	char *h_name;		/*正式主机名*/
	char **h_aliases;	/*主机别名*/
	int h_addrtype;		/*地址类型*/
	int h_length;		/*地址长度*/
	char **h_addr_list;	/*指向IPv4或IPv6的地址指针数组*/
}
调用该函数后就能返回hostent结构体的相关信息.getaddrinfo函数涉及到一个addrinfo的结构体,如下所示:
struct addrinfo
{
	int ai_flags;		/*AI_PASSIVE,AI_CANONNAME;*/
	int ai_family;		/*地址族*/
	int ai_socktype;	/*socket类型*/
	int ai_protocol;	/*协议类型*/
	size_t ai_addrlen;	/*地址长度*/
	char *ai_canoname;	/*主机名*/
	struct sockaddr *ai_addr;/*socket结构体*/
	struct addrinfo *ai_next;/*下一个指针链表*/
}
对hostent结构体而言,addrinfo结构体包含更多的信息.
gethostbyname函数语法要点如下:


如果该函数发生错误时,但全局变量errno中不存储错误代码,h_errno中存储的才是错误代码.通过herrno函数访问变量h_errno,该函数的使用与perror的用法一样,如果例子中的29行.

此外,还可以通过gethostname函数获得本地主机的名字,返回的名字可以用于gethostbyname,该函数的声明如下:

#include <unistd.h>
int gethostname(char *hostname, size_t size);
hostname: 一个指向将要存放主机名的缓冲区指针;
size:缓冲区的长度.

getaddrinfo函数的语法要点如下:


在调用之前,首先要对hints服务线索进行设置.它是一个addrinfo 结构体,下图列举了该结构体常见的选项值.


3.连接服务器

当客户端完成创建socket,填充服务器信息结构等工作后,就可以连接服务器了,如例子中的第51行.connect函数的语法要点如下:


当connect函数出错时,全局变量errno含有下面的值:

EACCES,EPERM 用户试图在套接字广播标志没有设置的情况下连接广播地址或由于防火墙策略导致连接失败.
EADDRINUSE 本地地址处于使用状态.
EAFNOSUPPORT 参数serv_add中的地址非合法地址.
EAGAIN 没有足够空闲的本地端口.
EALREADY 套接字为非阻塞套接字,并且原来的连接请求还未完成.
EBADF 非法的文件描述符.
ECONNREFUSED 远程地址并没有处于监听状态.
EFAULT 指向套接字结构体的地址非法.
EINPROGRESS 套接字为非阻塞套接字,且连接请求没有立即完成.
EINTR 系统调用的执行由于捕获中断而中止.
EISCONN 已经连接到该套接字.
ENETUNREACH 网络不可到达.
ENOTSOCK 文件描述符不与套接字相关.
ETIMEDOUT 连接超时.

当connect函数成功返回后,就可以使用sockfd作为与服务器连接的套接字描述符,一些之前提到的I/O函数,都可以用来与服务器进行通信了.

4.测试结果

将这两个程序分别编译为simple_server和simple_client,通过SecureCRT建立两个和linux操作系统的连接,分别运行这两个程序.


注意:localhost是本地循环地址,它代表本机的IP地址,用点分十进制表示为127.0.0.1.这是一个特殊的IP地址,通常用来测试IP协议是否正常.在本地调试网络程序时会经常用到这个地址.

2019-01-28 17:14:40 uffgfbft 阅读数 56
               

嵌入式linux的网络编程(2)--TCP Server程序设计

CSDN2013年度博客之星评选活动开始,本人有幸入围参加评选,如果博客中的文章对你有所帮助,请为 ce123 投上宝贵一票,非常感谢!
投票地址:http://vote.blog.csdn.net/blogstaritem/blogstar2013/ce123

 前面简单介绍了TCP/IP协议,事实上该协议非常复杂,要编写一个优秀的网络程序也非易事.下面我们通过一个例子的学习达到对网络编程有一个概貌性的理解.

1.TCP的通信过程

 一个典型的TCP通信过程如下:


 工作过程如下:服务器首先启动,通过调用socket建立一个套接字,然后调用bind将该套接字和本地网络地址联系在一起,再调用listen使套接字做好侦听的准备,并规定它的请求队列的长度,之后调用accept来接收连接.客户在建立套接字后就可以调用connect和服务器建立连接连接一旦建立,客户机和服务器之间就可以通过调用read和write来发送和接收数据.最后,待数据传送结束后,双方调用close关闭套接字.

2.TCP Server程序

 为了学习基于socket编程的基本流程和所用到的API函数,下面我们通过一个实际的例子来学习.该例子包含服务器程序和客户端程序.首先列出服务器程序的源码,后续的文章中再讲解客户端程序.

  1 /**************************************************************************************/  2 /*简介:TCPServer示例。                                                                  */  3 /*************************************************************************************/  4 #include <stdlib.h>    5 #include <stdio.h>   6 #include <errno.h>   7 #include <string.h>   8 #include <netdb.h>   9 #include <sys/types.h>  10 #include <netinet/in.h>  11 #include <sys/socket.h>  12  13 int main(int argc, char *argv[])  14 {  15         int sockfd,new_fd;         16         struct sockaddr_in server_addr;   17         struct sockaddr_in client_addr;   18         int sin_size,portnumber;   19         const char hello[]="Hello\n"; 20  21         if(argc!=2)  22         {  23                 printf("Usage:%s portnumber\a\n",argv[0]);  24                 exit(1);  25         }  26         if((portnumber=atoi(argv[1]))<0)  27         {  28                 printf("Usage:%s portnumber\a\n",argv[0]);  29                 exit(1);  30         }  31  32         /* 服务器端开始建立socket描述符 */  33         if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)  34         {  35                 printf("Socket error:%s\n\a",strerror(errno));  36                 exit(1);  37         }  38  39         /* 服务器端填充 sockaddr结构 */  40         bzero(&server_addr,sizeof(struct sockaddr_in));  41         server_addr.sin_family=AF_INET;  42         server_addr.sin_addr.s_addr=htonl(INADDR_ANY);  43         server_addr.sin_port=htons(portnumber);  44  45         /* 捆绑sockfd描述符 */  46         if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))== -1) 47         { 48                 printf("Bind error:%s\n\a",strerror(errno)); 49                 exit(1); 50         } 51  52         /* 监听sockfd描述符 */ 53         if(listen(sockfd,5)==-1) 54         { 55                 printf("Listen error:%s\n\a",strerror(errno)); 56                 exit(1); 57         } 58  59         while(1) 60         { 61                 /* 服务器阻塞,直到客户程序建立连接 */ 62                 sin_size=sizeof(struct sockaddr_in); 63                 if((new_fd=accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size))==-1) 64                 { 65                         printf("Accept error:%s(%d)\n\a",strerror(errno),errno); 66                         exit(1); 67                 } 68                 printf("Server get connection from %s\n", 69                                 (char *)inet_ntoa(client_addr.sin_addr)); 70  71                 if(write(new_fd,hello,strlen(hello))==-1) 72                 { 73                         printf("Write Error:%s\n",strerror(errno)); 74                         exit(1); 75                 } 76                 /* 这个通讯已经结束 */ 77                 close(new_fd); 78                 /* 循环下一个 */ 79         } 80  81         close(sockfd); 82         exit(0); 83 }

 接下来我们对该例子进行详细的讲解.

2.1.网络地址的表示

 在引入的众多的头文件后,源码的16和17行定义了两个sockaddr_in数据类型的变量.它们分别表示服务器和客户端的网络地址.网络地址的表示主要通过socketaddr和sockaddr_in来表示.下面首先介绍两个重要的数据类型:sockaddr和sockaddr_in,这两个结构类型都是用来保存socket信息的,如下所示:

struct sockaddr { unsigned short sa_family; /*地址族*/ char sa_data[14];  /*14字节的协议地址,包含该socket的IP地址和端口号。*/};struct sockaddr_in { short int sa_family;   /*地址族*/ unsigned short int sin_port; /*端口号*/ struct in_addr sin_addr; /*IP地址*/ unsigned char sin_zero[8]; /*填充0 以保持与struct sockaddr同样大小*/};// Internet address.struct in_addr {        union {                struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;                struct { u_short s_w1,s_w2; } S_un_w;                u_long S_addr; /* port in network byte order */        } S_un;#define s_addr  S_un.S_addr};// Socket address, internet style.struct sockaddr_in {        // struct sockaddr的一种特殊形式        short            sin_family;    /* address family: AF_INET */        u_short        sin_port;        /* port in network byte order */        struct in_addr sin_addr;        /* port in network byte order */        char            sin_zero[8];    /* 8 byte pad */};// Structure used by kernel to store most addresses.struct sockaddr {        u_short sa_family; /* address family */        char    sa_data[14]; /* up to 14 bytes of direct address */};

 这两个数据类型是等效的,可以相互转化,通常sockaddr_in数据类型使用更为方便.但需要注意的是sin_zero[8]是为了使两个结构体在内存中具有相同的尺寸,使用sockaddr_in的时候要把sin_zero全部设为0.在建立socketadd或sockaddr_in后,就可以对该socket进行适当的操作了.下图列出了该结构sa_family字段可选的常见值.

2011-01-22 12:02:00 jsfzdd 阅读数 4518

嵌入式Linux固件升级

开发需求

• 基于TCP/IP完成驱动模块和应用程序的更新、升级

特殊声明

该文档中驱动程序和应用程序统称为“固件”。

 

•   机:VMWare--Fedora 9

• 开发板:yc2440--64MB NandflashKernel:2.6.24.4

• 编译器:arm-linux-gcc-4.0.0

 

设计原理图

 

 

说明:

•  开发板启动FileServer应用程序,作为TCP/IPServer端,提供接收升级文件的服务。

• 当需要进行固件升级时,PC启动FileClient应用程序,作为TCP/IPClient端,提供发送升级文件的服务。

• Shell脚本文件StartShell判断是否有固件需要升级,如果有更新现有固件后,启动更新后的固件,如果没有,启动现有固件。

 

文件名称

所在目录

功能

FileClient

Linux PC下任意目录

PCTCP/IP客户端,

向开发板发送升级固件。

FileServer

Linux开发板

/tmp/update/

开发板TCP/IP服务端,

接收客户端发送的升级固件。

StartShell

Linux开发板

/etc/init.d/

替换相应固件,

启动相应固件。

 

实现步骤

1.      配置启动文件(开发板:192.168.1.168

在开发板中,编辑开机启动脚本/etc/init.d/rcS

#cp ~/StartShell /etc/init.d/

#vi /etc/init.d/rcS

 

在该文件的最后面,填写下面信息:

./StartShell

 

重新启动开发板。

 

2.      发送更新文件文件(Linux PC192.168.1.200

#./fileclient ./AppMain 192.168.1.168

#./fileclient ./helloworld.ko 192.168.1.168

 

上述更新文件,被发送至开发板的/tmp/update/FileServer所在的目录)目录中。

 

重新启动开发板,文件升级完成。

 

注:如果要动态加载驱动模块,首先必须在开发板上创建/lib/modules/2.6.24.4目录。

附件:

•  TCP/IP源码文件

http://download.csdn.net/source/2996852

 

注:TCP/IP 服务尽量使用大端口号,如:50000,否则服务器端会有Bind失败的情况出现。

服务器端如果有防火墙,需要开放该端口号,否则客户端会有connect失败的情况出现。

 

• StartShell脚本

 

#! /bin/sh

 

#判断是否有新的驱动文件,如果有进行替换

if [ -f /tmp/update/helloworld.ko ]

then

        echo "it is a new ko file"

 

        rm /lib/modules/helloworld.ko -f

        cp /tmp/update/helloworld.ko /lib/modules/

        rm /tmp/update/helloworld.ko -f

 

else

        echo "it is not a new ko file"

fi

 

insmod helloworld

#判断是否有新的应用程序文件,如果有进行替换

if [ -f /tmp/update/AppMain ]

then

        echo "it is a new app file"

 

        rm /root/application/AppMain -f

        cp /tmp/update/AppMain /root/application/

        chmod 777 /tmp/update/AppMain

        rm /tmp/update/AppMain -f

              

else

        echo "it is not a new app file"

fi

 

#启动应用程序

cd /root/application/

./AppMain &

 

#启动TCP/IP服务程序

cd /tmp/update/

./FileServer &

 

 

 

Embedded Linux firmware upgrade
Development requirement
• Based on TCP/IP to complete the driver modules and application updates, upgrades
Special Statement
Drivers program and application program in this document are collectively called the "firmware".

• Main machine: VMWare-Fedora 9
• Development Board: yc2440-64MB Nandflash; Kernel: 2.6.24.4
• Compiler: arm-linux-gcc-4.0.0

Design schematic
 

Description:
• Start FileServer application on development board, as the TCP/IP server, providing service to receive the upgrade file.
• When you need a firmware upgrade, PC starts FileClient application, as the TCP/IP client, providing service to send the upgrade file.
• Shell script file StartShell determine whether there is the firmware need to upgrade, if it is existed, launch the updated firmware, if not, start the existing firmware.

File

directory

function

FileClient

any directory in linux PC

PC TCP/IP client,
Send upgrade firmware to  development board.

FileServer

/tmp/update/ in

linux development board

Development board TCP/IP server,

receive the upgrade firmware from client.

StartShell

/etc/init.d/ in

linux development board

Replace firmware,

Start frimware.


Implementation steps
1. Configuration startup files (development board: 192.168.1.168)
In the development board, edit the boot script file /etc/init.d/rcS, input the following information:
# cp ~ /StartShell /etc/init.d/
# vi /etc/init.d/rcS

Finally, in the end of the document, input the following information:

 

./StartShell

Restart the development board.

2. Send the update file file (Linux PC: 192.168.1.200)
#./Fileclient ./AppMain 192.168.1.168
#./Fileclient ./Helloworld.ko 192.168.1.168

The update file is sent to the development board of the /tmp/update/(FileServer  directory) directory.

Restart the development board, file upgrade is OK.

Note: If you want to dynamically load the driver module, first the directory /lib/modules/2.6.24.4 must be created in development board.


Attachment:
• TCP/IP source code file
http://download.csdn.net/source/2996852

Note: TCP/IP service port to make use of large numbers, such as: 50000, otherwise there will be bind server failure situation.
If the server has a firewall, you need to open up the port number, otherwise the client will connect failure situation.

• StartShell script

#! /bin/sh

# Determine whether there is a new driver file, if there is to be replaced
if [ -f /tmp/update/helloworld.ko ]
then
        echo "it is a new ko file"

        rm /lib/modules/helloworld.ko -f
        cp /tmp/update/helloworld.ko /lib/modules/
        rm /tmp/update/helloworld.ko -f
else
        echo "it is not a new ko file"
fi

insmod helloworld
# Determine whether there is a new application file, if there is to be replaced
if [ -f /tmp/update/AppMain ]
then
        echo "it is a new app file"

        rm /root/application/AppMain -f
        cp /tmp/update/AppMain /root/application/ 
        
chmod 777 /tmp/update/AppMain
        rm /tmp/update/AppMain -f
else
        echo "it is not a new app file"
fi

# Start the application program
cd /root/application/
./AppMain &

# Start TCP/IP service program
cd /tmp/update/
./FileServer &

 

2007-11-12 20:14:00 yingfox 阅读数 359
导读:
  Linux自身具备一整套工具链,容易自行建立嵌入式系统的开发环境和交
  叉运行环境,并且可以跨越嵌入式系统开发中的仿真工具(ICE)的障碍。内
  核的完全开放使人们可以自己设计和开发出真正的硬实时系统,软实时系统
  在Linux中也容易得到实现。强大的网络支持使得可以利用Linux的网络协议
  栈将其开发成为嵌入式的TCP/IP网络协议栈。
  Linux提供了完成嵌入功能的基本内核和所需要的所有用户界面,它是多
  面的。它能处理嵌入式任务和用户界面。
  一个小型的嵌入式Linux系统只需要下面三个基本元素:
  * 引导工具
  * Linux微内核,由内存管理、进程管理和事务处理构成
  * 初始化进程
  如果要让它能干点什么且继续保持小型化,还得加上:
  * 硬件驱动程序
  * 提供所需功能的一个或更多应用程序。
  再增加功能,或许需要这些:
  * 一个文件系统(也许在ROM或RAM)中
  * TCP/IP网络堆栈
  下面我们就从精简内核、系统启动、驱动程序将、X-Window换成MicroW
  indows四个步骤介绍嵌入式Linux的实际开发。
  
  精简内核
  构造内核的常用命令包括:make config、dep、clean、mrproper、zIm
  age、bzImage、modules、modules_install。命令说明略。
  现在举个例子说明一下:
  我使用的是 Mandrake内附的 2.2.15。我没有修改任何一行程序码,完
  全只靠修改组态档得到这些数据。
  首先,使用 make config 把所有可以拿掉的选项都拿得。
  不要 floppy;不要SMP、MTRR;不要 Networking、SCSI;把所有的 bl
  ock device 移除,只留下 old IDE device;把所有的 character device
  移除;把所有的 filesystem 移除,只留下 minix;不要 sound 支援。相信
  我,我己经把所有的选项都移除了。这样做之后,我得到了一个 188K 的核
  心。
  还不够小吗? OK,再加上一招,请把下列两个档案中的 -O3,-O2 用 -O
  s 取代。
  ./Makefile
  ./arch/i386/kernel/
  Makefile
  这样一来,整个核心变小了 9K,成为 179K。
  不过这个核心恐怕很难发挥 Linux 的功能,因此我决定把网络加回去。
  把General中的 network support 加回去,重新编译,核心变成 189 K。10
  K就加上个 TCP/IP stack,似乎是很上算的生意。
  有stack没有driver也是枉然,所以我把 embedded board常用的RTL813
  9的driver加回去,195K。
  如果你需要 DOS 档案系统,那大小成为 213K。如果 minix 用 ext2 换
  代,则大小成长至 222K。
  Linux所需的内存大约在600K~800K之间。1MB内存就可能可以开机了,
  但不太有用,因为连载入C程序库都有困难。2MB内存应该就可以做点事了,
  但要到 4MB以上才可以执行一个比较完整的系统。
  因为Linux 的filesystem 相当大,大约在 230K 左右,占了 1/3 的体
  积。内存管理占了80K,和核心其它部分的总和差不多。TCP/IP stack 占了
  65K,驱动程序占了120K。SysV IPC占了 21K,必要的话可以拿掉,核心档应
  该可以再小个10K左右。
  如果要裁剪核心大小,应该动那里呢? 答案很明显,当然是文件系统。
  Linux 的 VFS简化了档案系统的设计,buffer cache, directory cache增加
  了系统的效率。但这些embedded系统根本就用处不大。如果可以把它们拿掉
  ,核心可以马上缩小 20K 左右。如果跳过整个 VFS,直接将文件系统写成一
  个 driver 的型式,应该可以将 230K缩减至50K左右。整个核心缩到100K左
  右。
  
  系统启动
  系统的启动顺序及相关文件仍在核心源码目录下,看以下几个文件:
  ./arch/$ARCH/boot/
  bootsect.s
  ./arch/$ARCH/boot/setup.s
  ./init/main.c
  bootsect.S 及 setup.S
  这个程序是Linux kernel的第一个程序,包括了Linux自己的bootstrap
  程序,但是在说明这个程序前,必须先说明一般IBM PC开机时的动作(此处的
  开机是指“打开PC的电源”)。
  一般PC在电源一开时,是由内存中地址FFFF:0000开始执行(这个地址一
  定在ROM BIOS中,ROM BIOS一般是在FEOOOh到FFFFFh中),而此处的内容则是
  一个jump指令,jump到另一个位于ROM BIOS中的位置,开始执行一系列的动
  作。
  紧接着系统测试码之后,控制权会转移给ROM中的启动程序(ROM bootst
  rap routine)。这个程序会将磁盘上的第零轨第零扇区读入内存中,至于读
  到内存的哪里呢? --绝对位置07C0:0000(即07C00h处),这是IBM系列PC的特
  性。而位于Linux开机磁盘的boot sector上的,正是Linux的bootsect程序。
  
  把大家所熟知的MS DOS 与Linux的开机部分做个粗浅的比较。MS DOS 由
  位于磁盘上boot sector的boot程序负责把IO.SYS载入内存中,而IO.SYS则负
  有把DOS的kernel --MSDOS.SYS载入内存的重任。而Linux则是由位于boot s
  ector 的bootsect程序负责把setup及Linux的kernel载入内存中,再将控制
  权交给setup。
  
  驱动程序
  在Linux系统里,设备驱动程序所提供的这组入口点由一个结构来向系统
  进行说明。
  设备驱动程序所提供的入口点,在设备驱动程序初始化的时候向系统进
  行登记,以便系统在适当的时候调用。Linux系统里,通过调用register_ch
  rdev 向系统注册字符型设备驱动程序。
  在Linux里,除了直接修改系统核心的源代码,把设备驱动程序加进核心
  里以外,还可以把设备驱动程序作为可加载的模块,由系统管理员动态地加
  载它,使之成为核心的一部分。也可以由系统管理员把已加载的模块动态地
  卸载下来。Linux中,模块可以用C语言编写,用gcc编译成目标文件(不进行
  链接,作为*.o文件存在)。为此需要在gcc命令行里加上-c的参数。在成功
  地向系统注册了设备驱动程序后(调用register_chrdev成功后),就可以用
  mknod命令来把设备映射为一个特别文件。其它程序使用这个设备的时候,只
  要对此特别文件进行操作就行了。
  
  将X-Window换成MicroWindows
  MicroWindows是使用分层结构的设计方法。允许改变不同的层来适应实
  际的应用。在最底一层,提供了屏幕、鼠标/触摸屏和键盘的驱动,使程序能
  访问实际的硬件设备和其它用户定制设备。在中间一层,有一个轻巧的图形
  引擎,提供了绘制线条、区域填充、绘制多边形、裁剪和使用颜色模式的方
  法。在最上一层,提供了不同的API给图形应用程序使用。这些API可以提供
  或不提供桌面和窗口外形。目前,MicroWindows支持Windows Win32/WinCE
  GDI和Nano-X API。这些API提供了Win32和X窗口系统的紧密兼容性,使得别
  的应用程序可以很容易就能移植到MicroWindows上。

本文转自
http://www.embeded.cn/article/36.htm
没有更多推荐了,返回首页