
好好学习,天天向上
关于版本: GRUB2 使之版本号为1.98之后的grub;GRUB legacy(版本为0.97)是指GRUB,而非GRUB2,grub是指 grub1.97 和以前的,grub 2 指的是 grub1.98和以后的,现在已经发布grub2.00 了。一般还是把grub2 称作grub。 注意,目前我所知道的grub 2 一般用于linux下,windows下继续使用古董 grub4dos0.4.4 顶多使用chenall网友修改的 0.4.5 0.4.6。grub4dos应该是从grub1.97修改到windows下的 检测版本: $ grub-install -v grub-install (GNU GRUB 1.98-1ubuntu10) GRUB2与GRUB的区别: 1.GRUB2引导菜单启动项是从/boot自动生成的,不是有menu.lst配置的。2.执行grub-update之后会自动更新启动项列表,自动添加有效的操作系统项目3.分区编号发生变化:第一个分区现在是1而不是0,但第一个设备仍然以0开始计数,如hd0 配置文件的不同更为明显: /boot/grub/menu.lst - 已经被/boot/grub/grub.cfg代替。/boot/grub.cfg - 即使是root也不要编辑它,它在每次update-grub后自动生成。/etc/default/grub - 改变引导菜单外观的主要配置文件/etc/grub.d/ - 各种用于生成grub.cfg的脚本文件,每次update-grub时会执行里面的文件下面列出几个有用的:/etc/grub.d/40_custom - 用户自定义的配置文件模板,它不会在update-grub之后被覆盖。 相关命令: grub-install [OPTION] <install_device>例如,下面这条语句可以在设备sda上恢复grub,详见重装Windows后,修复Ubuntu引导菜单 grub-install --root-directory=/mnt /dev/sda update-grub等价于:(在11.10中,这条指令竟然还是去更新/boot/grub/menu.lst) sudo make-kpkg --initrd --revision 01fcc(必须数字开头)--append-to-version -20120224 --config menuconfig kernel_image modules_image 在上层目录里找到deb安装包,用dpkg安装,生成vmlinuz后,再 grub-mkconfig -o /boot/grub/grub.cfg thinkpad@ubuntu:/boot/grub$ sudo grub-mkconfig -o /boot/grub/grub.cfgGenerating grub.cfg ...Found linux image: /boot/vmlinuz-3.2.7-20120224Found initrd image: /boot/initrd.img-3.2.7-20120224Found linux image: /boot/vmlinuz-3.0.0-12-genericFound initrd image: /boot/initrd.img-3.0.0-12-genericFound Windows 7 (loader) on /dev/sda1Skipping Windows 7 (loader) on Wubi systemdone
在虚拟机上yuv420可以正常显示 ,而945(D525)模块上却无法显示 ,后来验证了directdraw的yuv420也无法显示 ,由此怀疑显卡不支持 ,后把420转换为422显示。 420显示如下: /* 编译命令:arm-linux-gcc -o show2642 264showyuv2.c -I/usr/local/ffmpeg_arm/include/ -L/usr/local/ffmpeg_arm/lib/ -lswresample -lavformat -lavutil -lavcodec -lswscale -lx264 libSDL.a gcc -o test test.c -I/usr/local/ffmpeg/include/ -L/usr/local/ffmpeg/lib/ -lswresample -lavformat -lavutil -lavcodec -lswscale -lx264 -lSDL */ #include "stdio.h" #include "stdlib.h" #include "libavformat/avformat.h" #include "libavdevice/avdevice.h" #include "libswresample/swresample.h" #include "libavutil/opt.h" #include "libavutil/channel_layout.h" #include "libavutil/parseutils.h" #include "libavutil/samplefmt.h" #include "libavutil/fifo.h" #include "libavutil/intreadwrite.h" #include "libavutil/dict.h" #include "libavutil/mathematics.h" #include "libavutil/pixdesc.h" #include "libavutil/avstring.h" #include "libavutil/imgutils.h" #include "libavutil/timestamp.h" #include "libavutil/bprint.h" #include "libavutil/time.h" #include "libavutil/threadmessage.h" #include "SDL/SDL.h" //#include "libavfilter/avcodec.h" #include "libavcodec/avcodec.h" #if HAVE_SYS_RESOURCE_H #include <sys/time.h> #include <sys/types.h> #include <sys/resource.h> #elif HAVE_GETPROCESSTIMES #include <windows.h> #endif #if HAVE_GETPROCESSMEMORYINFO #include <windows.h> #include <psapi.h> #endif #if HAVE_SYS_SELECT_H #include <sys/select.h> #endif #if HAVE_TERMIOS_H #include <fcntl.h> #include <sys/ioctl.h> #include <sys/time.h> #include <termios.h> #elif HAVE_KBHIT #include <conio.h> #endif #if HAVE_PTHREADS #include <pthread.h> #endif #include <time.h> #include "libavutil/avassert.h" #define MAX_LEN 1024 * 50 ////此方法参考官网的例子 static void pgm_save(unsigned char *buf, int wrap, int xsize, int ysize, FILE *f) { // FILE *f; int i; // f = fopen(filename,"w"); // fprintf(f, "P5\n%d %d\n%d\n", xsize, ysize, 255); for (i = 0; i < ysize; i++) ;// fwrite(buf + i * wrap, 1, xsize, f); // fclose(f); } int main() { //下面初始化h264解码库 //avcodec_init(); int w = 720; int h = 576,retu; SDL_Rect rect; av_register_all(); AVFrame *pFrame_ = NULL; /* find the video encoder */ AVCodec *videoCodec = avcodec_find_decoder(AV_CODEC_ID_H264);//得到264的解码器类 if(!videoCodec) { printf("avcodec_find_decoder error\n"); return -1; } AVCodecParserContext *avParserContext = av_parser_init(AV_CODEC_ID_H264);//得到解析帧类,主要用于后面的帧头查找 if(!avParserContext) { printf("av_parser_init error\n"); return -1; } AVCodecContext *codec_ = avcodec_alloc_context3(videoCodec);//解码会话层 if(!codec_) { printf("avcodec_alloc_context3 error\n"); return -1; } //初始化参数,下面的参数应该由具体的业务决定 codec_->time_base.num = 1; codec_->frame_number = 1; //每包一个视频帧 codec_->codec_type = AVMEDIA_TYPE_VIDEO; codec_->bit_rate = 0; codec_->time_base.den = 25;//帧率 codec_->width = 720;//视频宽 codec_->height = 576;//视频高 if(avcodec_open2(codec_, videoCodec, NULL) >= 0)//打开解码器 { pFrame_ = av_frame_alloc();// Allocate video frame 成功打开解码器后, 此时可以分配帧内存, 当然你也可以在后面每次都分配、释放, 在此我省功夫, 只在开始分配一次 if (!pFrame_) { fprintf(stderr, "Could not allocate video frame\n"); exit(1); } } else { printf("avcodec_open2 error\n"); return -1; } AVPacket packet = {0}; int dwBufsize = 10; int frameFinished = dwBufsize;//这个是随便填入数字,没什么作用 av_init_packet(&packet); packet.data = NULL;//这里填入一个指向完整H264数据帧的指针 packet.size = 0;//这个填入H264数据帧的大小 FILE *myH264 = fopen("1.264", "rb");//解码的文件264 if(myH264 == NULL) { perror("cant open 264 file\n"); return -1; } FILE *yuvfile = fopen("my264.yuv", "wb");//成功解码后保存成的YUV文件, 可以用YUV工具打开浏览 if(yuvfile == NULL) { perror("cant open YUV file\n"); return -1; } int readFileLen = 1; char readBuf[MAX_LEN]; unsigned char *parseBuf = malloc(20*MAX_LEN);//这个地方浪费了我一个下午时间, 当时我用的是栈内存,即unsigned char parseBuf[20*MAX_LEN], 结果运行程序一直报错, 此处需要用堆内存才能正常解码 int parseBufLen = 0; int frameCount = 0; printf("begin...\n"); printf("readBuf address is %x\n", readBuf); /////////////////////////SDL init//////////////////////////////////////// SDL_Surface* hello = NULL; SDL_Surface* screen = NULL; //Start SDL // SDL_Init( SDL_INIT_EVERYTHING ); SDL_Init(SDL_INIT_VIDEO); //Set up screen screen = SDL_SetVideoMode( 1024, 768, 32, SDL_SWSURFACE ); SDL_Overlay* overlay = SDL_CreateYUVOverlay(w, h, SDL_YV12_OVERLAY, screen); SDL_LockSurface(screen); SDL_LockYUVOverlay(overlay); ////////////////////////////////////////////////////////////////////// while(readFileLen > 0)//开始解码工作 { //printf("begin...\n"); readFileLen = fread(readBuf, 1, sizeof(readBuf), myH264);//首先从文件里读出数据 if(readFileLen <= 0) { printf("read over\n"); break; } else { int handleLen = 0; int handleFileLen = readFileLen; while(handleFileLen > 0) { int nLength = av_parser_parse2(avParserContext, codec_, &parseBuf, &parseBufLen, readBuf + handleLen, handleFileLen, 0, 0, 0);//查找264帧头 handleFileLen -= nLength; handleLen += nLength; if(parseBufLen <= 0)//当parseBufLen大于0时,说明查找到了帧头 { continue; } packet.size = parseBufLen;//将查找到的帧长度送入 packet.data = parseBuf;//将查找到的帧内存送入 if(frameCount>100)break; //printf("parseBuf address is %x\n", parseBuf); while(packet.size > 0) {//下面开始真正的解码 int decodeLen = avcodec_decode_video2(codec_, pFrame_, &frameFinished, &packet); if(decodeLen < 0) break; packet.size -= decodeLen; packet.data += decodeLen; if(frameFinished > 0)//成功解码 { int picSize = codec_->height * codec_->width; //int newSize = picSize * 1.5; //申请内存 //unsigned char *buf = malloc(newSize); int height = pFrame_->height; int width = pFrame_->width; //printf("OK, get data\n"); //printf("Frame height is %d\n", height); //printf("Frame width is %d\n", width); frameCount ++; printf("Frame count is %d\n", frameCount); pgm_save(pFrame_->data[0], pFrame_->linesize[0],//保存Y codec_->width, codec_->height, yuvfile); pgm_save(pFrame_->data[1], pFrame_->linesize[1],//保存U codec_->width/2, codec_->height/2, yuvfile); pgm_save(pFrame_->data[2], pFrame_->linesize[2],//保存V codec_->width/2, codec_->height/2, yuvfile); ///有了YUV数据, 后面可以用FFMPEG提供的转换方法,将其转成RGB数据,进行后续的显示或其它的图像处理工作 ////sdl int i; for(i=0;i<576;i++) {//fwrite(buf + i * wrap, 1, xsize, f); memcpy(overlay->pixels[0]+i*1280, pFrame_->data[0]+i*pFrame_->linesize[0], 720); } for(i=0;i<288;i++) { memcpy(overlay->pixels[2]+i*640, pFrame_->data[1]+i*pFrame_->linesize[1], 360); memcpy(overlay->pixels[1]+i*640, pFrame_->data[2]+i*pFrame_->linesize[2], 360); } SDL_UnlockYUVOverlay(overlay); SDL_UnlockSurface(screen); rect.w = w; rect.h = h; rect.x = rect.y = 0; SDL_DisplayYUVOverlay(overlay, &rect); //sdl SDL_Delay(40); } else printf("failed to decodec\n"); } } } } //////释放工作 avcodec_close(codec_); av_free(codec_); av_free_packet(&packet); av_frame_free(&pFrame_); //SDL SDL_FreeYUVOverlay(overlay); SDL_FreeSurface(screen); //Quit SDL SDL_Quit(); fclose(yuvfile); fclose(myH264); } 422显示如下: /* 编译命令:arm-linux-gcc -o show2642 264showyuv2.c -I/usr/local/ffmpeg_arm/include/ -L/usr/local/ffmpeg_arm/lib/ -lswresample -lavformat -lavutil -lavcodec -lswscale -lx264 libSDL.a gcc -o test test.c -I/usr/local/ffmpeg/include/ -L/usr/local/ffmpeg/lib/ -lswresample -lavformat -lavutil -lavcodec -lswscale -lx264 -lSDL */ #include "stdio.h" #include "stdlib.h" #include "libavformat/avformat.h" #include "libavdevice/avdevice.h" #include "libswresample/swresample.h" #include "libavutil/opt.h" #include "libavutil/channel_layout.h" #include "libavutil/parseutils.h" #include "libavutil/samplefmt.h" #include "libavutil/fifo.h" #include "libavutil/intreadwrite.h" #include "libavutil/dict.h" #include "libavutil/mathematics.h" #include "libavutil/pixdesc.h" #include "libavutil/avstring.h" #include "libavutil/imgutils.h" #include "libavutil/timestamp.h" #include "libavutil/bprint.h" #include "libavutil/time.h" #include "libavutil/threadmessage.h" #include "SDL/SDL.h" //#include "libavfilter/avcodec.h" #include "libavcodec/avcodec.h" #if HAVE_SYS_RESOURCE_H #include <sys/time.h> #include <sys/types.h> #include <sys/resource.h> #elif HAVE_GETPROCESSTIMES #include <windows.h> #endif #if HAVE_GETPROCESSMEMORYINFO #include <windows.h> #include <psapi.h> #endif #if HAVE_SYS_SELECT_H #include <sys/select.h> #endif #if HAVE_TERMIOS_H #include <fcntl.h> #include <sys/ioctl.h> #include <sys/time.h> #include <termios.h> #elif HAVE_KBHIT #include <conio.h> #endif #if HAVE_PTHREADS #include <pthread.h> #endif #include <time.h> #include "libavutil/avassert.h" #define MAX_LEN 1024 * 50 ////此方法参考官网的例子 ////此方法参考官网的例子 static void pgm_save(unsigned char *buf, int wrap, int xsize, int ysize, FILE *f) { // FILE *f; int i; // f = fopen(filename,"w"); // fprintf(f, "P5\n%d %d\n%d\n", xsize, ysize, 255); for (i = 0; i < ysize; i++) ;// fwrite(buf + i * wrap, 1, xsize, f); // fclose(f); } int main() { //下面初始化h264解码库 //avcodec_init(); int w = 720; int h = 576,retu; SDL_Rect rect; av_register_all(); AVFrame *pFrame_ = NULL; /* find the video encoder */ AVCodec *videoCodec = avcodec_find_decoder(AV_CODEC_ID_H264);//得到264的解码器类 if(!videoCodec) { printf("avcodec_find_decoder error\n"); return -1; } AVCodecParserContext *avParserContext = av_parser_init(AV_CODEC_ID_H264);//得到解析帧类,主要用于后面的帧头查找 if(!avParserContext) { printf("av_parser_init error\n"); return -1; } AVCodecContext *codec_ = avcodec_alloc_context3(videoCodec);//解码会话层 if(!codec_) { printf("avcodec_alloc_context3 error\n"); return -1; } //初始化参数,下面的参数应该由具体的业务决定 codec_->time_base.num = 1; codec_->frame_number = 1; //每包一个视频帧 codec_->codec_type = AVMEDIA_TYPE_VIDEO; codec_->bit_rate = 0; codec_->time_base.den = 25;//帧率 codec_->width = 720;//视频宽 codec_->height = 576;//视频高 if(avcodec_open2(codec_, videoCodec, NULL) >= 0)//打开解码器 { pFrame_ = av_frame_alloc();// Allocate video frame 成功打开解码器后, 此时可以分配帧内存, 当然你也可以在后面每次都分配、释放, 在此我省功夫, 只在开始分配一次 if (!pFrame_) { fprintf(stderr, "Could not allocate video frame\n"); exit(1); } } else { printf("avcodec_open2 error\n"); return -1; } AVPacket packet = {0}; int dwBufsize = 10; int frameFinished = dwBufsize;//这个是随便填入数字,没什么作用 av_init_packet(&packet); packet.data = NULL;//这里填入一个指向完整H264数据帧的指针 packet.size = 0;//这个填入H264数据帧的大小 FILE *myH264 = fopen("1.264", "rb");//解码的文件264 if(myH264 == NULL) { perror("cant open 264 file\n"); return -1; } FILE *yuvfile = fopen("my264.yuv", "wb");//成功解码后保存成的YUV文件, 可以用YUV工具打开浏览 if(yuvfile == NULL) { perror("cant open YUV file\n"); return -1; } int readFileLen = 1; char readBuf[MAX_LEN]; unsigned char *parseBuf = malloc(20*MAX_LEN);//这个地方浪费了我一个下午时间, 当时我用的是栈内存,即unsigned char parseBuf[20*MAX_LEN], 结果运行程序一直报错, 此处需要用堆内存才能正常解码 int parseBufLen = 0; int frameCount = 0; printf("begin...\n"); printf("readBuf address is %x\n", readBuf); /////////////////////////SDL init//////////////////////////////////////// SDL_Surface* hello = NULL; SDL_Surface* screen = NULL; //Start SDL // SDL_Init( SDL_INIT_EVERYTHING ); SDL_Init(SDL_INIT_VIDEO); //Set up screen screen = SDL_SetVideoMode( 720, 576, 32, SDL_SWSURFACE ); SDL_Overlay* overlay = SDL_CreateYUVOverlay(w, h, SDL_YUY2_OVERLAY, screen); SDL_LockSurface(screen); SDL_LockYUVOverlay(overlay); unsigned char yuv422[768*576*2]; ////////////////////////////////////////////////////////////////////// while(readFileLen > 0)//开始解码工作 { //printf("begin...\n"); readFileLen = fread(readBuf, 1, sizeof(readBuf), myH264);//首先从文件里读出数据 if(readFileLen <= 0) { printf("read over\n"); break; } else { int handleLen = 0; int handleFileLen = readFileLen; while(handleFileLen > 0) { int nLength = av_parser_parse2(avParserContext, codec_, &parseBuf, &parseBufLen, readBuf + handleLen, handleFileLen, 0, 0, 0);//查找264帧头 handleFileLen -= nLength; handleLen += nLength; if(parseBufLen <= 0)//当parseBufLen大于0时,说明查找到了帧头 { continue; } packet.size = parseBufLen;//将查找到的帧长度送入 packet.data = parseBuf;//将查找到的帧内存送入 if(frameCount>100)break; //printf("parseBuf address is %x\n", parseBuf); while(packet.size > 0) {//下面开始真正的解码 int decodeLen = avcodec_decode_video2(codec_, pFrame_, &frameFinished, &packet); //if(decodeLen < 0)break; packet.size -= decodeLen; packet.data += decodeLen; if(frameFinished > 0)//成功解码 { int picSize = codec_->height * codec_->width; //int newSize = picSize * 1.5; //申请内存 //unsigned char *buf = malloc(newSize); int height = pFrame_->height; int width = pFrame_->width; //printf("OK, get data\n"); //printf("Frame height is %d\n", height); //printf("Frame width is %d\n", width); frameCount ++; printf("Frame count is %d\n", frameCount); pgm_save(pFrame_->data[0], pFrame_->linesize[0],//保存Y codec_->width, codec_->height, yuvfile); pgm_save(pFrame_->data[1], pFrame_->linesize[1],//保存U codec_->width/2, codec_->height/2, yuvfile); pgm_save(pFrame_->data[2], pFrame_->linesize[2],//保存V codec_->width/2, codec_->height/2, yuvfile); ///有了YUV数据, 后面可以用FFMPEG提供的转换方法,将其转成RGB数据,进行后续的显示或其它的图像处理工作 ////sdl int i; /* for(i=0;i<576;i++) {//fwrite(buf + i * wrap, 1, xsize, f); memcpy(overlay->pixels[0]+i*720, pFrame_->data[0]+i*pFrame_->linesize[0], 720); } for(i=0;i<288;i++) { memcpy(overlay->pixels[2]+i*360, pFrame_->data[1]+i*pFrame_->linesize[1], 360); memcpy(overlay->pixels[1]+i*360, pFrame_->data[2]+i*pFrame_->linesize[2], 360); }*/ int k=0,y,x; //yuv420 -> yuv422 for( y=0;y<576;y++) { for( x=0;x<720;x++) { yuv422[k++] = pFrame_->data[0][y*pFrame_->linesize[0]+x]; yuv422[k++] = x%2==0?pFrame_->data[1][(y/2)*pFrame_->linesize[1]+x/2]:pFrame_->data[2][(y/2)*pFrame_->linesize[2]+x/2]; } } memcpy(overlay->pixels[0],yuv422, codec_->width*codec_->height*2); SDL_UnlockYUVOverlay(overlay); SDL_UnlockSurface(screen); rect.w = w; rect.h = h; rect.x = rect.y = 0; SDL_DisplayYUVOverlay(overlay, &rect); //sdl SDL_Delay(40); } else printf("failed to decodec\n"); } } } } //////释放工作 avcodec_close(codec_); av_free(codec_); av_free_packet(&packet); av_frame_free(&pFrame_); //SDL SDL_FreeYUVOverlay(overlay); SDL_FreeSurface(screen); //Quit SDL SDL_Quit(); fclose(yuvfile); fclose(myH264); }
1、确认Makefile中指定的config.mak(在ffmpeg根目录下)中:CONFIG_FFPLAY=yes,如果不是需要重新./configure 该处还有ffmpeg、ffprobe、ffserver可以打开。 2、编译时需要安装libsdl1.2-dev,命令为sudo apt-get install libsdl1.2-dev,如果安装失败,之前如果安装过sdl-devel包最好将其卸载。根据错误原因来解决。 ------------------------------------------分割线------------------------------------------ 推荐阅读: Linux下编译FFmpeg之下载源文件并编译 http://www.linuxidc.com/Linux/2012-02/54565.htm Linux 编译升级 FFmpeg 步骤 http://www.linuxidc.com/Linux/2013-08/88190.htm CentOS 5.6 上安装 FFMPEG http://www.linuxidc.com/Linux/2011-09/42793.htm 在Ubuntu下安装FFmpeg http://www.linuxidc.com/Linux/2012-12/75408.htm Ubuntu 14.04下PPA安装FFmpeg 2.2.2 http://www.linuxidc.com/Linux/2014-05/101322.htm ------------------------------------------分割线------------------------------------------ 可能需要附加包: 附加包: sudo apt-get install libsdl-image1.2-dev sudo apt-get install libsdl-mixer1.2-dev sudo apt-get install libsdl-ttf2.0-dev sudo apt-get install libsdl-gfx1.2-dev 编译选项可以把编码、复用、滤波器等包关了,会小些。 3、make,结束后发现ffplay已经生成了 使用的时候可能还会提醒安装libav-tools,看提示 --------------------------------------------------------------------------------------------------------------- 在linux系统找一个合适的目录: 输入:Git clone git://source.ffmpeg.org/ffmpeg.git ffmpeg获取ffmpeg源码 cd ffmpeg进入其目录 ./configure --prefix=/usr/local --enable-memalign-hack --enable-shared make make install 这样就安装好了。 但是这样做并没有我们需要的ffplay,所以要先安装SDL 到SDL官网下载其源码, 解压:tar -zxvf SDL-1.2.15.tar.gz cd SDL-1.2.15.tar.gz ./configure --prefix=/usr/local make make install 这样就安装好了SDL。 接下来重新安装一下ffmpeg后就可以在/usr/local/bin/目录下看到有ffplay了。 可以测试一下是否安装成功: 找一个有.flv视频的目录, 运行:ffplay xxx.flv 就弹出窗口播放视频了。。。。
1.ffmpeg下载地址: http://www.ffmpeg.org/download.html 2.解压 1 $ tar zvfj ffmpeg.tar.bz2 这里作者假设已经重命名为ffmpeg.tar.bz2 3.解压后进入ffmpeg的文件夹,查看readme, 可以看到和正常的编译安装步骤无异configure && make && make install 1 $ cd folder_of_ffmpeg 1 $ ./configure --enable-shared --prefix=/usr/local/ffmpeg 编译FFMPEG时,出现了 ffmpeg yasm not found, use –disable-yasm for a crippled build,是因为 FFMPEG为了提高编译速度,使用了汇编指令,如果系统中没有yasm指令的话,就会出现上述的问题。解决办法是:A 如果是Windows系统, 从网上下载一个 yasm.exe 并安装在mingw/bin下面,重新编译,就不会出现该错误了;B 如果是Linux系统,则更简单,直接在终端输入 sudo apt-get install yasm (centos 输入sudo yum install yasm),安装好后,重新编译就 OK了 1 $ sudo apt-get install yasm 1 $ ./configure --enable-shared --prefix=/usr/local/ffmpeg 这一次,编译成功, 出现一大串字符, 大致如下: 4.安装 1 $ make 1 $ make install 可见直接make install会出现权限问题, 因为之前编译时指定的文件夹是/usr/local/ffmpeg, 所以需要sudo权限 1 $ sudo make install ok,安装成功, 在命令行下试一下命令使用 1 $ /usr/local/ffmpeg/bin/ffmpeg 这时候出现ffmpeg: error while loading shared libraries: libavdevice.so.56: cannot open shared object file: No such file or directory的错误.(部分高版本缺少的是libavdevice.so.54) 我们尝试在系统中找到这个库: 1 $ sudo find / -name "libavdevice.so.56" 由结果可见, 在我们编译后的/usr/local/ffmpeg/lib/中存在libavdevice.so.56, 我们需要将这个库链接写到/etc/ld.so.conf中然后执行sudo ldconfig, 操作如下: 1 $ sudo echo '/usr/local/ffmpeg/lib/libavdevice.so.56' >> /etc/ld.so.conf 1 $ sudo ldconfig 现在, 全部搞定啦~
linux下有没有TurboC2.0那样的画点、线、圆的图形函数库,有没有grapihcs.h,或者与之相对应或相似的函数库是什么?有没有DirectX这样的游戏开发库?SDL就是其中之一。 SDL(Simple DirectMedia Layer)是一个夸平台的多媒体游戏支持库,其中包含了对图形、声音、游戏杆、线程等的支持,目前可以运行在许多平台上,其中包括linux的 FrameBuffer控制台、svgalib、X Window环境,以及Windows DirectX、BeOS等。SDL是编写夸平台游戏和多媒体应用的优秀平台,与Windows的DirectX有的一比。主页:http: //www.libsdl.org。 SDL库几乎已经成了目前流行的Linux的标配的多媒体库,系统安装时一般都已经默认安装了它们。利用SDL库开发应用程序,首先,要在程序中声明要使用的相应的头文件,比如:#include <SDL/SDL.h>,然后,在编译时指出要连接的SDL库即可,比如:gcc -lSDL test.c -o test。SDL库一般位于系统的标准头文件目录/usr/include里,编译器会在这个目录里找相应的头文件,如果要进一步省略“SDL/”,则必须在编译时指定头文件的具体位置,例如:gcc -I /usr/include/SDL -lSDL test.c -o test。也可以:gcc `sdl-config-libs-cflags` test.c -o test。“`”不是单引号,而是位于键盘左上方的反引号。 要在linux控制台字符界面的环境下进行图形开发,还要打开framebuffer功能,方法是修改/boot/grub/grub.conf配置文件,在kernel...一行后面添加vga=0x317。如下:title Fedora Core (2.6.15-1.2054_FC5) root (hd0,5) kernel /vmlinuz-2.6.15-1.2054_FC5 ro root=LABEL=/ rhgb quiet vga=0x0317 initrd /initrd-2.6.15-1.2054_FC5.img关于VGA值与显示器分辨率的关系如表: 640X480 800X600 1024X768 1280X10248位色 0x301 0x303 0x305 0x30716位色 0x311 0x314 0x317 0x31A24位色 0x312 0x315 0x318 0x31B初始化图形模式要加载和初始化SDL库需要调用SDL_Init()函数,该函数以一个参数来传递要激活的子系统的标记,返回-1表示初始化失败。下表列出来SDL的各个子系统:标记 表示SDL_INIT_VIDEO 视频子系统SDL_INIT_AUDIO 音频子系统SDL_INIT_CDROM 光驱子系统SDL_INIT_TIMER 计时器子系统SDL_INIT_JOYSTICK 游戏杆子系统SDL_INIT_EVERYTHING 全部子系统要同时激活多个子系统,可以把相应的标记按位或,如:SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO);初始化SDL库之后,还需要设置一下视频模式,通过调用SDL_SetVideoMode()来完成:SDL_Surface *screen;screen=SDL_etVideoMode(640,480,16,SDL_SWSURFACE);/*640 X 480 X 16位色*/SDL_Surface 定义在SDL_video.h中,它是一个绘图平面,所有的绘图操作都是在其上完成的。在退出图形模式时由SDL自动处理。不需要用时需显示的释放:SDL_FreeSurface(surface);先来看一下一个完整的例子://ex_sdl.c#include <stdlib.h>#include <SDL.h>int main(){ SDL_Surface *screen; Uint32 color; if ( SDL_Init( SDL_INIT_VIDEO) < 0 ) { fprintf(stderr, "无法初始化SDL: %s\n", SDL_GetError()); exit(1); } SDL_ShowCursor(0); screen = SDL_SetVideoMode(640, 480, 16, SDL_SWSURFACE); /*640 X 480 X 16位色*/ if ( screen == NULL ) { fprintf(stderr, "无法设置640x480x16位色的视频模式:%s\n", SDL_GetError()); exit(1); } atexit(SDL_Quit); color = SDL_MapRGB(screen->format,0,0,255); /*蓝色*/ SDL_FillRect(screen,&screen->clip_rect,color); /*整个屏幕填充颜色*/ SDL_UpdateRect(screen,0,0,0,0); /*更新整个屏幕*/ SDL_Delay(5000); /*延迟5秒钟*/}atexit(SDL_Quit);的作用是在程序退出时调用SDL_Quit()函数,这样就不必在每个要退出的地方都调用SDL_Quit()。
Linux忘记开机密码怎么办?1. 开机ESC/Shift,在出现grub画面时,用上下键选中你平时启动linux的那一项,然后按e键2. 再次用上下键选中你平时启动linux的那一项(类似于kernel/boot/vmlinuz-2.4.18-14 ro root=LABEL=/),然后按e键3. 修改你现在见到的命令行,加入single,结果如下:kernel /boot/vmlinuz-2.4.18-14 single ro root=LABEL=/ single4. 回车返回,然后按b键启动,即可直接进入linux命令行5.用password/passwd 命令修改密码
一、ldconfig ldconfig是一个动态链接库管理命令,为了让动态链接库为系统所共享,还需运行动态链接库的管理命令--ldconfig。 ldconfig 命令的用途,主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态 链接库(格式如前介绍,lib*.so*),进而创建出动态装入程序(ld.so)所需的连接和缓存文件.缓存文件默认为 /etc/ld.so.cache,此文件保存已排好序的动态链接库名字列表. linux下的共享库机制采用了类似于高速缓存的机制,将库信息保存在/etc/ld.so.cache里边。 程序连接的时候首先从这个文件里边查找,然后再到ld.so.conf的路径里边去详细找。 这就是为什么修改了ld.so.conf要重新运行一下ldconfig的原因 补充一点,ldconfig在/sbin里面。 ldconfig几个需要注意的地方 1. 往/lib和/usr/lib里面加东西,是不用修改/etc/ld.so.conf的,但是完了之后要调一下ldconfig,不然这个library会找不到 2. 想往上面两个目录以外加东西的时候,一定要修改/etc/ld.so.conf,然后再调用ldconfig,不然也会找不到 比如安装了一个MySQL到/usr/local/mysql,mysql有一大堆library在/usr/local/mysql/lib下面,这时 就需要在/etc/ld.so.conf下面加一行/usr/local/mysql/lib,保存过后ldconfig一下,新的library才能在 程序运行时被找到。 3. 如果想在这两个目录以外放lib,但是又不想在/etc/ld.so.conf中加东西(或者是没有权限加东西)。那也可以,就是export一个全局变 量LD_LIBRARY_PATH,然后运行程序的时候就会去这个目录中找library。一般来讲这只是一种临时的解决方案,在没有权限或临时需要的时 候使用。 4. ldconfig做的这些东西都与运行程序时有关,跟编译时一点关系都没有。编译的时候还是该加-L就得加,不要混淆了。 5. 总之,就是不管做了什么关于library的变动后,最好都ldconfig一下,不然会出现一些意想不到的结果。不会花太多的时间,但是会省很多的事。 二、ldd 作用:用来查看程序运行所需的共享库,常用来解决程序因缺少某个库文件而不能运行的一些问题。ldd命令原理(摘自网络)1、首先ldd不是一个可执行程序,而只是一个shell脚本2、ldd能够显示可执行模块的dependency,其原理是通过设置一系列的环境变量,如下:LD_TRACE_LOADED_OBJECTS、LD_WARN、LD_BIND_NOW、LD_LIBRARY_VERSION、LD_VERBOSE等。当LD_TRACE_LOADED_OBJECTS环境变量不为空时,任何可执行程序在运行时,它都会只显示模块的dependency,而程序并不真正执行。要不你可以在shell终端测试一下,如下:(1) export LD_TRACE_LOADED_OBJECTS=1(2) 再执行任何的程序,如ls等,看看程序的运行结果3、ldd显示可执行模块的dependency的工作原理,其实质是通过ld-linux.so(elf动态库的装载器)来实现的。我们知道,ld-linux.so模块会先于executable模块程序工作,并获得控制权,因此当上述的那些环境变量被设置时,ld-linux.so选择了显示可执行模块的dependency。4、实际上可以直接执行ld-linux.so模块,如:/lib/ld-linux.so.2 --list program(这相当于ldd program)
许多windows非常熟悉ipconfig命令行工具,它被用来获取网络接口配置信息并对此进行修改。Linux系统拥有一个类似的工具,也就是ifconfig(interfaces config)。通常需要以root身份登录或使用sudo以便在Linux机器上使用ifconfig工具。依赖于ifconfig命令中使用一些选项属性,ifconfig工具不仅可以被用来简单地获取网络接口配置信息,还可以修改这些配置。 1.命令格式: ifconfig [网络设备] [参数] 2.命令功能: ifconfig 命令用来查看和配置网络设备。当网络环境发生改变时可通过此命令对网络进行相应的配置。 3.命令参数: up 启动指定网络设备/网卡。 down 关闭指定网络设备/网卡。该参数可以有效地阻止通过指定接口的IP信息流,如果想永久地关闭一个接口,我们还需要从核心路由表中将该接口的路由信息全部删除。 arp 设置指定网卡是否支持ARP协议。 -promisc 设置是否支持网卡的promiscuous模式,如果选择此参数,网卡将接收网络中发给它所有的数据包 -allmulti 设置是否支持多播模式,如果选择此参数,网卡将接收网络中所有的多播数据包 -a 显示全部接口信息 -s 显示摘要信息(类似于 netstat -i) add 给指定网卡配置IPv6地址 del 删除指定网卡的IPv6地址 <硬件地址> 配置网卡最大的传输单元 mtu<字节数> 设置网卡的最大传输单元 (bytes) netmask<子网掩码> 设置网卡的子网掩码。掩码可以是有前缀0x的32位十六进制数,也可以是用点分开的4个十进制数。如果不打算将网络分成子网,可以不管这一选项;如果要使用子网,那么请记住,网络中每一个系统必须有相同子网掩码。 tunel 建立隧道 dstaddr 设定一个远端地址,建立点对点通信 -broadcast<地址> 为指定网卡设置广播协议 -pointtopoint<地址> 为网卡设置点对点通讯协议 multicast 为网卡设置组播标志 address 为网卡设置IPv4地址 txqueuelen<长度> 为网卡设置传输列队的长度 4.使用实例: 实例1:显示网络设备信息(激活状态的) 命令: ifconfig 输出: [root@localhost ~]# ifconfigeth0 Link encap:Ethernet HWaddr 00:50:56:BF:26:20 inet addr:192.168.120.204 Bcast:192.168.120.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:8700857 errors:0 dropped:0 overruns:0 frame:0 TX packets:31533 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:596390239 (568.7 MiB) TX bytes:2886956 (2.7 MiB)lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 UP LOOPBACK RUNNING MTU:16436 Metric:1 RX packets:68 errors:0 dropped:0 overruns:0 frame:0 TX packets:68 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:2856 (2.7 KiB) TX bytes:2856 (2.7 KiB) 说明: eth0 表示第一块网卡, 其中 HWaddr 表示网卡的物理地址,可以看到目前这个网卡的物理地址(MAC地址)是 00:50:56:BF:26:20 inet addr 用来表示网卡的IP地址,此网卡的 IP地址是 192.168.120.204,广播地址, Bcast:192.168.120.255,掩码地址Mask:255.255.255.0 lo 是表示主机的回坏地址,这个一般是用来测试一个网络程序,但又不想让局域网或外网的用户能够查看,只能在此台主机上运行和查看所用的网络接口。比如把 HTTPD服务器的指定到回坏地址,在浏览器输入 127.0.0.1 就能看到你所架WEB网站了。但只是您能看得到,局域网的其它主机或用户无从知道。 第一行:连接类型:Ethernet(以太网)HWaddr(硬件mac地址) 第二行:网卡的IP地址、子网、掩码 第三行:UP(代表网卡开启状态)RUNNING(代表网卡的网线被接上)MULTICAST(支持组播)MTU:1500(最大传输单元):1500字节 第四、五行:接收、发送数据包情况统计 第七行:接收、发送数据字节数统计信息。 实例2:启动关闭指定网卡 命令: ifconfig eth0 up ifconfig eth0 down 输出: 说明: ifconfig eth0 up 为启动网卡eth0 ;ifconfig eth0 down 为关闭网卡eth0。ssh登陆linux服务器操作要小心,关闭了就不能开启了,除非你有多网卡。 实例3:为网卡配置和删除IPv6地址 命令: ifconfig eth0 add 33ffe:3240:800:1005::2/64 ifconfig eth0 del 33ffe:3240:800:1005::2/64 输出: 说明: ifconfig eth0 add 33ffe:3240:800:1005::2/64 为网卡eth0配置IPv6地址; ifconfig eth0 add 33ffe:3240:800:1005::2/64 为网卡eth0删除IPv6地址; 练习的时候,ssh登陆linux服务器操作要小心,关闭了就不能开启了,除非你有多网卡。 实例4:用ifconfig修改MAC地址 命令: ifconfig eth0 hw ether 00:AA:BB:CC:DD:EE 输出: [root@localhost ~]# ifconfig eth0 down //关闭网卡[root@localhost ~]# ifconfig eth0 hw ether 00:AA:BB:CC:DD:EE //修改MAC地址[root@localhost ~]# ifconfig eth0 up //启动网卡[root@localhost ~]# ifconfigeth0 Link encap:Ethernet HWaddr 00:AA:BB:CC:DD:EE inet addr:192.168.120.204 Bcast:192.168.120.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:8700857 errors:0 dropped:0 overruns:0 frame:0 TX packets:31533 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:596390239 (568.7 MiB) TX bytes:2886956 (2.7 MiB)lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 UP LOOPBACK RUNNING MTU:16436 Metric:1 RX packets:68 errors:0 dropped:0 overruns:0 frame:0 TX packets:68 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:2856 (2.7 KiB) TX bytes:2856 (2.7 KiB)[root@localhost ~]# ifconfig eth0 hw ether 00:50:56:BF:26:20 //关闭网卡并修改MAC地址 [root@localhost ~]# ifconfig eth0 up //启动网卡[root@localhost ~]# ifconfigeth0 Link encap:Ethernet HWaddr 00:50:56:BF:26:20 inet addr:192.168.120.204 Bcast:192.168.120.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:8700857 errors:0 dropped:0 overruns:0 frame:0 TX packets:31533 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:596390239 (568.7 MiB) TX bytes:2886956 (2.7 MiB)lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 UP LOOPBACK RUNNING MTU:16436 Metric:1 RX packets:68 errors:0 dropped:0 overruns:0 frame:0 TX packets:68 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:2856 (2.7 KiB) TX bytes:2856 (2.7 KiB) 说明: 实例5:配置IP地址 命令: 输出: [root@localhost ~]# ifconfig eth0 192.168.120.56 [root@localhost ~]# ifconfig eth0 192.168.120.56 netmask 255.255.255.0 [root@localhost ~]# ifconfig eth0 192.168.120.56 netmask 255.255.255.0 broadcast 192.168.120.255 说明: ifconfig eth0 192.168.120.56 给eth0网卡配置IP地:192.168.120.56 ifconfig eth0 192.168.120.56 netmask 255.255.255.0 给eth0网卡配置IP地址:192.168.120.56 ,并加上子掩码:255.255.255.0 ifconfig eth0 192.168.120.56 netmask 255.255.255.0 broadcast 192.168.120.255 /给eth0网卡配置IP地址:192.168.120.56,加上子掩码:255.255.255.0,加上个广播地址: 192.168.120.255 实例6:启用和关闭ARP协议 命令: ifconfig eth0 arp ifconfig eth0 -arp 输出: [root@localhost ~]# ifconfig eth0 arp [root@localhost ~]# ifconfig eth0 -arp 说明: ifconfig eth0 arp 开启网卡eth0 的arp协议; ifconfig eth0 -arp 关闭网卡eth0 的arp协议; 实例7:设置最大传输单元 命令: ifconfig eth0 mtu 1500 输出: [root@localhost ~]# ifconfig eth0 mtu 1480[root@localhost ~]# ifconfigeth0 Link encap:Ethernet HWaddr 00:50:56:BF:26:1F inet addr:192.168.120.203 Bcast:192.168.120.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1480 Metric:1 RX packets:8712395 errors:0 dropped:0 overruns:0 frame:0 TX packets:36631 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:597062089 (569.4 MiB) TX bytes:2643973 (2.5 MiB)lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 UP LOOPBACK RUNNING MTU:16436 Metric:1 RX packets:9973 errors:0 dropped:0 overruns:0 frame:0 TX packets:9973 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:518096 (505.9 KiB) TX bytes:518096 (505.9 KiB)[root@localhost ~]# ifconfig eth0 mtu 1500[root@localhost ~]# ifconfigeth0 Link encap:Ethernet HWaddr 00:50:56:BF:26:1F inet addr:192.168.120.203 Bcast:192.168.120.255 Mask:255.255.255.0 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:8712548 errors:0 dropped:0 overruns:0 frame:0 TX packets:36685 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:597072333 (569.4 MiB) TX bytes:2650581 (2.5 MiB)lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 UP LOOPBACK RUNNING MTU:16436 Metric:1 RX packets:9973 errors:0 dropped:0 overruns:0 frame:0 TX packets:9973 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:518096 (505.9 KiB) TX bytes:518096 (505.9 KiB)[root@localhost ~]# 说明: 设置能通过的最大数据包大小为 1500 bytes 备注:用ifconfig命令配置的网卡信息,在网卡重启后机器重启后,配置就不存在。要想将上述的配置信息永远的存的电脑里,那就要修改网卡的配置文件了。
复制代码 代码如下: if list then do something here elif list then do another thing here else do something else here fi EX1: 复制代码 代码如下: #!/bin/shSYSTEM=`uname -s` #获取操作系统类型,我本地是linuxif [ $SYSTEM = "Linux" ] ; then #如果是linux的话打印linux字符串echo "Linux" elif [ $SYSTEM = "FreeBSD" ] ; then echo "FreeBSD" elif [ $SYSTEM = "Solaris" ] ; then echo "Solaris" else echo "What?" fi #ifend 基本上和其他脚本语言一样。没有太大区别。不过值得注意的是。[]里面的条件判断。 1、字符串判断 str1 = str2 当两个串有相同内容、长度时为真 str1 != str2 当串str1和str2不等时为真 -n str1 当串的长度大于0时为真(串非空) -z str1 当串的长度为0时为真(空串) str1 当串str1为非空时为真 2、数字的判断 int1 -eq int2 两数相等为真 int1 -ne int2 两数不等为真 int1 -gt int2 int1大于int2为真 int1 -ge int2 int1大于等于int2为真 int1 -lt int2 int1小于int2为真 int1 -le int2 int1小于等于int2为真 3、文件的判断 -r file 用户可读为真 -w file 用户可写为真 -x file 用户可执行为真 -f file 文件为正规文件为真 -d file 文件为目录为真 -c file 文件为字符特殊文件为真 -b file 文件为块特殊文件为真 -s file 文件大小非0时为真 -t file 当文件描述符(默认为1)指定的设备为终端时为真 4、复杂逻辑判断 -a 与 -o 或 ! 非 结尾 语法虽然简单,但是在SHELL里使用的时候,他的功能变得强大了。 ===================================================================== 附 表: [ -a FILE ] 如果 FILE 存在则为真。 [ -b FILE ] 如果 FILE 存在且是一个块特殊文件则为真。 [ -c FILE ] 如果 FILE 存在且是一个字特殊文件则为真。 [ -d FILE ] 如果 FILE 存在且是一个目录则为真。 [ -e FILE ] 如果 FILE 存在则为真。 [ -f FILE ] 如果 FILE 存在且是一个普通文件则为真。 [ -g FILE ] 如果 FILE 存在且已经设置了SGID则为真。 [ -h FILE ] 如果 FILE 存在且是一个符号连接则为真。 [ -k FILE ] 如果 FILE 存在且已经设置了粘制位则为真。 [ -p FILE ] 如果 FILE 存在且是一个名字管道(F如果O)则为真。 [ -r FILE ] 如果 FILE 存在且是可读的则为真。 [ -s FILE ] 如果 FILE 存在且大小不为0则为真。 [ -t FD ] 如果文件描述符 FD 打开且指向一个终端则为真。 [ -u FILE ] 如果 FILE 存在且设置了SUID (set user ID)则为真。 [ -w FILE ] 如果 FILE 如果 FILE 存在且是可写的则为真。 [ -x FILE ] 如果 FILE 存在且是可执行的则为真。 [ -O FILE ] 如果 FILE 存在且属有效用户ID则为真。 [ -G FILE ] 如果 FILE 存在且属有效用户组则为真。 [ -L FILE ] 如果 FILE 存在且是一个符号连接则为真。 [ -N FILE ] 如果 FILE 存在 and has been mod如果ied since it was last read则为真。 [ -S FILE ] 如果 FILE 存在且是一个套接字则为真。 [ FILE1 -nt FILE2 ] 如果 FILE1 has been changed more recently than FILE2, or 如果 FILE1 exists and FILE2 does not则为真。 [ FILE1 -ot FILE2 ] 如果 FILE1 比 FILE2 要老, 或者 FILE2 存在且 FILE1 不存在则为真。 [ FILE1 -ef FILE2 ] 如果 FILE1 和 FILE2 指向相同的设备和节点号则为真。 [ -o OPTIONNAME ] 如果 shell选项 “OPTIONNAME” 开启则为真。 [ -z STRING ] “STRING” 的长度为零则为真。 [ -n STRING ] or [ STRING ] “STRING” 的长度为非零 non-zero则为真。 [ STRING1 == STRING2 ] 如果2个字符串相同。 “=” may be used instead of “==” for strict POSIX compliance则为真。 [ STRING1 != STRING2 ] 如果字符串不相等则为真。 [ STRING1 < STRING2 ] 如果 “STRING1” sorts before “STRING2” lexicographically in the current locale则为真。 [ STRING1 > STRING2 ] 如果 “STRING1” sorts after “STRING2” lexicographically in the current locale则为真。 [ ARG1 OP ARG2 ] “OP” is one of -eq, -ne, -lt, -le, -gt or -ge. These arithmetic binary operators return true if “ARG1” is equal to, not equal to, less than, less than or equal to, greater than, or greater than or equal to “ARG2”, respectively. “ARG1” and “ARG2” are integers. #!/bin/sh echo "0: linux running on initrd.img" echo "1: linux running on usb ext2 filesystem" read -p "select: " data if [ $data = "0" ]; then echo "0: linux running on initrd.img selected" sleep 3 sh else echo "1: linux running on usb ext2 filesystem selected" sleep 3 fi
如果不考虑内存大小的限制,在linux下面,fd (即file descriptor)的数量来自2个限制(阈值)。其一:是操作系统的限制。这个限制主要是在linux内核中,我们知道,用户程序的fopen操作最后都通过system call进入到linux kenrel。linux kernel会对此进行检查,防止某个用户占用太多的系统资源。现在的内核都可以通过sysctl命令在开机的时候来调整。他是不是还有一个代码级别的最大值(如定义了宏),我没有仔细研究关于此的代码,故不能确定。但据我所知,某些程序开上万个fd也是有在用的。命令sysctl fs.file-max=655360可以调整内核的阈值,当然你得有root权限。想一劳永逸,参考/etc/sysctl.conf,用命令man sysctl.conf命令sysctl -a可以显示所有的能够调整参数。其二:是用户进程的限制。举例,在bash环境下启动的程序将继承bash缺省的或用户定制的限制。这个限制可以通过bash的内部命令ulimit来调整,当然不能高过操作系统的限制。比如命令:ulimit -n 20规定了在当前bash环境下运行的程序只能同时打开20个fd,但是如果你做上面的测试程序,则只有17个。还有3个哪里去了?动脑筋想一下你应该能找到答案。ulimit -n 命令只能往下调fd,不能往上调。如果你改的过小了,想反悔?好像只能关闭当前的bash再重新开启一个。那么,bash又是从哪里继承的呢?参考/etc/security/limits.conf,用命令man limits.confulimit -a可以显示包括fd在内的全部阈值:如最大数据段大小、最大代码段大小、最大栈大小、用户能创建的进程的最大数目、一个进程中线程的最大数目。试试在s2服务器上运行此命令,结果应该是最大可同时打开1024个fd。寻求更多的信息?老办法:输入命令man bash,然后查找ulimit。
我们有时候有需要在busybox基础上,制作linux,可是却不知道具体怎么做,这里将对基于busybox的linux小系统制作做出详细的步骤说明。准备环境:1、一个Redhat完整系统的虚拟机,本次实例使用的是Redhat Enterprise Linux 5.82、在主虚拟机上添加一块硬盘作为小系统的存储盘,这里添加的是IDE硬盘,3、准备linux内核源码以及busybox源码,这里使用linux-2.6.38.5和busybox-1.20.2版本4、复制当前系统上的内核配置(/usr/src/kernel/2.6.18-308.el5-i686/.config),做略微修改;若当前系统内核版本与小linux的内核版本不同,可准备一个匹配的内核蓝本。我这里由于主系统的内核版本比较老,所以准备了一个较新的内核蓝本(kernel-2.6.38.1-i686.cfg)进行修改编译。具体过程:一、编译内核1、将/root下的内核源码解压缩至/usr/src下的linux-2.6.38.5,并给其创建一个连接,命名为linux 2、在linux内核链接文件中以/root/kernel-2.6.38.1-i686.cfg为蓝本编译内核(1)复制内核蓝本至/usr/src/linux目录下,命名为.config(2)对当前内核进行编译,可根据实际需要来选择各种功能。本次实例主要是将ext3文件系统以及pcnet32的vmware虚拟机网卡驱动直接装载进内核,其他的按需选择。结束后将编译的功能可自动保存至.config文件中。最后执行make SUBDIR=/arch 进行编译【进入手动编译内核界面,前提是grouplist里已安装"Development Tools""Development Libraries"组,若未安装,则配置yum源,安装这两个包组】【在Device Drivers --> Network device support --> Ethernet (10 or 100Mbit) --> 查找AMD PCnet32 PCI support ,将其改为* 即直接编译进内核】【在File systems --> 中将Ext3装载进内核】【保存退出】【执行make SUBDIR=/arch进行编译,注:上述必须在/usr/src/linux中进行】 编译过程大概需要一段时间,可等其编译完成,也可提前做下一步。二、编译busybox1、在新硬盘上分区,这里需要一个大小100M的主分区作为小系统的boot分区,一个512M大小的主分区为小系统的根分区。将这两个分区格式化后,分别挂载至/mnt/boot和/mnt/sysroot目录。 (忘了说,w保存退出) # 同步磁盘2、安装grub 3、编译busybox【此处的默认配置提供很多我们需要的程序,因此不进行过多的修改,只需将编译选项改改,编译成一个不使用共享库的静态二进制文件,从而避免了对主系统机的共享库产生依赖;但你也可以不选择此项,而完成编译后把其依赖的共享库复制至目标系统上的/lib目录中即可;这里采用前一种办法。】Busybox Settings --> Build Options --> Build BusyBox as a static binary (no shared libs)【保存退出】【接着进行make install编译 注:是在busybox-1.20.2目录下进行】三、制作initrd(1)创建一个目录专门用来实现基于busybox的initrd(2)在/tmp/initrd下制作initrd【提供基本目录】 【创建init脚本】# vim /tmp/init# chmod +x init 加执行权限【制作initrd;归档并压缩当前目录下的所有文件至/mnt/boot/initrd.gz】四、内核编译成功后,装载内核五、提供grub配置文件# vim /mnt/boot/grub/grub.conf六、建立一个真正的根文件系统【将busybox-1.20.2/_install/*复制到/mnt/sysroot/ 即将busybox制作的rootfs作为小linux的根文件系统】【创建所需的目录,即建立rootfs】【创建两个必要的设备文件】【配置init及其所需要inittab文件,即为init进程提供配置文件】# vim /mnt/sysroot/etc/inittab【建立系统初始化脚本】# vim /mnt/sysroot/etc/rc.d/rc.sysinit【提供开机自动挂载的配置文件etc/fstab】# vim /mnt/sysroot/etc/fstab【为了适应我们习惯了使用的bash,这里将bash复制过去,并将之前的脚本中的sh改为bash】将etc/inittab中设定的sh改为bash即可# vim /mnt/sysroot/etc/inittab【进行同步】 # sync # sync # sync # sync # sync 七、测试启动装有小系统硬盘的目标主机,看是否顺利执行各个命令。
linux内核initrd文件自定义方法 重新编译内核后,可能加入了自定义的模块,就有可能需要修改init文件,而init文件就在initrd中,这里记录下操作步骤,以防遗忘。 1. cp /boot/initrd-3.2.img /tmp/mylinux/initrd-3.2.img.gz 这里之所以进行改名,是因为initrd-3.2.img是经过gzip压缩过的,所以需要对其解压,但是gzip对解压的文件的文件后缀名又有要求,所以就先进行改名。 2. gunzip initrd-3.2.9.img.gz 3. cpio -id < initrd-3.2.9.img 经过以上三步,就在当前目录下解压了initrd文件,从而得到了init文件。 根据自己的需求修改init文件后,通过下面命令重新生成initrd文件。 4. find . | cpio -H newc -o | gzip -9 > /boot/initrd-3.2.9.img 注意一下内容摘自网上资料,留作参考: en_init_cpio获取 gen_init_cpio,工具 ,gen_init_cpio是编译内核时得到的,在内核源代码的 usr 目录下,我们可以通过 以下步骤获取它,进入内核源代码 执行 :# make menuconfig# make usr/这样即编译好gen_init_cpio,gen_initramfs_list.sh 在内核源代码的 script 目录下,将这两个 文件 copy 到 /tmp 目录下,/tmp/initrd 为 解压好的 initrd 目录,执行以下命令 制作initrd : #制作initrd : # gen_initramfs_list.sh initrd/ > filelist # gen_init_cpio filelist >initrd.img # gzip initrd.img # mv initrd.img initrd-'uname –r’.img只有用这个方式压缩的initrd ,在Linux系统重启的时候才能 一正确的文件格式 boot 起来,也可以用这种方式修改安装光盘的initrd文件 然后 进行系统安装。3. 如何在 initrd 中添加新的驱动,以 ahci.ko 为例 3.1 gen_init_cpio # cp initrd-‘uname –r‘.img /tmp/initrd;cd /tmp/initrd #cpio –ivdum < initrd-‘uname –r’.img; # mv initrd-‘uname –r’.img ../ #cd /tmp/initrd #vim init加上一行 insmod /lib/ahci.ko #cp ahci.ko lib/ #cd /tmp # gen_initramfs_list.sh initrd/ > filelist # gen_init_cpio filelist >initrd.img # gzip initrd.img # mv initrd.img initrd-‘uname –r’.img至此,新的initrd文件initrd-‘uname –r’.img中就包含了ahci的驱动程序了 ,这种方式是最简单有效的。 3.2 mkinitrd(1) Add “alias scsi_hostadapter ahci” at /etc/modprobe.conf(2) copy ahci.ko to “/lib/module/$(kernel-version)”/kernel/drivers/scsi”(3) mkinitrd initrd.img ‘uname -r’至此,新的initrd文件initrd-‘uname –r’.img中就包含了ahci的驱动程序了 . #释放cpio格式的initrd: mv initrd.img imitrd.img.gz gunzip initrd.img.gz cpio -i --make-directories < initrd.img #释放centos6.2系统的initramfs.img 1."gunzip initrd.img-2.6.27-7-generic.gz",得到一个未压缩的initrd.img-2.6.27-7-generic 2. ”cpio -iv < initrd.img-2.6.27-7-generic",提取成功 #制作cpio格式的initrd(新2012年使用过的) #cd /root/busybox-1.15.3/rootfs9260 #find . | cpio -H newc -o > ../initrd_cpio.img #制作cpio格式的initrd(2009年制作的LFS的方式):dd if=/dev/zero of=/tmp/rootfs bs=1k count=35000losetup /dev/loop0 /tmp/rootfsmkfs.ext2 –F –i 2000 /tmp/rootfsmkdir /tmp/loopmount –o loop /tmp/rootfs /tmp/loop#然后将刚才建立的基本系统拷贝到/tmp/loopcp /lfs/* /tmp/loop –arfpfind . | cpio –o –H newc | gzip –c > /tmp/initrd.img
Linux 的 initrd 技术是一个非常普遍使用的机制,linux2.6 内核的 initrd 的文件格式由原来的文件系统镜像文件转变成了 cpio 格式,变化不仅反映在文件格式上, linux 内核对这两种格式的 initrd 的处理有着截然的不同。本文首先介绍了什么是 initrd 技术,然后分别介绍了 Linux2.4 内核和 2.6 内核的 initrd 的处理流程。最后通过对 Linux2.6 内核的 initrd 处理部分代码的分析,使读者可以对 initrd 技术有一个全面的认识。为了更好的阅读本文,要求读者对 Linux 的 VFS 以及 initrd 有一个初步的了解。 2 评论 1.什么是 Initrd initrd 的英文含义是 boot loader initialized RAM disk,就是由 boot loader 初始化的内存盘。在 linux内核启动前, boot loader 会将存储介质中的 initrd 文件加载到内存,内核启动时会在访问真正的根文件系统前先访问该内存中的 initrd 文件系统。在 boot loader 配置了 initrd 的情况下,内核启动被分成了两个阶段,第一阶段先执行 initrd 文件系统中的"某个文件",完成加载驱动模块等任务,第二阶段才会执行真正的根文件系统中的 /sbin/init 进程。这里提到的"某个文件",Linux2.6 内核会同以前版本内核的不同,所以这里暂时使用了"某个文件"这个称呼,后面会详细讲到。第一阶段启动的目的是为第二阶段的启动扫清一切障爱,最主要的是加载根文件系统存储介质的驱动模块。我们知道根文件系统可以存储在包括IDE、SCSI、USB在内的多种介质上,如果将这些设备的驱动都编译进内核,可以想象内核会多么庞大、臃肿。 Initrd 的用途主要有以下四种: 1. linux 发行版的必备部件 linux 发行版必须适应各种不同的硬件架构,将所有的驱动编译进内核是不现实的,initrd 技术是解决该问题的关键技术。Linux 发行版在内核中只编译了基本的硬件驱动,在安装过程中通过检测系统硬件,生成包含安装系统硬件驱动的 initrd,无非是一种即可行又灵活的解决方案。 2. livecd 的必备部件 同 linux 发行版相比,livecd 可能会面对更加复杂的硬件环境,所以也必须使用 initrd。 3. 制作 Linux usb 启动盘必须使用 initrd usb 设备是启动比较慢的设备,从驱动加载到设备真正可用大概需要几秒钟时间。如果将 usb 驱动编译进内核,内核通常不能成功访问 usb 设备中的文件系统。因为在内核访问 usb 设备时, usb 设备通常没有初始化完毕。所以常规的做法是,在 initrd 中加载 usb 驱动,然后休眠几秒中,等待 usb设备初始化完毕后再挂载 usb 设备中的文件系统。 4. 在 linuxrc 脚本中可以很方便地启用个性化 bootsplash。 回页首 2.Linux2.4内核对 Initrd 的处理流程 为了使读者清晰的了解Linux2.6内核initrd机制的变化,在重点介绍Linux2.6内核initrd之前,先对linux2.4内核的initrd进行一个简单的介绍。Linux2.4内核的initrd的格式是文件系统镜像文件,本文将其称为image-initrd,以区别后面介绍的linux2.6内核的cpio格式的initrd。 linux2.4内核对initrd的处理流程如下: 1. boot loader把内核以及/dev/initrd的内容加载到内存,/dev/initrd是由boot loader初始化的设备,存储着initrd。 2. 在内核初始化过程中,内核把 /dev/initrd 设备的内容解压缩并拷贝到 /dev/ram0 设备上。 3. 内核以可读写的方式把 /dev/ram0 设备挂载为原始的根文件系统。 4. 如果 /dev/ram0 被指定为真正的根文件系统,那么内核跳至最后一步正常启动。 5. 执行 initrd 上的 /linuxrc 文件,linuxrc 通常是一个脚本文件,负责加载内核访问根文件系统必须的驱动, 以及加载根文件系统。 6. /linuxrc 执行完毕,真正的根文件系统被挂载。 7. 如果真正的根文件系统存在 /initrd 目录,那么 /dev/ram0 将从 / 移动到 /initrd。否则如果 /initrd 目录不存在, /dev/ram0 将被卸载。 8. 在真正的根文件系统上进行正常启动过程 ,执行 /sbin/init。 linux2.4 内核的 initrd 的执行是作为内核启动的一个中间阶段,也就是说 initrd 的 /linuxrc 执行以后,内核会继续执行初始化代码,我们后面会看到这是 linux2.4 内核同 2.6 内核的 initrd 处理流程的一个显著区别。 回页首 3.Linux2.6 内核对 Initrd 的处理流程 linux2.6 内核支持两种格式的 initrd,一种是前面第 3 部分介绍的 linux2.4 内核那种传统格式的文件系统镜像-image-initrd,它的制作方法同 Linux2.4 内核的 initrd 一样,其核心文件就是 /linuxrc。另外一种格式的 initrd 是 cpio 格式的,这种格式的 initrd 从 linux2.5 起开始引入,使用 cpio 工具生成,其核心文件不再是 /linuxrc,而是 /init,本文将这种 initrd 称为 cpio-initrd。尽管 linux2.6 内核对 cpio-initrd和 image-initrd 这两种格式的 initrd 均支持,但对其处理流程有着显著的区别,下面分别介绍 linux2.6 内核对这两种 initrd 的处理流程。 cpio-initrd 的处理流程 1. boot loader 把内核以及 initrd 文件加载到内存的特定位置。 2. 内核判断initrd的文件格式,如果是cpio格式。 3. 将initrd的内容释放到rootfs中。 4. 执行initrd中的/init文件,执行到这一点,内核的工作全部结束,完全交给/init文件处理。 image-initrd的处理流程 1. boot loader把内核以及initrd文件加载到内存的特定位置。 2. 内核判断initrd的文件格式,如果不是cpio格式,将其作为image-initrd处理。 3. 内核将initrd的内容保存在rootfs下的/initrd.image文件中。 4. 内核将/initrd.image的内容读入/dev/ram0设备中,也就是读入了一个内存盘中。 5. 接着内核以可读写的方式把/dev/ram0设备挂载为原始的根文件系统。 6. .如果/dev/ram0被指定为真正的根文件系统,那么内核跳至最后一步正常启动。 7. 执行initrd上的/linuxrc文件,linuxrc通常是一个脚本文件,负责加载内核访问根文件系统必须的驱动, 以及加载根文件系统。 8. /linuxrc执行完毕,常规根文件系统被挂载 9. 如果常规根文件系统存在/initrd目录,那么/dev/ram0将从/移动到/initrd。否则如果/initrd目录不存在, /dev/ram0将被卸载。 10. 在常规根文件系统上进行正常启动过程 ,执行/sbin/init。 通过上面的流程介绍可知,Linux2.6内核对image-initrd的处理流程同linux2.4内核相比并没有显著的变化, cpio-initrd的处理流程相比于image-initrd的处理流程却有很大的区别,流程非常简单,在后面的源代码分析中,读者更能体会到处理的简捷。 4.cpio-initrd同image-initrd的区别与优势 没有找到正式的关于cpio-initrd同image-initrd对比的文献,根据笔者的使用体验以及内核代码的分析,总结出如下三方面的区别,这些区别也正是cpio-initrd的优势所在: cpio-initrd的制作方法更加简单 cpio-initrd的制作非常简单,通过两个命令就可以完成整个制作过程 #假设当前目录位于准备好的initrd文件系统的根目录下 bash# find . | cpio -c -o > ../initrd.img bash# gzip ../initrd.img 而传统initrd的制作过程比较繁琐,需要如下六个步骤 #假设当前目录位于准备好的initrd文件系统的根目录下 bash# dd if=/dev/zero of=../initrd.img bs=512k count=5 bash# mkfs.ext2 -F -m0 ../initrd.img bash# mount -t ext2 -o loop ../initrd.img /mnt bash# cp -r * /mnt bash# umount /mnt bash# gzip -9 ../initrd.img 本文不对上面命令的含义作细节的解释,因为本文主要介绍的是linux内核对initrd的处理,对上面命令不理解的读者可以参考相关文档。 cpio-initrd的内核处理流程更加简化 通过上面initrd处理流程的介绍,cpio-initrd的处理流程显得格外简单,通过对比可知cpio-initrd的处理流程在如下两个方面得到了简化: 1. cpio-initrd并没有使用额外的ramdisk,而是将其内容输入到rootfs中,其实rootfs本身也是一个基于内存的文件系统。这样就省掉了ramdisk的挂载、卸载等步骤。 2. cpio-initrd启动完/init进程,内核的任务就结束了,剩下的工作完全交给/init处理;而对于image-initrd,内核在执行完/linuxrc进程后,还要进行一些收尾工作,并且要负责执行真正的根文件系统的/sbin/init。通过图1可以更加清晰的看出处理流程的区别: 图1内核对cpio-initrd和image-initrd处理流程示意图 cpio-initrd的职责更加重要 如图1所示,cpio-initrd不再象image-initrd那样作为linux内核启动的一个中间步骤,而是作为内核启动的终点,内核将控制权交给cpio-initrd的/init文件后,内核的任务就结束了,所以在/init文件中,我们可以做更多的工作,而不比担心同内核后续处理的衔接问题。当然目前linux发行版的cpio-initrd的/init文件的内容还没有本质的改变,但是相信initrd职责的增加一定是一个趋势。 回页首 5.linux2.6内核initrd处理的源代码分析 上面简要介绍了Linux2.4内核和2.6内核的initrd的处理流程,为了使读者对于Linux2.6内核的initrd的处理有一个更加深入的认识,下面将对Linuxe2.6内核初始化部分同initrd密切相关的代码给予一个比较细致的分析,为了讲述方便,进一步明确几个代码分析中使用的概念: rootfs: 一个基于内存的文件系统,是linux在初始化时加载的第一个文件系统,关于它的进一步介绍可以参考文献[4]。 initramfs: initramfs同本文的主题关系不是很大,但是代码中涉及到了initramfs,为了更好的理解代码,这里对其进行简单的介绍。Initramfs是在 kernel 2.5中引入的技术,实际上它的含义就是:在内核镜像中附加一个cpio包,这个cpio包中包含了一个小型的文件系统,当内核启动时,内核将这个cpio包解开,并且将其中包含的文件系统释放到rootfs中,内核中的一部分初始化代码会放到这个文件系统中,作为用户层进程来执行。这样带来的明显的好处是精简了内核的初始化代码,而且使得内核的初始化过程更容易定制。Linux 2.6.12内核的 initramfs还没有什么实质性的东西,一个包含完整功能的initramfs的实现可能还需要一个缓慢的过程。对于initramfs的进一步了解可以参考文献[1][2][3]。 cpio-initrd: 前面已经定义过,指linux2.6内核使用的cpio格式的initrd。 image-initrd: 前面已经定义过,专指传统的文件镜像格式的initrd。 realfs: 用户最终使用的真正的文件系统。 内核的初始化代码位于 init/main.c 中的 static int init(void * unused)函数中。同initrd的处理相关部分函数调用层次如下图,笔者按照这个层次对每一个函数都给予了比较详细的分析,为了更好的说明,下面列出的代码中删除了同本文主题不相关的部分: 图2 initrd相关代码的调用层次关系图 init函数是内核所有初始化代码的入口,代码如下,其中只保留了同initrd相关部分的代码。 static int init(void * unused){ [1] populate_rootfs(); [2] if (sys_access((const char __user *) "/init", 0) == 0) execute_command = "/init"; else prepare_namespace(); [3] if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) printk(KERN_WARNING "Warning: unable to open an initial console.\n"); (void) sys_dup(0); (void) sys_dup(0); [4] if (execute_command) run_init_process(execute_command); run_init_process("/sbin/init"); run_init_process("/etc/init"); run_init_process("/bin/init"); run_init_process("/bin/sh"); panic("No init found. Try passing init= option to kernel."); } 代码[1]:populate_rootfs函数负责加载initramfs和cpio-initrd,对于populate_rootfs函数的细节后面会讲到。 代码[2]:如果rootfs的根目录下中包含/init进程,则赋予execute_command,在init函数的末尾会被执行。否则执行prepare_namespace函数,initrd是在该函数中被加载的。 代码[3]:将控制台设置为标准输入,后续的两个sys_dup(0),则复制标准输入为标准输出和标准错误输出。 代码[4]:如果rootfs中存在init进程,就将后续的处理工作交给该init进程。其实这段代码的含义是如果加载了cpio-initrd则交给cpio-initrd中的/init处理,否则会执行realfs中的init。读者可能会问:如果加载了cpio-initrd, 那么realfs中的init进程不是没有机会运行了吗?确实,如果加载了cpio-initrd,那么内核就不负责执行realfs的init进程了,而是将这个执行任务交给了cpio-initrd的init进程。解开fedora core4的initrd文件,会发现根目录的下的init文件是一个脚本,在该脚本的最后一行有这样一段代码: ……….. switchroot --movedev /sysroot 就是switchroot语句负责加载realfs,以及执行realfs的init进程。 对cpio-initrd的处理 对cpio-initrd的处理位于populate_rootfs函数中。 void __init populate_rootfs(void){ [1] char *err = unpack_to_rootfs(__initramfs_start, __initramfs_end - __initramfs_start, 0); [2] if (initrd_start) { [3] err = unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start, 1); [4] if (!err) { printk(" it is\n"); unpack_to_rootfs((char *)initrd_start, initrd_end - initrd_start, 0); free_initrd_mem(initrd_start, initrd_end); return; } [5] fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 700); if (fd >= 0) { sys_write(fd, (char *)initrd_start, initrd_end - initrd_start); sys_close(fd); free_initrd_mem(initrd_start, initrd_end); } } 代码[1]:加载initramfs, initramfs位于地址__initramfs_start处,是内核在编译过程中生成的,initramfs的是作为内核的一部分而存在的,不是 boot loader加载的。前面提到了现在initramfs没有任何实质内容。 代码[2]:判断是否加载了initrd。无论哪种格式的initrd,都会被boot loader加载到地址initrd_start处。 代码[3]:判断加载的是不是cpio-initrd。实际上 unpack_to_rootfs有两个功能一个是释放cpio包,另一个就是判断是不是cpio包, 这是通过最后一个参数来区分的, 0:释放 1:查看。 代码[4]:如果是cpio-initrd则将其内容释放出来到rootfs中。 代码[5]:如果不是cpio-initrd,则认为是一个image-initrd,将其内容保存到/initrd.image中。在后面的image-initrd的处理代码中会读取/initrd.image。 对image-initrd的处理 在prepare_namespace函数里,包含了对image-initrd进行处理的代码,相关代码如下: void __init prepare_namespace(void){ [1] if (initrd_load()) goto out; out: umount_devfs("/dev"); [2] sys_mount(".", "/", NULL, MS_MOVE, NULL); sys_chroot("."); security_sb_post_mountroot(); mount_devfs_fs (); } 代码[1]:执行initrd_load函数,将initrd载入,如果载入成功的话initrd_load函数会将realfs的根设置为当前目录。 代码[2]:将当前目录即realfs的根mount为Linux VFS的根。initrd_load函数执行完后,将真正的文件系统的根设置为当前目录。 initrd_load函数负责载入image-initrd,代码如下: int __init initrd_load(void) { [1] if (mount_initrd) { create_dev("/dev/ram", Root_RAM0, NULL); [2] if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) { sys_unlink("/initrd.image"); handle_initrd(); return 1; } } sys_unlink("/initrd.image"); return 0; } 代码[1]:如果加载initrd则建立一个ram0设备 /dev/ram。 代码[2]:/initrd.image文件保存的就是image-initrd,rd_load_image函数执行具体的加载操作,将image-nitrd的文件内容释放到ram0里。判断ROOT_DEV!=Root_RAM0的含义是,如果你在grub或者lilo里配置了 root=/dev/ram0 ,则实际上真正的根设备就是initrd了,所以就不把它作为initrd处理 ,而是作为realfs处理。 handle_initrd()函数负责对initrd进行具体的处理,代码如下: static void __init handle_initrd(void){ [1] real_root_dev = new_encode_dev(ROOT_DEV); [2] create_dev("/dev/root.old", Root_RAM0, NULL); mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY); [3] sys_mkdir("/old", 0700); root_fd = sys_open("/", 0, 0); old_fd = sys_open("/old", 0, 0); /* move initrd over / and chdir/chroot in initrd root */ [4] sys_chdir("/root"); sys_mount(".", "/", NULL, MS_MOVE, NULL); sys_chroot("."); mount_devfs_fs (); [5] pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD); if (pid > 0) { while (pid != sys_wait4(-1, &i, 0, NULL)) yield(); } /* move initrd to rootfs' /old */ sys_fchdir(old_fd); sys_mount("/", ".", NULL, MS_MOVE, NULL); /* switch root and cwd back to / of rootfs */ [6] sys_fchdir(root_fd); sys_chroot("."); sys_close(old_fd); sys_close(root_fd); umount_devfs("/old/dev"); [7] if (new_decode_dev(real_root_dev) == Root_RAM0) { sys_chdir("/old"); return; } [8] ROOT_DEV = new_decode_dev(real_root_dev); mount_root(); [9] printk(KERN_NOTICE "Trying to move old root to /initrd ... "); error = sys_mount("/old", "/root/initrd", NULL, MS_MOVE, NULL); if (!error) printk("okay\n"); else { int fd = sys_open("/dev/root.old", O_RDWR, 0); printk("failed\n"); printk(KERN_NOTICE "Unmounting old root\n"); sys_umount("/old", MNT_DETACH); printk(KERN_NOTICE "Trying to free ramdisk memory ... "); if (fd < 0) { error = fd; } else { error = sys_ioctl(fd, BLKFLSBUF, 0); sys_close(fd); } printk(!error ? "okay\n" : "failed\n"); } handle_initrd函数的主要功能是执行initrd的linuxrc文件,并且将realfs的根目录设置为当前目录。 代码[1]:real_root_dev,是一个全局变量保存的是realfs的设备号。 代码[2]:调用mount_block_root函数将initrd文件系统挂载到了VFS的/root下。 代码[3]:提取rootfs的根的文件描述符并将其保存到root_fd。它的作用就是为了在chroot到initrd的文件系统,处理完initrd之后要,还能够返回rootfs。返回的代码参考代码[7]。 代码[4]:chroot进入initrd的文件系统。前面initrd已挂载到了rootfs的/root目录。 代码[5]:执行initrd的linuxrc文件,等待其结束。 代码[6]:initrd处理完之后,重新chroot进入rootfs。 代码[7]:如果real_root_dev在 linuxrc中重新设成Root_RAM0,则initrd就是最终的realfs了,改变当前目录到initrd中,不作后续处理直接返回。 代码[8]:在linuxrc执行完后,realfs设备已经确定,调用mount_root函数将realfs挂载到root_fs的 /root目录下,并将当前目录设置为/root。 代码[9]:后面的代码主要是做一些收尾的工作,将initrd的内存盘释放。 到此代码分析完毕。
第一步:裁减内核打开终端,输入:cd /usr/src/linux2.4,然后输入make xconfig.现在编译内核正式开始了1.1 “code maturity level options”选项,代表代码的成熟等级,因为这是一个试验的部分,我们不需要,所以关闭它。1.2 “Loadable module support”可加载模块支持可加载模块是指内核代码(kernel code)的一些片断,比如驱动程序,当编译内核的时候它们也被单独编译。因此,这些代码不是内核的一部分,但是当需要它的时候,它可以被加载并使用。内核代码编译成可加载模块,可以使内核更小,而且更稳定。所以里面的三项我们全选。1.3 Processor type and features:处理器类型和特色在这里,你可以选择你的处理器(Processor)的类型,决定是否选择不同的选项。通常“/dev/cpu”选项更高级,多数用户并不需要选择它。 “High Memory Support”只有当你的计算机有超过1GB内存(不是磁盘空间)时才是必须的。多数计算机的内存从64到512MB(并且拥有8到60GB硬盘空间),因此“High Memory Support”通常并不使用。实际上现在所有的处理器都集成了浮点运算单元(译者注:从586级开始所有CPU集成了浮点运算单元),所以通常你可以不选择这个选项。 “MTRR”选项允许在PCI或者AGP总线众进行更快速的通讯。由于现在所有系统都将它们的显卡接在PCI或AGP总线上,你通常需要选择 “MTRR”:无论如何,打开这个选项通常都是安全的——即使你的机器没有使用 PCI或AGP总线的显卡。对称多处理器(SMP)需要能够支持超过一个处理器的主板,比如一块支持双Pentium II处理器的主板。 “Symmetric multi-processing”选项保证内核能够以最佳方式加载双处理器。最后一个选项(“APIC”选项)通常也需要多处理器,但它通常是关闭的。所以在这一栏我只选了MTRR,其他都不选。1.4 General setup:常规内核选项。Networking support,linux网络支持,必须选上,否则无法编译内核。Pci support 因为现在所有系统都使用PCI总线,所以选上。PCI access mode PCI存取模式,选择any.System V IPC 程序通信和同步,选上BSD process accounting 保持进程结束时产生的错误代码,选上Sysctl support 允许程序修改某些内核选项而不需要重新编译内核或者重新启动计算机,选上Kernel support for a.out binaries a.out的执行文件是比较古老的可执行代码,但有些程序还要用上,所以选上Kernel support for ELF binaries ,现在的可执行程序格式,选上Power management support 电源管理支持,选上1.5 Memory Technology Devices (MTD),配置存储设备(Memory Technology Devices),这个选项使Linux可以读取闪存卡(Flash Card)之类的存储器,关闭1.6 Parallel port support,配置并口(parallel port).在USB技术出现以前,并口是最常用的连接计算机和打印机、扫描仪的方式,关闭1.7 Plug and Play configuration配置即插即用(PnP)设备.因为我不需要USB设备,所以我关闭这个选项1.8 Block devices 配置块设备(block devices)Normal floppy disk support我要使用软驱,所以选上1.9 Multi-device support (RAID and LVM) 配置多驱动器(multiple devices)不需要RAID(廉价冗余磁盘阵列)或者LVM支持,所以全部关闭1.10 Networking options 网络配置选项Packet Socket选项用来与网卡进行通信而不需要在内核中实现网络协议,选上Unix domain sockets 进行网络链接,选上TCP/IP networking此选项包括了Internet和内部网络所需要的协议。选上1.11 Telephony Support 电话支持,不需要,关闭1.12 ATA/IDE/MFM/RLL support 配置对ATA,IDE,MFM和RLL的支持(硬盘的通讯协议)现在都用ATA,IDE格式硬盘,所以选上Enhanced IDE/MFM/RLL disk/cdrom/tape/floppy support,基本上所有的计算机都使用IDE/ATAPI界面,因此选上Include IDE/ATAPI CDROM support 支持光驱的时候需要,关闭Include IDE/ATAPI TAPE support 关闭Include IDE/ATAPI FLOPPY support 关闭CMD640 chipset bugfix/support 关闭1.13 SCSI support 配置SCSI支持.没有SCSI硬盘,也不用USB设备,所以关闭1.14 Fusion MPT device support 为灰色,系统自动选关闭1.15 I2O device support 配置I2O设备支持(I2O Device Support)没有I2O界面,所以选择关闭。1.16 Network device support 配置网卡支持(Network Device Support)Network device support 在没有网卡支持的情况下,很难将内核编译成功,选上Dummy net driver support(虚拟网卡驱动),系统会经常用到虚拟网卡,选为一个可加载模块。Ethernet(10 or 100Mbit)配置以太网卡(Ethernet Device)根据自己的实际情况配置,比如我有一块3c509/3c529芯片的3com卡,则选3com cards并将3c509/3c529选为可加载模块1.17 Amateur Radio support 配置业余广播支持(Amateur Radio Support)不需要,关闭1.18 IrDA (infrared) support配置红外线(无线)通讯支持 不需要,关闭1.19 ISDN subsystem 配置ISDN.不需要,关闭1.20 Old CD-ROM drivers (not SCSI, not IDE) 配置老CDROM.没有老CDROM,关闭1.21 Input core support 配置Input Core Support. 这个选项提供了2.4.x内核中最重要的特性之一的USB支持。 Input core support是处于内核与一些USB设备之间的层(Layer)。我不需要USB支持,所以,关闭1.22 Character devices配置字符型设备(Character Devices)virtual terminal允许在XWindow中打开xterm和使用字符界面登录,选上support for console on virtual terminal告诉内核将诸如模块错误、内核错误启动信息之类的警告信息发送到什么地方,在XWindow下,通常设置一个专门的窗口来接收内核信息,但是在字符界面下,这些信息通常被发送到第一个虚拟终端(Virtual Terminal),所以,选上standard/generic (8250/16550 and compatible UARTs) serial support,内核支持串行口,选上mouse support (not serial and bus mice)用的是PS/2鼠标,所以。选上PS/2 mouse (aka "auxiliary device" support) 用的是PS/2鼠标,所以。选上unix98 PTY support 使用远程使用自己机器上的xterm,不需要,关闭(除此以外的本栏选项,其他的选项全部选择关闭)1.23 Multimedia devices 配置多媒体设备“Multimedia Devices”不需要,关闭1.24 Crypto Hardware suppor,关闭1.25 File systems配置文件系统(File System)DOS FAT support windowsNT/2000文件系统,不选择,关闭ISO 9660 CDROM file system support 光驱支持,不需要,关闭/proc file system support /proc目录中的文件包含了关于系统状态的许多重要信息,比如那些中断正在使用 所以选择,打开Second extended fs support此选项针对Linux的标准文件系统(Ext2 FS) 必须打开这个选项,所以,打开UDF file system support不需要,关闭Network file systems假如计算机处于一个需要使用Network file systems选项的大型网络之中,否则不需要,所以,关闭Partition Types此选项是一个很高级但对于有效的使用Linux内核来说并不必要的选项,关闭native language support在这个菜单中,可以选择那些编码将被Linux用来处理DOS和Windows下的文件名,因为刚才选择了不支持DOS 和WINDOWS,所以,关闭1.26 Console drivers 配置控制台驱动,VGA text console选项在VGA模式下启动字符模式,打开video mode selection support此选项使启动的时候能够使用字符模式的分辨率,不需要,关闭1.27 sound 声卡配置根据自己的声卡选择相关的驱动,比如我的是nforce系列的声卡,所以我就选择了这一个系列的。1.28 USB support配置USB支持因为我将USB驱动关闭了,所以我比需要配置USB支持,所以,关闭1.29 Kernel hacking 配置“kernel hacking”选项系统默认1.30 Save and Exit 保存并退出(注:没有提到的选项,安系统默认选项)1.31 在终端输入命令make dep (读取配置过程生成的配置文件,创建对应于配置的依赖关系树)1.32 make clean (完成删除前面步骤留下的文件,以避免出现一些错误)1.33 make bzImage (完全编译压缩内核)到此,裁减内核就完成了,这个内核大小是740k 第二步:根文件系统的制作 boot/root盘由两部分组成,即核心和根文件系统。要把这两部分都放到一个1.44MB的软盘上去,通常要对内核和根文件系统进行压缩,压缩核心的最好方法是进行重新编译内核,将一些不必要的支持去掉,这一步我们已经完成了,下面我们是做一个根文件系统的压缩包。对于根文件系统的压缩包括两方面的问题,第一是只保留必要的根文件系统组件,第二是将根文件系统做成一个压缩包,类似于内核工作的原理。根文件系统概述 一个根文件系统必须包括支持完整Linux系统的全部东西,它至少应包括以下几项: •基本文件系统结构 •至少含有以下目录:/dev、 /proc、 /bin、 /etc、 /lib、 /usr、 /tmp •最基本的应用程序,如sh、 ls、 cp、 mv等 •最低限度的配置文件,如rc、 inittab、 fstab等 •设备:/dev/hd*、 /dev/tty*、 /dev/fd0 •基本程序运行所需的库函数 以上所需文件一般情况下会超过1.44M,因此我们是先准备好内容后再压缩到软盘中,当用软盘启动时,再把文件解压到内存中,形成一个虚拟盘(RAMDISK),通过RAMDISK控制系统启动。为了能创建以上的根文件系统,必须有一个空闲的能够放下大约4MB文件的RAMDISK。系统缺省情况下已替我们建好了一个大小为4096KB的RAMDISK,其设备名一般为/dev/ram0,我们就使用它来保存我们预先准备好的根文件系统。 创建根文件系统(1)在终端输入命令:mke2fs -m 0 -i 2000 /dev/ram0,这样就创建了一个 虚拟盘 mke2fs将会自动判断设备容量的大小并相应地配置自身,-m 0 参数防止它给root保留空间,这样会腾出更多的有用空间。(2)接着把虚拟盘挂在节点/mnt上:在终端输入命令:mount -t ext2 /dev/ram0 /mnt/floppy (3) 接着是创建目录。根文件系统最少应该有如下8个目录:/dev — 设备/proc — proc 文件系统所需目录/etc — 系统配置文件/sbin — 重要的系统程序/bin — 基本应用程序/lib — 共享函数库/mnt — 装载其他磁盘节点/usr — 附加应用程序执行如下命令创建这些目录:#cd /mnt/floppy#mkdir dev proc etc sbin bin lib mnt usr(4)接下来的工作就是确定各个目录下的内容了:/dev:/dev中含有系统不可缺少的设备文件。用命令:cp –dpr /dev/{console,fd0.hda,hda8,hda9,hda10,initctl,initrd,kmem,mem,null,ram,ram0,ramdisj,sda,tty1,tty} /mnt/flopp/dev/ 将console,fd0.hda,hda8,hda9,hda10,initctl,kmem,mem,null,ram,ram0,ramdisj, sda, tty1,tty等必须的设备文件复制到dev文件夹中。其中参数-dpr是为了保证连接文件仍然不变。Console为系统控制台设备,非常重要;Fd0,第一个软驱;Had,hda8,hda9,hda10 为硬盘设备,其中hda8,hda9,hda10不是必须的;Initctl 为一个FIFO设备,和init有关;Initrd 初始化设备;Kmem 内核虚拟内存;Mem 访问物理内存;Null null设备;Ram ram disk 设备,是/dev/ram0应用initrd机制所必须的;Tty 当前tty设备;•/etc:这个目录中含有一些必不可少的系统配置文件。用命令:cp /etc/{default,ld.so.cache,ld.so.conf,login.defs,fstab,groub,init.d,inittab,issue,modules.conf,mtab,nsswitch.conf,pam.d,profile,rc.d} /mnt/floppy/etc/ 将文件default,ld.so.cache,ld.so.conf,login.defs,fstab,groub,init.d,inittab, issue,modules.conf,mtab,nsswitch.conf,pam.d,profile,rc.d复制到当前文件夹etc下面。Default 某个命令的缺省设置;Ld.so.cache 由idconfig命令根据/etc/id.so.conf文件产生;ld.so.conf 库文件路径配置文件;login.defs 全局缺省设置;fstab 文件系统列表, fstab应包括:/dev/ram0 / ext2 defaults/dev/fd0 / ext2 defaults/proc /proc proc defaultsinit.d符号连接到/etc/rc.d/init.dinittab init配置文件inittab包括:id:2:initdefault:si::sysinit:/etc/rc1:2345:respawn:/sbin/getty 9600 tty12:23:respawn:/sbin/getty 9600 tty2 modules.conf 模块的配置文件mtab 已经挂载的文件系统列表nsswitch.conf name service switch 的配置文件pam.d放置PAM配置文件的目录profile 系统环境变量和登陆配置文件rc.d 放置启动脚本的目录/bin和/sbin:该目录中包含有必不可少的应用程序,在该目录下放置 init, getty,login, mount,以次来运行rc的外壳shell。/lib: 该目录中包含有你的启动盘启动过程中所需要的共享函数库。 几乎所有的程序都需要libc库,列一下目录/lib中的libc:libext2fs.so.2,libcom_err.so.2,libuuid.so.1,libc.so.6,ld-linux.so.2 ,libnss_files*,pam_unix.so(5)打包完成了上述工作,卸下虚拟盘,拷贝到一个文件中,然后压缩。umount /mntdd if=/dev/ram0 bs=1k | gzip -v9>gj.gz压缩结束后,就拥有了一个压缩的根文件系统,这个压缩文件的名字叫gi.gz,检查它的大小,如果大了,还得删除一些东西。第三:组织引导盘有了根文件系统和内核之后,最后的工作就是把它们组织在一起。接下来创建一个内核文件系统。把一张干净的软盘插入软驱,在上面创建ext2文件系统。相继在shell中输入命令:mke2fs /dev/fd0 (创建文件系统) mount /dev/fd0 /mnt/floppy(挂载软盘) rm -rf /mnt/floppy/lost+found(删除系统生成的文件夹) mkdir /mnt/floppy{boot,dev}(创建两个文件夹)mkdir /mnt/floppy/boot/grub 再执行: cp -R /dev/{null,fd0} /mnt/floppy/devcp /boot/grub/stage1 /mnt/floppy/boot/grub cp /boot/grub/stage2 /mnt/floppy/boot/grub 接着拷贝启动加载器boot.b到目录/boot中, cp /boot/boot.b /mnt/floppy/boot把grub引导写到软盘上面#grub在 grub>; 提示符处,输入: grub>; root (fd0) grub>; setup (fd0) grub>; quit 写完引导后#cp vmlinuz-jou /mnt/floppy/boot (复制内核到boot文件夹下)#cp gj.gz /mnt/floppy/boot (复制压缩文件系统到boot文件夹下)#cp /boot/grub/grub.conf /mnt/floppy/boot/grub (把引导配置文件复制到grub下面)编辑grub.conf, 内容如下:timeout 10 default 0 title My little Linux root (fd0) kernel /boot/vmlinuz-jou ro root=/dev/ram0 initrd /boot/ gj.gz 然后制作grub.conf的link文件menu.lst#ln -s /mnt/floppy/boot/grub/grub.conf /mnt/floppy/boot/grub/menu.lst#umount /mnt/floppy(退出软盘)制作完成!
1.什么裁剪? 本篇文章的主要目的是让笔者和读者更深的认识Linux系统的运作方式,大致内容就是把Linux拆开自己一个个组件来组装,然后完成一个微型的Linux系统.下面,让我们来实现吧..写的不好的地方请指教. 2.原理 大家都知道,操作系统的启动流程是(主要是Linux):POST—>BIOS—>MBR—kernel-->initrd-->sbin/init, POST,BIOS都不是我们管的,所以这里我们从MBR开始,Linux的系统引导先主要是用的grub这个软件,grub引导系统了,然后启动内核,内核调用initrd来实现最基本的操作系统, 3.接下来,实际操作(所有操作均在虚拟机上实现) 3-1.首先我们得创建一个新的磁盘,来保存我们的grub和内核等关键程序(直接在虚拟机上添加新的磁盘) 笔者这里已经添加好了,就不演示添加的过程了,笔者的地盘分了两个区,分别是100M的主盘,和1G的主盘,名字为 /dev/sdg1和/dev/sdg2,首先在/mnt目录下创建两个文件夹:如图 3-2.挂载/dev/sdg1到/mnt/boot,挂载/dev/sdg2到/mnt/sysroot 3-3.大家知道,启动系统的时候除了硬件方面,首先就是要有引导程序,所以我们把引导程序安装到/mnt/boot 使用命令:grub-install –-root-directory=/mnt /dev/sdg1 3-4.有了引导程序就需要有我们的内核了,没有内核怎么启动啊,但是内核的启动又要依赖initrd(CentOS6),所以我们要把这两个文件都复制过去,使用命令: cp /boot/vmlinuz-2.6.32-358.el6.x86_64 /mnt/boot/vmlunuz cp /boot/initramfs-2.6.32-358.el6.x86_64.img /mnt/boot/initramfs.img 3-5.这样一个简单的操作系统的雏形就做好了,但是OS的操作依赖于shell,所以我们得把bash shell复制到/dev/sgd2目录下,所以我们得把/bin/bash以及bash依赖的库复制过去,可以使用ldd `which bash`查看bash依赖的库文件有哪些,笔者这里自己有一个简单的script脚本,就不手动复制了 3-6.接下来要配置grub文件,不然系统怎么找到你的硬件,所以接下来在/mnt/boot/grub下创建一个grub.conf的配置文件,内容如下 3-7.接下来还要在根目录下,也就是挂载在/mnt/sysroot下创建一个proc的文件夹.就可以把这块新的硬盘当成其他系统的启动盘了,我们来试试,新创建一个空的虚拟机不用教了吧.然后添加我们的这块磁盘,就可以开机启动了 # mkdir dev proc etc sbin bin lib mnt usr # cp /bin /tmp/boot/ # cp –dpr /dev/{console,fd0.hda,hda8,hda9,hda10,initctl,initrd,kmem,mem,null,ram,ram0,ramdisj,sda,tty1,tty} /tmp/boot/ # cp/etc/{default,ld.so.cache,ld.so.conf,login.defs,fstab,groub,init.d,inittab,issue,modules.conf,mtab,nsswitch.conf,pam.d,profile,rc.d} /tmp/boot/ 3-8.接下来我们来给他添加命令,其实很简单的,只要把命令的所在目录和命令所有依赖的库复制到/mnt/sysroot目录下就行了.这里我用脚本完成,就不演示了,笔者复制了一些常用的命令如:ls 3-9.笔者给这个小系统创建了几个目录了当然是在原主机上添加的,是不是越来越像一个系统了 3-10.好了!系统到这里就差不多了,不过我们还得修修,大家在3-6可以看到,我们的init=/bin/bash,这时候就会有同学问了,有没搞错,Linux系统化初始化不都是调用/sbin/init的么,你怎么调用了/bin/bash,没错,其实这里指向什么就调什么,那我们现在重新让他指向/sbin/init吧,首先在/mnt/sysroot下建立一个init文件,在里面添加如下字符:完成后记得给/sbin/init一个快 执行权限,然后把/mnt/boot/grub/grub.conf中的指向改成init=/sbin/init 3-11.如下图,执行成功了 3-12.最后,我们该给他添加一个网络模块了,哦哦,在3-9步我们发现磁盘还是只读的,所以得重新把他挂载成读写的,如下图: 3-13.终于可以给他加网络模块了,Linux的系统是单内核,但是支持模块化,所以咯,我们来给他加个网络模块吧,我们值需要先把原系统的网络模块复制到我们的微系统中,Linux的模块都在/lib/module/[内核版本号下],我们首先用lspci或者lsmod查看网络模块信息,然后复制到微系统中,如下: 最后安装网络模块,使用命令insmod [path] 好了,整个系统安装完成了! 1. linux内核编译: 具体步骤:# tar zxvf linux-2.6.tar.gz -C /usr/src# cd /usr/src/linux2.6# make menuconfig # make# make modules_install# cp arch/x86/boot/bzImage /boot/vmlinuz-2.6 # make install 对比/boot/grub/grub.cfg文件的改动 2. 安装启动盘(U盘、硬盘) # Fdisk /dev/sdb #mkfs.ext2 /dev/sdb1 # mkdir /tmp/boot ; mount /dev/sdb1 /tmp/boot # grub-install --root-directory=/tmp/boot --no-floppy/dev/sdb # cp /boot/grub/grub.conf /tmp/boot/boot/grub/ # cp /boot/grub//boot/grub/splash.xpm.gz /tmp/boot/boot/grub/ # cp /boot/vmlinuz-2.6.34/mnt/boot/vmlinuz # cp /boot/initramfs-2.6.34.img/mnt/boot/initramfs.img 3. 构建系统目录 # mkdir dev proc etc sbin bin lib mnt usr # cp /bin /tmp/boot/ # cp –dpr /dev/{console,fd0.hda,hda8,hda9,hda10,initctl,initrd,kmem,mem,null,ram,ram0,ramdisj,sda,tty1,tty} /tmp/boot/ # cp/etc/{default,ld.so.cache,ld.so.conf,login.defs,fstab,groub,init.d,inittab,issue,modules.conf,mtab,nsswitch.conf,pam.d,profile,rc.d} /tmp/boot/ 4. 编辑grub.conf 5. 添加必要的命令 例如:ls Ldd ls,添加对应的依赖库文件
前面“Linux应用程序Helloworld入门”已经提到在Linux下每个可执行文件都依赖于几个最为基本的动态库,其中一个就是linux-gate.so.1。 从上面ldd给出的结果可以看出,这个linux-gate.so.1动态库有一些异样,libc.so.6的实际动态库路径在/lib/tls/i686/cmov/libc.so.6,而ld-linux.so.2是在/lib/ld-linux.so.2。那么不禁要问一个问题linux-gate.so.1这个动态库的路径是什么,是文件系统中那个文件呢?其实这个文件是内核映射上去的,并非存在实际的动态库文件,对于这个具体问题我们后续再做详细分析,这里仅仅做如何获取linux-gate.so.1动态库的方法。 通常情况下,比如在SUSE10, suse11系统上,linux-gate.so.1被映射到ffffe000-fffff000这个高端内存段里面。此时将这段内存导出到文件比较简单,可以使用下面脚本,帮助各位导出: VDSO_FILE_NAME=linux-gate.dso cat /proc/self/maps|grep "vdso" VDSO_ADDR=`cat /proc/self/maps|grep "vdso" |awk -F '-' '{print $1 }'` echo "Current VDSO address is 0x$VDSO_ADDR" VDSO_BLOCK=`echo |awk '{print substr("'${VDSO_ADDR}'",1,5)}'` ((SKIP_BLOCKS=16#$VDSO_BLOCK)) echo "We have $SKIP_BLOCKS blocks before VDSO library" echo "Ready to generate $VDSO_FILE_NAME from block $SKIP_BLOCKS" dd if=/proc/self/mem of=$VDSO_FILE_NAME bs=4096 skip=$SKIP_BLOCKS count=1 echo "Generate $VDSO_FILE_NAME Done" 在suse系统上执行的结果: ~> ./cat_linux_gate_so.sh ffffe000-fffff000 ---p 00000000 00:00 0 [vdso] Current VDSO address is 0xffffe000 We have 1048574 blocks before VDSO library Ready to generate linux-gate.dso from block 1048574 1+0 records in 1+0 records out 4096 bytes (4.1 kB) copied, 4.2e-05 seconds, 97.5 MB/s Generate linux-gate.dso Done ~> file -b linux-gate.dso ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), stripped ~> objdump -T linux-gate.dso linux-gate.dso: 文件格式 elf32-i386 DYNAMIC SYMBOL TABLE: ffffe400 l d .text 00000000 .text ffffe478 l d .eh_frame_hdr 00000000 .eh_frame_hdr ffffe49c l d .eh_frame 00000000 .eh_frame ffffe620 l d .useless 00000000 .useless ffffe400 g DF .text 00000014 LINUX_2.5 __kernel_vsyscall 00000000 g DO *ABS* 00000000 LINUX_2.5 LINUX_2.5 ffffe440 g DF .text 00000007 LINUX_2.5 __kernel_rt_sigreturn ffffe420 g DF .text 00000008 LINUX_2.5 __kernel_sigreturn 在Ubuntu 上情况比较复杂,在此也耽误了不少时间,因为ubuntu映射到的内存地址是不固定的,每个进程映射的位置都是不同的。 多次执行: cat /proc/self/maps|grep "vdso" b7f47000-b7f48000 r-xp b7f47000 00:00 0 [vdso] b7f5f000-b7f60000 r-xp b7f5f000 00:00 0 [vdso] b7f54000-b7f55000 r-xp b7f54000 00:00 0 [vdso]
通过grub-install命令把grub安装到u盘 ①准备一个u盘,容量不限,能有1MB都足够了。 ②把u盘格式化(我把u盘格式化成FAT、fat32格式了,最后证明也是成功的)。③开启linux系统,打开命令行终端,进入root模式,然后输入命令行:mount /dev/sdb3 /tmp/bootgrub-install --root-directory=/tmp/boot --no-floppy /dev/sdb注意:上面/dev/sdb是我的u盘,在linux系统里的盘符吧,那个/dev/sdb3为什么是“3”,这个因不同的实际情况而不同吧。总的来说,/dev/sdb就是我的u盘的名字,/dev/sdb3就是我的u盘的一个分区。④这个时候,你会发现,你的u盘,已经多了一个boot目录,里面有一些内容,这个时候,boot目录的路径是/dev/sdb3/boot/。⑤把“/boot/grub/grub.conf”和“/boot/grub/splash.xpm.gz”,复制到“/dev/sdb3/boot/grub/”下面(也就是“u盘/boot/grub/”)。⑥然后把/dev/sdb3/boot/grub/grub.conf修改成以下内容:# grub.conf generated by anaconda## Note that you do not have to rerun grub after making changes to this file# NOTICE: You do not have a /boot partition. This means that# all kernel and initrd paths are relative to /, eg.# root (hd0,0)# kernel /boot/vmlinuz-version ro root=/dev/sda1# initrd /boot/initrd-version.img#boot=/dev/sdadefault=0timeout=5splashimage=(hd0,0)/boot/grub/splash.xpm.gztitle Red Hat Enterprise Linux Server 1280*1024(3.4.0) root (hd0,0) kernel /boot/vmlinuz-3.4.0 root=/dev/sda1 selinux=0 init=/sbin/init vga=795 fb:on initrd /boot/initrd.img-3.4.0 title Red Hat Enterprise Linux Server 1600*1200(3.4.0) root (hd0,0) kernel /boot/vmlinuz-3.4.0 root=/dev/sda1 selinux=0 init=/sbin/init vga=858 fb:on initrd /boot/initrd.img-3.4.0⑦重启计算机,在BIOS里,设置为从u盘启动,即可。 说明:grub2.0以后支持的是grub.cfg set default=0 set timeout=5 set gfxmode=1280x1024 menuentry 'Red Hat Enterprise Linux Server 1280*1024(3.4.0)' { set gfxpayload=1280x1024x32,1024x768x32,800x600x32,800x600x16,800x600,640x480 linux /boot/vmlinuz-3.4.0 root=/dev/sda1 selinux=0 init=/sbin/init vga=795 initrd /boot/initrd.img-3.4.0 } 制作步骤: 先用cfdisk 在U盘中,创建个两个分区,第二个一会儿作为boot分区。 0:mkfs.ext3 /dev/sdc51:mount /dev/sdc5 /tmp/boot2:grub-install --root-directory=/tmp/boot --no-floppy /dev/sdc(*注意*) 自己修改一下menu.lst文件吧。这个简单。****************************************************************************以上全部推翻重写。需要懂得理论上的知识,才能做出正确的结果。****************************************************************************首先理解引导中U盘引导部分的过程。1:当BISO通电引导到磁盘时,会检查MBR区域(MBR:446+DPT:64+ENDFLAG:2=512)MBR里面必须存在引导程序,通常会是我们熟悉grub,dos,95dos引导等。否则无法引导。这里做个简要说明:平常我们将系统安装硬盘上,通常安装都是安装windows,再安装linux,而通常当安装linux时,会提示你是否安装到MBR里面,或者安装到你的某个磁盘分区里面,通常是你的/boot的所在分区。 安装在MBR,或者某个磁盘分区。这2者之间存在差别。a:当安装到MBR时,会将已经存在的Win自己在MBR的引导程序替代,变成GRUB引导。b:当安装到某个分区时,会由存在的Win的引导程序,引导到那个分区的Grub,再有Grub引导到各个操作系统。这就是2者的差别。win没有为其他系统考虑过,到了自己的磁盘分区之后,对于引导其它系统,没有提供可以直接引导的命令,只能进入后修改它的boot.ini文件。而grub提供命令命令菜单,可以进行手工引导。 2:grub的制作笔者发现win只能自动识别出U盘的第一个分区,而对于第二个分区无动于衷。 所以做了这样的分区方案。第一个分区用来存储数据,第二个分区用来放置引导信息,这样不至于在使用过程中,自己创建的grub的boot分区,被别人勿删除,或者格式化。 我的1G U盘分区如下: /dev/sdc1 950M ntfs 用于平时的数据存储/dev/sdc5 60M ext3 (boot标识,可以引导启动,用cfdisk时,很容易修改。) 用于存储引导文件。 执行命令mount /dev/sdc5 /tmp/bootgrub-install --root-directory=/tmp/boot --no-floppy /dev/sdc此处脚下留神,必须讲grub安装到/dev/sdc,否则目前对于U盘的MBR来讲,没有可以用的引导程序。此命令执行后,会在/tmp/boot/目录,也就是/dev/sdc5,下面产生目录/boot/grub.里面有*stage*等文件。最后在/boot/grub 目录中建立menu.lst文件。文件内容如下:timeout 20default 0 title windows xpmap (hd0) (hd1)map (hd1) (hd0)rootnoverify (hd1,0)makeactivechainloader +1 讲一下,如果你用U盘引导后,他会把自己标记为hd0。而已经安装在硬盘上的win操作系统认为自己的宿主硬盘是hd0,此时引导win操作系统时就会造成盘符错位。因此我们需要用map进行一下映射转换。 map 的解释及使用如下:map TO_DRIVE FROM_DRIVE 映射 驱动器FROM_DRIVE 到TO_DRIVE. 当你链式引导向dos一样的操作系统,并且该系统没有在第一个驱动器上时,必须进行该映射。 that's Ok!****************************************************************************随着我的使用的愈加频繁,menu.lst上面的内容多了起来。****************************************************************************下面分享一下,里面的内容。首先是目录结构。 root@kook:~# fdisk -l /dev/sdbDisk /dev/sdb: 1010 MB, 1010826752 bytes32 heads, 61 sectors/track, 1011 cylindersUnits = cylinders of 1952 * 512 = 999424 bytes Device Boot Start End Blocks Id System/dev/sdb1 1 951 928145+ 6 FAT16/dev/sdb2 * 952 1011 58560 83 Linuxroot@kook:~# mount /dev/sdb2 /tmp/boot/root@kook:~# tree -d /tmp/boot/tmp/boot|-- boot| `-- grub|-- centos4.4|-- centos4.4-64|-- lost+found`-- ubuntu7046 directories 下面是menu.lst的内容。 root@kook:~# cat /tmp/boot/boot/grub/menu.lstcolor light-gray/bluetimeout 20default 0title Windows 95/98/NT/2000map (hd0) (hd1)map (hd1) (hd0)rootnoverify (hd1,0)makeactivechainloader +1title CentOS 4.4 x86_64 Net Installroot (hd0,1)kernel /centos4.4-64/vmlinuz root=/dev/hda2 ro singleinitrd /centos4.4-64/initrd.imgboottitle CentOS 4.4 i386 Net Installroot (hd0,1)kernel /centos4.4/vmlinuz root=/dev/hda2 ro singleinitrd /centos4.4/initrd.imgboottitle Ubuntu 7.04 AMD 64 Net Installroot (hd0,1)kernel /ubuntu704/linux root=/dev/hda2 ro singleinitrd /ubuntu704/initrd.gzboot 注意:1:成功的关键是MBR的内容。可以用dd if=/dev/sdc of=/tmp/mbr.bin bs=446 count=1 提取 出。hexdump -C/tmp/mbr.bin 查看2:有时候MBR会有问题。用这个命令清零吧。 dd if=/dev/zero of=/dev/sdc bs=446 count=1 因为一个是0.97版(grub-legacy),一个是2.02版(grub2)grub-legacy 没有 /boot/grub/grub.cfg 这个文件,而是使用 menu.lst 文件。 root (hd0,0)kernel /boot/vmlinuz-3.10xxxinitrd /boot/initrd-3.10xxxboot这个格式是grub格式,或者grub一代root (hd0,msdos0)linux /boot/vmlinuz-3.10xxxinitrd /boot/initrd-3.10xxxboot这是grub2的写法rhel6以前都采用grub一代,到rhel7则采用grub2模块化引导管理器grub和grub2的异同还请参考官方文档或自行百度!
给vmware的Linux虚拟机添加硬盘 1、先将虚拟机Power Off,在Virtual Machine Setting对话框里点击左下角的“Add”,选择“Hard Disk”,之后选择“Create a new virtual disk”,分配容量,其他默认配置就可以了。 2、启动虚拟机系统,用root登陆,利用 # ls /dev/sd* 的命令可以看到最后有一个sdb 或sdc(如果是添加的第三块硬盘会是sdc),它没有sdb1和sdb2或sdc1和sdc2之类的设备文件,说明系统检查出来了这块硬盘但还没有分区格式化。这样就是上一步添加的硬盘。 在命令行用fdisk -l查看是否识别了新硬盘,如果添加的是IDE硬盘,就应该看到hdb,如果是SCSI硬盘,看到的就应该是sdb。 www.2cto.com Disk /dev/sdc: 1073 MB, 1073741824 bytes 255 heads, 63 sectors/track, 130 cylinders Units = cylinders of 16065 * 512 = 8225280 bytes Disk identifier: 0x00000000 Disk /dev/sdc doesn't contain a valid partition table 因为我之前已经加过一块硬盘了,所以这里显示sdc。 3、使用fdisk /dev/sdc, 然后输入类似如下的参数: n (add a new partition) p (建立主分区) 1(第一个分区) 1(第一个柱面) 1024(最后一个柱面) w (保存) 选择好这些选项中选择“w”,即将分区表写入硬盘并退出,这时再用fdisk -l查看就会发现: Disk /dev/sdc: 1073 MB, 1073741824 bytes 255 heads, 63 sectors/track, 130 cylinders Units = cylinders of 16065 * 512 = 8225280 bytes Disk identifier: 0x5404b953 Device Boot Start End Blocks Id System www.2cto.com 4、用mkfs命令将其格式化,mkfs.ext3 /dev/sdc1 5、建立一个要把新硬盘mount到得目录,比如建立一个 /work 再用mount命令将其挂载,mount -t ext3 /dev/sdc1 /work 6、最后,修改/etc/fstab文件,把要mount的新硬盘追加到后面,如下: vi /etc/fstab /dev/sdb1 /work ext3 rw,user,auto,exec 1 2 /dev/sdc1 /work defaults 1 2 defaults == rw, suid, dev, exec, auto, nouser, and async.
现在有两种方案:一种基于 M-LVDS (基于总线的多节点通信) ,有其 特定的电气要求;另外一种是基于 LVDS 的点到点通信,具体说明如 下: 基于 M-LVDS 的总线通信: 基于 M-LVDS 的全双工通信 特点: 1 )工作速率最高可达 100 Mbps (50 MHz) 。 2 ) -1 V 至 +3.4 V 的共模电压范围内利用低至 50 mV 的差分输入检测 总线状态。 3 )总线引脚上提供最高可达± 15 kV 的 ESD 保护。 4 )总线最多支持与 32 个节点连接。 缺点: 1 )速度不是很快 2 )通信不是很方便,需要编程还原数据。按现有的芯片来看,只能 从芯片处得到想要的 0 和 1 数据, 发送数据也是, 只能发送 0 和 1 来 进行通信。 3 )无更多资料参考,网上除了官网的说明文档,很难找到与其相关 的资料。 基于 LVDS 的点到点通信: GM8116 是一款总线 LVDS 串行 / 解串行器( SerDes ),它将 16 位比特 信息串行化,嵌入时钟位,并将总共 18 位的信息转换为 BLVDS 信号 通过一对导线发送。该时钟作为 2 个比特嵌入,高时钟比特 C1 和低 时钟比特 C0 。这两个时钟比特将数据组帧,并且向接收器提供精密 的定时信息。接收器使用这个定时信息来恢复数据。 特点: 1 )最高全双工数据传输速率: 2.56Gbps 。 2 )内部集成 PLL 和 CDR 电路,无需任何外部元件。 3 )锁定随机输入数据并支持热插拔功能。 优缺点: 这类芯片基于点到点的通信,不满足总线形式的电气要求; 可以将数据串行化,操作简单方便。通讯速度快,可以满足所需要的 通讯要求。
FILE * fd=fopen("\\\\.\\PHYSICALDRIVE0","rb+"); char buffer[512]; fread(buffer,512,1,fd); //then you can edit buffer[512] as your wish...... fseek(fd,0,SEEK_SET); //很重要 memset(buffer,0,512); fwrite(buffer,512,1,fd); //把修改后的MBR写入到你的机器 fclose(fd); //大功告成 重启系统就启动不了了。。。。。。 大白菜下显示硬盘
《EDKII Build Process:EDKII项目源码的配置、编译流程[3]》博文目录: 3. EDKII Build Process(EDKII项目源码的配置、编译流程) ->3.1 The General Process Of EDKII Build(EDKII项目源码的配置、编译一般流程) ->3.1.1 Tool chain:BaseTools ->3.1.2 Setup build shell environment ->3.1.3 Modify Conf Files ->3.1.4 Build ->3.2 The Process Of EDKII Build on Windows(Windows环境下EDKII项目源码的配置、编译流程) ->3.1.1 Tool chain:BaseTools ->3.2.2 Setup build shell environment ->3.2.3 Modify Conf Files ->3.2.4 Build Hello World! (and the rest of MdeModulePkg) ->3.2.4 Build Hello World! (and the rest of MdeModulePkg) ->3.3 The Process Of EDKII Build on Linux(Linux环境下EDKII项目源码的配置、编译流程) ->3.3.1 Tool chain: Build the EDK II BaseTools ->3.3.2 Setup build shell environment ->3.3.3 Modify Conf Files ->3.3.4 Build Hello World! (and the rest of MdeModulePkg) 3.1 The General Process Of EDKII Build(EDKII项目源码的配置、编译一般流程) 3.1.1 Tool chain:BaseTools 《EDKII_UserManual_0_7》手册第一章《EDKII Introduction》overview中在列举EDKII优势(The features introduced by EDKII include:)最后一条: 更为强大的编译系统(Enhanced build system.): 编译系统是基于跨操作系统平台的python语言。它是用户可以通过修改几个配置文件来选择不同的工具链、编译规则、编译目标对象。(Its infrastructure is based on Python that is independent of the operating system. It exposes several configuration files that a user can utilize to choose the various tool-chains, even the build rule or generated target. ) EDKII工具链包括build、GenFV、GenFW等,工具链位于BaseTools目录下,Windows平台下可执行工具位于\edk2\BaseTools\Bin\Win32目录下,Linux平台下可执行工具位于edk2/BaseTools/BinWrappers/PosixLike目录下。如果相应目录下没有这些工具就需要编译BaseTools来获得这些工具。 3.1.2 Setup build shell environment 在工作目录下在命令行下运行环境设置脚本(Run edksetup script from the command line prompt at the Work Space directory): Windows Comand Prompt: D:\edk2> edksetup.bat Linux like: bash$ . edksetup.sh BaseTools 3.1.3 Modify Conf Files 修改配置文件 我们可以通过配置Conf/target.txt和tools_def.txt文件来指定工具链将指定的包或模块编译成目标平台代码。 3.1.3.1 配置Conf/target.txt文件: ACTIVE_PLATFORM = MdeModulePkg/MdeModulePkg.dsc TOOL_CHAIN_TAG = GCC46 TARGET_ARCH = IA32 这样我们可以只在命令行中运行“build”命令即可,会读取Conf/target.txt文件下这些变量的作为默认编译参数。[这种方式一定程度上简化了编译过程,不必每次都指定当前要编译的模块和目标平台代码,但可能也会缺少下面第二种方式的灵活性] 3.1.3.2 检查/修改tools_def.txt确保编译器路径正确Check/Modify tools_def Information: 检查相应工具链VS/Gcc、iasl路径是否正确。 3.1.4 Build 我们可以通过两种方式来指定工具链将指定的包或模块编译成目标平台代码。 一种方式是配置Conf/target.txt文件,运行“build”命令会解析target.txt读取默认配置参数。 另一种方式是通过“build”命令行指定: Build命令是编译UEFI工程常用的命令。Build命令有三个常用参数:-a、-p、-m。 *-a(Architecture):用来选择目标平台架构。可供选择的平台架构:IA32(32位X86 CPU)、X64(64位X86_64 CPU)、IPF(Itanium Processor Family)、ARM和EBC(EFI Byte Code);默认的参数在Conf/target.txt中设置。 *-p(Package/Platform):用来指定要编译的package或platform。-p的参数是这个package或platform的.dsc文件;默认的参数在Conf/target.txt中设置。 *-m(Module):用来指定要编译模块。如果不指定-m选项,build将编译.dsc文件指定的所有模块。 Build命令的其他参数:(build -h 查看) --version show program's version number and exit -h, --help show this help message and exit显示帮助信息。 -a TARGETARCH,--arch=TARGETARCH 选择目标平台架构,该选项被指定会取代Conf/target.txt中的TARGET_ARCH。 -p PLATFORMFILE,--platform=PLATFORMFILE 通过指定.dsc文件指定要编译的package,该选项将会Conf/target.txt文件ACTIVE_PLATFORM。 -m MODULEFILE, --module=MODULEFILE Build the module specified by the INF file name argument. -b BUILDTARGET, --buildtarget=BUILDTARGET 选择编译成DEBUG还是RELEASE。指定该选项后会取代target.txt文件中TARGET。 -t TOOLCHAIN, --tagname=TOOLCHAIN 选择tools_def.txt中定义的编译工具,例如VS2012:-t vs2012 -n THREADNUMBER 编译器使用的线程数量,指定后取代target.txt文件中MAX_CONCURRENT_THREAD_NUMBER指定的线程数量. Less than 2 will disable multi-thread builds.小于2就禁止多线程编译。 -u, --skip-autogen Skip AutoGen step.跳过AutoGen这一步。 -c, --case-insensitive Don't check case of file name.文件名不区分大小写。 -w, --warning-as-error Treat warning in tools as error.将警告做错误处理。 -j LOGFILE, --log=LOGFILE Put log in specified file as well as on console.将编译信息输出到文件。 -s, --silent Make use of silent mode of (n)make.使用沉默模式执行make或nmake。 -q, --quiet Disable all messages except FATAL ERRORS.编译过程中只显示严重错误信息。 -d DEBUG, --debug=DEBUG Enable debug messages at specified level.在指定的级别启用调试消息。 -D MACROS, --define=MACROS Macro: "Name [= Value]".定义宏。 3.2 The Process Of EDKII Build on Windows(Windows环境下EDKII项目源码的配置、编译流程) 3.2.1 Tool chain:BaseTools 配置Windows开发环境时,我们没有编译EDKII工具链,因为EDKII源码包中包含了Windows下的EDKII工具链可执行文件。 3.2.2 Setup build shell environment D:\> cd edk2 D:\edk2> edksetup.bat 3.2.3 Modify Conf files 3.2.3.1 Set Build Target Information:配置target.txt文件 通过记事本修改配置目录Conf/下的target.txt files和tools_def.txt文件,来配置我们当前要编译的包和使用的相应工具链VS2013。(You will need to edit the Conf/tools_def.txt and Conf/target.txt files. These changes will enable the MdeModulePkg to be built using the VS2013.) You will need to edit the Conf\target.txt file. First, change the ACTIVE_PLATFORM to the MdeModulePkg: D:\edk2> notepad Conf\target.txt ACTIVE_PLATFORM should look like this in Conf\target.txt: ACTIVE_PLATFORM = MdeModulePkg/MdeModulePkg.dsc Modify TOOL_CHAIN_TAG in target.txt for the toolchain installed on your system. There are many options, so review the tools_def.txt to find the appropriate toolchain for your system. Search for 'Supported Tool Chains' in tools_def.txt to see the valid options for TOOL_CHAIN_TAG. TOOL_CHAIN_TAG = VS2013x86 3.2.3.2 Check/Modify tools_def Information:检查tools_def.txt确保编译器路径正确 检查相应工具链VS2013和iasl路径是否正确。 DEFINE VS2013x86_BIN = C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin DEFINE VS2013x86_DLL = C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7 \IDE;DEF(VS2013x86_BIN) DEFINE VS2013x86_BINX64 = DEF(VS2013x86_BIN)\x86_amd64 ...... DEFINE WIN_ASL_BIN_DIR = C:\ASL DEFINE WIN_IASL_BIN = DEF(WIN_ASL_BIN_DIR)\iasl.exe DEFINE WIN_ASL_BIN = DEF(WIN_ASL_BIN_DIR)\asl.exe 3.2.4 Build Hello World! (and the rest of MdeModulePkg) Now you should be able to simply run the build command to compile the MdeModulePkg. D:\edk2> build As a tangible result of the build, you should have the HelloWorld UEFI application. If you have a UEFI system available to you which matches the processor architecture that you built, then this application should be able to run successfully under the shell. D:\edk2> dir /s Build\MdeModule\HelloWorld.efi [注:dir /s specified directory显示指定目录和所有子目录中的文件] 3.3 The Process Of EDKII Build on Linux(Linux环境下EDKII项目源码的配置、编译流程) 3.3.1 Build the EDK II BaseTools 在配置Windows开发环境时,我们没有编译EDKII工具链,因为EDKII源码包中包含了Windows下的EDKII工具链可执行文件。但是由于Linux发行版本众多,EDKII源码包中没有包含EDKII工具链可执行文件,需要我们用make、gcc等编译工具编译BaseTools生成Linux环境下可执行文件。 linux@ubuntu:~/src/edk2$ make -C BaseTools [注:-C:Change directory ;] 编译后,edk2/BaseTools/BinWrappers/PosixLike/目录下文件: 3.3.2 Setup build shell environment linux@ubuntu:~$ cd ~/src/edk2 linux@ubuntu:~/src/edk2$ export EDK_TOOLS_PATH=$HOME/src/edk2/BaseTools linux@ubuntu:~/src/edk2$ . edksetup.sh BaseTools 注意这里环境变量加载命令“. edksetup.sh BaseTools”而非“./edksetup.sh BaseTools” “. edksetup.sh BaseTools”等同于“source edksetup.sh BaseTools”。“source edksetup.sh BaseTools”会将脚本“edksetup.sh BaseTools”中的命令读入当前shell环境变量中,并在当前环境变量中执行,而“./edksetup.sh BaseTools”仅仅是在当前shell下执行脚本。 所以要是执行“./edksetup.sh BaseTools”命令会出现“No command 'build' found”错误提示。 正确加载shell环境变量: 运行脚本“./edksetup.sh BaseTools”,未加载环境变量情况: 3.3.3 Modify Conf Files 通过vi修改配置目录Conf/下的target.txt files和tools_def.txt文件,来配置我们当前要编译的包和使用的相应工具链Gcc4.6。(You will need to edit the Conf/tools_def.txt and Conf/target.txt files. These changes will enable the MdeModulePkg to be built using the GCC 4.6 compiler.) 3.3.3.1 Set Build Target Information:配置target.txt文件 For the Conf/target.txt file, find the following lines:(将Conf/target.txt文件中要编译的当前包、工具链变量) ACTIVE_PLATFORM = Nt32Pkg/Nt32Pkg.dsc TOOL_CHAIN_TAG = MYTOOLS And change the corresponding lines to match these:(修改为如下:要编译的当前包MdeModulePkg,工具链是本博文Ubuntu12.0VMware中已安装的Gcc4.6) ACTIVE_PLATFORM = MdeModulePkg/MdeModulePkg.dsc TOOL_CHAIN_TAG = GCC46 注意:通过“gcc --version”命令可以查看当前环境下Gcc版本,gcc4.5.*这里工具链配置为GCC45,同理gcc4.6.*这里工具链配置为GCC46。(Note: The 'gcc --version' command can be used to find out your GCC version. Use theGCC45 toolchain for gcc 4.5.* and the GCC46 toolchain for gcc 4.6.*.) Optionally, you may consider finding:(修改源码要生成的目标平台架构) TARGET_ARCH = IA32 ...and changing it if your GCC 4.6 installation supports 64-bit builds. You can change it to either 'X64', or even 'IA32 X64' which will build both architectures. 3.3.3.2 Check/Modify tools_def Information:检查/修改tools_def.txt确保编译器路径正确 检查相应工具链Gcc4.6和iasl路径是否正确。 DEFINE GCC46_IA32_PREFIX = /usr/bin/ DEFINE GCC46_X64_PREFIX = /usr/bin/ 3.3.4 Build Hello World! (and the rest of MdeModulePkg) 现在可以运行简单的编译命令“build”来编译MdeModulePkg包。(Now you should be able to simply run the build command to compile the MdeModulePkg.) linux@ubuntu:~/src/edk2$ build 可以通过ls命令One result of the build is that you should have the HelloWorld UEFI application: linux@ubuntu: ls Build/MdeModule/DEBUG_*/*/HelloWorld.efi
许多分布式计算系统都可以实时或接近实时地处理大数据流。下面对三种Apache框架分别进行简单介绍,然后尝试快速、高度概述其异同。 Apache Storm 在Storm中,先要设计一个用于实时计算的图状结构,我们称之为拓扑(topology)。这个拓扑将会被提交给集群,由集群中的主控节点(master node)分发代码,将任务分配给工作节点(worker node)执行。一个拓扑中包括spout和bolt两种角色,其中spout发送消息,负责将数据流以tuple元组的形式发送出去;而bolt则负责转换这些数据流,在bolt中可以完成计算、过滤等操作,bolt自身也可以随机将数据发送给其他bolt。由spout发射出的tuple是不可变数组,对应着固定的键值对。 Apache Spark Spark Streaming是核心Spark API的一个扩展,它并不会像Storm那样一次一个地处理数据流,而是在处理前按时间间隔预先将其切分为一段一段的批处理作业。Spark针对持续性数据流的抽象称为DStream(DiscretizedStream),一个DStream是一个微批处理(micro-batching)的RDD(弹性分布式数据集);而RDD则是一种分布式数据集,能够以两种方式并行运作,分别是任意函数和滑动窗口数据的转换。 Apache Samza Samza处理数据流时,会分别按次处理每条收到的消息。Samza的流单位既不是元组,也不是Dstream,而是一条条消息。在Samza中,数据流被切分开来,每个部分都由一组只读消息的有序数列构成,而这些消息每条都有一个特定的ID(offset)。该系统还支持批处理,即逐次处理同一个数据流分区的多条消息。Samza的执行与数据流模块都是可插拔式的,尽管Samza的特色是依赖Hadoop的Yarn(另一种资源调度器)和Apache Kafka。 共同之处 以上三种实时计算系统都是开源的分布式系统,具有低延迟、可扩展和容错性诸多优点,它们的共同特色在于:允许你在运行数据流代码时,将任务分配到一系列具有容错能力的计算机上并行运行。此外,它们都提供了简单的API来简化底层实现的复杂程度。 三种框架的术语名词不同,但是其代表的概念十分相似: 对比图 下面表格总结了一些不同之处: 数据传递形式分为三大类: 最多一次(At-most-once):消息可能会丢失,这通常是最不理想的结果。 最少一次(At-least-once):消息可能会再次发送(没有丢失的情况,但是会产生冗余)。在许多用例中已经足够。 恰好一次(Exactly-once):每条消息都被发送过一次且仅仅一次(没有丢失,没有冗余)。这是最佳情况,尽管很难保证在所有用例中都实现。 另一个方面是状态管理:对状态的存储有不同的策略,Spark Streaming将数据写入分布式文件系统中(例如HDFS);Samza使用嵌入式键值存储;而在Storm中,或者将状态管理滚动至应用层面,或者使用更高层面的抽象Trident。 用例 这三种框架在处理连续性的大量实时数据时的表现均出色而高效,那么使用哪一种呢?选择时并没有什么硬性规定,最多就是几个指导方针。 如果你想要的是一个允许增量计算的高速事件处理系统,Storm会是最佳选择。它可以应对你在客户端等待结果的同时,进一步进行分布式计算的需求,使用开箱即用的分布式RPC(DRPC)就可以了。最后但同样重要的原因:Storm使用Apache Thrift,你可以用任何编程语言来编写拓扑结构。如果你需要状态持续,同时/或者达到恰好一次的传递效果,应当看看更高层面的Trdent API,它同时也提供了微批处理的方式。 使用Storm的公司有:Twitter,雅虎,Spotify还有The Weather Channel等。 说到微批处理,如果你必须有状态的计算,恰好一次的递送,并且不介意高延迟的话,那么可以考虑Spark Streaming,特别如果你还计划图形操作、机器学习或者访问SQL的话,Apache Spark的stack允许你将一些library与数据流相结合(Spark SQL,Mllib,GraphX),它们会提供便捷的一体化编程模型。尤其是数据流算法(例如:K均值流媒体)允许Spark实时决策的促进。 使用Spark的公司有:亚马逊,雅虎,NASA JPL,eBay还有百度等。 如果你有大量的状态需要处理,比如每个分区都有许多十亿位元组,那么可以选择Samza。由于Samza将存储与处理放在同一台机器上,在保持处理高效的同时,还不会额外载入内存。这种框架提供了灵活的可插拔API:它的默认execution、消息发送还有存储引擎操作都可以根据你的选择随时进行替换。此外,如果你有大量的数据流处理阶段,且分别来自不同代码库的不同团队,那么Samza的细颗粒工作特性会尤其适用,因为它们可以在影响最小化的前提下完成增加或移除的工作。 使用Samza的公司有:LinkedIn,Intuit,Metamarkets,Quantiply,Fortscale等。 结论 本文中我们只对这三种Apache框架进行了简单的了解,并未覆盖到这些框架中大量的功能与更多细微的差异。同时,文中这三种框架对比也是受到限制的,因为这些框架都在一直不断的发展,这一点是我们应当牢记的
大数据应用正在从概念走向现实,而企业在大数据应用开发时,软件的弹性(Resilient)正在成为决定大数据应用成败的关键因素。弹性差的应用无法应对大规模的数据集,在测试和运营中也缺乏透明度,而且也不安全。 · 避免大数据应用在生产环境中掉链子的最佳办法就是在开发阶段就开发弹性应用,例如:健壮性、经过测试、可改变、可审计、高安全、可监控。 · 可以说,开发出弹性大数据应用既是一个技术工作,也是一个哲学问题。Concurrent的SupreetOberoi近日撰文提出大数据应用开发八大基本原则: · 一、为弹性大数据应用描绘一个蓝图 · 第一步是为企业大数据应用创建一个系统的架构和方法,要处理什么数据?那些类型的分析最重要?软件架构需要承载那些指标、审计、安全和运营功能? · 另外一些需要考虑的问题:那些技术最关键?哪些技术只是图一时之便?你的蓝图需要准确评估当前架构的问题所在。 · 二、数据规模不再是问题 · 如果应用无法处理更大规模的数据集,那么它就缺乏弹性,弹性应用应当能够处理任意规模的数据集(包括数据深度、广度、频度等),数据弹性还只对新技术的兼容,缺乏弹性的应用需要不断配置修改应用来适应不断更新的大数据技术,对于企业来说是时间、资源和金钱上的无底洞。 · 三、透明度 · 对于复杂应用来说,查找扩展性等弹性相关问题还很难实现自动化。关键是锁定问题的根源所在:是代码、数据还是架构抑或网络问题?并非每个应用都要具备这种透明度,但大一些的平台应当具备足够的透明度,让所有开发者和运营人员都能在问题发生时立刻找到根源并采取措施。 · 一旦发现问题,最为关键的是将找到应用行为对应的代码——最好是通过发现问题的监控应用。大多数情况下,访问代码会涉及到多个开发人员,执行起来流程将非常曲折。 · 四、抽象,事关高效和简洁 · 弹性应用总是面向未来的,通常采用抽象层来简化开发、提升效率,允许采用不同的技术实现。作为架构的一部分,弹性开发的抽象层能够避免开发者陷入技术实现的细节泥潭中。简洁性则能方便数据科学家使用应用访问所有类型的数据源。如果没有抽象技术,产品的生产力会大打折扣,修改成本增高,而用户则为复杂性所困扰。 · 五、安全:审计与合规 · 弹性应用能自我审计,能够显示谁使用了应用,谁有权限使用,访问了哪些数据以及政策如何实施。在应用开发阶段就将这些功能考虑进去是应对日益增长的大数据隐私、安全、治理和控制挑战的关键所在。 · 六、完整度与测试驱动的开发 · 弹性应用的一个基本要求就是不能遗失任何数据,数据完整性的丧失往往会导致严重的后果,例如金融企业会因为程序代码弄丢了一两行交易数据而在反洗钱或金融欺诈调查中遭受处罚。 · 七、数据便携性 · 不断发展的业务需求驱动技术不断做出改变,因此,大数据应用也应当能够在多个平台和产品上运行。最终的目标是让最终用户能够通过SQL和标准API访问数据(无论是否实时)。例如,一个先进的大数据平台应当允许原本由Hadoop存储MapReduce处理的数据,转移到Spark或Tez中进进行处理,而且这个过程不需要或尽可能少地改动代码。 · 八、不要搞个人主义 · 大数据应用的开发不应当依赖某个高手的个人才华,代码应当在多个开发者之间分享、评估和保有。这个策略让整个团队,而不是个人,对应用质量负责。
http://blog.csdn.net/hellochina15/article/details/49722815 在HelloX开发团队的努力下,以及Winzent Tech公司(总部在瑞典斯德哥尔摩)的支持下,HelloX最新版本V1.78已成功移植到MinnowBoard MAX开发板上。相关源代码已经发布到github上(github.com/hellox-project/HelloX_OS),欢迎感兴趣的朋友下载测试。 MinnowBoardMAX是在Intel的支持下,由Circuit公司开发的一款基于Intel ATOM处理器的卡片式电脑,具备超高的性能,丰富的扩展性,以及相对较低的功耗和成本。是Intel进军物联网领域的重大举措。在今年第三季度发布的Windows 10 IoT版本,就是以该款开发板作为主要的硬件开发平台。下面是MinnowBoard MAX的外观: 本质上,MinnowBoard MAX是一款PC架构的卡片电脑,所不同的是,它不带显示器,也不带键盘和鼠标等用户输入设备,只提供USB,SPI,GPIO等常用的计算机接口,这些也是物联网领域最常用的通信接口。对HelloX来说,移植到该开发板上的难点有两个: 1. 缺省情况下,MinnowBoard MAX的固件是基于UEFI标准的计算机固件,而当前版本的HelloX尚不支持UEFI,因此需要一份传统计算机上的BIOS来引导HelloX。Winzent公司专门为MinnowBoard MAX开发板定制了一个传统的BIOS,同时提供了及时专业的技术支持。在他们的支持下,我们成功刷新了MinnowBoard MAX的引导固件,成功完成HelloX的启动; 2. 由于MinnowBoard MAX没有传统的键盘和鼠标等输入设备,只能采用USB接口的键盘和鼠标。而当前版本的HelloX尚不具备USB支持功能,因此为了支持MinnowBoard MAX,不得不增加USB功能的支持。这不是一个简单的工作,我们用了将近两个月的时间,移植和优化了大约1万行代码,才实现了完整的USB功能,包括对USB OHCI/UHCI(USB 1.0/1.1)的支持,USB EHCI(USB2.0)的支持,甚至USB3.0(xHCI)的支持。 目前来说,HelloX已经可以完整的运行在MinnowBoard MAX开发板上,能够支持USB的键盘和鼠标,能够访问USB接口的存储设备。 在此,感谢HelloX开发团队,尤其是Tywind Huang做出的努力。 后续我们将把MinnowBoard MAX开发板作为HelloX的主要开发环境之一,在此基础上,充分利用该板子提供的物联网接口能力,实现各种各样的物联网应用。甚至考虑对MinnowBoard MAX进行优化和定制,推出基于该板子的产品。 对于HelloX操作系统的应用定位,再在这里解释澄清一下: 1. HelloX始终定位为物联网操作系统,具备物联网操作系统的主要特征,比如内核高度伸缩,高度可裁剪,以适应硬件碎片化的需要。当前版本的HelloX,通过调整配置,能够实现从10几K大小,到500K大小的伸缩,几乎可以适应任何物联网领域的需要。除此之外,还支持软硬件分离特征,通过Java虚拟机机制,实现应用代码与CPU指令的完整隔离。毕竟在物联网领域,CPU的种类太多,不像PC时代,只要针对x86实现一款软件就可以打遍天下。如果没有软硬件分离的特征,从理论上说,每个应用都需要去适配所有的CPU类型,这无疑是不现实的。同时,HelloX还通过动态可加载的机制,来动态变化物联网后台支持。这也是非常关键的特征,据统计,目前市面上已经商用的物联网后台系统,就已经超过了165个。显然一款物联网产品,不可能绑定到一个物联网平台上。通过实时的加载和卸载物联网后台支持代码,可以轻松实现后台的切换。这类似于个人手机,可以通过更换SIM卡的方式,实现运营商的更换; 2. 第二种应用场景,本质上也是物联网领域,但是单独拿出来说明一下,那就是物联网网关。所谓物联网网关,基本上就是一个通信转换设备,可以把局域内的无线通信,比如蓝牙,Zigbee,Z-Wave,NFC,等等,转换为IP协议,并送到物联网后台上。同时,物联网网关也根据物联网平台发布的策略或规则,来进行本地事件的逻辑处理。比如,在电视机被关闭的情况下,立即切断智能开关的电源。这种联动机制,是不需要上升到物联网后台处理的,只需要在网关层面处理就可以了。HelloX瞄准这种物联网网关应用场景,因为这个关键设备,会是未来物联网领域最关键的一个环节。我们基于MinnowBoard MAX构筑开发环境,也是基于这个应用场景考虑的; 3. 第三种应用场景,可以概括为“给您一个新的选项”。随着功能的逐渐丰富,HelloX已经具备通用操作系统的基础能力,比如网络支持,文件系统,USB支持等等,而且从一开始就是以个人计算机作为硬件环境。如果您的应用场景很单纯,不需要windows这样的巨无霸,同时又不愿意去裁剪Linux内核,或者不想用GCC去开发应用,而想用更易用的Visual Studio开发应用,那么HelloX或许是您可以考虑的一种选项,至少可以评估一下。 下图是最新版本的HelloX,在我的DELL电脑上运行了一天之后的情况,截至目前,没有发现任何异常: 欢迎感兴趣的朋友加入我们,让我们一起构筑物联网时代的基础软件平台。联系方式: QQ群:38467832,QQ:89007638
http://blog.csdn.net/hellochina15/article/details/7253350 本文介绍如何在Windows 7操作系统和Virtual PC 2007虚拟机上安装Hello China操作系统,Hello China的版本是V1.75。对于Windows XP等非Windows 7操作系统,由于不能直接支持虚拟硬盘,不能按照本文介绍的方法安装Hello China的GUI功能,但是可以安装内核和基于字符界面的shell。 HelloChina在Virtual PC上的启动过程 首先介绍一下Hello ChinaV1.75在Virtual PC上的启动过程。为了最大可能的降低安装和使用的复杂性,V1.75版本在Virtual PC上是通过虚拟软盘启动的。Hello China的内核和核心驱动程序(比如键盘驱动、鼠标驱动、IDE接口硬盘驱动、文件系统等)等文件都集成在了虚拟软盘中。这样通过虚拟软盘启动计算机,操作系统的核心模块就直接从虚拟软盘中加载到内存并执行。内核初始化完成之后,Hello China会进入字符shell模式,这时候用户就可以运行字符模式命令了。 在字符模式下,用户输入GUI命令,即可进入图形模式的shell。一旦用户输入gui命令,Hello China会在硬盘的第一个分区(用C:标识,与Windows类似)的PTHOUSE目录下,寻找hcngui.bin文件,这个文件即是Hello China图形模式模块的可执行二进制文件。一旦找到这个文件,Hello China内核就会把它读入内存,然后运行该模块。因此要在虚拟机上支持图形模式,则必须创建一个虚拟硬盘,并分区和格式化。完成后在其第一个分区上创建PTHOUSE目录,把hcngui.bin等文件拷贝到该目录上就可以了。 GUI模块创建图形shell线程和其它图像模式的核心线程,然后初始化安装在Hello China上的应用程序。所谓初始化应用程序,指的是GUI模块读取所有应用程序的特征数据,比如应用程序名字,应用程序的图标,等等,然后把所有这些应用程序显示在图形界面的主窗口上。最终的启动结果如下: 其中左边部分就是图形界面主窗口,里面列举出了所有已安装在Hello China上的应用程序。一旦用户用鼠标点击某个应用程序,该程序就会被运行。 在Hello China V1.75的实现中,所有应用程序都是安装(实际上就是拷贝)在硬盘第一个分区(C:分区)的HCGUIAPP目录下。在GUI模块的初始化过程中,会读取该目录下的所有文件,一旦发现一个合法的应用程序,则就会进一步读取其特性数据,比如名称(上图中的显示名称)、图标等,然后加载到主窗口中。因此,要运行Hello China V1.75的应用程序,还必须在硬盘的第一个分区上创建一个HCGUIAPP目录,然后把所有应用程序文件拷贝到该目录即可。 Hello China V1.75应用程序就是一个后缀是HCX(Hello China eXecutable)的文件,该文件包含了应用程序的可执行二进制代码、应用程序的图标、应用程序的名称和版本等信息。HCX文件是由一个叫做hcxbuild(随Hello China V1.75的SDK一起发行)的程序构建的,具体构建方法,请参考下一个章节。 总结起来,在Virtual PC上安装Hello China,需完成下列工作: 1、 创建虚拟软盘文件,用于引导Hello China; 2、 创建一个虚拟硬盘,并至少创建一个分区,格式化该分区(建议格式化为NTFS)。Hello China会自动把分区的分区标识设置为C:; 3、 在C:分区上创建PTHOUSE和HCGUIAPP两个目录,把Hello China的外围模块(比如GUI模块)拷贝到PTHOUSE目录下,把应用程序(HCX文件)拷贝到HCGUIAPP目录下。 这样即可启动Hello China了。 Windows 7操作系统添加了对虚拟硬盘的支持,这样就使得上述过程变得非常简单,下面进行详细说明。 HelloChina V1.75安装包说明 Hello China安装包中包含了安装需要的所有文件和辅助工具,主要有: 1、 虚拟软驱文件(vfloppy.vfd)文件; 2、 Hello China的内核模块; 3、 Hello China的外围模块,比如GUI模块等; 4、 一些应用程序文件; 5、 相关辅助工具。 针对Virtual PC的安装包,放在bin/VirtualPC目录下。下面在介绍安装步骤的时候,会对上述文件的使用进行说明。 步骤一:下载和安装Virtual PC 2007 Virtual PC 2007(以上版本也可)可从Microsoft的官方网站上下载,具体可在google上搜索Virtual PC 2007,找到Microsoft网站上的具体链接,点击下载即可。该软件完全免费,也无需注册。而且根据作者的经验,下载速度也非常快。 下载后双击即可安装,无需做特殊设置,采用其默认安装设置即可。 步骤二:创建一个虚拟机 这个步骤也简单,运行步骤一中安装的VirtualPC,在Virtual PC的控制台中点击“New…”菜单,即可启动一个虚拟机的创建过程。假设创建的虚拟机名字是“Hello China”,无需做特殊设置,采用默认设置即可。但是在创建虚拟硬盘一步时,选择“A new hard disk”,如下: 在下一步中,指定虚拟硬盘的存放位置(可采用缺省位置)即可。对于虚拟硬盘的尺寸,可以直接使用缺省尺寸,也可以指定一个尺寸。若指定尺寸,建议至少在256M以上。 最后点击finish按钮,即可完成虚拟机的创建。 对于非Windows 7操作系统,由于不能直接支持虚拟硬盘的创建和操作,因此下列介绍的步骤三、四、五不能适用。读者可直接跳到步骤六,完成基于字符界面的Hello China的安装。 步骤三:对虚拟硬盘进行分区并格式化 Windows 7已经支持虚拟硬盘功能。在“我的电脑”上点击右键,点击“管理”菜单,启动windows计算机管理工具。在计算机管理工具上,点击左边导航树中“磁盘管理”选项,出现磁盘管理界面。点击右面面板的“更多操作”菜单,可出现虚拟硬盘操作菜单。进一步选择“附加VHD”菜单,在显示的对话框中,选择步骤三中创建的虚拟硬盘文件,点击确定即可。这时候windows的磁盘管理器就会把这个虚拟硬盘挂接到磁盘管理界面上了,比如下图中: 其中“磁盘2”就是刚刚挂接的虚拟硬盘。 在上图中“没有初始化“位置处点击右键,在弹出的菜单中选择”初始化硬盘“,采用默认设置初始化该硬盘即可。所谓初始化,指的是操作系统会建立这个硬盘的MBR扇区,并写入相关数据。需要注意的是,虚拟硬盘刚刚创建完成时,是没有任何数据的。要使这个虚拟硬盘可用,必须对之进行初始化。 初始化完成之后,只是写入了MBR扇区,但是具体分区操作还没有完成。这时候可在初始化后的磁盘上,点击鼠标右键,在出现的菜单中,选择“新建简单卷…“,采用默认设置,即可完成虚拟硬盘的分区和格式化工作。 完成格式化后,虚拟硬盘就可用了。可以看到,系统会增加一个新的盘符,这时就可以像使用普通硬盘一样使用虚拟硬盘了。 假设windows 7为新增加的虚拟硬盘分配的盘符是J:,本文后续部分将会以J:为标识引用该虚拟硬盘。 步骤四:准备Hello China V1.75安装目录 把Hello China软件包中bin目录下的VirtualPC目录,拷贝到任意一个本地硬盘(注意,这里不是虚拟硬盘)上,然后把VirtualPC目录下的install目录,在拷贝到虚拟硬盘上(即J:盘)。 步骤五:在虚拟硬盘上安装Hello China 进入J:盘的install目录,运行batch.bat文件,即可完成Hello China在虚拟硬盘J上的安装。Batch.bat文件主要是创建了HCGUIAPP和PTHOUSE两个目录,然后把相关文件拷贝到了这两个目录下。同时也拷贝了一些相关文件到J盘的根目录下。下列是成功安装后J:盘的内容: 其中PTHOUSE目录中存放的是HelloChina的外围功能模块,比如GUI模块、网络模块等。而HCGUIAPP目录中存放的是基于GUI模块的HelloChina应用程序,即HCX文件。 步骤六:将虚拟软驱与虚拟机进行关联 由于Hello China是使用虚拟软盘启动虚拟机的,最后一步就是把虚拟软盘文件(vfloppy.vfd)与虚拟机进行关联,该文件在VirtualPC目录下。在Virtual PC的控制台中,双击步骤二中创建的虚拟机,启动之。由于没有可启动设备,虚拟机无法正常启动。使用鼠标拖住vfloppy.vfd文件,拖到虚拟机窗口下面的软驱图标上,这时候就实现了虚拟软驱与虚拟机的关联。 重新启动虚拟机,正常情况下应该可进入HelloChina的字符模式了。 步骤七:验证Hello China是否成功安装 完成上述步骤之后,虚拟机应该可以正常启动,并进入Hello China的字符操作界面了。在字符界面下执行help命令,可看到一些帮助信息。运行version命令,可看到Hello China的版本信息,如下: 在字符模式下,运行fs命令,进入文件系统操作程序。执行fslist,应该可以看到C:分区,如下: 输入“exit“,退出fs程序,然后输入gui命令,即可进入图形模式,显示所有图形应用程序。点击任何一个应用程序即可运行,比如点击”CPI统计“程序,运行结果如下: 在图形模式下,输入“ctrl+alt+del“组合键(虚拟机下,可点击Action菜单,选择该组合键),即可退到字符模式。 Hello China V1.75测试版源代码和安装文件,可从下列链接下载: http://download.csdn.net/detail/hellochina15/4059717 任何技术问题,欢迎加入QQ群:38467832 进行讨论。
上一篇文章,Windows7上使用VMWare搭建iPhone开发环境介绍了在windows上安装Mac os x操作系统的方法,本篇文章将介绍Xcode的安装及第一个应用程序的开发 1.下载并安装Xcode 安装好Mac操作系统后,打开里面的浏览器就可以直接进入到Apple的官方网站,在网站里面搜索xcode就可以进入到Xcode的下载界面 在下载的过程中需要注册一个Apple ID,很简单的这里就不详细介绍了。下面的插图是我开发环境里面的搜索后的列表,供大家参考 这里需要注意下,不是所有的Xcode都可以使用的,Xcode和Mac OS X操作系统需要一定的相互匹配关系的,具体的情况可以参考下面 Xcode各个版本和Mac OS X对应关系 我这里下载的Xcode4.5。下载之后,是一个.dmg类型的文件。该类型的文件对于Mac OS X来说就相当于exe类型对于Windows的关系,只要点击dmg文件就可以运行该应用了 启动后的界面如下图 2.创建第一个应用Hello World 和学习所有的开发语言一样,第一个应用从Hello开始 启动Xcode之后,点击Create a new Xcode project按钮,如下图 进入到如下界面 这个界面是Xcode提供的模板,供开发者选择。对于我们的第一个程序,选择Single View Application模板。选择该模板后点击Next按钮,进入的项目属性的编辑界面 对于上面的这个界面中,Product Name就是项目的名称,Organization Name就是组织名了,Company Identifier就是公司的标示符了,这个对于 使用开发者账号开发应用时需要注意,需要与开发者账号注册时使用的标识符一直,对于开发者账号开发APP并且发布及真机调试的相关内容我们将在后面的内容中介绍, 本文暂不考虑,只要知道它是做什么的就行。 Devices可以选择该应用是iPhone还是iPad上的应用,默认的情况是两者都可以兼容。我们选择默认就可以了 最后勾选掉图中标红的两个复选框,点击Next进入项目编辑界面。 点击上图中标红的文件,可以看到右侧的界面如下 这里就是最终呈现在iPhone上的画面效果,默认的情况下是没有任何控件的,图中的Logon这个圆角按钮时我后来添加进去的。 做法是,点击右下角导航部分的Round rect Button如下图 点击项目中的ViewController.h在@end前面输入下面的代码 [objc] view plain copy -(IBAction)showMessage; 接着进入到ViewController.m中在@end前面输入下面的代码 [objc] view plain copy - (IBAction)showMessage { UIAlertView *helloWorldAlert = [[UIAlertView alloc] initWithTitle:@"My First App" message:@"Hello, World!" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [helloWorldAlert show]; } 第一句话UIAlertView *helloWorldAllert是在定义一个消息框,可以类似的理解为C++中的MessageBox。后面的内容是在 初始化一些基本的属性,比如标题、提示内容、各个按钮的名称等。这里需要注意的是,所有的内容都是使用@加上字符串来定义的。 最后一句的helloWorldAlert show是要将这个提示框显示出来。注意不要想C++或者是Java等语言那样使用点号来调用show方法。 这些编辑之后,注意保存。可以使用快捷键win S(Mac里面是Command S)。 最后,我们需要将按钮和定义好的方法关联在一起。 点击iPhone.xib文件,进入到Editor界面。先点击Control键,然后用鼠标拖动按钮到File‘s Owner界面,选择关联方法如下图。 选择上图出现的showMessage方法,保存文件后,点击左上角的运行按钮在虚拟机上运行我们编译好的程序,或者是快捷键win R。构建是win B。 运行后如下图 点击Logon按钮,可以看到我们定义好的showMessage方法的运行效果,弹出Hello world的提示框。效果如下图所示 这样,整个程序就开发完毕了 参考文章: 1.http://hi.baidu.com/zyb_debug/item/7ebbb012a4073ba6feded5d6 2.IOS从入门到精通
http://www.kekaoxing.com/m/view.php?aid=22604 GJB150.1A-2009 军用装备实验室环境试验方法第1部分:通用要求(代替GJB150.1-86)GJB150.2A-2009 军用装备实验室环境试验方法第2部分:低气压(高度)试验(代替GJB150.2-86)GJB150.3A-2009 军用装备实验室环境试验方法GJB150.4A-2009 军用装备实验室环境试验方法GJB150.5A-2009 军用装备实验室环境试验方法GJB150.7A-2009 军用装备实验室环境试验方法第7部分:太阳辐射试验(代替GJB150.7-86)GJB150.8A-2009 军用装备实验室环境试验方法第8部分:淋雨试验(代替GJB150.8-86)GJB150.9A-2009 军用装备实验室环境试验方法第9部分:湿热试验(代替GJB150.9-86)GJB150.10A-2009 军用装备实验室环境试验方法第GJB150.11A-2009 军用装备实验室环境试验方法第11部分 盐雾试验(代替GJB150.11-86)GJB150.12A-2009 军用装备实验室环境试验方法第12部分:砂尘试验(代替GJB150.12-86)GJB150.13A-2009 军用装备实验室环境试验方法第GJB150.14A-2009 军用装备实验室环境试验方法第14部分:浸渍试验(代替GJB150.14-86)GJB150.15A-2009 军用装备实验室环境试验方法GJB150.16A-2009 军用装备实验室环境试验方法第16部分振动试验(代替GJB150.16-86)GJB150.17A-2009 军用装备实验室环境试验方法第17部分:噪声试验(代替GJB150.17-86)GJB150.18A-2009 军用装备实验室环境试验方法GJB150.20A-2009 军用装备实验室环境试验方法第20部分:炮击振动试验(代替GJB150.20-86)GJB150.21A-2009 军用装备实验室环境试验方法第21部分:风压试验(代替GJB150.21-87)GJB150.22A-2009 军用装备实验室环境试验方法第22部分:积冰/冻雨试验(代替GJB150.22-87)GJB150.23A-2009 军用装备实验室环境试验方法第23部分:倾斜和摇摆试验(代替GJB150.23-91)GJB150.24A-2009 军用装备实验室环境试验方法GJB150.25A-2009 军用装备实验室环境实验方法GJB150.26-2009 军用装备实验室环境试验方法第GJB150.27-2009 军用装备实验室环境实验方法GJB150.28-2009 军用装备实验室环境试验方法GJB150.29-2009 军用装备实验室环境试验方法第29部分:弹道冲击试验GJB150.30-2009 舰船冲击试验 暂缺GJB150.6A-2009 GJB150.19A-2009 GJB150.30-2009 三个系列, 可靠性论坛关于GJB 150贴子交流: [标准] GJB150-2009 全套上传旧版:GJB 150《军用装备实验室环境试验方法》标准下载 GJB150-2009系列标准由sunjj上传.感谢sunjj对中国可靠性网的支持
AXI全称Advanced eXtensible Interface,是Xilinx从6系列的FPGA开始引入的一个接口协议,主要描述了主设备和从设备之间的数据传输方式。在ZYNQ中继续使用,版本是AXI4,所以我们经常会看到AXI4.0,ZYNQ内部设备都有AXI接口。其实AXI就是ARM公司提出的AMBA(Advanced Microcontroller Bus Architecture)的一个部分,是一种高性能、高带宽、低延迟的片内总线,也用来替代以前的AHB和APB总线。第一个版本的AXI(AXI3)包含在2003年发布的AMBA3.0中,AXI的第二个版本AXI(AXI4)包含在2010年发布的AMBA 4.0之中。 AXI协议具有如下特点:. 总线的地址/控制和数据通道是分离的;. 支持不对齐的数据传输;. 在突发数据传输中只需要首地址;. 同时具有分离读/写数据通道;. 支持显著传输访问和乱序访问;. 更加容易进行时序收敛 AXI4包含三种接口:. AXI4——For high-performance memory-mapped requirements.. AXI4-Lite——For simple, low-throughput memory-mapped communication (for example, to and from control and status registers).. AXI4-Stream——For high-speed streaming data. 从上面的描述可以看出,AXI4协议相当于原来的AHB协议,提供高速的系统内部互连通道,可以支持burst模式,主要用于处理器访问存储等需要高速数据的场合;AXI4-Lite为外设童工单个数据传输,相当于原来的APB协议,用于访问一些低速外设;AXI4-Stream接口就像FIFO一样,数据传输的时候不需要地址,而是主从设备直接连续读写数据,主要用于如视频、高速AD、PCIe、DMA接口等需要高速数据传输的场合,跟Xilinx原来的Local Link协议类似。 AXI InterconnectAXI协议严格的讲是一个点对点的主从接口协议,当多个外设需要互相交互数据时,我们需要加入一个AXI Interconnect模块,也就是AXI互联矩阵,作用是提供将一个或多个AXI主设备连接到一个或多个AXI从设备的一种交换机制(有点类似于交换机里面的交换矩阵)。Xilinx为我们提供了实现这种互联矩阵的IP核axi_interconnect_1,在前面的例子中,我们在XPS中可以看到。这个IP核最多可以支持16个主设备、16个从设备,如果需要更多的接口,可以多加入几个IP核。关于AXI Interconnect更多的知识,可参考Xilinx官方文档DS768。 AXI4和AXI4-Lite接口包含5个不同的通道:. Read Address Channel. Write Address Channel. Read Data Channel. Write Data Channel. Write Response Channel 其中每个通道都是一个独立的AXI握手协议。下面两个图分别显示了读和写的模型: ZYNQ中的AXI接口共有9个,主要用于PS与PL的互联,包含以下三个类型:. AXI_ACP接口,是ARM多核架构下定义的一种接口,中文翻译为加速器一致性端口,用来管理DMA之类的不带缓存的AXI外设,PS端是Slave接口。. AXI_HP接口,是高性能/带宽的AXI3.0标准的接口,总共有四个,PL模块作为主设备连接。主要用于PL访问PS上的存储器(DDR和On-Chip RAM). AXI_GP接口,是通用的AXI接口,总共有四个,包括两个32位主设备接口和两个32位从设备接口。 其实,在具体设计中我们往往不需要在连接这个地方做太多工作,就像上一个例子中,我们加入IP核以后,系统会自动使用AXI接口将我们的IP核与处理器连接起来,我们只需要再做一点补充就可以了。不过,这部分概念还是了解比较好。
常用YUV转RGB [java] view plaincopyprint? public class YuvToRGB { private static int R = 0; private static int G = 1; private static int B = 2; //I420是yuv420格式,是3个plane,排列方式为(Y)(U)(V) public static int[] I420ToRGB(byte[] src, int width, int height){ int numOfPixel = width * height; int positionOfV = numOfPixel; int positionOfU = numOfPixel/4 + numOfPixel; int[] rgb = new int[numOfPixel*3]; for(int i=0; i<height; i++){ int startY = i*width; int step = (i/2)*(width/2); int startU = positionOfV + step; int startV = positionOfU + step; for(int j = 0; j < width; j++){ int Y = startY + j; int U = startU + j/2; int V = startV + j/2; int index = Y*3; RGB tmp = yuvTorgb(src[Y], src[U], src[V]); rgb[index+R] = tmp.r; rgb[index+G] = tmp.g; rgb[index+B] = tmp.b; } } return rgb; } private static class RGB{ public int r, g, b; } private static RGB yuvTorgb(byte Y, byte U, byte V){ RGB rgb = new RGB(); rgb.r = (int)((Y&0xff) + 1.4075 * ((V&0xff)-128)); rgb.g = (int)((Y&0xff) - 0.3455 * ((U&0xff)-128) - 0.7169*((V&0xff)-128)); rgb.b = (int)((Y&0xff) + 1.779 * ((U&0xff)-128)); rgb.r =(rgb.r<0? 0: rgb.r>255? 255 : rgb.r); rgb.g =(rgb.g<0? 0: rgb.g>255? 255 : rgb.g); rgb.b =(rgb.b<0? 0: rgb.b>255? 255 : rgb.b); return rgb; } //YV16是yuv422格式,是三个plane,(Y)(U)(V) public static int[] YV16ToRGB(byte[] src, int width, int height){ int numOfPixel = width * height; int positionOfU = numOfPixel; int positionOfV = numOfPixel/2 + numOfPixel; int[] rgb = new int[numOfPixel*3]; for(int i=0; i<height; i++){ int startY = i*width; int step = i*width/2; int startU = positionOfU + step; int startV = positionOfV + step; for(int j = 0; j < width; j++){ int Y = startY + j; int U = startU + j/2; int V = startV + j/2; int index = Y*3; //rgb[index+R] = (int)((src[Y]&0xff) + 1.4075 * ((src[V]&0xff)-128)); //rgb[index+G] = (int)((src[Y]&0xff) - 0.3455 * ((src[U]&0xff)-128) - 0.7169*((src[V]&0xff)-128)); //rgb[index+B] = (int)((src[Y]&0xff) + 1.779 * ((src[U]&0xff)-128)); RGB tmp = yuvTorgb(src[Y], src[U], src[V]); rgb[index+R] = tmp.r; rgb[index+G] = tmp.g; rgb[index+B] = tmp.b; } } return rgb; } //YV12是yuv420格式,是3个plane,排列方式为(Y)(V)(U) public static int[] YV12ToRGB(byte[] src, int width, int height){ int numOfPixel = width * height; int positionOfV = numOfPixel; int positionOfU = numOfPixel/4 + numOfPixel; int[] rgb = new int[numOfPixel*3]; for(int i=0; i<height; i++){ int startY = i*width; int step = (i/2)*(width/2); int startV = positionOfV + step; int startU = positionOfU + step; for(int j = 0; j < width; j++){ int Y = startY + j; int V = startV + j/2; int U = startU + j/2; int index = Y*3; //rgb[index+R] = (int)((src[Y]&0xff) + 1.4075 * ((src[V]&0xff)-128)); //rgb[index+G] = (int)((src[Y]&0xff) - 0.3455 * ((src[U]&0xff)-128) - 0.7169*((src[V]&0xff)-128)); //rgb[index+B] = (int)((src[Y]&0xff) + 1.779 * ((src[U]&0xff)-128)); RGB tmp = yuvTorgb(src[Y], src[U], src[V]); rgb[index+R] = tmp.r; rgb[index+G] = tmp.g; rgb[index+B] = tmp.b; } } return rgb; } //YUY2是YUV422格式,排列是(YUYV),是1 plane public static int[] YUY2ToRGB(byte[] src, int width, int height){ int numOfPixel = width * height; int[] rgb = new int[numOfPixel*3]; int lineWidth = 2*width; for(int i=0; i<height; i++){ int startY = i*lineWidth; for(int j = 0; j < lineWidth; j+=4){ int Y1 = j + startY; int Y2 = Y1+2; int U = Y1+1; int V = Y1+3; int index = (Y1>>1)*3; RGB tmp = yuvTorgb(src[Y1], src[U], src[V]); rgb[index+R] = tmp.r; rgb[index+G] = tmp.g; rgb[index+B] = tmp.b; index += 3; tmp = yuvTorgb(src[Y2], src[U], src[V]); rgb[index+R] = tmp.r; rgb[index+G] = tmp.g; rgb[index+B] = tmp.b; } } return rgb; } //UYVY是YUV422格式,排列是(UYVY),是1 plane public static int[] UYVYToRGB(byte[] src, int width, int height){ int numOfPixel = width * height; int[] rgb = new int[numOfPixel*3]; int lineWidth = 2*width; for(int i=0; i<height; i++){ int startU = i*lineWidth; for(int j = 0; j < lineWidth; j+=4){ int U = j + startU; int Y1 = U+1; int Y2 = U+3; int V = U+2; int index = (U>>1)*3; RGB tmp = yuvTorgb(src[Y1], src[U], src[V]); rgb[index+R] = tmp.r; rgb[index+G] = tmp.g; rgb[index+B] = tmp.b; index += 3; tmp = yuvTorgb(src[Y2], src[U], src[V]); rgb[index+R] = tmp.r; rgb[index+G] = tmp.g; rgb[index+B] = tmp.b; } } return rgb; } //NV21是YUV420格式,排列是(Y), (VU),是2 plane public static int[] NV21ToRGB(byte[] src, int width, int height){ int numOfPixel = width * height; int positionOfV = numOfPixel; int[] rgb = new int[numOfPixel*3]; for(int i=0; i<height; i++){ int startY = i*width; int step = i/2*width; int startV = positionOfV + step; for(int j = 0; j < width; j++){ int Y = startY + j; int V = startV + j/2; int U = V + 1; int index = Y*3; RGB tmp = yuvTorgb(src[Y], src[U], src[V]); rgb[index+R] = tmp.r; rgb[index+G] = tmp.g; rgb[index+B] = tmp.b; } } return rgb; } //NV12是YUV420格式,排列是(Y), (UV),是2 plane public static int[] NV12ToRGB(byte[] src, int width, int height){ int numOfPixel = width * height; int positionOfU = numOfPixel; int[] rgb = new int[numOfPixel*3]; for(int i=0; i<height; i++){ int startY = i*width; int step = i/2*width; int startU = positionOfU + step; for(int j = 0; j < width; j++){ int Y = startY + j; int U = startU + j/2; int V = U + 1; int index = Y*3; RGB tmp = yuvTorgb(src[Y], src[U], src[V]); rgb[index+R] = tmp.r; rgb[index+G] = tmp.g; rgb[index+B] = tmp.b; } } return rgb; } //NV16是YUV422格式,排列是(Y), (UV),是2 plane public static int[] NV16ToRGB(byte[] src, int width, int height){ int numOfPixel = width * height; int positionOfU = numOfPixel; int[] rgb = new int[numOfPixel*3]; for(int i=0; i<height; i++){ int startY = i*width; int step = i*width; int startU = positionOfU + step; for(int j = 0; j < width; j++){ int Y = startY + j; int U = startU + j/2; int V = U + 1; int index = Y*3; RGB tmp = yuvTorgb(src[Y], src[U], src[V]); rgb[index+R] = tmp.r; rgb[index+G] = tmp.g; rgb[index+B] = tmp.b; } } return rgb; } //NV61是YUV422格式,排列是(Y), (VU),是2 plane public static int[] NV61ToRGB(byte[] src, int width, int height){ int numOfPixel = width * height; int positionOfV = numOfPixel; int[] rgb = new int[numOfPixel*3]; for(int i=0; i<height; i++){ int startY = i*width; int step = i*width; int startV = positionOfV + step; for(int j = 0; j < width; j++){ int Y = startY + j; int V = startV + j/2; int U = V + 1; int index = Y*3; RGB tmp = yuvTorgb(src[Y], src[U], src[V]); rgb[index+R] = tmp.r; rgb[index+G] = tmp.g; rgb[index+B] = tmp.b; } } return rgb; } //YVYU是YUV422格式,排列是(YVYU),是1 plane public static int[] YVYUToRGB(byte[] src, int width, int height){ int numOfPixel = width * height; int[] rgb = new int[numOfPixel*3]; int lineWidth = 2*width; for(int i=0; i<height; i++){ int startY = i*lineWidth; for(int j = 0; j < lineWidth; j+=4){ int Y1 = j + startY; int Y2 = Y1+2; int V = Y1+1; int U = Y1+3; int index = (Y1>>1)*3; RGB tmp = yuvTorgb(src[Y1], src[U], src[V]); rgb[index+R] = tmp.r; rgb[index+G] = tmp.g; rgb[index+B] = tmp.b; index += 3; tmp = yuvTorgb(src[Y2], src[U], src[V]); rgb[index+R] = tmp.r; rgb[index+G] = tmp.g; rgb[index+B] = tmp.b; } } return rgb; } //VYUY是YUV422格式,排列是(VYUY),是1 plane public static int[] VYUYToRGB(byte[] src, int width, int height){ int numOfPixel = width * height; int[] rgb = new int[numOfPixel*3]; int lineWidth = 2*width; for(int i=0; i<height; i++){ int startV = i*lineWidth; for(int j = 0; j < lineWidth; j+=4){ int V = j + startV; int Y1 = V+1; int Y2 = V+3; int U = V+2; int index = (U>>1)*3; RGB tmp = yuvTorgb(src[Y1], src[U], src[V]); rgb[index+R] = tmp.r; rgb[index+G] = tmp.g; rgb[index+B] = tmp.b; index += 3; tmp = yuvTorgb(src[Y2], src[U], src[V]); rgb[index+R] = tmp.r; rgb[index+G] = tmp.g; rgb[index+B] = tmp.b; } } return rgb; } }
安装完毕启动后,明显慢的要死,登陆后竟然是一个空白的桌面环境,Ctrl+Alt+T 根本没有任何反应。唯一的反应就是右键能够创建文件和文档。 同时打开的窗口没有最大化,最小化及关闭按钮。 GOOGLE了一翻,发现很多国外同胞也遇到了同样的问题,结论是:显卡驱动问题(gma3150显卡驱动)。 解决过程1: 1, 给厂家要Ubuntu12.04的显卡驱动,其实就是一个shell, 一摞要安装的包。 2,准备安装新驱动,即执行该SHELL,但是没法打开Terminal, 咋办? 3,离开X窗口,来到虚拟终端来解决吧: Ctrl+Alt+F5 (F1-F6均可),普通用户操作(提醒在root用户下是不行的!), cd /media 此时应该可以看到自己插入的U盘,内涵驱动SHELL哦。 sudo cp xx ~/xx 将SHELL拷贝到家目录 cd 到家目录 修改一下sudo chmod a+x 吧。 然后执行 sudo ./xx.sh (此时,请确保计算机连接到了Internet上) 等待数分钟后,reboot系统。 OK ------神经正常的Ubuntu12.04 回归了!!! 系统配置: CPU D525 @1.8GHz / 2G RAM/500G HDD 集成显卡型号:VGA Intel Corporation Atom Processor D4XX/D5XX/N4XX/N5XX Integrated Graphics Controller(rev 02) Intel的集显真的是让人无语啊!!! 解决过程2:(不推荐) 添加驱动管理器:(安装系统推荐驱动) 安装命令: sudo add-apt-repository ppa:noobslab/appssudo apt-get updatesudo apt-get install ddm .../# ddm 安装系统推荐驱动。 解决过程3:(不推荐) 直接用以下方法 升级 intel显卡驱动卸载掉现在的驱动sudo apt-get remove xserver-xorg-video-intel再安装驱动sudo apt-get install xserver-xorg-video-intel -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 一、升级intel显卡驱动系统-系统管理-新立得软件管理包, 设置-软件库-第三方软件,添加 以下两个地址deb http://ppa.launchpad.net/ubuntu-x-swat/x-updates/ubuntu jaunty maindeb-src http://ppa.launchpad.net/ubuntu-x-swat/x-updates/ubuntu jaunty main刷新,会提示有两个部分需要升级(intel显卡驱动)如果出现错误提示:GPG签名验证错误: http://ppa.launchpad.net intrepid Release: 由于没有公钥,下列签名无法进行行验证: NO_PUBKEY 5A9BF3BB4E5E17B5解决方法:下载密匙gpg --keyserver subkeys.pgp.net --recv 5A9BF3BB4E5E17B5添加密匙直接用以下方法 升级 intel显卡驱动卸载掉现在的驱动sudo apt-get remove xserver-xorg-video-intel再安装驱动sudo apt-get install xserver-xorg-video-intel二、运行sudo gedit /etc/X11/xorg.conf把Section “Device”Identifier “Configured Video Device”EndSection修改为Section “Device”Identifier “intel”EndSection把Section “Screen”Identifier “Default Screen”Monitor “Configured Monitor”Device “Configured Video Device”EndSection修改为Section “Screen”Identifier “Default Screen”Monitor “Configured Monitor”Device “intel”EndSection三、修改compiz的配置sudo gedit /etc/xdg/compiz/compiz-manager新起一行,增加 SKIP_CHECKS=yes, 保存四、安装compiz的配置管理器运行 sudo apt-get install compizconfig-settings-manager或者直接用新立德软件包管理器搜索并安装compizconfig-settings-manager五、重启系统,打开系统-首选项- compizconfig设置管理器至此成功打开桌面效果和3D
( 1 ).前言 2003 年以后, fmslabs 的 RTLinux Free 版本为 3.2Pre ,和以前的 RTLinux 3.1 比较,不再需要必须从 2.4.4 的内核上安装。 RTLinux 3.2 支持的 Linux 内核为 2.4.19/2.4.20/2.4.21Pre5 ( 2 ).准备 目前计算机上已经存在系统为 Vine Linux 2.5 ,内核为 2.4.18. 硬盘总共大小为 100G, 前 4G 为 dos 主分区,安装 windows 系统,使用 OSLoader 启动,后 96G 为 Extend 分区其中分配如下: hda1 FAT32 Window 4G, hda5 Ext3 /boot 500M hda6 FAT32 Window 40G hda7 Ext3 /root 40G hda8 Ext3 /home 4G 其余给 linux swap ( 3 ).下载 首先,从下载 RTLinux 3.2 和 2.4.20 的内核 2.4.20 内核下载地址 ftp://ftp.kernel.org/pub/linux/kernel/v2.4/linux-2.4.20.tar.bz2 RTLinux 3.2 的下载地址 http://www2.fsmlabs.com/3.2-free.html 填好注册表格后,即可下载 ( 4 ).复制,解压缩和建立目录 以 root 身份登录,建立如下目录 cd /usr/src mkdir rtlinux 将下载的文件复制到此,此时此目录下内容如下: ls /usr/src/rtlinux linux-2.4.20.tar.bz2 rtlinux-3[1].2-pre2.tar.bz2 现在解开压缩包 bunzip2 linux-2.4.20.tar.bz2 | tar –xvf – bunzip2 rtlinux-3[1].2-pre2.tar.bz2 | tar –xvf – 此时目录结构如下 ls /usr/src/rtlinux linux-2.4.20.tar.bz2 rtlinux-3[1].2-pre2.tar.bz2 linux-2.4.20 rtlinux-3.2-pre2 现在建立必要的连接 : ll /usr/src 会发现这样的一个连接 linux à /usr/src/linux-2.4.18 删除这个连接 cd /usr/src rm linux 然后建立一个新的符号连接: ln –s /usr/src/rtlinux/linux-2.4.20 linux 至此,已经将新内核连接到 /usr/src/linux 上了。 ( 5 ).打补丁,配置内核 下面给内核打 Realtime 的补丁。注意到 /usr/src/rtlinux/rtlinux/rtlinux-3.2-pre2 下有一个目录名字叫 patches ,里面内容如下: kernel_patch-2.4.19-rtl3.2-pre2 README kernel_patch-2.4.20-rtl3.2-pre2 kernel_patch-2.4.21-pre5-rtl3.2-pre2 选择其中的 2.4.20 的补丁,运行 patch 命令: cd /usr/src/linux patch -p1 < /usr/src/rtlinux/rtlinux-3.2-pre2/patches/kernel_patch-2.4.20-rtl3.2-pre2 此时 realtime 的补丁已经打入新内核中 然后运行编译配置命令, cd /usr/src/linux 如果在 xwindow 下运行 make xconfig & 如果没有安装 xwindows ,运行 make menuconfig 出现内核配置对话框,一般用缺省配置就可以,但是注意以下一些点 : 5.1. 如果目前的 linux 分区为 ext3 分区,内核缺省选项可能是不支持,在 FileSystem 中,选择支持 ext3 文件系统。 5.2. 如果硬盘中存在 dos 分区,并且这些分区在 /etc/fstab 中指定为启动后自动 mount ,则必须配置支持 dos 分区格式 fat16 和 fat32 ,在 FileSystem 中,选择支持 vfat 文件系统。如果需要,选择支持 ntfs 文件系统 5.3. 如果网卡特殊,注意配置特殊的网卡驱动程序,本计算机使用了 Realtek 8139 网卡。在 Network device support 中选择 Ethernet(10 or 100 Mbit) 然后选中 Realtek RTL-8139 PCI Fast Ethernet Adaptor support 。 有些选象有 3 个选象 y,m,n 分别对应 yes :该模块被直接编译进入内核,内核会因此变大 modular :该模块可以在内核启动时被装载,这样内核不会变大,但是可以得到相应的功能 no :不安装 全部配置完成后,选择存盘并且退出。下面可以开始编译内核了 make dep clean bzImage 开始编译,需要若干时间,因机器而异,可能长达数小时。如果编译成功,最后显示 Boot sector 512 bytes Setup is xxxxx bytes. System is xxxxx KB 一般会提示,内核过大,无法复制到一张软盘上。忽略这些提示 如果编译过程出现错误,必须重新配置内核编译选项 cd /usr/src/linux make xconfig & 或者 make menuconfig 配置需要若干技巧和经验。具体可以参考相关的文章。 下面将相应的驱动程序模块,安装到指定位置( /lib/modules/2.4.20-rtl3.2-pre2 ),供新内核启动时装载这些模块。 make modules modules_install 最后检查一下编译是否完整,利用下面的命令 depmod –a 如果没有错误,即可进入下一步,每次重新 make dep clean bzImage 成功后,都要注意运行 make modules modules_install ,否则,重新启动新内核时,可能发生找不到驱动程序的情况 目标机:redhat9(内核版本为2.4.20-8),GCC编译器为3.2版本(可用GCC -v来查询版本号;) 注:如果编译器gcc版本是2.96,那么在多处理器电脑上安装RTLinux则需要修改/usr/src/RTLinux/Linux/Makefile中的代码 CC = $(CROSS_COMPILE)gcc改变编译器为kgcc(gcc 2.91),改变后的代码为:CC = kgcc实际上,使用gcc2.91,2.95和3.x都没有问题。(由于本人GCC版本为3.2版本,直接使用,并未对其他GCC版本做测试)。 操作系统要求:实时操作系统为RTLinux3.2;内核版本为Kernel-2.4.23 安装步骤: 1.安装LINUX操作系统(如REDHAT9) 2.RTLINUX实时操作系统安装 1)将内核源码linux-2.4.23.tar.gz与实时操作系统RTLinux.rar复制到/usr/src目录下;并进行解压(由于RTLinux安装时并没有安装rar解压工具,下载rarlinux-3.5.1.tar.gz,并安装) 2)安装RTLinux实时补丁 将RTLINUX加压后文件夹下的rtlinux-3.2-rc1下patchs目录下的kernel_patch-2.4.23-rtl3.2-pre3复制到内核解压的linux-2.4.23目录下,并打包 cp /usr/src/root/rtlinux-3.2-rc1/patchs/kernel_patch-2.4.23-rtl3.2-pre3 /usr/src/linux-2.4.23 cd /usr/src/linux-2.4.23 patch -p1 < ./patchs/kernel_patch-2.4.23-rtl3.2-pre3 3)建立软连接,并配置编译内核 ln -s /usr/src/linux-2.4.23 /usr/src/root/rtlinux-3.2-rc1/linux cd /usr/src/root/rtlinux-3.2-rc1/linux cp /boot/config-2.3.20-8 .config make menuconfig(注:此步骤尤其重要;最后启动失败;多半因为此处配置错误) Loadable module support-> [*]Enable loadable module support,RTLinux使用模块功能来加载实时任务,此功能必须存在。 processer type and features---> (****)processor family(空格进行选择,此处选择的是Pentium-4(本身CPU为酷睿双核)) General Setup---> [ ]Advanced Power Management BIOS(此处一定不要选择,机器的APM功能一定要关闭,它会抢夺RTLinux对硬件的控制)File System---> [ * ]Ext3 journalling file system support [ * ]JBD(ext3)debugging support make dep make bzImage make modules make modules_install make install 4)将重新安装的内核加入启动选项 cp arch/i386/boot/bzImage /boot/rtzImage gedit /boot/grub/menu.lst(在打开的文件末尾加入如下内容) title RTLinux,kernel 2.4.23-rtl3.2-pre3 root(hd0,0)(此处标注的是内核文件放置的分区即/boot分区,可用df -l 来查询,若/boot为/dev/hdc1,则此处) Kernel /rtzImage ro root =/dev/hdc3 initrd /initrd-2.4.20-rtl3.2-pre3.img 5)重新启动 reboot 6)RTLinux的bug修正(注意此处若不修正bug,在之后的rtlinux内核配置编译则会产生关于“xargs不能大于20k”的error而使得编译无法继续)重启电脑,在启动界面下选择RTLinux进入 在rpmseek.com下载findutils-4.1.7-9.src.rpm,放于/usr/src下安装rpm -ivh findutils-4.1.7-9.src.rpm //在rpm文件包所在文件夹下会生成redhat文件夹,其中有一些资源文件cp /usr/src/redhat/SOURCES/findutils-4.1.20.tar.bz2 /usr/localcd /usr/localtar xjvf findutils-4.1.7.tar.bz2cd findutils-4.1.7./configure 注释掉xargs.c中的如下两行:gedit xargs/xargs.c /* if (arg_max > 20 * 1024) arg_max = 20 * 1024;*/ cd .. make make install cp /usr/bin/xargs /usr/bin/xargsoldcp /usr/local/bin/xargs /usr/bin 7)RTLinux配置和编译 cd /usr/src/root/rtlinux-3.2-rc1 make clean make menuconfig POSIX Support options---> [ * ]POSIX Signals[ * ]POSIX Timers 编译RTLinux之前需要修改浮点路径,去掉/usr/src/rtlinux/examples/fp/Makefile和usr/src/rtlinux/examples/v1api/fp/Makefile中的第一个-lm make dep make modules make devices make install 7)重启 reboot 8)测试 make regression 成功则显示为:Testing multiple loads of rtl.o… [OK] Testing …… …… 否则回到第3步重新配置内核 9)RTLinux简单操作 启动rtlinux:rtlinux start查看rtlinux:rtlinux status关闭rtlinux:rtlinux stop
bootloader支持启动多个Linux 内核安装(X86平台) 1、 cparch/x86/boot/bzImage /boot/vmlinuz-$version 2、 cp $initrd /boot/ 3、 修改/etc/grub.conf或者/etc/lilo.conf $version为所编译的内核版本号 文件linux-2.6.32.2.tar.bz2在目录/home/x86下。 #tar jxvflinux-2.6.32.2.tar.bz2 #cd linux-2.6.32 #makedistclean #cp/boot/config-2.6.18-53.el5 .config(使用正在使用的RHEL5的内核配置作为参考) #makemenuconfig(直接退出保存,使用默认的配置即可) #makebzImage(在X86平台,zImage只能小于512K的内核) 生成后的内核映像bzImage位于arch/x86/boot下。 #makemodules_install 时间有点长,出去溜达下。。。 完成安装后,编译好的内核模块会从内核源代码目录拷贝至/lib/modules(虚拟机的该目录,不是编译内核的目录!)下。 #cd x86 #mkinitrdinitrd-2.6.32 2.6.32 initramdisk的作用:提供一种让内核可以简单使用的ramdkisk的能力。这些能力包括:格式化一个ramdisk;加载文件系统内容到ramdisk;将 ramdisk作为根文件系统。 完成操作后,在当前目录会生成initrd-2.6..32。 由于Linux系统启动时,会从/boot目录下来寻找内核文件和init ramdisk,所以需将内核和init ramdisk拷贝至/boot目录下。 #cp/home/x86/linux-2.6.32/arch/boot/bzImage/boot/vmlinuz-2.6.32。 vmlinuz-2.6.32可以任意取名 #cp/home/x86/initrd-2.6.32 /boot 为了让grub在启动时能提供一项我们自己制作的Linux内核的选择项,需要改grub的配置文件,在原有内容的基础上,添加以下代码。 #vim/etc/grub.config title RedHat Enterprise Linux Server (2.6.18-53.el5) root (hd0,0) kernel /vmlinuz-2.6.18-53.el5ro root=/dev/VolGroup00/LogVol00 rhgb quiet initrd /initrd-2.6.18-53.el5.img 以下是添加的 titleMy Linux root (hd0,0) kernel /vmlinuz-2.6.32ro root=/dev/VolGroup00/LogVol00 rhgb quiet initrd /initrd-2.6.32 重启虚拟机 注意:Press any key to enter the menu 选择My Linux 我在想CF卡上抑制系统时候出现问题,请求大虾帮助! 问题是这样的! 我要做一个CF卡上的linux系统,要求开机引导后就可以运行应用程序! 我的应用程序是叫hello,在终端打印hello,world! 我现在是这样做的! 第一阶段:制作CF卡上的LINUX系统 环境:redhat linux 9.0 工具:CF卡和CF卡读卡器(对系统来说相当于USB设备,就是/dev/sda) 制作步骤: 1. 将CF卡分一个区,并格式化为ext2文件系统,然后mount在/mnt/usb上, 2. 然后准备根文件系统!我用busybox工具,首先将busybox-1.00下载并静态编译!得到一个_install目录 目录里面有/sbin/bin /usr 目录 和一个连接文件 linuxrc。我将这些目录和文件拷贝到CF上, 即:cp -a/_install/* /mnt/usb 3. 然后建立其它文件系统录: /dev /etc /etc/rc.d /lib /proc/tmp /var /mnt /boot /boot/grub 然后在/dev 下建立console tty tty1 ttyS0 ttyS1 null ram0 hda hda1 hdb hdb1 hdc hdc1 sda sda1 在/lib 下 拷入hello程序需要使用的共享库libc-2.3.2.so ld-2.3.2.so libdl-2.3.2.so等 在/etc 下建立一些需要的脚本文件,rc.sysinit, inittab, fstab 其中:inittab内容如下: id:2:initdefaults: si::sysinit:/etc/rc.d/rc.sysinit ::askfirst:/bin/bash rc.sysinit内容如下: #!/bin/bash umount -a mount -a fstab内容如下: /dev/sda1 / ext2 defaults1 1 none /proc proc defaults 0 0 4. 建立initrd.img 我直接将redhat 9.0 /boot下的initrd-2.4.20-8.img拷过! 5.将redhat /boot下的vmlinuz-2.4.20-8内核拷到 /boot 下 6. 这样文件系统全部完成了! 第二阶段: 引导系统 工具: grub 作为bootloader 步骤: 首先将/boot/grub 目录下的 stage1stage2 拷贝到 CF卡上/boot/grub目录下面 然后运行grub程序! grub>; root (hd1,0) grub>; setup (hd1) grub>; quit 成功装如grub ,然后在/boot/grub目录下建立grub.conf脚本文件 default=0 timeout=10 titleCFlinux root (hd0,0) kernel /boot/vmlinuz-2.4.20-8 ro /dev/sda1 initrd /boot/initrd-2.4.20-8.img 然后开机,设定从USB启动, grub可以正确引导,不过系统报错: init can't find !我的busybox是静态编译的!
花了一天的时间,终于把ubuntu12.04 的linux内核版本从3.13.0升级到3.4.0 升级后,系统更加稳定.具体步骤:# wget http://www.kernel.org/pub/linux/kernel/v3.x/linux-3.4.tar.gz# tar zxvf linux-3.4.tar.gz -C /usr/src# cd /usr/src/linux-3.4# make menuconfig# make# make modules_install# cp arch/x86_64/boot/bzImage /boot/vmlinuz-3.4# make install 对比/boot/grub/grub.cfg文件的改动 Make Menuconfig简介 make menuconfig 图形化的内核配置make mrproper -----删除不必要的文件和目录. #make config(基于文本的最为传统的配置界面,不推荐使用) #make menuconfig(基于文本选单的配置界面,字符终端下推荐使用) #make xconfig(基于图形窗口模式的配置界面,Xwindow下推荐使用) #make oldconfig(如果只想在原来内核配置的基础上修改一些小地方,会省去不少麻烦) 目的都是生成一个.config文件,这三个命令中,make xconfig的界面最为友好,如果你可以使用Xwindow,你就用这个好了,这个比较方便,也好设置。如果你不能使用Xwindow,那么就使用make menuconfig好了。界面虽然比上面一个差点,总比make config的要好多了。 选择相应的配置时,有三种选择,它们分别代表的含义如下: Y--将该功能编译进内核 N--不将该功能编译进内核 M--将该功能编译成可以在需要时动态插入到内核中的模块 make modules_install指定ko安装路径 make modules_install INSTALL_MOD_PATH=/home/luther/gliethttp_dir应用安装指定 make install DESTDIR=/home/luther/gliethttp_dir 最近对linux内核进行了编译,并安装在了vmware上。linux内核:linu-2.6.29 操作系统:redhat 5 1.linux内核源码(如果没有,可以从www.kernel.ogr上下载) 2.将内核源码解压到工作目录(这个目录可以是任意的,但是路径不能带有空格) tar jxf linux-2.6.29.tar.bz2 3.进入内核源码,使用命令(make distclean、make clean、make mrproper三个命令中的一个即可) 4.拷贝参考配置文件为内核目录下.config cp /boot/config-2.6.18.53.e15 .config 5.配置内核 配置内核的方式有4种: make config make menuconfig make xconfig make oldconfig这四条命令的区别如下:make config是基于文本的最为传统的配置界面,即字符界面。比较适合在dos下使用。make menuconfig:基于文本选单的配置界面,比较适合在终端字符下使用。Make xconfig:基于图形窗口模式的配置界面,可以直接通过鼠标来选择。Make oldconfig:只是对一些新功能进行配置。 6.编译内核镜像:make bzImage(bzImage位于arch/x86/boot/目录下) 问题:make bzImage提示如下错误(这个错误不是每个人都会遇到的,可以用gcc--help查看你的gcc版本,如果你的版本是4.6或者4.6之后的应该会有下面的问题,如果比4.6的早就不会出现这个问题):gcc: 错误: elf_i386:没有那个文件或目录make[2]: *** [arch/x86/vdso/vdso32-int80.so.dbg] 错误 1make[1]: *** [arch/x86/vdso] 错误 2make: *** [arch/x86] 错误 2 解决方法:这个问题是由于 gcc 4.6 不再支持 linker-style 架构。将 arch/x86/vdso/Makefile 中,以 VDSO_LDFLAGS_vdso.lds 开头所在行的 "-m elf_x86_64" 替换为 "-m64"。以 VDSO_LDFLAGS_vdso32.lds 开头所在行的 "-m elf_x86" 替换为 "-m32"。 7.编译内核模块:make modules(源于启动的菜单型配置界面中选择<m>的项) 8.安装内核模块:make modules_install(代码会拷在/lib/modules/2.6.29目录下) 9制作initrd:mkinitrd initrd-2.6.29 2.6.29(如果是ubuntu,则使用mkinitramfs命令) 10.拷贝initrd和内核镜像到/boot cp linu-2.6.29/arch/x86/boot/bzImage /boot/vmlinuz-2.6.29 cp initrd-2.6.29 /boot 11.修改grub的配置文件/etc/grub.conf 后面的四行是参照上面的修改的。 12.测试新安装的内核是否能用,使用命令reboot重启系统,按住空格键不放,直到进入grub界面
在Linux操作系统中,有一项特殊的功能——初始化内存盘INITRD(INITial Ram Disk)技术,而且内核支持压缩的文件系统映像。有了这两项功能,我们可以让Linux系统从小的初始化内存盘启动,并把系统内存的一部分作为根文件系统挂载。 Ramdisk就是将内存的一部分分配为一个分区并作为硬盘来使用。对于系统运行时不断使用的程序,将它们放在Ramdisk中将加快计算机的操作,如大数据量的网络服务器、无盘工作站等。为了能够使用Ramdisk,我们在编译内核时须将block device中的Ramdisk支持选上,它下面还有两个选项,一个是设定Ramdisk的大小,默认是4096k;另一个是设定默认个数。如果要使用initrd,还得选上的支持。它既可以直接编译进内核,也可以编译成模块,在需要的时候加载。我们由于在启动时就用它,所以必须将它直接编译进内核。 下面是2.6内核对模块选择路径: Linux Kernel Configuration -> Device Drivers ->Block devices ->RAM block device support ->Default number of RAM disks (设定Ramdisk的个数,默认是16) ->Default RAM disk size (kbytes) (设定Ramdisk的大小,默认是4096k) Linux Kernel Configuration ->General setup ->Inital RAM filesystem and RAM disk(initramfs/initrd) support 如果对Ramdisk的支持已经编译进内核,我们就可以使用它了。首先在/mnt目录下创建目录ram,运行mkdir /mnt/ram;然后对/dev/ram0创建文件系统,运行mke2fs /dev/ram0;最后挂载上/dev/ram,运行mount /dev/ram0 /mnt/ram,就可以象对普通硬盘一样对它进行操作了。值得注意的是,在创建文件系统的时候,在屏幕上输出1024 inodes ,4096 blocks,即ramdisk大小为4M=4096个块,但是我们挂载上之后,用命令df –k /dev/ram查看时,显示出来ramdisk大小只有3963K,这是由于文件系统本身占用了一些空间。(这个空间是在编译核心时就由Default RAM disk size (kbytes)确定下来) 我们能根据需要改变ramdisk的大小。如我们要把默认的4M增大到8M,当ramdisk是直接编译进内核的情况下,可在grub配置文件 grub.conf中加入ramdisk=8192 ,运行grub后,重启计算机后,ramdisk大小变为8M。 例如要设置Ramdisk的大小为8M,在grub中可以用: # grub.conf - default=0 timeout=10 splashimage=(hd0,0)/grub/splash.xpm.gz title Redice Linux root (hd0,0) kernel /vmlinuz ro root=LABEL=/ hdc=ide-scsi ramdisk=8192 initrd /initrd 这样Ramdisk的大小就变成16M了。这个参数是Ramdisk直接编译到核心时才能使用的,如果Ramdisk编译为模块,则应该使用模块参数来设置Ramdisk的大小: a、在模块加载配置文件 /etc/modules.conf中加入一行: options rd rd_size=8192, b、在加载rd模块是在后面加上说明,即insmod rd rd_size=8192。 # insmod rd rd_size=8192 编译到核心时,可以通过下面的一些核心命令行参数来配置Ramdisk: ramdisk_size - ramdisk的大小(Kbytes); ramdisk - 与ramdisk_size的作用相同; ramdisk_blocksize - ramdisk的块大小,默认情况为1024; 当以模块的形式译时,模块支持以下几个加载参数: rd_size - 同上面的ramdisk_size或ramdisk参数; rd_blocksize - 同上面的ramdisk_blocksize; 或者在启动是作为启动行参数ramdisk=8192; 创建initrd ramdisk 映像 上面已经提到,Ramdisk需要先格式化然后才能使用。那么,如果核心希望使用ramdisk该如何做呢?于是initrd产生了,initrd全称是 initial RAM disk ,它提供一种让核心可以简单使用Ramdisk的能力,简单的说,这些能力包括: 格式化一个 Ramdisk; 加载文件系统内容到Ramdisk; 将Ramdisk作为根文件系统; 我们可以将initrd形像的比作Norton Ghost备份的硬盘分区,而Linux启动阶段的Ramdisk相当于一个未格式化的硬盘分区,核心可以直接将initrd的内容释放到一个未初始化的Ramdisk里,这个过程与Ghost恢复一个分区的过程十分相似。于是,相应的内容被加载到相应的Ramdisk中,同时,这个Ramdisk也被格式化成某种由initrd格式所表达的分区格式。 initrd与Ghost备份的分区有许多相似之处,例如,它有一定的大小,包含分区上的文件系统格式等。initrd支持的格式包括:Ext2文件系统、Romfs文件系统、cramfs文件系统、minix文件系统、如果核心选择了Gzip支持(通常这是默认的,在init/do_mounts_rd.c中定义的BUILD_CRAMDISK宏)还可以使用Gzip压缩的initrd。相关的代码可以在核心源码 drivers/block/rd.c:identify_ramdisk_image中找到。 制作initrd initrd 主要有两种格式:传统的ramdisk和cpio格式(这种格式的好处是内核原生不需要额外的文件系统支持) 制作initrd传统的作法是通过软盘(显然过时了,不介绍了)、ramdisk或loop设备(/dev/loop)。通过ramdisk来制作的方法比较简单(以ext2文件系统为例): 通过ramdisk # mkfs.ext2 /dev/ram0 # mount /dev/ram0 /mnt/rd # cp _what_you_like_ /mnt/rd # 把需要的文件复制过去 # dd if=/dev/ram0 of=/tmp/initrd # gzip -9 /tmp/initrd 这个过程也最能够解释initrd的本质,对于Linux来说,Ramdisk的一个块设备,而initrd是这个块设备上所有内容的“克隆”(由命令dd来完成)而生成的文件。核心中加载initrd相关的代码则用于完成将相反的过程,即将这一个文件恢复到Ramdisk中去。 通过loop设备来制作initrd的过程: dd if=/dev/zero of=/tmp/initrd bs=1024 count=4096 # 制作一个4M的空白文件 losetup /dev/loop0 /tmp/initrd # 映射到loop设备上; mkfs.ext2 /dev/loop0 # 创建文件系统; mount /dev/loop0 /mnt/rd cp _what_you_like_ /mnt/rd # 复制需要的文件; umount /mnt/rd losetup -d /dev/loop0 gzip -9 /tmp/initrd 通过cpio来制作initrd的过程: cd /path/to # 到需要复制的文件的目录 find . |cpio -o -H newc |gzip -c > ../initrd.gz 不过,现在已经有了一些更好的工具来完成这些工作,包括genromfs(uClinux里常用的工具),genext2fs,mkcramfs、mkinitrd等。这些工具提供了一些方便开发的新特性,例如,不需要上面烦索的过程,只要将文件复制到某个目录中,将其作为根目录,即可生成initrd;另一个重要的改进是,这些工具都可以以普通用户的身份来生成initrd。 我们现在做的initrd及initrd制作工具源码的svn在:http://dev.inlsd.org/svn/inlsd-initrd/trunk也可以用web方式通过trac来查看http://dev.inlsd.org/projects/inlsd-initrd
首先简单介绍一下CAN总线,关于CAN总线是谁发明的,CAN总线的历史,CAN总线的发展,CAN总线的应用场合,这些,通通不说。这里只是以我个人理解,简单说说CAN通信。CAN总线的端点没有地址(除非自己在帧里定义地址),CAN总线通信不用地址,用标识符,不是单独的谁发给谁,而是,你总是发送给整个网络。然后每个节点都有过滤器,对网络上的传播的帧的标识符进行过滤,自己想要什么样的帧,可以设置自己的过滤器,接收相关的帧信息。如果两个节点同时发送怎么办?这个不用我们担心,CAN控制器会自己仲裁,让高优先级的帧先发。 然后我们可以了解一下stm32的CAN控制器。 如上图所示,stm32有两个can控制器,can1(主),和can2(从),其中过滤器的设置是通过can1来设置,其他工作模式,波特率等,可以各自设置。每个控制器有三个发送邮箱,两个fifo,每个fifo有三个接收邮箱。 发送:选择一个空的发送邮箱,把帧信息写到该发送邮箱的寄存器里,请求发送,控制器就会根据标识符的优先级把帧先后发送出去。 接收:如果接收到的帧的标识符能过过滤表的一系列过滤,该帧信息就会保存在fifo接收邮箱的寄存器里。 过滤器:stm32f407共有28组过滤器,每组过滤器可以设置关联到fifo0或者fifo1,每组都包括两个32位存储器,可以配置成一个32位有位屏蔽功能的标识符过滤器,或者两个32位完全匹配的标识符过滤器,或者两个16位有位屏蔽功能的标识符过滤器,或者四个16位完全匹配的标识符过滤器。如下图所示: 我所说的完全匹配的意思是,接收到的帧的标识符每一位都要跟过滤器对应的位一样,才能过得了这个过滤器。有位屏蔽功能的意思是一个寄存器放标识符,一个放屏蔽掩码,屏蔽掩码为1的位对应的接收到的帧的标识符的位与对应的放标识符的寄存器的位一致,就能通过。 为了过滤出一组标识符,应该设置过滤器组工作在屏蔽位模式。 为了过滤出一个标识符,应该设置过滤器组工作在标识符列表模式。 应用程序不用的过滤器组,应该保持在禁用状态。 过滤器组中的每个过滤器,都被编号为(叫做过滤器号,图 32.1.11 中的 n)从 0 开始,到某最大数值-取决于过滤器组的模式和位宽的设置。 举个简单的例子,我们设置过滤器组 0 工作在:1 个 32 位过滤器-标识符屏蔽模式,然后置 CAN_F0R1=0XFFFF0000,CAN_F0R2=0XFF00FF00。其中存放到 CAN_F0R1 的值就是期收到的 ID,即我们希望收到的 ID(STID+EXTID+IDE+RTR)最好是:0XFFFF0000。而FF00FF00 就是设置我们需要必须关心的 ID,表示收到的 ID,其位[31:24]和位[15:8]这 16 个的必须和 CAN_F0R1 中对应的位一模一样,而另外的 16 个位则不关心,可以一样,也可以一样,都认为是正确的 ID,即收到的 ID必须是 0XFFxx00xx,才算是正确的(x 表示不关心)。 传输一位的时间和波特率的计算: CAN控制器的波特率是由APB时钟线和CAN位时序寄存器CAN_BTR的TS2[3:0]、TS1[2:0]和BRP[9:0]确定的,其中,TS1[2:0]定义了时间段1占用多少个时间单元,TS2[3:0]定义了时间段2占用多少个时间单元,BRP[9:0]定义对APB1时钟的分频。 PS:设置波特率为1M 其中Tpclk为APB1的时钟周期,假设为 Tpclk = 1/42M 0≦TS1≦7 0≦TS2≦15 0≦BRP≦1021 根据以上数据,有 (TS2+TS1+3)(BRP+1)=42 令BRP=2,有 TS2+TS1=11 令TS1=8,TS2=3 设置步骤: 1. 设置中断优先级分组(如果之前没有设置),这个最好一个程序里只在开头设置一次。 2. 使能相关GPIO时钟。 3. 选择相关GPIO引脚的复用功能。 4. 设置相关GPIO引脚为复用模式。 5. 设置相关GPIO引脚的速度,方式。 6. 设置主控制寄存器MCR,进入初始化模式 7. 等待进入初始化模式 8. 设置波特率。 9. 其他设置。 10. 如果要用到中断,在中断使能寄存器IER中使能相关中断响应。 11. 如果要用到中断,设置相关中断优先级(NVIC_IP)。 12. 如果要用到中断,使能相关中断(NVIC_ISER)。 13. 设置主控制寄存器MCR,进入正常工作模式。 14. 设置FMR,使过滤器组工作在初始化模式。 15. 设置FMR的CAN2SB,确定CAN2的过滤器组从哪一组开始。 16. 设置用到的过滤器组的工作方式。 17. 设置用到的过滤器组的位宽。 18. 给fifo0和fifo2划分(关联)过滤组。 19. 禁用用到的过滤器组。 20. 设置过滤器组的标识符,帧类型等。 21. 使能相关过滤器组。 22. 设置FMR,使过滤器组工作在正常模式。 23. 如果要用中断,编写中断服务函数(函数名是固定的)。 24. 中断服务函数里检查是哪个中断。 25. 编写相应服务程序。 电路请参见本博客:小工具之——CAN收发器 程序: [plain] view plaincopy /************************************ 标题:操作CAN的练习 软件平台:IAR for ARM6.21 硬件平台:stm32f4-discovery 主频:168M 描述:通过硬件收发器连接CAN1,CAN2 组成一个两个端点的网络 CAN1循环发出数据帧 CAN2接收过滤数据帧 用uart把CAN2接收到 的数据帧发到超级终端 author:小船 data:2012-08-14 *************************************/ #include <stm32f4xx.h> #include "MyDebugger.h" #define RECEIVE_BUFFER_SIZE 20 u32 CAN2_receive_buffer[RECEIVE_BUFFER_SIZE][4]; u8 UART_send_buffer[1800]; u8 Consumer = 0; u8 Producer = 0; u32 Gb_TimingDelay; void Delay(uint32_t nTime); void TIM7_init();//定时1s u32 get_rece_data(); void CAN_GPIO_config(); void main () { u32 empty_box; SysTick_Config(SystemCoreClock / 1000); //设置systemtick一毫秒中断 SCB->AIRCR = 0x05FA0000 | 0x400; //中断优先级分组 抢占:响应=3:1 MyDebugger_Init(); TIM7_init(); MyDebugger_Message( "\n\rtesting......\n\r" , sizeof("\n\rtesting......\n\r")/sizeof(char) ); CAN_GPIO_config(); RCC->APB1ENR |= ((1<<25)|(1<<26));//使能CAN1、CAN2时钟 CAN1->MCR = 0x00000000; /* 请求进入初始化模式 禁止报文自动重传 自动唤醒模式 */ CAN1->MCR |= ((1<<0)|(1<<4)|(1<<5)); CAN1->MCR &= ~(1<<16);//在调试时,CAN照常工作 while(!(CAN1->MSR & 0xfffffffe)) //等待进入初始化模式 { MyDebugger_LEDs(orange, on); } MyDebugger_LEDs(orange, off); /* 正常模式 重新同步跳跃宽度(1+1)tq TS2[2:0]=3 TS1[3:0]=8 BRP[9:0]=2 ps: tq = (BRP[9:0] + 1) x tPCLK, tBS2 = tq x (TS2[2:0] + 1), tBS1 = tq x (TS1[3:0] + 1), NominalBitTime = 1 × tq+tBS1+tBS2, BaudRate = 1 / NominalBitTime 波特率设为1M */ CAN1->BTR = ((0<<30)|(0x01<<24)|(3<<20)|(8<<16)|(2<<0)); CAN1->MCR &= ~(0x00000001);//正常工作模式 CAN2->MCR = 0x00000000; /* 请求进入初始化模式 禁止报文自动重传 自动唤醒模式 */ CAN2->MCR |= ((1<<0)|(1<<4)|(1<<5)); CAN2->MCR &= ~(1<<16);//在调试时,CAN照常工作 while(!(CAN2->MSR & 0xfffffffe)) //等待进入初始化模式 { MyDebugger_LEDs(orange, on); } MyDebugger_LEDs(orange, off); /* 正常模式 重新同步跳跃宽度(1+1)tq TS2[2:0]=3 TS1[3:0]=8 BRP[9:0]=2 ps: tq = (BRP[9:0] + 1) x tPCLK, tBS2 = tq x (TS2[2:0] + 1), tBS1 = tq x (TS1[3:0] + 1), NominalBitTime = 1 × tq+tBS1+tBS2, BaudRate = 1 / NominalBitTime 波特率设为1M */ CAN2->BTR = ((0<<30)|(0x01<<24)|(3<<20)|(8<<16)|(2<<0)); CAN2->IER &= 0x00000000; /* FIFO1消息挂号中断使能 FIFO1满中断使能 FIFO1溢出中断使能 */ CAN2->IER |= ((1<<4)|(1<<5)|(1<<6)); NVIC->IP[65] = 0xa0; //抢占优先级101,响应优先级0 NVIC->ISER[2] |= (1<<1); //使能中断线65,也就是can2_rx1中断 CAN2->MCR &= ~(0x00000001);//正常工作模式 //总共有28组过滤器 CAN1->FMR |= 1; //过滤器组工作在初始化模式 CAN1->FMR &= 0xffffc0ff;//CAN2的过滤器组从14开始 CAN1->FMR |= (14<<8); CAN1->FM1R |= (1<<14);//过滤器组14的寄存器工作在标识符列表模式 //位宽为16位,2个32位分为四个16位寄存器,过滤四个标识符 //CAN1->FS1R |= (1<<15);//过滤器组15为单个32位寄存器,用于扩展标识符 CAN1->FFA1R = 0x0fffc000;//0~13号过滤器组关联到fifo0,14~27号过滤器组关联到fifo1 CAN1->FA1R &= ~(1<<14);//禁用过滤器组14 /* 过滤器组0寄存器分为4个十六位过滤器: 标识符列表: 过滤器编号 匹配标准标识符 RTR IDE EXID[17:15] 0 0x7cb(111 1100 1011b) 数据帧 标准标识符 000b 1 0x4ab(100 1010 1011b) 数据帧 标准标识符 000b 2 0x7ab(111 1010 1011b) 数据帧 标准标识符 000b 3 0x40b(100 0000 1011b) 数据帧 标准标识符 000b */ CAN1->sFilterRegister[14].FR1 &= 0x00000000; CAN1->sFilterRegister[14].FR2 &= 0x00000000; CAN1->sFilterRegister[14].FR1 |= ((0x7cb<<5)|(0<<4)|(0<<3)); CAN1->sFilterRegister[14].FR1 |= ((0x4ab<<21)|(0<<20)|(0<<19)); CAN1->sFilterRegister[14].FR2 |= ((0x7ab<<5)|(0<<4)|(0<<3)); CAN1->sFilterRegister[14].FR2 |= ((0x40b<<21)|(0<<20)|(0<<19)); CAN1->FA1R |= (1<<14);//使能过滤器组14 CAN1->FMR &= ~1; //过滤器组正常工作 while(1) { /* 选择空的发送邮箱: 标准标识符0x7ab(111 1010 1011b) 数据帧 不使用扩展标识符 */ if( CAN1->TSR & ((1<<26)|(1<<27)|(1<<28)) ) { empty_box = ((CAN1->TSR>>24) & 0x00000003); CAN1->sTxMailBox[empty_box].TIR = (0x7ab<<21); CAN1->sTxMailBox[empty_box].TDTR &= 0xfffffff0; CAN1->sTxMailBox[empty_box].TDTR |= 0x00000008;//发送数据长度为8 CAN1->sTxMailBox[empty_box].TDLR = 0x12345678; CAN1->sTxMailBox[empty_box].TDHR = 0x9abcdef0; CAN1->sTxMailBox[empty_box].TIR |= (1<<0);//请求发送 } else { MyDebugger_LEDs(orange, on); } Delay(100); /* 选择空的发送邮箱: 标准标识符0x4ab(100 1010 1011b) 数据帧 不使用扩展标识符 */ if( CAN1->TSR & ((1<<26)|(1<<27)|(1<<28)) ) { empty_box = ((CAN1->TSR>>24) & 0x00000003); CAN1->sTxMailBox[empty_box].TIR = (0x4ab<<21); CAN1->sTxMailBox[empty_box].TDTR &= 0xfffffff0; CAN1->sTxMailBox[empty_box].TDTR |= 0x00000008;//发送数据长度为8 CAN1->sTxMailBox[empty_box].TDLR = 0x56781234; CAN1->sTxMailBox[empty_box].TDHR = 0x9abcdef0; CAN1->sTxMailBox[empty_box].TIR |= (1<<0);//请求发送 } else { MyDebugger_LEDs(orange, on); } Delay(100); /* 选择空的发送邮箱: 标准标识符0x7cb(100 1010 1011b) 数据帧 不使用扩展标识符 */ if( CAN1->TSR & ((1<<26)|(1<<27)|(1<<28)) ) { empty_box = ((CAN1->TSR>>24) & 0x00000003); CAN1->sTxMailBox[empty_box].TIR = (0x7cb<<21); CAN1->sTxMailBox[empty_box].TDTR &= 0xfffffff0; CAN1->sTxMailBox[empty_box].TDTR |= 0x00000006;//发送数据长度为6 CAN1->sTxMailBox[empty_box].TDLR = 0x56781234; CAN1->sTxMailBox[empty_box].TDHR = 0x00009abc; CAN1->sTxMailBox[empty_box].TIR |= (1<<0);//请求发送 } else { MyDebugger_LEDs(orange, on); } Delay(100); /* 选择空的发送邮箱: 标准标识符0x40b(100 0000 1011b) 数据帧 不使用扩展标识符 */ if( CAN1->TSR & ((1<<26)|(1<<27)|(1<<28)) ) { empty_box = ((CAN1->TSR>>24) & 0x00000003); CAN1->sTxMailBox[empty_box].TIR = (0x40b<<21); CAN1->sTxMailBox[empty_box].TDTR &= 0xfffffff0; CAN1->sTxMailBox[empty_box].TDTR |= 0x00000004;//发送数据长度为4 CAN1->sTxMailBox[empty_box].TDLR = 0x56781234; CAN1->sTxMailBox[empty_box].TDHR = 0x00000000; CAN1->sTxMailBox[empty_box].TIR |= (1<<0);//请求发送 } else { MyDebugger_LEDs(orange, on); } Delay(100); } } /**************************************** 函数名:CAN_GPIO_config 参数:无 返回值:无 功能:设置CAN1,2控制器用到IO口 CAN1_TX---------PD1 CAN1_RX---------PB8 CAN2_TX---------PB13 CAN2_RX---------PB5 ****************************************/ void CAN_GPIO_config() { RCC->AHB1ENR |= ((1<<1) | (1<<3));//使能GPIOB、D时钟 GPIOB->AFR[0] |= 0x00900000; //AF9 GPIOB->AFR[1] |= 0x00900009; GPIOD->AFR[0] |= 0x00000090; GPIOB->MODER &= 0xF3FCF3FF; //第二功能 GPIOB->MODER |= 0x08020800; GPIOD->MODER &= 0xFFFFFFF3; GPIOD->MODER |= 0x00000008; GPIOB->OSPEEDR &= 0xF3FCF3FF; //50M GPIOB->OSPEEDR |= 0x08020800; GPIOD->OSPEEDR &= 0xFFFFFFF3; GPIOD->OSPEEDR |= 0x00000008; GPIOB->PUPDR &= 0xF3FCF3FF; //上拉 GPIOB->PUPDR |= 0x04010400; GPIOD->PUPDR &= 0xFFFFFFF3; GPIOD->PUPDR |= 0x00000004; } /**************************************** 函数名:CAN2_RX1_IRQHandler 参数:无 返回值:无 功能:CAN2fifo1接收中断处理 把信息存进循环队列 ****************************************/ void CAN2_RX1_IRQHandler() { if(CAN2->RF1R & (0x00000003))//接收到新的消息,fifo1非空 { Producer++; if(Producer == RECEIVE_BUFFER_SIZE)Producer = 0; if(Producer != Consumer) { CAN2_receive_buffer[Producer][0] = CAN2->sFIFOMailBox[1].RIR; CAN2_receive_buffer[Producer][1] = CAN2->sFIFOMailBox[1].RDTR; CAN2_receive_buffer[Producer][2] = CAN2->sFIFOMailBox[1].RDLR; CAN2_receive_buffer[Producer][3] = CAN2->sFIFOMailBox[1].RDHR; } else { if(Producer == 0)Producer = RECEIVE_BUFFER_SIZE; Producer--; MyDebugger_LEDs(blue, on); } CAN2->RF1R |= (1<<5);//释放邮箱 } if(CAN2->RF1R & (1<<3))//fifo0满 { MyDebugger_LEDs(red, on); CAN2->RF1R &= ~(1<<3); } if(CAN2->RF1R & (1<<4))//fifo0溢出 { MyDebugger_LEDs(red, on); CAN2->RF1R &= ~(1<<4); } } /**************************************** 函数名:TIM7_init 参数:无 返回值:无 功能:初始化定时器7 作1s定时用 ****************************************/ void TIM7_init() { RCC->APB1ENR |= (1<<5); //打开TIM7时钟 TIM7->PSC = 8399; //对时钟84M进行8400分频,使得计数频率为10k TIM7->ARR = 10000; //定时一秒 TIM7->CNT = 0; //清空计数器 TIM7->CR1 |= (1<<7); //自动重装载预装载使能 TIM7->DIER |= 1; //使能中断 NVIC->IP[55] = 0xe0; NVIC->ISER[1] |= (1<<(55-32)); TIM7->CR1 |= 1; //开始计时 } /**************************************** 函数名:TIM7_IRQHandler 参数:无 返回值:无 功能:定时器7中断处理 1s定时到 把can2收到的信息转换格式 用usrt发送到超级终端显示 ****************************************/ void TIM7_IRQHandler(void) { u32 length; if(TIM7->SR) { length = get_rece_data(); MyDebugger_Message( UART_send_buffer, length ); TIM7->SR &= ~(0x0001); } } /**************************************** 函数名:get_rece_data 参数:无 返回值:length 整理后要发送数据的长度 功能:把循环队列的信息取出 进行格式转换 把信息存到uart发送缓冲区 ****************************************/ u32 get_rece_data() { u8 filter_No; u8 Data_length; char i; u32 length = 0; const char ascii[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; while(1) { if(Producer != Consumer) { Consumer++; if(Consumer == RECEIVE_BUFFER_SIZE)Consumer=0; UART_send_buffer[length++] = '\n'; UART_send_buffer[length++] = '\r'; //Filter No.xx UART_send_buffer[length++] = 'F'; UART_send_buffer[length++] = 'i'; UART_send_buffer[length++] = 'l'; UART_send_buffer[length++] = 't'; UART_send_buffer[length++] = 'e'; UART_send_buffer[length++] = 'r'; UART_send_buffer[length++] = ' '; UART_send_buffer[length++] = 'N'; UART_send_buffer[length++] = 'o'; UART_send_buffer[length++] = '.'; filter_No = (CAN2_receive_buffer[Consumer][1]>>8) & 0x000000ff; UART_send_buffer[length++] = filter_No%100/10 + '0'; UART_send_buffer[length++] = filter_No%10 + '0'; UART_send_buffer[length++] = '\n'; UART_send_buffer[length++] = '\r'; //DataLength:x UART_send_buffer[length++] = 'D'; UART_send_buffer[length++] = 'a'; UART_send_buffer[length++] = 't'; UART_send_buffer[length++] = 'a'; UART_send_buffer[length++] = 'L'; UART_send_buffer[length++] = 'e'; UART_send_buffer[length++] = 'n'; UART_send_buffer[length++] = 'g'; UART_send_buffer[length++] = 't'; UART_send_buffer[length++] = 'h'; UART_send_buffer[length++] = ':'; Data_length = CAN2_receive_buffer[Consumer][1] & 0x0000000f; UART_send_buffer[length++] = Data_length % 10 + '0'; UART_send_buffer[length++] = '\n'; UART_send_buffer[length++] = '\r'; if(CAN2_receive_buffer[Consumer][0] & (1<<1)) { UART_send_buffer[length++] = 'R'; UART_send_buffer[length++] = 'e'; UART_send_buffer[length++] = 'm'; UART_send_buffer[length++] = 'o'; UART_send_buffer[length++] = 't'; UART_send_buffer[length++] = 'e'; UART_send_buffer[length++] = 'F'; UART_send_buffer[length++] = 'r'; UART_send_buffer[length++] = 'a'; UART_send_buffer[length++] = 'm'; UART_send_buffer[length++] = 'e'; } else { UART_send_buffer[length++] = 'D'; UART_send_buffer[length++] = 'a'; UART_send_buffer[length++] = 't'; UART_send_buffer[length++] = 'a'; UART_send_buffer[length++] = 'F'; UART_send_buffer[length++] = 'r'; UART_send_buffer[length++] = 'a'; UART_send_buffer[length++] = 'm'; UART_send_buffer[length++] = 'e'; } UART_send_buffer[length++] = '\n'; UART_send_buffer[length++] = '\r'; if(CAN2_receive_buffer[Consumer][0] & (1<<2)) { UART_send_buffer[length++] = 'e'; UART_send_buffer[length++] = 'x'; UART_send_buffer[length++] = 't'; UART_send_buffer[length++] = ' '; UART_send_buffer[length++] = 'I'; UART_send_buffer[length++] = 'D'; UART_send_buffer[length++] = ':'; UART_send_buffer[length++] = ascii[CAN2_receive_buffer[Consumer][0] >> 31]; UART_send_buffer[length++] = ascii[(CAN2_receive_buffer[Consumer][0] >> 27)& 0x0000000f]; UART_send_buffer[length++] = ascii[(CAN2_receive_buffer[Consumer][0] >> 23)& 0x0000000f]; UART_send_buffer[length++] = ascii[(CAN2_receive_buffer[Consumer][0] >> 19)& 0x0000000f]; UART_send_buffer[length++] = ascii[(CAN2_receive_buffer[Consumer][0] >> 15)& 0x0000000f]; UART_send_buffer[length++] = ascii[(CAN2_receive_buffer[Consumer][0] >> 11)& 0x0000000f]; UART_send_buffer[length++] = ascii[(CAN2_receive_buffer[Consumer][0] >> 7)& 0x0000000f]; UART_send_buffer[length++] = ascii[(CAN2_receive_buffer[Consumer][0] >> 3)& 0x0000000f]; } else { UART_send_buffer[length++] = 's'; UART_send_buffer[length++] = 't'; UART_send_buffer[length++] = 'd'; UART_send_buffer[length++] = ' '; UART_send_buffer[length++] = 'I'; UART_send_buffer[length++] = 'D'; UART_send_buffer[length++] = ':'; UART_send_buffer[length++] = ascii[CAN2_receive_buffer[Consumer][0] >> 29]; UART_send_buffer[length++] = ascii[(CAN2_receive_buffer[Consumer][0] >> 25)& 0x0000000f]; UART_send_buffer[length++] = ascii[(CAN2_receive_buffer[Consumer][0] >> 21)& 0x0000000f]; } UART_send_buffer[length++] = '\n'; UART_send_buffer[length++] = '\r'; UART_send_buffer[length++] = 'D'; UART_send_buffer[length++] = 'a'; UART_send_buffer[length++] = 't'; UART_send_buffer[length++] = 'a'; UART_send_buffer[length++] = ':'; if(Data_length > 4) { for(i = 2*Data_length - 8; i > 0; i--) UART_send_buffer[length++] = ascii[(CAN2_receive_buffer[Consumer][3] >> ((i-1)*4))& 0x0000000f]; for(i = 8; i > 0; i--) UART_send_buffer[length++] = ascii[(CAN2_receive_buffer[Consumer][2] >> ((i-1)*4))& 0x0000000f]; } else { for(i = 2*Data_length; i > 0; i--) UART_send_buffer[length++] = ascii[(CAN2_receive_buffer[Consumer][2] >> ((i-1)*4))& 0x0000000f]; } UART_send_buffer[length++] = '\n'; UART_send_buffer[length++] = '\r'; } else break; } return length; } void Delay(uint32_t nTime) { Gb_TimingDelay = nTime; while(Gb_TimingDelay != 0); } void SysTick_Handler(void) { if (Gb_TimingDelay != 0x00) { Gb_TimingDelay--; } } 运行结果:
nt send( SOCKET s, const char FAR *buf, int len, int flags ); 不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。 该函数的第一个参数指定发送端套接字描述符; 第二个参数指明一个存放应用程序要发送数据的缓冲区; 第三个参数指明实际要发送的数据的字节数; 第四个参数一般置0。 这里只描述同步Socket的send函数的执行流程。当调用该函数时, (1)send先比较待发送数据的长度len和套接字s的发送缓冲的长度, 如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR; (2)如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议 还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len (3)如果len大于剩余空间大小,send就一直等待协议把s的发送缓冲中的数据发送完 (4)如果len小于剩余 空间大小,send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。 如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。 要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如 果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执 行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回 SOCKET_ERROR) 注意:在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。 通过测试发现,异步socket的send函数在网络刚刚断开时还能发送返回相应的字节数,同时使用select检测也是可写的,但是过几秒钟之后,再send就会出错了,返回-1。select也不能检测出可写了。 2. recv函数 int recv( SOCKET s, char FAR *buf, int len, int flags); 不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。该函数的第一个参数指定接收端套接字描述符; 第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据; 第三个参数指明buf的长度; 第四个参数一般置0。 这里只描述同步Socket的recv函数的执行流程。当应用程序调用recv函数时, (1)recv先等待s的发送缓冲中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR, (2)如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数 据,那么recv就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以 在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的), recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。 注意:在Unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。 阻塞就是干不完不准回来, 非组赛就是你先干,我现看看有其他事没有,完了告诉我一声 我们拿最常用的send和recv两个函数来说吧... 比如你调用send函数发送一定的Byte,在系统内部send做的工作其实只是把数据传输(Copy)到TCP/IP协议栈的输出缓冲区,它执行成功并不代表数据已经成功的发送出去了,如果TCP/IP协议栈没有足够的可用缓冲区来保存你Copy过来的数据的话...这时候就体现出阻塞和非阻塞的不同之处了:对于阻塞模式的socket send函数将不返回直到系统缓冲区有足够的空间把你要发送的数据Copy过去以后才返回,而对于非阻塞的socket来说send会立即返回WSAEWOULDDBLOCK告诉调用者说:"发送操作被阻塞了!!!你想办法处理吧..." 对于recv函数,同样道理,该函数的内部工作机制其实是在等待TCP/IP协议栈的接收缓冲区通知它说:嗨,你的数据来了.对于阻塞模式的socket来说如果TCP/IP协议栈的接收缓冲区没有通知一个结果给它它就一直不返回:耗费着系统资源....对于非阻塞模式的socket该函数会马上返回,然后告诉你:WSAEWOULDDBLOCK---"现在没有数据,回头在来看看" 读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取:while(rs){buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);if(buflen < 0){ // 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读 // 在这里就当作是该次事件已处理处. if(errno == EAGAIN) break; else return; } else if(buflen == 0) { // 这里表示对端的socket已正常关闭. } if(buflen == sizeof(buf) rs = 1; // 需要再次读取 else rs = 0;}还有,假如发送端流量大于接收端的流量(意思是epoll所在的程序读比转发的socket要快),由于是非阻塞的socket,那么send()函数虽然返回,但实际缓冲区的数据并未真正发给接收端,这样不断的读和发,当缓冲区满后会产生EAGAIN错误(参考man send),同时,不理会这次请求发送的数据.所以,需要封装socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,返回-1表示出错。在socket_send()内部,当写缓冲已满(send()返回-1,且errno为EAGAIN),那么会等待后再重试.这种方式并不很完美,在理论上可能会长时间的阻塞在socket_send()内部,但暂没有更好的办法.ssize_t socket_send(int sockfd, const char* buffer, size_t buflen){ssize_t tmp;size_t total = buflen;const char *p = buffer;while(1){ tmp = send(sockfd, p, total, 0); if(tmp < 0) { // 当send收到信号时,可以继续写,但这里返回-1. if(errno == EINTR) return -1; // 当socket是非阻塞时,如返回此错误,表示写缓冲队列已满, // 在这里做延时后再重试. if(errno == EAGAIN) { usleep(1000); continue; } return -1; } if((size_t)tmp == total) return buflen; total -= tmp; p += tmp;}return tmp;}
每次开机都自动检查磁盘,检测通过后下次还是一样,NTFS/FAT32分区都有可能有这样的情况,即使重装系统,仍可能出现同样情况,但是硬盘可以通过Dell 随机带的检测程序解决方法:在命令行窗口中输入CHKDSK /F X: (X:是每次开机都自动检查的磁盘分区)CHKDSK /F 可以恢复文件系统错误, 并可以试图恢复坏扇区,如果无法修复就标记坏扇区,以避免文件被误写入导致丢失如CHKDSK /F提示已修复或无问题,开机仍然自动检查磁盘的情况:输入 CHKNTFS /X E: (每次启动时不自动检查E: )如有多个卷的话这样输入 CHKNTFS /X D: E: F: (对FAT32格式分区同样有效)最近有好多同事问开机取消磁盘检查方法,而且都强调是正常关机,但是每次开机时会自动询问是否扫描磁盘。遇到这种问题,可以按照以下步骤进行处理:(1) 运行Fsutil dirty query DriveLetter命令,检查该磁盘是否设置了Dirty Bit。如果是的话,可能是硬盘本身的问题,请联系硬盘厂商或者计算机经销商进行检测。如果需要防止系统自动检测标记Dirty Bit的卷,可以运行以下命令进行排除:chkntfs /x DriveLetter(2) 检查任务计划、启动项里有没有相应的加载项,有的话删除即可。(3) 打开注册表编辑器,进入以下注册表项:HKEY_LOCAL_MACHINE\SYSTEM\CURRENTCONTROLSET\CONTROL\Session Manager检查其下的多字符串键值BootExecute,是否为类似以下的数值数据:autocheck autochk /r \??\D:如果是的话,删除其中/r \??\D:即可。如何取消开机磁盘检测 如试一下: 1.选择“开始→运行”,在运行对话框中键入“chkntfs /t:0”,即可将磁盘扫描等待时间设置为0;如果要在计算机启动时忽略扫描某个分区,比如C盘,可以输入“chkntfs /x c:”命令;如果要恢复对C盘的扫描,可使用“chkntfs /d c:”命令,即可还原所有chkntfs默认设置,除了自动文件检查的倒计时之外。 2、单击“开始→运行”,在“运行”对话框中输入“regedit”打开注册表编辑器,依次选择“HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager”子键,然后在右侧窗口中找到“BootExecute”键值项并将其数值清空,最后按“F5”键刷新注册表即可。(此法可取消开机时系统对所有磁盘的扫描) 注:C:\\>chkntfs/? 在启动时显示或修改磁盘检查。 CHKNTFS volume [...] CHKNTFS /D CHKNTFS /T[:time]\\r\\nCHKNTFS /X volume [...] CHKNTFS /C volume [...] volume: 指定驱动器(后面跟一个冒号)、装入点或卷名。 /D 将计算机恢复成默认状态, 启动时检查所有驱动器,并对有问题的驱动器执行 chkdsk 命令。 /T:time 将 AUTOCHK 初始递减计数时间改成指定的时间量,单位为秒数。如果没有指定时间,则显示当前设置。 /X 排除启动时不作检查的驱动器。上次执行此命令排除的驱动器此时无效。 /C 安排启动时检查驱动器,如果驱动器有问题,运行 chkdsk。 如果没有指定命令选项,CHKNTFS 会显示每一驱动器有问题的位的状态。 从上面可以看出,输入:chkntfs /x c: 可以实现非法关机不扫描C盘。
经过前几讲,主要目的就是准备一些“原材料”,熟悉一些“命令”,实际上是“战前演练准备”。下面要进入“实战状态”,成败在此一举。 一、通过前面的准备,主要准备了以下材料1.一张桌面背景图片(1024X768的BMP格式图片)2.Programs文件夹(含有BsExplorer以及需要集成的程序)3.BsExplorer中的bs_desktop.ini、bs_start.ini已经配置完毕。4.winpeshl.ini文件已经制作完毕5.掌握了“命令”的含义。 二、接下来只需简单几步,即可大功告成。 1.设置 Windows PE 构建环境 单击“开始-程序-Microsoft Windows AIK”以管理员身份运行“管理工具命令提示” 2.设置 Windows PE 构建环境 输入 copype.cmd x86 E:\mywinpe 结果如下: 3.复制并转移文件 copy E:\mywinpe\winpe.wim E:\mywinpe\ISO\sources\boot.wim 4.查看E:\mywinpe\iso\sources\下boot.wim的信息 dism /get-wiminfo /wimfile:E:\mywinpe\iso\sources\boot.wim 5..装载映像 Dism /Mount-Wim /Wimfile:E:\mywinpe\ISO\sources\boot.wim /index:1 /MountDir:E:\mywinpe\mount 6.自定义设置 将之前准备好的Program文件夹复制到E:\mywinpe\mount目录下 将winpeshl.ini、桌面背景图片,复制到E:\mywinpe\mount\windows\system32目录下7.设置WinPE的暂存空间为512MB(一般为128、256、512,根据你添加的程序等决定大小,太小会影响启动速度) dism /image:E:\mywinpe\mount /Set-ScratchSpace:512 8.提交并卸载映像(应该先把打开的文件夹关闭,以免卸载错误) Dism /unmount-Wim /MountDir:E:\mywinpe\mount /Commit 9.封装成ISO镜像文件pe.iso,保存到E:\mywinpe Oscdimg -n -m -o -bE:\mywinpe\etfsboot.com E:\mywinpe\iso E:\mywinpe\pe.iso ----------至此大功告成! 注意: 1.注意在32位Windows7下只能制作32位的PE,在64位下可以制作32位或64位PE。 2.如果操作失误,请重启电脑。删除mywinpe文件夹,然后单击“开始-程序-Microsoft Windows AIK”以管理员身份运行“管理工具命令提示”输入 dism /cleanup-wim来清空日志,然后再次重复以上操作。
1.在现有的Windows7条件下,自动在E盘建立mywinpe文件夹,设置 Windows PE 构建环境,并保存到E:\mywinpe下 copype.cmd x86 E:\mywinpe 2.将E:\mywinpe下的winpe.wim复制到E:\mywinpe\ISO\sources下,并命名为boot.wim copy E:\mywinpe\winpe.wim E:\mywinpe\ISO\sources\boot.wim 3.查看E:\mywinpe\iso\sources\下boot.wim的信息 dism /get-wiminfo /wimfile:E:\mywinpe\iso\sources\boot.wim 4.装载映像(类似于解压,解压到E:\mywinpe\mount目录下)。根据上面查到的信息(我的是:索引1,所以下面这条命令中用/index:1) Dism /Mount-Wim /Wimfile:E:\mywinpe\ISO\sources\boot.wim /index:1 /MountDir:E:\mywinpe\mount 5.设置WinPE的暂存空间为256MB(一般为128、256、512,根据你添加的程序等决定大小,太小会影响启动速度) dism /image:E:\mywinpe\mount /Set-ScratchSpace:256 6.提交并卸载映像(类似于压缩) Dism /unmount-Wim /MountDir:E:\mywinpe\mount /Commit 7.封装成ISO镜像文件pe.iso,保存到E:\mywinpe Oscdimg -n -m -o -bE:\mywinpe\etfsboot.com E:\mywinpe\iso E:\mywinpe\pe.iso 8.失败后,清理过时的文件日志 dism /cleanup-wim 、启动挂接及其它设置 1、winpeshl.ini 手动创建一个winpeshl.ini文件,内容为: [LaunchApp]AppPath=%systemdrive%\Tools\BsExplorer\Explorer.exe 然后拷贝到mount\windows\system32下面即可,制作好的WINPE会自动到这个目录下找winpeshl.ini文件,并运行Explorer.exe。 2、修改WINPE桌面 如需修改桌面图片,只需要创建一个1024*768的bmp文件并命名为winpe.bmp然后也拷贝到mount\windows\system32下面并覆盖原图片即可。设置语句如下:
Windows PE的全名是WindowsPreinstallationEnvironment(WinPE)直接从字面上翻译就 是“Windows预安装环境”。微软的本意是:WinPE仅用做系统维护,并设置了各种限制。可以简单的理解为:PE是Windows系统的超级精简版、超级权限版(以系统system账户登录)!对于无法进入系统、修复系统、分区、重装系统等问题都可以进入PE进行操作,因此PE是系统维护强大的武器! 微软原版的PE,只有“命令行”即DOS窗口。网上流传的各种版本都是“高手们”修改出来的,甚至有的PE可以作为系统来使用,这都偏离了微软的本意。我不主张“肆意扩展”PE的功能来彰显“技术”,但是“可视化操作界面、常用功能的集成”这些都是必要且必须的,我会在以后的文章中逐一解说。 通常PE启动有两种方式:1.加载到内存 2.直接在某个介质(例如光盘)启动。两种方式各有利弊:加载到内存就会对内存大小要求高;直接在某种介质启动速度就会减慢。但是就如今电脑配置飞速发展的趋势来看,内存大小已经不是限制因素,因此多数是以“加载到内存”的方式启动。 下面简要介绍Windows PE的命名规则: Windows PE 1.x表示Windows XP内核。(x表示系统版本,例如SP1) Windows PE 1.5表示Windows 2003内核。(x表示系统版本,例如SP1) Windows PE 2.x表示Windows Vista内核。(x表示系统版本,例如SP1) Windows PE 3.x表示Windows 7内核。(x表示系统版本,例如SP1) Windows PE 4.x表示Windows 8内核。(x表示系统版本,例如SP1) 举例:PE 1.3表示Windows XP SP3内核的PE。 Windows PE 1.x(1.5类似)引导过程(以网上常见的PE为例): 第1步.内核启动:光盘启动后,自动寻找光盘WXPE目录下的SETUPLDR.BIN,并加载它(始引导文件,相当于NT系统的NTLDR),需要同目录下的NTDETECT.COM(同NT系统的同名文件);引导文件中指示了WINNT.SIF(WINNT.XPE)的位置(相当于XP系统BOOT.INI)。 --------------------------------------------------------------------- WINNT.XPE内容如下: [SetupData] BootDevice = "ramdisk(0)" BootPath = "\WXPE\System32\" OsLoadOptions = "/minint /fastdetect /rdpath=MiniPE\winpe.IM_" 以上内容表示: 1.以“ramdisk方式”加载光盘镜像文件; 2.启动后的系统路径为\WXPE\System32,这里相当于我们平常的\Windows\System32; 3.该配置文件指示了系统镜像(IS_、ISO、IM_或IMA,也就是常说的“内核”)的位置。 ---------------------------------------------------------------------- 第2步.加载外置程序:外置程序的加载依赖于PECMD.INI(有的存在于WINPE.IS_ 的WXPE\SYSTEM32\目录下;有的在Programs文件夹的某个目录下...),其中形如"LOAD \MiniPE\WinPE.INI"的语句即为加载外置程序的配置文件(WinPE.INI)。这个文件可以在任何可见分区(PE下可见的分区),PECMD在执行时自动搜索所有“可见分区\MiniPE\下的WinPE.INI”,再根据WinPE.INI实现加载外部程序。(例如外置程序在PE.WIM包里面,或者直接存在某个文件夹下,说明:以上目录各个版本PE名称略有不同)。 简单理解:光盘引导文件(例如pe.bif)——SETUPLDR.BIN——WINNT.XPE——启动PE1.x。 -------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------- Windows PE 3.x(2.x类似)引导过程(详见Waik说明文件): 简明过程:光盘启动后自动加载引导文件,将控制权交给Bootmgr,Bootmgr读取Boot\BCD,根据BCD文件的信息读取Sources\Boot.wim文件,进入PE。 详细过程(微软Waik说明文档): 1.加载特定媒体上的启动扇区(MBR)。将控制传递给Bootmgr。Bootmgr从启动配置数据(BCD)中提取基本的启动信息,并将控制权传递给Boot.wim中包含的Winload.exe文件。然后Winload.exe加载相应的硬件抽象层(HAL),并加载系统注册表配置单元和必需的启动驱动程序。完成加载后,将会准备执行内核Ntoskrnl.exe的环境。 2.执行Ntoskrnl.exe,完成环境设置。将控制权传递给会话管理器(SMSS)。 3.SMSS加载注册表的剩余部分,配置运行Win32子系统(Win32k.sys)的环境及其各种进程。SMSS加载用于创建用户会话的Winlogon进程,然后启动服务和剩余的非必要设备驱动程序及安全子系统(LSASS)。 4.Winlogon.exe根据注册表值HKLM\SYSTEM\Setup\CmdLine来运行设置。如果存在%SYSTEMDRIVE%\sources\setup.exe,Winpeshl.exe将启动它,否则Winpeshl.exe将查找在%SYSTEMROOT%\system32\winpeshl.ini中指定的应用程序。如果未指定任何应用程序,则Winpeshl.exe将执行cmd /k %SYSTEMROOT%\system32\startnet.cmd。默认情况下,Windows PE包含一个将启动Wpeinit.exe的Startnet.cmd文件。Wpeinit.exe加载网络资源,并与网络组件(如DHCP)进行协调。 5.Wpeinit.exe完成后,将显示命令提示符窗口。此时,WinPE 3.1的启动过程完成。 简单理解:光盘引导文件(例:pe.bif)——bootmgr——BCD——boot.wim——启动PE3.x。
1. 使用imagex /apply或imagex /mountrw将WIM镜像文件mount到某个文件夹,假设为d:\tmp\winpe_x86\mount. 例: imagex /mountrw winpe.wim 1 d:\tmp\winpe_x86\mount 2. 使用peimg /list d:\tmp\winpe_x86\mount查看已经安装的package 3. 使用peimg /install= d:\tmp\winpe_x86\mount 或 peimg /uninstall = d:\tmp\winpe_x86\mount,对package进行修改。也可以向镜像目录中添加第三方软件,即直接将软件夹拷贝到镜像中相应目录。对镜像的修改结束后进入下一步 4. 根据实际自身情况决定是否使用peimg /prep对最终Boot WIm镜像大小进行优化,该操作不可逆。以后对该镜像的操作将受限。例: peimg /prep d:\tmp\winpe_x86\mount d:\tmp\winpe_x86\winpe.wim "winpe",将生成最终经优化的boot image ==> winpe.wim.或使用imagex /boot /compress max /capture d:\tmp\winpe_x86\mount d:\tmp\winpe_x86\winpe.wim "winpe",生成没有经过优化的boot image.不过此后还可以对该镜像镜像修改。 5. 使用imagx /unmount /commit d:\tmp\winpe_x86\winpe.wim "winpe",修改提交到wim,并从mount目录卸载镜像。
//获取分辨率 int m_nWindwMetricsX = ::GetSystemMetrics(SM_CXSCREEN); int m_nWindwMetricsY = ::GetSystemMetrics(SM_CYSCREEN); //修改分辨率 DEVMODE lpDevMode;lpDevMode.dmBitsPerPel=32;lpDevMode.dmPelsWidth=1024;lpDevMode.dmPelsHeight=768;lpDevMode.dmSize=sizeof(lpDevMode);lpDevMode.dmFields =DM_PELSWIDTH|DM_PELSHEIGHT|DM_BITSPERPEL;LONG result;result=ChangeDisplaySettings(&lpDevMode,0);if (result==DISP_CHANGE_SUCCESSFUL){ AfxMessageBox(L"修改成功!"); ChangeDisplaySettings(&lpDevMode,CDS_UPDATEREGISTRY);// //使用CDS_UPDATEREGISTRY表示次修改是持久的,// //并在注册表中写入了相关的数据}else{ AfxMessageBox(L"修改失败,恢复原有设置!"); ChangeDisplaySettings(NULL,0);}
1.CAN协议 1.1 帧类型 通讯时使用下面5个类型的帧: 数据帧 遥控帧 错误帧 过载帧 帧间空隙 在所有这些帧中,数据帧和遥控帧由用户设置,而其它帧则由CAN硬件设置。 数据和遥控帧有两种格式:标准和扩展格式。标准格式有11bit的ID,而扩展格式则是29bit的ID。 每个帧的用处见表6,每个帧的结构见图10到图14 表6 帧类型和每种类型帧的作用 2.2 数据帧 数据帧由发送单元使用,用来发送信息给接收单元,这是用户操作的基本帧。 数据帧有7个域组成。图15显示了数据帧的结构。 (1)帧开始(SOF):这个域表示数据帧的开始。 (2)仲裁域:这个域表示一个帧的优先级 (3)控制域:这个域表示保留位和数据字节数 (4)数据域:这是数据内容,0-8个字节的数据能被发送 (5)CRC域:这个域用于检查帧的传输错误。 (6)ACK域:是对帧已经被正常接收的一个证实。 (7)帧结束:指示数据帧结束 (1)帧开始(SOF),对标准的或扩展的格式都是一样的。它指示一帧的开始,由1bit的显性位组成。 显性电平和隐性电平: 总线上的电平有显性电平和隐性电平两种。 总线上执行逻辑上的线“与”时,显性电平的逻辑值为“0”,隐性电平为“1”。 “显性”具有“优先”的意味,只要有一个单元输出显性电平,总线上即为显性电平,并且,“隐性”具有“包容”的意味,只有所有的单元都输出隐性电平,总线上才为隐性电平。(显性电平比隐性电平更强) (2)仲裁域,这个域表示数据的优先级别。这个域的结构,对标准和扩展的格式是有差别的。 注1:关于ID: 标准格式的ID有11bit,从ID28到ID18被依次发送,禁止高7位全为隐性。(禁止设定:ID=1111111xxxx)。这样总共有(2048-16)个ID能被使用。 扩展格式的ID有29个bit。基本ID从ID28到ID18,扩展ID由ID17到ID0表示,基本ID和标准格式ID相同,禁止高7bit全都为隐性,(禁止设定:基本ID=1111111xxxx)。这样总共有(2048-16)个ID能被使用。 在任何情况下,总线上不可能有多个设备在同一时刻使用同一个ID传输数据帧。 (3)控制域,占6个bit,指示要传输信息的数据字节数,这个域的结构,对标准和扩展的格式是有差别的。如图18所示 注1:保留位(r0,r1),保留位必须以显性电平传送,然而,在接收侧可以接收显性、隐性集任意组合的电平。 注2:数据长度码(DLC),数据长度码与数据的字节对应关系见表7所示。数据的字节数必须是0-8个字节,但接收方对DLC=9-15的情况并不视为错误。 (4)数据域,对标准的或扩展的格式都是一样的。这个域是传输的数据,可以是0到8个字节,字节数载控制域中指明。数据输出开始于MSB。如图19所示: (5)CRC域,对标准的或扩展的格式都是一样的。这个域用来检查帧是否有传输错误,它由15bit CRC码和一个bitCRC定界符(delimiter)(separating bit分隔bit) CRC的产生方法是采用下面的多项式:,CRC的计算范围是SOF、仲裁域、控制域、数据域。在接收侧,会对接收到的数据帧的这些域进行CRC计算,如果计算结果与收到的CRC不一致,则表明存在传输错误。 (6)ACK域,是对一帧已被正常接收的一个确认信号,由2个bit组成,一个是ACK的slot,一个是ACK的定界符(delimiter),如图21所示: 注1:发送单元的ACK域,发送单元以隐性bit发送ACK slot和ACK 的delimiter。 注2:接收单元的ACK域,正确接收到信息的接收单元在接收帧的ACK slot里发送一个显性bit,以通知发送单元其已经正确接收完毕,这又称“sending ACK”或“returning ACK”。 “Returning an ACK”: 所有接收单元只要不是处于bus-off或休眠状态,只有正确接收信息的单元才能发送ACK。发送单元并不发送ACK。如果总线上除了发送单元,没有其它单元能接收信息,则No ACK被返回。为了通讯的建立,除了发送单元外,至少需要有一个单元能够接收信息。如果总线上有2个或更多个单元能接收到信息,如果它们中任意一个正常接收到信息,则会有ACK被返回。 (7)帧结束,指示一帧结束,由7个隐性位组成。如图22 1.3 遥控帧 遥控帧是接收单元请求发送单元发送一个信息,遥控帧有6个域组成。如图23显示的那样,除了没有数据域外其它与数据帧的结构是一样的。 (1) 帧开始(SOF):这个域表示数据帧的开始。 (2) 竞争域:这个域表示数据的优先级,具有同样ID的数据帧被请求。 (3) 控制域:这个域表示保留位和数据字节数 (4) CRC域:这个域用于检查帧的传输错误。 (5) ACK域:是对帧已经被正常接收的一个证实。 (6) 帧结束:指示遥控帧的结束 遥控帧和数据帧: 数据帧和遥控帧之间的不同 遥控帧没有数据域,在仲裁域里的RTR位是隐性电平,而数据帧RTR则为显性的。 没有数据的数据帧与遥控帧可以通过RTR为来区分 遥控帧没有数据域,其数据长度码用来干什么? 遥控帧的数据长度码的值表示对应请求的数据帧的数据长度码。 没有数据域的数据帧用来干什么? 例如,数据帧可以被各单元用来作为周期连接确认/应答,或者仲裁域本身带有实质性信息。 1.4 错误帧 这个帧用来通知在传输期间发生了一个错误,错误帧由一个错误标志和一个错误定界符组成,错误帧由CAN的硬件来发送。图24显示了错误帧的结构。 (1) 错误标志:有2种错误标志类型:主动错误和被动错误标志 a)主动错误标志:6个显性位 b) 被动错误标志:6个隐性位 (2) 错误定界符:由8个隐性位组成。 注1:错误标志重叠:取决于连接到总线上的各单元检测出错误的时间,错误标志可能一个重叠在另一个上,总共可达12bit长度。 注2:主动错误标志:处于主动错误状态的单元检测出错误时输出的错误标志。 注3:被动错误标志:处于被动错误状态的单元检测出错误时输出的错误标志。 1.5 过载帧 这个帧被接收单元用来通知还没有准备好接收帧。它由一个过载标志和一个过载定界符组成。图25显示了错误帧的结构。 (1) 过载标志:由6个显性位组成,过载标志与错误帧的主动错误标志具有相同的结构。 (2) 过载定界符:由8个隐性位组成,过载定界符与错误帧的错误定界符具有相同的结构。 注1:错误标志重叠:向错误标志一样,取决于时间,过载标志可能一个重叠在另一个上,总共可达12bit长度。 1.6 帧间间隔 这个帧用来隔开数据帧和遥控帧。数据和遥控帧可通过插入帧间间隔与前面传输的任何帧(数据帧、遥控帧、错误帧、过载帧)分开。 过载帧和错误帧前不能插入帧间间隔。如图26所示。 (1)间隔:由3个隐性位组成。在间隔期间如果检测到显性电平,则必须发送过载帧,然而,如果间隔的第3bit是显性电平,间隔被认为是SOF (2)总线空闲:是隐性电平,长度没有限制(它可以是0bit长)。当总线处于这种状态的时候,总线被认为是自由空闲的,任何单元都可以启动发送信息。 (3)暂停传输(传输暂停期):有8个隐性位组成。只在处于被动错误状态的单元刚发送一个消息后的帧间隔中包含的段。 1.7 优先级的决定 在总线空闲状态,最先开始发送消息的单元获得发送权。 多个单元同时开始发送时,各发送单元从仲裁域的第一位开始进行仲裁。连续输出显性电平最多的单元可继续发送。丢失竞争的单元在下一bit进入接收操作。 仲裁的过程如图27所示。 (1)数据帧和遥控帧的优先级 具有相同 ID 的数据帧和遥控帧在总线上竞争时,仲裁段的最后一位(RTR)为显性位的数据帧具有优先权,可继续发送。 数据帧和遥控帧的仲裁过程如图28所示。 (2)标准格式和扩展格式的优先级 标准格式 ID 与具有相同ID 的遥控帧或者扩展格式的数据帧在总线上竞争时,标准格式的RTR 位为显性位的具有优先权,可继续发送。 标准格式和扩展格式的仲裁过程如图29所示。 1.8 位填充 位填充是一种周期性重同步收/发操作的功能,为了防止接收节点间时序由于累积而导致的错误,如果5个bit持续了同样的电平,则添加1个bit的反向数据位。 如图30显示的位填充机制: (1)发送单元的操作 在发送数据帧和遥控帧的时候,SOF-CRC段间的数据,相同电平如果持续5bit,在下一bit(第6bit)则要插入1bit与前5bit反向的电平。 (2)接收单元的操作 在接收数据帧和遥控帧的时候,SOF-CRC段间的数据,相同电平如果持续5bit,需要删除下一bit(第6bit)再接收。如果这第6bit的电平与前5bit相同,则被视为错误,且发送错误帧。 1.9 错误的种类 有5种类型的错误,可能有2个或更多的错误同时发生: 位错误 填充错误 CRC错误 格式错误 ACK错误 表8列出了这些错误的种类、内容、错误检测帧和检测单元。 位错误由向总线上输出数据帧、遥控帧、错误帧、过载帧的单元和输出ACK的单元、输出错误的单元来检测。 在仲裁段输出隐性电平,但检测出显性电平时,将被视为仲裁失利,而不是位错误。 在仲裁段作为填充位输出隐性电平时,但检测出显性电平时,将不视为位错误,而是填充错误。 发送单元在ACK 段输出隐性电平,但检测到显性电平时,将被判断为其它单元的ACK 应答,而非位错误。 输出被动错误标志(6 个位隐性位)但检测出显性电平时,将遵从错误标志的结束条件,等待检测出连续相同6 个位的值(显性或隐性),并不视为位错误。 (2) 格式错误 即使接收单元检测出EOF(7 个位的隐性位)的最后一位(第8 个位)为显性电平,也不视为格式错误。 即使接收单元检测出数据长度码(DLC)中9∼15 的值时,也不视为格式错误。 1.10 错误帧的输出时序 检测到发生错误的单元输出一个错误标志,以通知其它单元。 处于主动错误状态的单元输出的错误标志为主动错误标志;处于被动错误状态的单元输出的错误标志为被动错误标志。 发送单元发送完错误帧之后,将再次发送数据帧或遥控帧。 错误标志输出时序如表9: 1.11 位时序 在没有重新同步情况下,发送单元每秒传输的位数称之为位速率。1位由下面4个段组成。 同步段(SS) 传播时间段(PTS) 相位缓冲段1(PBS1) 相位缓冲段2(PBS2) 这些段又由称之为Time Quantum(以下称为Tq)的最小时间单位构成。 1位分为4个段,每个段由若干个Tq构成,这称为位时序。 1位由多少个Tq构成、每个段由多少个Tq构成等是可以设定的。通过设置bit时序。使得可以设定一个采样点以使总线上多个单元可同时采样,所谓采样点就是在这一时刻总线上的电平被锁存,这个锁存的电平作为位的值。采样点的位置在PBS1的结束处。 表10描述了各段的作用和Tq 数。1个位的构成如图31所示。 段名称 段的作用 Tq数 同步段(SS) 多个连接在总线上的单元通过此段实现时序的定时调整,以便同步进行接收和发送的工作。 隐性电平到显性电平或显性电平到隐性电平变化的边沿被期望出现在本段。 1 8-25 传播时间段(PTS) 用于吸收网络上的物理延迟的段。包括发送单元的输出延迟、总线上信号的传播延迟、接收单元的输入延迟。 这个段的时间是以上延迟时间累加和的两倍。 1-8 相位缓冲段1(PBS1) 当信号边沿不能出现在SS 段时,此段用来矫正误差。 由于各单元以各自独立的时钟来工作,细微的时钟误差都会累积起来,PBS 段可用于吸收此误差。 为了吸收一个时钟误差,在SJW设置的范围内增减PBS1和PBS2,PBS1和PBS2越大,允许误差越大,但是通讯速度会降低。 1-8 相位缓冲段2(PBS2) PBS1或IPT中较大者(见注1和2) 重新同步跳转宽度(SJW) 因时钟频率偏差、传送延迟等原因,某些单元可能会失去同步。SJW是所能校正的最大失去同步的宽度。 1-4*PBS1 注1:IPT代表信息处理时间,是以采样点作为起始的时间段,用于计算后续位的位电平。这是硬件在一个采样点后立刻改变位的电平所必须要的。这个时间等于或小于2Tq,。 注2:因为采样点是处于PBS1结束处,所以IPT和PBS2重叠。当IPT = 2Tq时,PBS2不可能选为1,因此,PBS2必须是2到8Tq。 注3:重新同步的结果使相位缓冲段1增长,或使相位缓冲段2 缩短。相位缓冲段加长或缩短的数量有一个上限,此上限由SJW(重新同步跳转宽度)给定。重新同步跳转宽度应设置于1和最小值之间(此最小值为4*PBS1)。 可以从一位值转换到另一位值的过渡过程得到时钟信息。这里有一个属性,即:只有后续位的一固定最大数值才具有相同的数值。这个属性使总线单元在帧期间重新同步于位流成为可能。可用于重新同步的两个过渡过程之间的最大的长度为29个位时间。 1.12 同步是如何获得的? CAN总线的通讯是采用NRZ(Non-Return to Zero,非归0)码,数据本身并不携带时钟信息,也即在每一位的开始或结束没有同步信号,发送单元以位时序同步的方式开始发送帧数据,接收单元根据总线电平的变化进行同步并进行接收工作。 然而,发送器和接收器之间由于彼此的时钟误差或传输路径的相位误差可能会失去同步关系,因此接收单元在接收帧的时候,必须通过硬件同步或重新同步调整它的操作时序。 1.13 硬件同步 在总线空闲状态时,接收单元检测到SOF,就会执行这个同步调整过程。“隐式”电平跳变到“显式”电平的边缘的时间点被认为是SS,而不管SJW的值 图32显示了硬件同步机制。 如果沿出现在SS里,沿的相位误差e=0; 如果沿位于采集点(PBS1结束之前)之前,e>0; 如果沿位于采集点之后,e<0; 1.14 重新同步机制 在接收过程中检测到总线电平发生了改变时执行重新同步操作。 每当检测到一个边沿(总线电平的改变),收发单元根据SJW值通过增加PBS1段或减少PBS2段,来调整同步。但,如果发生了超出SJW值的误差时,最大调整量不能超过SJW值。 图33显示了重新同步机制。 1.15 调整同步的规则 硬件同步和再同步的执行遵从如下规则: 1) 在1个位时间里(或者说在2个采样点之间),只允许一个同步(或者说只进行一次同步调整)。 2) 只有当采样点之前的总线电平和边沿后的总线电平不同时,该边沿才能用于调整同步。 3) 如果出现隐性电平到显性电平变化的边沿,且条件(1)和(2)满足,将进行同步。 4) 如果在帧间间隙期间发生隐性电平到显性电平的信号边沿(除了间隙里的第一位),则总会执行硬件同步。 5) 如果发生从所有其它隐性电平到显性电平的信号边沿,则执行再同步。 6) 如果发送单元自身输出的显性电平被检测到有延迟,则不执行再同步。
CAN协议和标准规范 1 由ISO标准化的CAN协议 CAN协议已经由ISO标准化,有2个版本,如ISO11898和ISO11519-2,它们之间在数据链路层没什么不同,但是在物理层有些区别。 (1) 关于ISO11898:这个标准用于高速CAN通讯。开始的时候,数据链路层和物理层都在标准ISO11898中规定,后来被拆分为ISO11898-1(仅涉及数据链路层)和ISO11898-2(仅涉及物理层) (2) 关于ISO11519:这个标准用于低速(最高125kbps)CAN通讯 2 ISO11898和ISO11519-2之间的不同 图6显示了CAN规范的规定范围。三个物理层的子层:PLS(Physucal Signaling Sublayer物理信号子层)子层,PMA(Physical Medium Attachment物理介质连接)子层,MDI(Medium Dependent Interface介质相关接口)子层,PMA和MDI子层的定义是不一样的。 表3列出了ISO11898和ISO11519-2之间的物理层上的不同,图7现实了通讯速度和总线长度之间的关系。 通讯速度和总线长度需要由用户按照系统要求进行设置。 总线拓扑: CAN总线通常有2根线(CAN_High和CAN_Low)组成,CAN控制器通过一个收发器连接到总线上,总线的电平由CAN_High和CAN_Low的电位差来确定,总线有2个电平:显性和隐性,在任一给定的时间内,总线总是处于这2个电平之一。对于逻辑上“线与”的总线,显性和隐性电平被看作逻辑0和逻辑1,一个发送单元能够通过改变总线电平来送一个信息给接收单元。 ISO11898和ISO11519-2规定的物理层终端阻抗、显性电平、隐性电平差分电压是不同的。 图8显示了ISO11898和ISO11519-2物理层的特点,注意ISO11898和ISO11519-2要求一个收发器满足对应的标准,表4列出了满足ISO11898和ISO11519-2的主要收发器IC。 3 CAN和标准规范 除了ISO,CAN规范由工业标准组织如SAE标准化,以及由一些私立研究机构和公司进行了标准化。 表5类除了一些基本的标准规范,图9显示了通讯协议用于汽车按照通讯速度分级 SAE:代表汽车工程师协会(Societyof Automotive Engineers) NMEA:代表国家海洋教育者协会(NationalEducators Association) SDS:代表智能分布系统(SmartDistributed System) Class:SAE的分类名称
1.CAN总线是什么? CAN(Controller Area Network)是ISO国际标准化的串行通信协议。广泛应用于汽车、船舶等。具有已经被大家认可的高性能和可靠性。 CAN控制器通过组成总线的2根线(CAN-H和CAN-L)的电位差来确定总线的电平,在任一时刻,总线上有2种电平:显性电平和隐性电平。 “显性”具有“优先”的意味,只要有一个单元输出显性电平,总线上即为显性电平,并且,“隐性”具有“包容”的意味,只有所有的单元都输出隐性电平,总线上才为隐性电平。(显性电平比隐性电平更强)。 总线上执行逻辑上的线“与”时,显性电平的逻辑值为“0”,隐性电平为“1”。 下图显示了一个典型的CAN拓扑连接图。 连接在总线上的所有单元都能够发送信息,如果有超过一个单元在同一时刻发送信息,有最高优先级的单元获得发送的资格,所有其它单元执行接收操作。 2.CAN总线的特点 CAN总线协议具有下面的特点: 1) 多主控制 当总线空闲时,连接到总线上的所有单元都可以启动发送信息,这就是所谓的多主控制的概念。 先占有总线的设备获得在总线上进行发送信息的资格。这就是所谓的CSMA/CR(Carrier Sense MultipleAccess/Collosion Avoidance)方法 如果多个设备同时开始发送信息,那么发送最高优先级ID消息的设备获得发送资格。 2) 信息的发送 在CAN协议中,所有发送的信息要满足预先定义的格式。当总线没有被占用的时候,连接在总线上的任何设备都能起动新信息的传输,如果两个或更多个设备在同时刻启动信息的传输,通过ID来决定优先级。ID并不是指明信息发送的目的地,而是指示信息的优先级。如果2个或者更多的设备在同一时刻启动信息的传输,在总线上按照信息所包含的ID的每一位来竞争,赢得竞争的设备(也就是具有最高优先级的信息)能够继续发送,而失败者则立刻停止发送并进入接收操作。因为总线上同一时刻只可能有一个发送者,而其它均处于接收状态,所以,并不需要在底层协议中定义地址的概念。 3) 系统的灵活性 连接到总线上的单元并没有类似地址这样的标识,所以,添加或去除一个设备,无需改变软件和硬件,或其它设备的应用层软件。 4) 通信速度 可以设置任何通讯速度,以适应网络规模。 对一个网络,所有单元必须有相同的通讯速度,如果不同,就会产生错误,并妨碍网络通讯,然而,不同网络间可以有不同的通讯速度。 5) 远程数据请求 可以通过发送“遥控帧”,请求其他单元发送数据。 6) 错误检测、错误通知、错误恢复功能 所有单元均可以检测出错误(错误检测功能)。 检测到错误的单元立刻同时通知其它所有的单元(错误通知功能)。如果一个单元发送信息时检测到一个错误,它会强制终止信息传输,并通知其它所有设备发生了错误,然后它会重传直到信息正常传输出去(错误恢复功能)。 7) 错误隔离 在CAN总线上有两种类型的错误:暂时性的错误(总线上的数据由于受到噪声的影响而暂时出错);持续性的错误(由于设备内部出错(如驱动器坏了、连接有问题等)而导致的)。CAN能够区别这两种类型,一方面降低常出错单元的通讯优先级以阻止对其它正常设备的影响,另一方面,如果是一种持续性的错误,将这个设备从总线上隔离开。 8) 连接 CAN总线允许多个设备同时连接到总线上且在逻辑上没有数目上的限制。然而由于延迟和负载能力的限制,实际可连接得设备还是有限制的,可以通过降低通讯速度来增加连接的设备个数。相反,如果连接的设备少,通讯的速度可以增加。 3.错误 3.1 错误状态 设备总是处于下面三个状态之一: 1)主动错误状态 在此状态下,设备能够参加总线上的正常通讯。如果处于主动错误状态的设备检测到一个错误,它会发送一个主动错误标志,更细节见第6章的“CAN协议”。 2)被动错误状态 是指易于引起错误的状态。 尽管处于被动错误状态的设备能够参加总线上的通讯,但是在接收期间,它不可能主动地向其它设备发送错误通知,以避免影响它们的通讯。处于被动错误状态的设备即使检测到一个错误,如果其它处于主动错误状态的设备没曾检测到错误,那么也认为在总线上未曾出现过任何错误。 当处于被动错误状态的设备检测到一个错误的时候,它发送一个被动错误标志。 另外,处于被动错误状态的单元在发送结束后不能立刻再次开始发送。在开始下次发送前,在间隔帧期间内必须插入“暂停发送期”(由8个位的隐性位组成)。 更细节见第6章的“CAN协议”。 3)总线切断状态 处于此状态下时,设备不能参加总线的通讯。设备所有的收发操作都被禁止。 这些状态是通过发送错误计数器和接收错误寄存器来管理,相关错误状态由这些计数器值的组合来标识,错误状态和计数器值之间的关系见表1和图4。 3.2 错误计数器的值 发送和接收错误计数器的值按照规定的条件来改变。 表2小结了错误计数器值改变的条件。 在一个数据收发操作中可能会发生多个条件重叠。 错误计数器增加的时间发生在错误标志的第一bit位置。 4.CAN协议的基本概念 CAN协议包括OSI参考模型的传输层、数据链路层、物理层。图5显示了CAN协议每个层的定义。 数据链路层划分为MAC(Medium Access Control媒体存取控制)和LLC(Logical Link Control罗辑链路控制)。MAC子层组成CAN协议的核心。数据链路层的功能是将从物理层接收到的信号组织成有意义的信息,提供如传输错误控制等数据传输控制流程。更具体来说,包括:信息如何封装成一帧,数据冲突仲裁、应答、错误的检测或通知。数据链路层的这些功能通常由CAN控制器硬件来实现。 物理层定义信号的实际传输方式、位的时序、位的编码、同步的过程步骤,然而,CAN协议并没有定义了信号电平、通讯速度、采样点值、驱动器和总线电气特征、连接器形式。对每个系统,这些特征由用户自行确定。 在BOSCH公司的CAN协议中,并没有关于收发器和总线的电气特征的定义,而在ISO CAN协议中,如ISO11898和ISO11519-2却对此有明确的定义。
TCP/IP 资源:http://download.csdn.net/detail/mao0514/9061265 server: #include<stdio.h> #include<winsock2.h> void main() { SOCKET servsock,clisock; struct sockaddr_in sa; struct sockaddr_in cliaddr; int servport=6666; char buff[256]; WSADATA ws; int len,err; // 初始化Winsock if(WSAStartup(0x0101,&ws)!=0) { printf("WSAStartup() failed!\n"); return; } //创建套接字 printf("Create Socket...\n"); servsock=socket(AF_INET,SOCK_STREAM,0); //填充服务器地址结构 memset(&sa,0,sizeof(sa)); sa.sin_family=AF_INET; sa.sin_port=htons(servport); sa.sin_addr.s_addr=inet_addr("192.168.1.100"); //sa.sin_addr.s_addr=inet_addr("127.0.0.1"); //绑定套接字到服务器地址结构 printf("Binding...\n"); err=bind(servsock,(const sockaddr *)&sa,sizeof(sa)); if(err!=0) { fprintf(stderr,"Bind failed:%d\n",WSAGetLastError()); return; } //监听套接字 printf("Listening...\n"); err=listen(servsock,5); if(err!=0) { fprintf(stderr,"Listen failed:%d\n",WSAGetLastError()); return; } //等待连接请求 printf("Waitting Request...\n"); len=sizeof(cliaddr); clisock=accept(servsock,(struct sockaddr *)&cliaddr,&len); len=recv(clisock,buff,sizeof(buff),0); if(len>0) { buff[len]=0; printf("%s\n",buff); } printf("Accept Client:%s:%d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port)); sprintf(buff,"Welcome you %s",inet_ntoa(cliaddr.sin_addr)); //发送欢迎词 send(clisock,buff,strlen(buff),0); Sleep(1000); // recv(clisock,buff,strlen(buff),0); // printf("%s\n",buff); //关闭连接 closesocket(clisock); closesocket(servsock); WSACleanup(); }client: #include<winsock2.h> void main() { SOCKET sock; struct sockaddr_in sa; int err; int servport=6666; char buff[256]; int len; WSADATA ws; // 初始化Winsock if(WSAStartup(0x0101,&ws)!=0) { printf("WSAStartup() failed!\n"); return; } //创建套接字 sock=socket(AF_INET,SOCK_STREAM,0); //定义服务器地址结构 memset(&sa,0,sizeof(sa)); sa.sin_family=AF_INET; sa.sin_port=htons(servport); sa.sin_addr.s_addr=inet_addr("192.168.1.100"); // 连接服务器 err=connect(sock,(const sockaddr*)&sa,sizeof(sa)); // printf("test1"); while(1) { // printf("test1"); fgets(buff,256,stdin); // for(int i=0;i<100;i++) send(sock,buff,strlen(buff),0); if(strncmp(buff,"exit",4)==0) break; len=recv(sock,buff,sizeof(buff),0); if(len>0) { buff[len]=0; printf("%s\n",buff); } } //接收欢迎词 memset(buff,0,sizeof(buff)); len=recv(sock,buff,sizeof(buff),0); printf("%s\n",buff); //关闭连接 closesocket(sock); WSACleanup(); } UDP: #include<stdlib.h> #include<string.h> #include<winsock2.h> #define BUFSIZE 256 void main(void) { /////////////////////////初始化///////////////////// WSADATA wsaData; WSAStartup(0x0202,&wsaData); SOCKET m_socket; m_socket=socket(AF_INET,SOCK_DGRAM,0); if(INVALID_SOCKET==m_socket) { printf("套接字创建失败!"); return; } SOCKADDR_IN addrSock; addrSock.sin_family=AF_INET; addrSock.sin_port=htons(6000); addrSock.sin_addr.S_un.S_addr=htonl(INADDR_ANY); int retval; retval=bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR)); if(SOCKET_ERROR==retval) { closesocket(m_socket); printf("绑定失败!"); } /////////////////接收/////////////////////////// SOCKADDR_IN addrFrom; int len=sizeof(SOCKADDR),len2; char recvBuf[200]; char tempBuf[300]; SOCKADDR_IN addrTo; addrTo.sin_family=AF_INET; addrTo.sin_port=htons(6001); addrTo.sin_addr.S_un.S_addr=inet_addr("192.168.1.100"); while(TRUE) { retval=recvfrom(m_socket,recvBuf,200,0,(SOCKADDR*)&addrFrom,&len); if(SOCKET_ERROR==retval) break; for (int i=0; i<len; i++) { recvBuf[i] = toupper(recvBuf[i]); } printf(recvBuf); ///////////////////////发送////////////////////////// len2 = strlen(recvBuf); sendto(m_socket,recvBuf,len2+1,0,(SOCKADDR*)&addrTo,sizeof(SOCKADDR)); } //关闭socket closesocket(m_socket); WSACleanup(); exit(0); } //2 #include<stdlib.h> #include<string.h> #include<winsock2.h> #define BUFSIZE 256 void main(void) { WSADATA wsaData; WSAStartup(0x0202,&wsaData); SOCKET m_socket; m_socket=socket(AF_INET,SOCK_DGRAM,0); if(INVALID_SOCKET==m_socket) { printf("套接字创建失败!"); } SOCKADDR_IN addrSock; addrSock.sin_family=AF_INET; addrSock.sin_port=htons(6001); addrSock.sin_addr.S_un.S_addr=htonl(INADDR_ANY); int retval; retval=bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR)); if(SOCKET_ERROR==retval) { closesocket(m_socket); printf("绑定失败!"); } /////////////////接收/////////////////////////// SOCKADDR_IN addrFrom; int len=sizeof(SOCKADDR),len2; char recvBuf[200]; char tempBuf[300]; SOCKADDR_IN addrTo; addrTo.sin_family=AF_INET; addrTo.sin_port=htons(6000); addrTo.sin_addr.S_un.S_addr=inet_addr("192.168.1.100"); while(TRUE) { // scanf("%s",recvBuf); fgets(recvBuf,256,stdin); ///////////////////////发送////////////////////////// len2 = strlen(recvBuf); sendto(m_socket,recvBuf,len2+1,0, (SOCKADDR*)&addrTo,sizeof(SOCKADDR)); Sleep(1000); ////////////////接收///////////////////////////////// retval=recvfrom(m_socket,recvBuf,200,0,(SOCKADDR*)&addrFrom,&len); if(SOCKET_ERROR==retval) break; printf(recvBuf); } //关闭socket closesocket(m_socket); WSACleanup(); exit(0); }
我用串口精灵发送数据没有问题,但是接收数据没反应。 串口接受的时候必须要用中断的,你发送只靠单一的标志位是可以判断的,但是接受的时候,你是一直停留在while里面,我们判断接受是否完成,通过检测是否收到0x0D、0x0A的连续来检测是否结束。当检测到这个结束序列后,就会置位USART_RX_STA的最高位来标记已经会搜到一次数据。之后等待外部函数清空才可以第二次接受。 修改:超级终端发送设置,以换行作为发送末尾,OK while(1) { if(USART_RX_STA&0x8000) { len=USART_RX_STA&0x3fff;//µÃµ½´Ë´Î½ÓÊÕµ½µÄÊý¾Ý³¤¶È printf("\r\nÄú·¢Ë͵ÄÏûϢΪ:\r\n"); for(t=0;t<len;t++) { USART_SendData(USART1, USART_RX_BUF[t]); //Ïò´®¿Ú1·¢ËÍÊý¾Ý while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//µÈ´ý·¢ËͽáÊø } printf("\r\n\r\n");//²åÈë»»ÐÐ USART_RX_STA=0; }else { times++; if(times%5000==0) { printf("\r\nALIENTEK ̽Ë÷ÕßSTM32F407¿ª·¢°å ´®¿ÚʵÑé\r\n"); printf("ÕýµãÔ×Ó@ALIENTEK\r\n\r\n\r\n"); } if(times%200==0)printf("ÇëÊäÈëÊý¾Ý,ÒԻسµ¼ü½áÊø\r\n"); if(times%30==0)LED0=!LED0;//ÉÁ˸LED,ÌáʾϵͳÕýÔÚÔËÐÐ. delay_ms(10); } }
stm32的GPIO的配置模式有好几种,包括: 1. 模拟输入; 2. 浮空输入; 3. 上拉输入; 4. 下拉输入; 5. 开漏输出; 6. 推挽输出; 7. 复用开漏输出; 8. 复用推挽输出 如图是GPIO的结构原理图: 1.模拟输入 从上图我们可以看到,我觉得模拟输入最重要的一点就是,他不经过输入数据寄存器,所以我们无法通过读取输入数据寄存器来获取模拟输入的值,我觉得这一点也是很好理解的,因为输入数据寄存器中存放的不是0就是1,而模拟输入信号不符合这一要求,所以自然不能放进输入数据寄存器。该输入模式,使我们可以获得外部的模拟信号。 2.浮空输入 该输入状态,我的理解是,它的输入完全由外部决定,我觉得在数据通信中应该可以使用该模式。应为在数据通信中,我们直观的理解就是线路两端连接着发送端和接收断,他们都需要准确获取对方的信号电平,不需要外界的干预。所以我觉得这种情况适合浮空输入。比如我们熟悉的I2C通信。 3上拉输入 上拉输入就是在输入电路上使用了上拉电阻。这种模式的好处在于我们什么都不输入时,由于内部上拉电阻的原因,我们的处理器会觉得我们输入了高电平,这就避免了不确定的输入。这在要求输入电平只要高低两种电平的情况下是很有用的。 4下拉输入 和上拉输入类似,不过下拉输入时,在外部没有输入时,我们的处理器会觉得我们输入了低电平。 5开漏输出 开漏输出,输出端相当于三极管的集电极,所以适合与做电流驱动的应用。要得到高电平,需要上拉电阻才可以。 6推挽输出 推挽输出使用了推挽电路,结合推挽电路的特性,它是由两个MOSFET组成,一个导通的同时,另外一个截至,两个MOSFET分别连接高低电平,所以哪一个导通就会输出相应的电平。推挽电路速度快,输出能力强,直接输出高电平或者低电平。 7复用开漏和复用推挽 我们知道这只是对GPIO的复用而已。使普通的GPIO具有了别的功能。 推挽输出:可以输出高,低电平,连接数字器件; 推挽结构一般是指两个三极管分别受两互补信号的控制,总是在一个三极管导通的时候另一个截止。高低电平由IC的电源低定。 推挽电路是两个参数相同的三极管或MOSFET,以推挽方式存在于电路中,各负责正负半周的波形放大任务,电路工作时,两只对称的功率开关管每次只有一个导通,所以导通损耗小、效率高。输出既可以向负载灌电流,也可以从负载抽取电流。推拉式输出级既提高电路的负载能力,又提高开关速度。 详细理解: 如图所示,推挽放大器的输出级有两个“臂”(两组放大元件),一个“臂”的电流增加时,另一个“臂”的电流则减小,二者的状态轮流转换。对负载而言,好像是一个“臂”在推,一个“臂”在拉,共同完成电流输出任务。当输出高电平时,也就是下级负载门输入高电平时,输出端的电流将是下级门从本级电源经VT3拉出。这样一来,输出高低电平时,VT3 一路和 VT5 一路将交替工作,从而减低了功耗,提高了每个管的承受能力。又由于不论走哪一路,管子导通电阻都很小,使RC常数很小,转变速度很快。因此,推拉式输出级既提高电路的负载能力,又提高开关速度。 开漏输出:输出端相当于三极管的集电极. 要得到高电平状态需要上拉电阻才行. 适合于做电流型的驱动,其吸收电流的能力相对强(一般20ma以内). 开漏形式的电路有以下几个特点: 1.利用外部电路的驱动能力,减少IC内部的驱动。当IC内部MOSFET导通时,驱动电流是从外部的VCC流经R pull-up ,MOSFET到GND。IC内部仅需很下的栅极驱动电流。 2.一般来说,开漏是用来连接不同电平的器件,匹配电平用的,因为开漏引脚不连接外部的上拉电阻时,只能输出低电平,如果需要同时具备输出高电平的功能,则需要接上拉电阻,很好的一个优点是通过改变上拉电源的电压,便可以改变传输电平。比如加上上拉电阻就可以提供TTL/CMOS电平输出等。(上拉电阻的阻值决定了逻辑电平转换的沿的速度。阻值越大,速度越低功耗越小,所以负载电阻的选择要兼顾功耗和速度。) 3.OPEN-DRAIN提供了灵活的输出方式,但是也有其弱点,就是带来上升沿的延时。因为上升沿是通过外接上拉无源电阻对负载充电,所以当电阻选择小时延时就小,但功耗大;反之延时大功耗小。所以如果对延时有要求,则建议用下降沿输出。 4.可以将多个开漏输出的Pin,连接到一条线上。通过一只上拉电阻,在不增加任何器件的情况下,形成“与逻辑”关系。这也是I2C,SMBus等总线判断总线占用状态的原理。补充:什么是“线与”?: 在一个结点(线)上,连接一个上拉电阻到电源VCC或VDD和n个NPN或NMOS晶体管的集电极C或漏极D,这些晶体管的发射极E或源极S都接到地线上,只要有一个晶体管饱和,这个结点(线)就被拉到地线电平上.因为这些晶体管的基极注入电流(NPN)或栅极加上高电平(NMOS),晶体管就会饱和,所以这些基极或栅极对这个结点(线)的关系是或非NOR逻辑.如果这个结点后面加一个反相器,就是或OR逻辑. 其实可以简单的理解为:在所有引脚连在一起时,外接一上拉电阻,如果有一个引脚输出为逻辑0,相当于接地,与之并联的回路“相当于被一根导线短路”,所以外电路逻辑电平便为0,只有都为高电平时,与的结果才为逻辑1。 关于推挽输出和开漏输出,最后用一幅最简单的图形来概括: 该图中左边的便是推挽输出模式,其中比较器输出高电平时下面的PNP三极管截止,而上面NPN三极管导通,输出电平VS+;当比较器输出低电平时则恰恰相反,PNP三极管导通,输出和地相连,为低电平。右边的则可以理解为开漏输出形式,需要接上拉。 浮空输入:对于浮空输入,一直没找到很权威的解释,只好从以下图中去理解了 由于浮空输入一般多用于外部按键输入,结合图上的输入部分电路,我理解为浮空输入状态下,IO的电平状态是不确定的,完全由外部输入决定,如果在该引脚悬空的情况下,读取该端口的电平是不确定的。 上拉输入/下拉输入/模拟输入:这几个概念很好理解,从字面便能轻易读懂。 复用开漏输出、复用推挽输出:可以理解为GPIO口被用作第二功能时的配置情况(即并非作为通用IO口使用) 最后总结下使用情况: 在STM32中选用IO模式(1) 浮空输入_IN_FLOATING ——浮空输入,可以做KEY识别,RX1(2)带上拉输入_IPU——IO内部上拉电阻输入(3)带下拉输入_IPD—— IO内部下拉电阻输入(4) 模拟输入_AIN ——应用ADC模拟输入,或者低功耗下省电(5)开漏输出_OUT_OD ——IO输出0接GND,IO输出1,悬空,需要外接上拉电阻,才能实现输出高电平。当输出为1时,IO口的状态由上拉电阻拉高电平,但由于是开漏输出模式,这样IO口也就可以由外部电路改变为低电平或不变。可以读IO输入电平变化,实现C51的IO双向功能(6)推挽输出_OUT_PP ——IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的(7)复用功能的推挽输出_AF_PP ——片内外设功能(I2C的SCL,SDA)(8)复用功能的开漏输出_AF_OD——片内外设功能(TX1,MOSI,MISO.SCK.SS)STM32设置实例:(1)模拟I2C使用开漏输出_OUT_OD,接上拉电阻,能够正确输出0和1;读值时先GPIO_SetBits(GPIOB, GPIO_Pin_0);拉高,然后可以读IO的值;使用GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0);(2)如果是无上拉电阻,IO默认是高电平;需要读取IO的值,可以使用带上拉输入_IPU和浮空输入_IN_FLOATING和开漏输出_OUT_OD; 通常有5种方式使用某个引脚功能,它们的配置方式如下:1)作为普通GPIO输入:根据需要配置该引脚为浮空输入、带弱上拉输入或带弱下拉输入,同时不要使能该引脚对应的所有复用功能模块。2)作为普通GPIO输出:根据需要配置该引脚为推挽输出或开漏输出,同时不要使能该引脚对应的所有复用功能模块。3)作为普通模拟输入:配置该引脚为模拟输入模式,同时不要使能该引脚对应的所有复用功能模块。4)作为内置外设的输入:根据需要配置该引脚为浮空输入、带弱上拉输入或带弱下拉输入,同时使能该引脚对应的某个复用功能模块。5)作为内置外设的输出:根据需要配置该引脚为复用推挽输出或复用开漏输出,同时使能该引脚对应的所有复用功能模块。注意如果有多个复用功能模块对应同一个引脚,只能使能其中之一,其它模块保持非使能状态。
例子为单片机的“Hello World”级的流水灯实验——虽然只有一个,其中并不是将完整的代码给出,只是给出关键部分来说明“如何调用ST公司的的库来完成对硬件的控制,以及对库文件代码进行跟踪和分析至寄存器级”。所以从第一段代码往下看就可以了,要用到的函数和变量大部分会说明,至于寄存器级的,那就只能翻手册了。 GPIO(General Purpose Input/Output) - 通用输入/输出 main.c :此函数为主函数,控制LED,亮1s,灭1s 123456789101112 int main(void){ //LED初始化 LED_Configuration(); while(1) { GPIO_SetBits(GPIOB,GPIO_Pin_5); //置为1 Systick_DelayMs(1000); //延时1s,自己实现的,暂不说明 GPIO_ResetBits(GPIOB,GPIO_Pin_5); //置为0 Systick_DelayMs(1000); //延时1s }} stm32f10x_gpio.c GPIO_SetBits和GPIO_ResetBits 12345678910111213141516171819202122232425262728293031 /** * @brief Sets the selected data port bits. * @param GPIOx: where x can be (A..G) to select the GPIO peripheral. * @param GPIO_Pin: specifies the port bits to be written. * This parameter can be any combination of GPIO_Pin_x where x can be (0..15). * @retval None */void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin){ /* Check the parameters */ assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); assert_param(IS_GPIO_PIN(GPIO_Pin)); GPIOx->BSRR = GPIO_Pin;}/** * @brief Clears the selected data port bits. * @param GPIOx: where x can be (A..G) to select the GPIO peripheral. * @param GPIO_Pin: specifies the port bits to be written. * This parameter can be any combination of GPIO_Pin_x where x can be (0..15). * @retval None */void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin){ /* Check the parameters */ assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); assert_param(IS_GPIO_PIN(GPIO_Pin)); GPIOx->BRR = GPIO_Pin;} led.c 需要用到以下库文件:stm32f10x_rcc.c,stm32f10x_gpio.c-->STM32的I/O口初始化函数 12345678910111213141516 void LED_Configuration(void){ /*设置PB.5为输出模式,用于LED*/ //定义一个GPIO数据结构,存放设置的参数 GPIO_InitTypeDef GPIO_InitStructure; //要使用一个I/O口时,需要先打开相应I/O口所在口的时钟,如使能PB端口时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //先设置要配置的引脚,LED0-->PB.5 端口配置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //配置为推挽输出模式 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //配置I/O口速度为50MHz GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //根据设定参数初始化GPIOB GPIO_Init(GPIOB, &GPIO_InitStructure);} 下面为LED_Configuration中涉及到的结构体变量和一些其他变量的出处: stm32f10x_gpio.h 123456789101112131415 /** * @brief GPIO Init structure definition */typedef struct{ uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured. This parameter can be any value of @ref GPIO_pins_define */ GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins. This parameter can be a value of @ref GPIOSpeed_TypeDef */ GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins. This parameter can be a value of @ref GPIOMode_TypeDef */} GPIO_InitTypeDef; stm32f10x_gpio.h :每个GPIO口有0x10个。 123456789101112131415161718192021 /** @defgroup GPIO_pins_define * @{ */#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */#define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */#define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */#define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */#define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */#define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected */#define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected */#define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected */#define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected */#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */ stm32f10x_gpio.h : GPIO的模式,比51多多了 123456789101112131415 /** * @brief Configuration Mode enumeration */typedef enum{ GPIO_Mode_AIN = 0x0, /* 模拟输入模式 */ GPIO_Mode_IN_FLOATING = 0x04, /* 浮空输入模式 */ GPIO_Mode_IPD = 0x28, /* 下拉输入模式 */ GPIO_Mode_IPU = 0x48, /* 上拉输入模式 */ GPIO_Mode_Out_OD = 0x14, /* 通用推挽输出模式 */ GPIO_Mode_Out_PP = 0x10, /* 通用开漏输出模式 */ GPIO_Mode_AF_OD = 0x1C, /* 复用功能推挽输出模式 */ GPIO_Mode_AF_PP = 0x18 /* 复用功能开漏输出模式 */ } GPIOMode_TypeDef; 具体区别翻手册,也没记那么清楚~ stm32f10x_gpio.h : 赋予GPIO的运行频率 12345678910 /** * @brief Output Maximum frequency selection */typedef enum{ GPIO_Speed_10MHz = 1, GPIO_Speed_2MHz, GPIO_Speed_50MHz} GPIOSpeed_TypeDef; 枚举变量:GPIO_Speed_10MHz =1;GPIO_Speed_2MHz =2;GPIO_Speed_50MHz =3,速度写着呢,选一个就ok了~ stm32f10x.h 12345678910111213141516171819 #define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE) GPIOA=GPIOA_BASE=0x40000000+0x10000+0x800 通过查询STM32微控制器开发手册可以得知,STM32的外设起始基地址为0x40000000,而APB2总线设备起始地址相对于外设基地址的偏移量为0x10000,GPIOA设备相对于APB2总线设备起始地址偏移量为0x800。 注意:以上只是很小的一部分定义,stm32f10x.h文件的代码行数有8000多行。 下面为LED_Configuration中再配置完参数之后,将结构体中的参数写入到寄存器中的GPIO_Init初始化函数: stm32f10x_gpio.c 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 /** * @brief Initializes the GPIOx peripheral according to the specified * parameters in the GPIO_InitStruct. * @param GPIOx: where x can be (A..G) to select the GPIO peripheral. * @param GPIO_InitStruct: pointer to a GPIO_InitTypeDef structure that * contains the configuration information for the specified GPIO peripheral. * @retval None */void GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_InitStruct){ uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00; uint32_t tmpreg = 0x00, pinmask = 0x00; /* Check the parameters */ //用断言来检查参数是否正确 assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode)); assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin)); /*---------------------------- GPIO Mode Configuration -----------------------*/ //将工作模式暂存至currentmode变量中 currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F); //如果欲设置为任意一种输出模式,则再检查“翻转速率”参数是否正确 if((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00) { /* Check the parameters */ assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed)); /* Output mode */ currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed; } /*---------------------------- GPIO CRL Configuration ------------------------*/ /* Configure the eight low port pins */ //设置低8位引脚(即pin0~pin7) if(((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00) { //独处当前配置字 tmpreg = GPIOx->CRL; for(pinpos = 0x00; pinpos < 0x08; pinpos++) { pos = ((uint32_t)0x01) << pinpos; /* Get the port pins position */ //获取将要配置的引脚号 currentpin = (GPIO_InitStruct->GPIO_Pin) & pos; if(currentpin == pos) { //先清除对应引脚的配置字 pos = pinpos << 2; /* Clear the corresponding low control register bits */ pinmask = ((uint32_t)0x0F) << pos; tmpreg &= ~pinmask; /* Write the mode configuration in the corresponding bits */ //写入新的配置字 tmpreg |= (currentmode << pos); /* Reset the corresponding ODR bit */ //若欲配置为上拉/下拉输入,则需要配置BRR和BSRR寄存器 if(GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) { GPIOx->BRR = (((uint32_t)0x01) << pinpos); } else { /* Set the corresponding ODR bit */ if(GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU) { GPIOx->BSRR = (((uint32_t)0x01) << pinpos); } } } } //写入低8位引脚配置字 GPIOx->CRL = tmpreg; } /*---------------------------- GPIO CRH Configuration ------------------------*/ /* Configure the eight high port pins */ //设置高8位引脚(即pin8~pin15),流程和低8位引脚一致 if(GPIO_InitStruct->GPIO_Pin > 0x00FF) { tmpreg = GPIOx->CRH; for(pinpos = 0x00; pinpos < 0x08; pinpos++) { pos = (((uint32_t)0x01) << (pinpos + 0x08)); /* Get the port pins position */ currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos); if(currentpin == pos) { pos = pinpos << 2; /* Clear the corresponding high control register bits */ pinmask = ((uint32_t)0x0F) << pos; tmpreg &= ~pinmask; /* Write the mode configuration in the corresponding bits */ tmpreg |= (currentmode << pos); /* Reset the corresponding ODR bit */ if(GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) { GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08)); } /* Set the corresponding ODR bit */ if(GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU) { GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08)); } } } GPIOx->CRH = tmpreg; }} 这段程序的流程:首先检查由结构体变量GPIO_InitStructure所传入的参数是否正确,然后对GPIO寄存器进行“读出->修改->写入”操作,完成对GPIO设备的配置工作。 总结起来就是:固件库首先将各个设备所有的寄存器的配置字进行预先定义在stm32f10x.h文件中,然后封装在结构或枚举变量中(存在相对应的stm32f10x_xxx.h,其中xxx代表gpio,spi,exti等外设模块),待用户调用对应的固件库函数时,会根据用户传入的参数从这些封装好的结构体或枚举变量中取出对应的配置字,最后写入寄存器中,完成对底层寄存器的配置。 stdint.h 一些具有可移植性的变量重定义 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657 /* * 'signed' is redundant below, except for 'signed char' and if * the typedef is used to declare a bitfield. * '__int64' is used instead of 'long long' so that this header * can be used in --strict mode. *//* 7.18.1.1 *//* exact-width signed integer types */typedef signed char int8_t;typedef signed short int int16_t;typedef signed int int32_t;typedef signed __int64 int64_t;/* exact-width unsigned integer types */typedef unsigned char uint8_t;typedef unsigned short int uint16_t;typedef unsigned int uint32_t;typedef unsigned __int64 uint64_t;/* 7.18.1.2 *//* smallest type of at least n bits *//* minimum-width signed integer types */typedef signed char int_least8_t;typedef signed short int int_least16_t;typedef signed int int_least32_t;typedef signed __int64 int_least64_t;/* minimum-width unsigned integer types */typedef unsigned char uint_least8_t;typedef unsigned short int uint_least16_t;typedef unsigned int uint_least32_t;typedef unsigned __int64 uint_least64_t;/* 7.18.1.3 *//* fastest minimum-width signed integer types */typedef signed int int_fast8_t;typedef signed int int_fast16_t;typedef signed int int_fast32_t;typedef signed __int64 int_fast64_t;/* fastest minimum-width unsigned integer types */typedef unsigned int uint_fast8_t;typedef unsigned int uint_fast16_t;typedef unsigned int uint_fast32_t;typedef unsigned __int64 uint_fast64_t;/* 7.18.1.4 integer types capable of holding object pointers */typedef signed int intptr_t;typedef unsigned int uintptr_t;/* 7.18.1.5 greatest-width integer types */typedef signed __int64 intmax_t;typedef unsigned __int64 uintmax_t;
以 led闪烁中的flashLed函数例子: 库函数操作简单,但是效率不如寄存器操作的高; 寄存器操作很复杂,因为要熟悉上百个寄存器,但是程序效率很高 /**下面是通过直接操作库函数的方式实现IO控制**/ while(1) { GPIO_ResetBits(GPIOB,GPIO_Pin_9); //LED0对应引脚GPIOF.9拉低,亮 等同LED0=0; GPIO_SetBits(GPIOB,GPIO_Pin_10); //LED1对应引脚GPIOF.10拉高,灭 等同LED1=1; delay_ms(500); //延时300ms GPIO_SetBits(GPIOB,GPIO_Pin_9); //LED0对应引脚GPIOF.0拉高,灭 等同LED0=1; GPIO_ResetBits(GPIOB,GPIO_Pin_10); //LED1对应引脚GPIOF.10拉低,亮 等同LED1=0; delay_ms(500); //延时300ms } /** *******************下面注释掉的代码是通过 位带 操作实现IO口控制**************************************/ int main(void) { delay_init(168); //初始化延时函数 LED_Init(); //初始化LED端口 while(1) { LED0=0; //LED0亮 LED1=1; //LED1灭 delay_ms(500); LED0=1; //LED0灭 LED1=0; //LED1亮 delay_ms(500); } } /************************************************************************************************** **/ /** *******************下面注释掉的代码是通过 直接操作寄存器 方式实现IO口控制**************************************/ int main(void) { delay_init(168); //初始化延时函数 LED_Init(); //初始化LED端口 while(1) { GPIOF->BSRRH=GPIO_Pin_9;//LED0亮 GPIOF->BSRRL=GPIO_Pin_10;//LED1灭 delay_ms(500); GPIOF->BSRRL=GPIO_Pin_9;//LED0灭 GPIOF->BSRRH=GPIO_Pin_10;//LED1亮 delay_ms(500); } } /************************************************************************************************** **/ /*我想说我更喜欢这样的,呵呵*/ while(1) { //FlashLED(); *(unsigned int*)(0x40010c0c) |=0x200 ; delay_ms(100); *(unsigned int*)(0x40010c0c) &=0xfffffdff ; delay_ms(100); }