综合复习试题
第一部分:C语言
一、请填写BOOL , float, 指针变量 与“零值”比较的 if 语句。 提示:这里“零值”可以是0, 0.0 , FALSE或者“空指针”。例如 int 变量 n 与“零值”比较的 if 语句为: if ( n == 0 ) if ( n != 0 ) 以此类推。 请写出 BOOL flag 与“零值”比较的 if 语句: if ( flag ) if ( !flag ) 请写出 float x 与“零值”比较的 if 语句: 标准答案示例: const float EPSINON = 0.00001; if ((x >= - EPSINON) && (x <= EPSINON) 不可将浮点变量用“==”或“!=”与数字 比较,应该设法转化成“>=”或“<=”此 类形式。 请写出 char *p 与“零值”比较的 if 语句: 标准答案: if (p == NULL) if (p != NULL) 二、以下为Windows NT下的32位C++程序,请计算sizeof的值 char str[] = “Hello” ; void Func ( char str[100]) char *p = str ; { int n = 10; 请计算 请计算 sizeof( str ) = 4 sizeof (str ) = 6 } sizeof ( p ) = 4 void *p = malloc( 100 ); 请计算 sizeof ( n ) = 4 sizeof ( p ) = 4 三、简答题
1、头文件中的 ifndef/define/endif 干什么用? 答:防止该头文件被重复引用。
2、#include
filename.h
3、const 有什么用途?(请至少说明两种) 答:
(1)可以定义 const 常量
(2)const 可以修饰函数的参数、返回值,甚至函数的定义体。被 const 修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
4、在C++ 程序中调用被 C编译器编译后的函数,为什么要加 extern “C”声明?
答:C++语言支持函数重载,C 语言不支持函数重载。函数被 C++编译后在库中的名字
与 C 语言的不同。假设某个函数的原型为: void foo(int x, int y); 该函数被 C 编译器编译后在库中的名字为_foo,而 C++编译器则会产生像 _foo_int_int 之类的名字。
C++提供了 C 连接交换指定符号 extern“C”来解决名字匹配问题。
5、请简述以下两个for循环的优缺点 // 第一个 // 第二个 for (i=0; i void GetMemory(char *p) char *GetMemory(void) { { p = (char *)malloc(100); char p[] = \} return p; void Test(void) } { void Test(void) char *str = NULL; { GetMemory(str); char *str = NULL; strcpy(str, \str = GetMemory(); printf(str); printf(str); } } 请问运行Test函数会有什么样的结请问运行Test函数会有什么样的结果? 果? 答:程序崩溃。 答:可能是乱码。 因为 GetMemory 并不能传递动态内因为 GetMemory 返回的是指向“栈内存, 存” Test 函数中的 str 一直都是 NULL。 的指针,该指针的地址不是 NULL,但strcpy(str, \将使程其原现的内容已经被清除,新内容不可序崩 知。 溃。 Void GetMemory2(char **p, int num) void Test(void) { { *p = (char *)malloc(num); char *str = (char *) } malloc(100); void Test(void) strcpy(str, “hello”); { free(str); char *str = NULL; if(str != NULL) GetMemory(&str, 100); { strcpy(str, \ strcpy(str, “world”); printf(str); printf(str); } } 请问运行Test函数会有什么样的结} 果? 请问运行Test函数会有什么样的结答:(1)能够输出 hello 果? (2)内存泄漏 答:篡改动态内存区的内容,后果难以预 料,非常危险。 因为 free(str);之后,str 成为野指针, if(str != NULL)语句不起作用。 五、编写strcpy函数 已知strcpy函数的原型是 char *strcpy(char *strDest, const char *strSrc); 其中strDest是目的字符串,strSrc是源字符串。 (1)不调用C++/C的字符串库函数,请编写函数 strcpy char *strcpy(char *strDest, const char *strSrc); { assert((strDest!=NULL) && (strSrc !=NULL)); // 2分 char *address = strDest; // 2分 while( (*strDest++ = * strSrc++) != ‘\\0’ ) // 2分 NULL ; return address ; // 2分 } (2)strcpy能把strSrc的内容复制到strDest,为什么还要char * 类型的返回值? 答:为了实现链式表达式。 // 2 分 例如 int length = strlen( strcpy( strDest, “hello world”) ); 六:编程题 1、写出程序把一个链表中的接点顺序倒排 listNode* reverse_list( listNode* head) //逆序 { ListNode* new_head=head; if(head==NULL || head->next==NULL) return head; new_head = reverse_list(head->next); head->next->next=head; head->next=NULL; //防止链表成为一个环,这是最关键的。 return new_head; } 2、写出程序删除链表中的所有接点 void del_all(node *head) { node *p; while(head!=NULL) { p=head->next; free(head); head=p; } cout<<\释放空间成功!\} 3、使用冒泡、选择法、快速排序法对数组进行排序 1)“冒泡法” 冒泡法大家都较熟悉。其原理为从a[0]开始,依次将其和后面的元素比较, 若a[0]>a[i],则交换它们,一直比较到a[n]。 同理对a[1],a[2],...a[n-1]处理,即完成排序。 void bubble(int *a,int n) /*定义两个参数:数组首地址与数组大小*/ { int i,j,temp; for(i=0;i for(j=i+1;j temp=a[i]; a[i]=a[j]; a[j]=temp; } }/*注意循环的上下限*/ } } 冒泡法原理简单,但其缺点是交换次数多,效率低。 下面介绍一种源自冒泡法但更有效率的方法“选择法”。 (2)“选择法” 选择法循环过程与冒泡法一致,它还定义了记号k=i 然后依次把a[k]同后面元素比较,若a[k]>a[j],则使k=j. 最后看看k=i是否还成立,不成立则交换a[k],a[i] 这样就比冒泡法省下许多无用的交换,提高了效率。 void choise(int *a,int n) { int i,j,min,temp; for(i=0;i min=i; /*给记号赋值*/ for(j=i+1;j if(a[min]>a[j]) min=j; /*是min总是指向最小元素*/ } if(i!=min) { /*当min!=i是才交换,否则a[i]即为 最小*/ temp=a[i]; a[i]=a[min]; a[min]=temp; } } } 选择法比冒泡法效率更高,但说到高效率,非“快速法”莫属,现在就让我们来了解它。 (3)“快速法” 快速法定义了三个参数,(数组首地址*a,要排序数组起始元素下标i,要排序数组结束元素下标j). 它首先选一个数组元素(一般为a[ (i+j)/2 ],即中间元素)作为参照,把比它小的元素放到它的左边,比它大的放在右边。 然后运用递归,在将它左,右两个子数组排序,最后完成整个数组的排序。 下面分析其代码: void quick(int *a,int i,int j) { int m,n,temp; int k; m=i; n=j; k=a[(i+j)/2]; /*选取的参照*/ do { while( a[m] m++; /* 从左到右找比k大的元素*/ while( a[n] >k && n>i ) n--; /* 从右到左找比k小的元素*/ if(m<=n) { /*若找到且满足条件,则交换*/ temp=a[m]; a[m]=a[n]; a[n]=temp; m++; n--; } } while(m<=n); if(m quick(a,m,j); /*运用递归*/ if(n>i) quick(a,i,n); } (4)“插入法” 插入法是一种比较直观的排序方法。 它首先把数组头两个元素排好序,再依次把后面的元素插入适当的位置。 把数组元素插完也就完成了排序。 void insert(int *a,int n) { int i,j,temp; for(i=1;i temp=a[i]; /*temp为要插入的元素*/ j=i-1; while( j>=0&&temp { /*从a[i-1]开始找比a[i]小的数,同时把数组元素向后移*/ a[j+1]=a[j]; j--; } a[j+1]=temp; /*插入*/ } } (5)“shell法” shell法是一个叫 shell 的美国人与1969年发明的。 它首先把相距k(k>=1)的那几个元素排好序,再缩小k值(一般取其一半),再排序,直到k=1时完成排序。 下面让我们来分析其代码: void shell(int *a,int n) { int i,j,k,x; k=n/2; /*间距值*/ while(k>=1) { for(i=k;i x=a[i]; j=i-k; while(j>=0&&x a[j+k]=a[j]; j-=k; } a[j+k]=x; } k/=2; /*缩小间距值*/ } } #include /*为了打印方便,我们写一个print吧。*/ void print(int *a,int n) { int i; for(i=0;i main() { /*为了公平,我们给每个函数定义一个相同数组*/ int a1[]={13,0,5,8,1,7,21,50,9,2}; int a2[]={13,0,5,8,1,7,21,50,9,2}; int a3[]={13,0,5,8,1,7,21,50,9,2}; int a4[]={13,0,5,8,1,7,21,50,9,2}; int a5[]={13,0,5,8,1,7,21,50,9,2}; printf(\ print(a1,10); printf(\ bubble(a1,10); print(a1,10); printf(\ choise(a2,10); print(a2,10); printf(\ quick(a3,0,9); print(a3,10); printf(\ insert(a4,10); print(a4,10); printf(\ shell(a5,10); print(a5,10); } 第二部分:C++与QT 1. C++对C语言做了很多改进,下列描述中 (D) 使得C语言发生了质变,即从面向过程变成面向对象。 A)增加了一些新的运算符 B)允许函数重载,并允许设置默认参数 C)规定函数说明必须用原型 D)引进类和对象的概念 2. 下面说法中正确的是(B ) A) 一个类只能定义一个构造函数,但可以定义多哥析构函数(只能一个) B) 一个类只能定义一个析构函数,但可以定义多个构造函数 C) 构造函数与析构函数同名,只是名字前加了一个求反符号(~) D)构造函数(不)可以指定返回类型,而析构函数不能指定任何返回类型,即使是void类型也不可以 3. 下面关于友元的描述中,错误的是( D)。 A) 友元函数可以访问该类的私有数据成员。 B) 一个类的友元类中的成员函数都是这个类的友元函数。 C) 友元可以提高程序的运行效率。 D) 类与类之间的友元关系可以继承。 4. 有关析构函数的说法不正确的是(B)。 A) 析构函数有切仅有一个 B) 析构函数和构造函数一样(不)可以有形参 C) 析构函数的功能是用来释放一个对象 D) 析构函数无任何函数类型 5. 下列对重载函数的描述中,(A)是错误的。 A) 重载函数中不允许使用默认参数 B) 重载函数中编译时根据参数表进行选择 C) 不要使用重载函数来描述毫无相干的函数 D) 构造函数重载将会给初始化带来多种方式 6. 下面叙述错误的是( C ) A. 派生类可以使用private派生 B. 对基类成员的访问必须是无二义性的 C. 基类成员的访问能力在派生类中维持不变 D. 赋值兼容规则也适用于多继承的组合 一、选择题:(共10小题,每题3分) 1.类的构造函数被自动调用执行的情况是在定义该类的( C ) A)成员函数时 B)数据成员时 C)对象时 D)友元函数时 2.说明友元函数使用关键字( A ) A)friend B)static C)const D)colatile 3.已知类X中的一个成员函数说明如下: Void Set(x &a); 其中,X&a的含义是(C ) A)指向类X的指针为a B)将a的地址赋给变量Sst C)a是类X的对象引用,用来做为Set()的形参 D)变量X是a按位相与作为函数Set()的参数 4.关于new运算符的下列描述中( D )是错误的. //创建数组时,定义构造函数必须没有参数,或全部为默认参数 A) 它可以用来动态创建对象和对象数组 B) 使用它创建对象或对象数组,可以使用运算符DELETE删除 C) 使用它创建对象时要调用构造函数 D) 使用它调用对象数组时不许指定初始值 5.允许访问类的所有对象的私有成员,公有成员和保护成员的是( A ) A)友元函数 B)成员函数 C)内联函数 D)重载函数 6.局部变量可以隐藏全局变量,那么在有同名全局变量和局部变量的情形时,可以用( A ) 提供对全局变量的访问 A) 域运算符 B) 类运算符 C) 重载 D) 引用 7.缺省析构函数的函数体是( C ) A)不存在 B)随机产生的 C)空的 D)无法确定的、 8.通常拷贝构造函数的参数表是( C ) //在VC6.0下 A)该类对象名 //编译不会通过 B)该类对象的成员名 //编译可以通过,但不是通常的标准方法 C)该类对象的引用名 //标准拷贝构造函数的方法 D)该类对象的指针名 //编译可以通过,但不是通常的标准方法 9.对定义重载函数的下列要求中,(D ) 是错误的 A)要求参数的个数不同 B)要求参数中至少有一个类型不同 C)要求参数个数相同时,参数类型不同 D)要求函数的返回值不同 10.对于多重继承,有(D ) A.一个派生类只能有一个基类 B.一个基类只能产生一个派生类 C.一个基类必须产生多个派生类 D.一个派生类可有多个基类 7. C和C++有什么不同?(4分) 1.从源文件文件的扩展名上有不同 2.包含的头文件不同 3.输入输出不同 4.C语言是弱类型检查机制 5.C++支持面向对象的支持 6.C++具有灵活的局部变量说明 7.C++的结构,联合,枚举名可直接作为类型名、 8.??等(详情参考C++课“02_c++语言基础知识2”) 8. 面向对象的三个基本特征及各自的作用(6分) 1.封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private,protected,public) 2.继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外编码的能力、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。 3.多态:指函数重载和重写,重写的主要的功能是将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说:允许将子类类型的指针赋值给父类类型的指针。 9. c和c++中的struct有什么不同?(5分) 1. C语言中的结构体中的元素只能是数据,没有函数。(函数指针不算,其本质是指针); 2.C++语言的结构体的元素不仅可以有数据,还可以有函数; 3.C语言和C++语言的结构体中的成员的访问权限不一样,C++结构体可以设置其中的成员的访问权限; 10. const符号的作用有哪些?(5分) Const 修饰指定的变量为只读类型,注意:不能说const修饰或定义常量常用情况 (1)const char*p (2)char const*p (3)char*const p (4) void A::fun() const { //??} 如果const位于星号的左侧,则const是用来修饰指针所指向的变量,即指针指向为常量 如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。 如果const位于函数后面,则表示类A的成员函数fun不可以修改数据成员; 11. 引用与指针有什么区别?(6分) 1 引用必须被初始化,指针不必。 2 引用初始化以后不能被改变,指针可以改变所指的对象。 3 不存在指向空值的引用,但是存在指向空值的指针。 4 重载操作符使用引用可以完成串试操作 本质:引用是一个已存在变量的别名,其实就是这个变量本身;而指针是一个变量,其值是另一个变量的地址。 12. 重载(overload)和重写(overried,有的书也叫做“覆盖”)的区别?(4 分) 常考的题目。从定义上来说:重载和重写都是不同函数使用同一函数名的现象 重载的特征: 1.具有相同的作用域(在同一个类中定义或都是全局函数) 2.函数名字相同,但是参数的类型或个数不同 3.virtual关键字可有可无 重写的特征 1. 不同的作用域(分别存在基类和派生类中) 2. 函数名称和参数完全相同 3. 一般需要将基类函数设置为virtual类型 13. static 在类内修饰成员的作用是什么,如何引用这些成员?(4分) Static 修饰的成员称为静态成员,分为静态数据成员和静态成员函数 静态数据成员:无论创建多少个对象,这个几个静态数据成员成员在内存中只 值占一个这个数据类型的空间。 静态成员函数:首先,静态成员函数属于这个类,但它不是具体的某个特定对象的成员函数。静态成员函数一般只能访问这个类的静态数据成员。 引用方法:类名::静态成员; 对象.静态成员; 14. 虚析构函数的作用是什么?(6分) 如果一个基类的指针指向派生类对象,当用delete运算符释放这个指针时,能保证不仅调用基类的析构函数,还能调用派生类的析构函数,达到避免内存泄露的作用 15. 类成员函数的重载、覆盖和隐藏区别? a.成员函数被重载的特征: (1)相同的范围(在同一个类中); (2)函数名字相同; (3)参数不同; (4)virtual 关键字可有可无。 b.覆盖是指派生类函数覆盖基类函数,特征是: (1)不同的范围(分别位于派生类与基类); (2)函数名字相同; (3)参数相同; (4)基类函数必须有virtual 关键字。 c.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下: (1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。 (2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆) 三、编程题:(共3小题 每小题10分) 16. 编写一个字符串处理的类,MyString,实现拷贝构造,‘=’号重载 等基 本函数。 17. 使用MyString作为成员对象实现一个Person类,包含:姓名,年龄。 18. 实现一个Student 类:有性名,年龄,学号,老师姓名。 19. 编写类String的构造函数、析构函数和赋值函数 已知类String的原型为: class String { public: String(const char *str = NULL); // 普通构造函数 String(const String &other); // 拷贝构造函数 ~ String(void); // 析构函数 String & operate =(const String &other); // 赋值函数 private: char *m_data; // 用于保存字符串 }; 请编写String的上述4个函数。 标准答案: // String 的析构函数 String::~String(void) // 3 分 { delete [] m_data; // 由于 m_data 是内部数据类型,也可以写成 delete m_data; } // String 的普通构造函数 String::String(const char *str) // 6 分 { if(str==NULL) { m_data = new char[1]; *m_data = ‘\\0’; } else { int length = strlen(str); m_data = new char[length+1]; // 若能加 NULL 判断则更好 strcpy(m_data, str); } } // 拷贝构造函数 String::String(const String &other) // 3 分 { int length = strlen(other.m_data); m_data = new char[length+1]; // 若能加 NULL 判断则更好 strcpy(m_data, other.m_data); } // 赋值函数 String & String::operate =(const String &other) // 13 分 { // (1) 检查自赋值 // 4 分 if(this == &other) return *this; // (2) 释放原有的内存资源 // 3 分 delete [] m_data; // (3)分配新的内存资源,并复制内容 // 3 分 int length = strlen(other.m_data); m_data = new char[length+1]; // 若能加 NULL 判断则更好 strcpy(m_data, other.m_data); // (4)返回本对象的引用 // 3 分 return *this; } Qt图形库部分: 1、什么是Qt图形库,你是怎么理解的? Qt是一个跨平台的C++图像界面应用程序框架。 优点:跨平台、面向对象、丰富的API、信号与槽的消息机制、强大的开发工具、XML支持、多语言支持,支持开源协议等 2、Qt库的QApplication类有什么作用? 3、Qt怎么处理绘图事件和鼠标事件? 4、举例说明怎么实现自定义信号和槽。 信号是一个特定的标示,能通过emit发射 槽是函数,它和瀑普通的C++的成员函数几乎一样,唯一不同的是槽可以和信号连接在一起,当发射这个信号后,槽函数能被自动调用。 作用是对象之间通信的高级接口。 使用方式,通过QObject::connect(??)函数来连接。连接方式有多种:1,一个信号连接一个槽;2,一个信号也可以连接多个槽;4,多个信号可以连接一个槽;4,一个信号可以与另外一个信号相连接。 5、要设计开发一个扫雷游戏,写出你的设计思路。 6、Qt 中窗口对象的父子关系如何指定?指定父对象有什么作用及好处? (5分) 两种方法:1,通过构造函数的参数指定。2,调用成员函数 void QWidget::setParent( QWidget * parent ); 好处:1,父对象show都会递归调用其所有子对象,让他们都显示出来,hide也一样,2,父对象被销毁时会递归销毁其所有的子对象,让内存管理更加简单。 二、编程题: 1.继承自QWidget ,自定义一个Button, 功能如下: 1)enter与leave有不同的背景状态变化 ,提示:enterEvent(), leaveEvent(); 2)Press 与Release 也有不同的背景状态变化 3)当鼠标点击时可以发出clicked() 信号 4)创建一个窗口,测试自定义的Button 第三部分:linux系统编程 一:选择题 (1)下列不是Linux系统进程类型的是 ( B )。 A 交互进程 B 批处理进程 C 守护进程 D 就绪进程(进程状态) (2)终止一个前台进程可能用到的命令和操作( A) A: kill B : A 进程管理系统 B 内存管理系统 C I/O管理系统 D硬件管理系统 (4)进程有三种状态:( C ) A 准备态、执行态和退出态 B 精确态、模糊态和随机态 C 运行态、就绪态和等待态 D 手工态、自动态和自由态 (5)( B )不是进程和程序的区别。 A 程序是一组有序的静态指令,进程是一次程序的执行过程 B 程序只能在前台运行,而进程可以在前台或后台运行 C 程序可以长期保存,进程是暂时的 D 程序没有状态,而进程是有状态的 二:填空题 (1)列举八种常见的进程间通信方式_管道、命名管道、消息队列、信号、信号量、内存映射、共享内存、套接字____. (2)网络上两太主机的进程间通信方式为__套接字______. (3)命名管道比无名管道的优势__不需要有父子进程关系____. (4)消息队列比命名管道和无名管道的优势__更方便多个进程间的通讯______. (5)按照逻辑结构不同进行数据库划分,Sqlite 数据库属于哪一类关系型数据库_. (6)SQL语言包括四部分_数据定义___语言、_数据操作____语言、_数据查询____语 言、__数据控制___语言,select 属于__数据查询____语言。 三:问答题 1. Linux根目录下一般有哪些目录?每个目录的作用是什么? a) 2. 用gcc编译文件test.c,要经过哪几个步骤? a) .c(c源程序) ----(预处理)?.i(预处理后的c程序) b) .i ----(编译)?.s(汇编文件) c) .s(汇编文件)----(汇编)?.o (目标文件) d) .o(目标文件)----(连接)?elf (可执行程序) e) gcc -E hello.c -o hello.i f) gcc –S hello.i -o hello.s g) gcc –c hello.s -o hello.o h) gcc hello.o -o hello_elf 3. gcc的参数“-L”,“-l”“-I”分别是什么意义?举例说明。 -I DIR 指定额外的头文件搜索路径DIR -L DIR 指定额外的库函数搜索路径DIR -l LIBRARY 指定链接时需要的其它函数库“LIBRARY” 4. 写一个脚本完成如下功能: 首先,让使用者输入一个文件名,脚本作如下判断: (1)这个文件是否存在,若不存在则给出一个“Filename does noet exist”提示,并中断程序。 (2)若这个文件存在,则判断它是文件还是目录,结果输出“Filename is regular file”或“Filename is directory” (3)判断用户对这个文件或目录所具有的权限并显示出来。 5. 什么是系统调用?请写出你对系统调用的理解。 6. 什么是标准I/O库?标准I/O库和系统调用有什么区别? (1)库函数由两类函数组成 1.不需要操作硬件的直接由函数内部完成,并将结果反馈给应用程序,如:strcpy等字符串操作函数 2.需要操作硬件的,则通过封装系统调用实现相应的功能,如:printf等 (2)并不是所有的系统调用都被封装成了库函数,系统提供的很多功能都必 须通过系统 调用才能实现 (3)系统调用是需要时间的,程序中频繁的使用系统调用会降低程序的运行 效率 1.当运行内核代码时,CPU工作在系统态, 在系统调用发生前需要保存用户态的栈和内存环境,然后转入系统态工作 2.系统调用结束后,又要切换回用户态.这种环境的切换会消耗掉许多时间 (4)库函数访问文件时根据需要设置不同类型的缓冲区,从而减少了直接调用IO系统调用的次数,提高了访问效率 7. 什么是进程?用fork()创建一个子进程时,系统会做什么工作? 程序是一些指令的有序集合,而进程是程序执行的过程 , 其包括进程的创 建、调度和消亡 。 fork() 函数用于从一个已存在 的 进程中创建一个新进程 , 新进程称 子进程 , 原进程称为父进程 . 为 8. 管道和命名管道使用上有什么不同之处? (1)管道是最古老的 UNIX IPC 方式 1.半双工,数据在同一时刻只能在一个方向上流动 2.管道没有名字,只能在公共祖先的进程中使用 3.特殊的文件,不存在文件系统,并且只存在于内存中 4.一端写入,另一端读出 5.缓冲区是有限的(4K字节) 6.所传送的数据是无格式的,写入或读出的时候需约定数据的格式 7.从管道读数据是一次性操作,数据一旦被读,它就从管道中被抛弃, 释放空间 以便写更多的数据。 8. #include int pipe(int filedes[2]) ; 功能:经由参数 filedes 返回两个文件描述符 参数: filedes[0] 为读而打开 filedes[1] 为写而打开 filedes[1] 的输出是 filedes[0] 的输入 返回值: 成功:返回 0 失败:返回 -1 (2)命名管道 (FIFO) 1.FIFO 文件存在于文件系统中,但 FIFO 中的内容却存放在内存中 , 2.该缓冲区大小为 1 页,即 4K 字节。 3.FIFO 有名字,不同的进程可以通过该命名管道进行通信。 4.传送的数据是无格式的 。 5.读数据是一次性操作,一旦被读,就从 FIFO 中被抛 弃,释放空间以 便写更多的数据。 6.共享 FIFO 的进程执行完所有的 I/O 操作以后, FIFO 将继续保存 在文件系统中以便以后使用 7. #include #include int mkfifo( const char * pathname, mode_t mode) ; 参数: pathname : 路径名 , 也就是创建后 FIFO 的名字 。 mode : mode_t 类型的权限描述符。 返回值: 成功:返回 0 失败:如第一个参数指定的路径名已存在 , 会出错且返回-1 。 9. 信号的处理方式有几种?怎样指定信号的处理方式? (1)如下方法进行处理: 1.忽略此信号 大多数信号都可以使用这种方式处理,但 SIGKILL 和 SIGSTOP 决不能 被忽略 , 原因是 :它们向超级用户提供一种使进程终止的可靠方法。 对大多数信号来说,系统默认动作是终止该进程。 3.自定义信号处理函数 用用户定义的信号处理函数处理该信号。 (2)处理信号的函数主要有如下三种 1.signal 函数 #include typedef void ( * sighandler_t )(int) ; sighandler_t signal(int signum, sighandler_t handler); 功能: 注册信号处理函数,即确定收到信号后处理函 数的入口地址。 参数: signum :信号编号 2.信号集函数组 sigemptyset sigfillset sigaddset sigdelset sigismember sigprocmask 3.Sigaction 函数 #include int sigaction(int signum, handler 的取值: 忽略该信号: SIG_IGN 执行系统默认动作: SIG_DFL 自定义信号处理函数:处理函数名 成功:返回函数指针,指向signum上一次注册的信号处理函数。 失败:返回 SIG_ERR 2.执行系统默认动作 返回值: const struct sigaction * act, struct sigaction * oldact) ; 功能: 检查或修改指定信号的设置 ( 或同时执行这两 种操作。 参数: signum :信号编号 act :新的信号设置指针 oldact :旧的信号设置指针 如果 act 指针非空,则要改变 指定信号的设 置,如果 oldact 指针非空,则系统将此前 指定信号的设置存入 oldact 。 返回值: 10. 进程和线程有什么区别? (1)相似性 进程(heavyweight process, HWP),只有一个线程的进程, 线程(lightweight process, LWP) 。 (2)调度 线程是 CPU 调度和分派的基本单位。 进程是系统中程序执行和资源分配的基本单位。 (3)并发性 不仅进程间可并发执行,而且一个进程中的多线程间也可以并发执行,从而 能 更有效地使用系统资源和提高系统的吞吐量 (4)系统开销 在进程切换时,涉及到整个当前进程 CPU 环境的保存以及新被调度运行的 进程的 CPU 环境的设置,而线程切换只需要保存和设置少量寄存器的内容, 并不涉及存储器管理方面的操作。 同一个进程中的多个线程共享同一地址空间,致使它们之间的同步和通信的 实现也变得比较容易。 (5)拥有资源: 进程是拥有系统资源的一个独立的单位,它可以拥有自己的资源。线程自己 一般不拥有资源(有一点必不可少的资源,程序计数器 , 一组寄存器和栈), 但它可以访问其所属进程的资源,如进程代码段,数据段以及系统资源(已 打开的文件, I/O 设备等)。 成功: 0 失败: -1 11. 什么是线程的互斥和同步,举例说明怎么实现线程的互斥和同步? (1)同步 :(信号量) 两个或两个以上的线程在运行过程中协 同步调,按预定的先后次序运行。 — (2)互斥 : (互斥锁) 一个公共资源同一时刻只能被一个线程 使用,多个线程不能同时使用公共资源。 12. 实现三个进程 其中一个是父进程,其余两个是该父进程创建的子进程,其中一个子进程运行“ls –l”指令,另一个子进程在暂停5s之后退出,父进程等待子进程的退出信息,待收集到该信息,父进程就返回。 13. 编写一个多进程多线程的程序: 要求创建4个子进程,每个进程都分别创建2个线程,进程和线程的功能不做要求,可只提供简单的打印语句。 使用系统调用pipe()建立一条管道线,两个子进程分别向管道各写一句话: 14. 编制一段程序,实现进程的管道通信: Child process 1 is sending a message! Child process 2 is sending a message! 而父进程则从管道中读出来自于两个子进程的信息,显示在屏幕上。 要求:父进程先接收子进程P1发来的消息,然后再接收子进程P2发来的消息。 15.生产者消费者: 有一仓库,生产者负责生产产品,并放入仓库,消费者从仓库中拿走产品(消费) 要求: 仓库中每次只能进入一人 ( 生产者或消费者 ) 。 仓库中可存放产品的数量最多10个,当仓库放满时,生产者不能再放入产品。 当仓库空时,消费者不能从中取出产品。 生产、消费速度不同。 //信号量---线程间通信 //“生产者消费者” 问题 #include #define msleep(x) usleep(x*1000) #define PRODUCT_SPEED 200 //生产速度 #define CONSUM_SPEED 450 //消费速度 #define INIT_NUM 3 //仓库原有产品数 #define TOTAL_NUM 10 //仓库容量 sem_t p_sem, c_sem, sh_sem; int num=INIT_NUM; //仓库产品数量 /*生产延时*/ void produce_delay(void) { msleep(PRODUCT_SPEED*10); } /*消费延时*/ void consume_delay(void) { msleep(CONSUM_SPEED*10); } /*添加产品到仓库*/ int add_to_lib(void) { sem_wait(&sh_sem); //互斥 num++; msleep(300); sem_post(&sh_sem); return num; } /*从仓库中取出产品*/ int sub_from_lib(void) { sem_wait(&sh_sem); //互斥 num--; msleep(300); sem_post(&sh_sem); return num; } /*生产者线程*/ void *producer(void *arg) { int num = 0; while(1) { produce_delay(); // 生产延时 sem_wait(&p_sem); //生产信号量减一 num = add_to_lib(); printf(\ sem_post(&c_sem); //消费信号量加一 } } /*消费者线程*/ void *consumer(void *arg) { int num = 0; while(1) { sem_wait(&c_sem); //消费者信号量减一 num = sub_from_lib(); printf(\ sem_post(&p_sem); //生产者信号量加一 consume_delay(); //消费延时 } } int main(int argc, char *argv[]) { pthread_t tid1,tid2; sem_init(&p_sem,0,TOTAL_NUM-INIT_NUM); sem_init(&c_sem,0,INIT_NUM); sem_init(&sh_sem,0,1); pthread_create(&tid1,NULL,producer,NULL); pthread_create(&tid2,NULL,consumer,NULL); pthread_join(tid1,NULL); pthread_join(tid2,NULL); return 0; } 第四部分:网络编程 1) :路由器工作在哪一层(A) A:链路层 B:网络层 C:传输层 D:应用层 2) 一台主机要实现通过局域网与另一个局域网通信,需要做的工作是 (D) 。 A: 配置域名服务器 B :定义一条本机指向所在网络的路由 C :定义一条本机指向所在网络网关的路由 D :定义一条本机指向目标网络网关的路由 3) 下列提法中,不属于ifconfig命令作用范围的是 (D) 。 A: 配置本地回环地址 B :配置网卡的IP地址 C :激活网络适配器 D: 加载网卡到内核中 4) 下面的网络协议中,面向连接的的协议是:(D) 。 A:传输控制协议 C :网际协议 B :用户数据报协议 D: 网际控制报文协议 5) 在局域网络内的某台主机用ping命令测试网络连接时发现网络内部的主机都可以连同,而不能与公网连通,问题可能是(C) A 主机IP设置有误 B 没有设置连接局域网的网关 C 局域网的网关或主机的网关设置有误 D 局域网DNS服务器设置有误 6) DHCP是动态主机配置协议的简称,其作用是可以使网络管理员通过一台服务 器来管理一个网络系统,自动地为一个网络中的主机分配(D)地址。 A :UDP B :MAC C: TCP D: IP 7) 在TCP/IP模型中,应用层包含了所有的高层协议,在下列的一些应用协议 中,(B)是能够实现本地与远程主机之间的文件传输工作。 A telnet B FTP C: SNMP D: NFS 8) 当我们与某远程网络连接不上时,就需要跟踪路由查看,以便了解在网络的 什么位置出现了问题,满足该目的的命令是(C)。 A :ping B: ifconfig C :traceroute D: netstat 9) 关于代理服务器的论述,正确的是( A)。 A :使用internet上已有的公开代理服务器,只需配置客户端。 B :代理服务器只能代理客户端http的请求。 C :设置好的代理服务器可以被网络上任何主机使用。 D :使用代理服务器的客户端没有自己的ip地址。 10) 公司需要把192.168.3.0 /255.255.255 网段划分成10个子网,子网掩码应 该是(C )? A:255.255.255.5 C:255.255.255.240 二:填空题 1) 网络192.168.220.0/24 定向广播地址是(192.168.220.0),受限的广播地址为(255.255.255.255 ),定向广播和受限广播的区别(); 在IP地址中的全1地址表示仅在本网络上(就是你这个主机所连接的局域网)进行广播。这种广播叫做受限的广播(limited broadcast) 如果net-id是具体的网络号,而host-id是全1,就叫做定向广播(directed broadcast),因为这是对某一个具体的网络(即net-id指明的网络)上的所有主机进行广播的一种地址。 对于192.168.1.25/24的网段 IP地址全0,即0.0.0.0为本地主机 IP地址全1,即255.255.255.255为本地广播(可能有几个不同的网段) 主机号全0,即192.168.1.0为本网段网络地址 主机号全1,即192.168.1.255为本网段广播地址 网络号全0,主机号全1,即0.0.0.255为本地网段 2) tcp/ip模型中进程到进程之间通信属于(传输)层,主机到主机属于(网络)层,设备到设备属于(链路)层,程序到程序属于(应用)层。 3) Ping命令的功能为(检测主机 )。 4) 进行远程登录的命令是 ( telenet )。 5) DNS 域名系统的作用是( 将域名和IP地址相互映射 )。 B:255.255.255.4 D:255.255.255.248 三:问答题(本题共4小题,每题6分共24分) 1) 简述TCP/IP协议中各层的主要功能,各有哪些主要协议。 TCP/IP 协议 族 应用层:应用程序间沟通的层 FTP 、 Telnet 、 HTTP 、 SMTP etc. 传输层:提供进程间的数据传送服务 这一层负责传送数据,提供应用程序端到端的逻辑通信 传输控制协议( TCP ),用户数据报协议( UDP ) 网络层:提供基本的数据封包传送功能 尽最大可能的让每个数据包都能够到达目的主机 IP协议 ( 网际协议 ) 链路层:对实际的网络媒体进行管理,定义如何使用实际 网络来传送数据 2) 什么是TCP 、UDP ?协议优缺点,应用场合? (1)TCP 协议 1.提供进程间通信能力 2.TCP 协议可以对包进行排序并检查错误,同时实现虚电路间的连接 3.TCP 数据包中包括序号和确认序号 4.未按照顺序收到的包可以被排序,而损坏的包可以被重传 5.面向连接的服务(例如 Telnet 、 FTP 、 Rlogin 、 X Windows 和 SMTP )需要高度的可靠性,它们使用TCP 来实现 (2)UDP 协议 1.提供进程间通信能力 2.UDP 与 TCP 位于同一层 3.不对数据包的顺序进行检查 4.没有错误检测和重传机制 5.应用于面向无连接的服务 6.主要用于那些面向查询 —— 应答的服务 7.例如 NFS 、 NTP (网络时间协议)和 DNS (域名解析协议) 8.欺骗 UDP 包比欺骗 TCP 包更容易,因为 UDP 没有建立初始化连接 (也可以称为握手),即与 UDP 相关的服务面临着更大的危险 3) 简述TCP/IP协议中三次握手的过程及涵义 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进 入SYN_SEND状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。 第二次握手:服务器收到syn包,必须确认客户的ACK(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态; 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认 包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。 三次握手(three times handshake;three-way handshaking)所谓的“三次握手”即对每次发送的数据量是怎样跟踪进行协商使数据段的发送和接收同步,根据所接收到的数据量而确定的数据确认数及数据发送、接收完毕后何时撤消联系,并建立虚连接。 4) 简述TCP/IP模型中应用层数据从主机A发送到主机B的整个过程。 /****************1_send_broad.c*****************/ 5) 利用setsockopt()使用嵌套字支持广播,并编写两个程序,其中一个 发送广播数据,另一个接收广播数据。 #include #define BROAD_IP \#define BROAD_PORT 2425 #define BROAD_MSG \ int main(int argc, char **argv) { int fd_sock = 0; const int socket_opt_bc = 1; struct sockaddr_in broad_addr; bzero(&broad_addr, sizeof(broad_addr)); broad_addr.sin_family = AF_INET; broad_addr.sin_port = htons(BROAD_PORT); inet_pton(AF_INET, BROAD_IP, &broad_addr.sin_addr); if((fd_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0){ perror(\ exit(1); } if(setsockopt(fd_sock, SOL_SOCKET, SO_BROADCAST, &socket_opt_bc, (socklen_t)sizeof(socket_opt_bc)) != 0){ perror(\ return -1; }// 设置广播选项 while(1){ sendto(fd_sock, BROAD_MSG, strlen(BROAD_MSG), 0, (struct sockaddr*)&broad_addr, sizeof(struct sockaddr)); } close(fd_sock); } /*****************1_recv_broad.c*********************/ #include #include #define SELF_PORT 2425 int main(int argc, char **argv) { int fd_sock = 0; char msg_buf[512] = \ int ret = 1; struct sockaddr_in self_addr; bzero(&self_addr, sizeof(self_addr)); self_addr.sin_family = AF_INET; self_addr.sin_port = htons(SELF_PORT); self_addr.sin_addr.s_addr = htonl(INADDR_ANY); if((fd_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0){ perror(\ exit(1); } if(bind(fd_sock, (struct sockaddr*)&self_addr, sizeof(struct sockaddr)) == -1){ perror(\ close(fd_sock); exit(2); }// 绑定 while(ret > 0){ bzero(msg_buf, sizeof(msg_buf)); ret = recvfrom(fd_sock, msg_buf, sizeof(msg_buf), 0, NULL, NULL); printf(\ } close(fd_sock); exit(EXIT_SUCCESS); } 6) 编写一个UDP类型的client/server实例。 /********************qq_thread.c**************************/ #include #include //接收线程:负责接收消息并显示 void *recv_thread(void* arg) { int udpfd = (int)arg; struct sockaddr_in addr; socklen_t addrlen = sizeof(addr); bzero(&addr,sizeof(addr)); while(1) { char buf[200] = \ char ipbuf[16] = \ recvfrom(udpfd, buf, sizeof(buf), 0, (struct sockaddr*)&addr, &addrlen); printf(\in_addr,ipbuf,sizeof(ipbuf)),buf); write(1,\ } return NULL; } int main(int argc,char *argv[]) { char buf[100] = \ int udpfd = 0; pthread_t tid; struct sockaddr_in addr; struct sockaddr_in cliaddr; //对套接字地址进行初始化 bzero(&addr,sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(8001); addr.sin_addr.s_addr = htonl(INADDR_ANY); bzero(&cliaddr,sizeof(cliaddr)); cliaddr.sin_family = AF_INET; cliaddr.sin_port = htons(8001); //创建套接口 if( (udpfd = socket(AF_INET,SOCK_DGRAM, 0)) < 0) { perror(\ exit(-1); } //设置端口 if(bind(udpfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror(\ close(udpfd); exit(-1); } printf(\\\\192.168.221.X\\\to sendmsg to somebody\\n\ //创建接收线程 pthread_create(&tid, NULL, recv_thread, (void*)udpfd); //创建线程 printf(\设置字体颜色 fflush(stdout); while(1) { //主线程负责发送消息 write(1,\表示标准输出 fgets(buf, sizeof(buf), stdin); //等待输入 buf[strlen(buf) - 1] = '\\0'; //确保输入的最后一位是'\\0' if(strncmp(buf, \ { char ipbuf[INET_ADDRSTRLEN] = \ inet_pton(AF_INET, buf+6, &cliaddr.sin_addr); //给addr套接字地址再赋值. printf(\%s successful!\\n\pbuf))); continue; } else if(strncmp(buf, \ { close(udpfd); exit(0); } sendto(udpfd, buf, strlen(buf),0,(struct sockaddr*)&cliaddr, sizeof(cliaddr)); } return 0; } 7) 编写一个UDP端口扫描程序。 8) 如何在编程中防止缓冲区溢出? 9) 写一个TCP 并发的服务器,实现echo功能。 /* ********************************************* * Filename: tcp_echo_pthread.c * Description: * Compiler: gcc * *********************************************/ #include #include #include void *Client_Process(void *arg) { int recvLen = 0; char recvbuf[2048] = \ // 接收缓冲区 int connfd = (int)arg; while((recvLen = read(connfd, recvbuf, sizeof(recvbuf))) > 0) { write(connfd, recvbuf, recvLen); } printf(\ close(connfd); return NULL; } int main(int argc, char *argv[]) { int sockfd = 0; // 套接字 struct sockaddr_in servAddr; // 服务器地址结构体 unsigned short port = 8000; // 监听端口 pthread_t thread_id; if(argc > 1) // 由参数接收端口 { port = atoi(argv[1]); } printf(\ sockfd = socket(AF_INET, SOCK_STREAM, 0);//创建TCP套接字 if(sockfd < 0) { perror(\ exit(-1); } bzero(&servAddr, sizeof(servAddr)); // 初始化服务器地址 servAddr.sin_family = AF_INET; servAddr.sin_port = htons(port); servAddr.sin_addr.s_addr = htonl(INADDR_ANY); printf(\ if(bind(sockfd, (struct sockaddr*)&servAddr, sizeof(struct sockaddr)) != 0) { perror(\ close(sockfd); exit(-1); } if(listen(sockfd, 10) != 0) { perror(\ close(sockfd); exit(-1); } printf(\ while(1) { char cliIP[INET_ADDRSTRLEN] = \用保存客户端IP地址 struct sockaddr_in cliAddr; //用于保存客户端地址 socklen_t cliAddrLen = sizeof(cliAddr); //必须初始化 int connfd = accept(sockfd, (struct sockaddr*)&cliAddr, &cliAddrLen);// 获得一个已经建立的连接 if(connfd < 0) { perror(\ close(sockfd); exit(-1); } inet_ntop(AF_INET, &cliAddr.sin_addr, cliIP, INET_ADDRSTRLEN); printf(\ if(connfd > 0) { pthread_create(&thread_id ,NULL,(void *)Client_Process,(void *)connfd); pthread_detach(thread_id); } } close(sockfd); return 0; } 10) 写一个UDP echo服务器 //=============================================== // 文件名称:udp_echo_srv.c // 功能描述:UDP Echo Server // 维护记录:2008-12-25 V1.0 // 2011-08-15 V1.1 //================================================ #include int main(int argc, char *argv[]) { int sockfd = 0; struct sockaddr_in bindAddr; unsigned short port = 8000; if(argc > 1) { port = atoi(argv[1]); } printf(\ sockfd = socket(AF_INET, SOCK_DGRAM, 0); if(sockfd < 0) { perror(\ exit(-1); } bzero(&bindAddr, sizeof(bindAddr)); bindAddr.sin_family = AF_INET; bindAddr.sin_port = htons(port); bindAddr.sin_addr.s_addr = htonl(INADDR_ANY); printf(\ if(bind(sockfd, (struct sockaddr*)&bindAddr, sizeof(bindAddr)) != 0) { perror(\ close(sockfd); exit(-1); } printf(\ while(1) { char recvBuf[2048] = \ char cliIP[INET_ADDRSTRLEN] = \ int recvLen = 0; struct sockaddr_in cliAddr; socklen_t cliAddrLen = sizeof(cliAddr); recvLen = recvfrom(sockfd, recvBuf, 2048, 0, (struct sockaddr*)&cliAddr, &cliAddrLen); printf(\ip = %s\\n\inet_ntop(AF_INET, &cliAddr.sin_addr, cliIP, INET_ADDRSTRLEN)); sendto(sockfd, recvBuf, recvLen, 0, (struct sockaddr*)&cliAddr, cliAddrLen); } close(sockfd); return 0; } //==================================================== // 文件名称:udp_echo_cli.c // 功能描述:UDP Echo Client // 维护记录:2008-12-25 V1.0 // 2011-08-15 V1.1 //==================================================== #include int main(int argc, char *argv[]) { int sockfd = 0; struct sockaddr_in srvAddr; unsigned short port = 8000; char *srvIP = \ if( argc > 1 ) //服务器ip地址 { srvIP = argv[1]; } if( argc > 2 ) //服务器端口 { port = atoi(argv[2]); } sockfd = socket(AF_INET, SOCK_DGRAM, 0);//创建UDP套接字 if(sockfd < 0) { perror(\ exit(-1); } bzero(&srvAddr, sizeof(srvAddr)); srvAddr.sin_family = AF_INET; srvAddr.sin_port = htons(port); inet_pton(AF_INET, srvIP, &srvAddr.sin_addr); printf(\ while(1) { char sendBuf[2048] = \ int len = 0; fgets(sendBuf, sizeof(sendBuf), stdin); sendto(sockfd, sendBuf, strlen(sendBuf), 0, (struct sockaddr*)&srvAddr, sizeof(srvAddr)); len = recvfrom(sockfd, sendBuf, sizeof(sendBuf), 0, NULL, NULL); sendBuf[len] = '\\0'; printf(\ } close(sockfd); return 0; } 第五部分:嵌入式平台架构 1. 什么是嵌入式系统,描述一下你对嵌入式系统的理解? 定义:以应用为中心,以计算机技术为基础,且软硬件可裁减,适应应用系统对功能、可靠性、成本、体积、功耗的严格要求的专用计算机系统 2. 搭建一个完整的嵌入式Linux开发平台需要做哪些工作,描述一下详细步 骤? 通常基于linux系统的嵌入式开发步骤如下: 1.开发目标硬件系统: 如选择微处理器、Flash及其它外设等 2.建立交叉开发环境: 安装交叉编译工具链、配置串口通信工具、配置网络通信工具等 3.开发Bootloader: 移植Bootloader,如vivi 4.移植linux内核: 如linux2.4.18内核移植 5.开发根文件系统: 如CRAMFS文件系统的制作 6.开发相关硬件的驱动程序:如LCD、网卡、GPIO等 Application7.开发上层的应用程序:如网络、QT GUI开发 嵌---------------------------------------------------------------- Device Driver入式嵌入式系统开发流程(有操作系 系统Root File Systerm 统) 一般的 硬件开发 开Embedded OS Kernel启动加载程 (bootloader) 发 方向 操作系统内核(kernel) BootLoader 根文件系统 (root) HardWare 设备驱动 (driver) 应用程序 (进程、线程、GUI、网 络、数据库等) Application裸--------------------------------------------------------------- 机程 嵌入式系统开发流程 序Device Driver的(无操作系统) 一般 硬件开发(PCB、原理图) 开Start.s发 启动代码(硬件初始化) 方 设备驱动(裸机下驱动) 向HardWare 应用程序(单任务) ------------------------------------------------------------------- 3. 什么是Bootloader,详细描述一下你对Bootloader的理解。常用的 Bootloader有哪些? (1)Bootloader:启动引导程序 (2)bootloader一般特指在操作系统下: 1.在操作系统运行之前运行的一段或多段程序 2.初始化硬件设备、建立系统的内存空间映射图,将系统的软件硬件环境带 到一个合适的状态,为调用操作系统内核准备好正确的环境 3.把操作系统内核映像加载到RAM中,并将系统控制权交给它 (3)针对不同的cpu架构对bootloader的要求不同 1.针对X86上有LILO、GRUB、ntloader等 2.针对ARM架构的有u-boot、vivi、armboot等 3.针对ppc架构的有ppcboot等 4. Bootloader的启动过程一般分为两个阶段,以vivi为例说明Bootloader的两个阶段完成什么工作。 系统复位输出vivi 版本号 禁止看门狗开发板初始化关闭中断内存映射初始化系统时钟初始化堆存储器(bank0-bank7)设置初始化MTD设备uart初始化初始化私有数据拷贝第二阶段代码初始化内置命令跳到第二阶段代码启动vivi-shell或boot vivi的stage1实际完成的主要任务: 1.禁用看门狗、关闭所有中断、初始化系统时钟 (为中断提供服务通常是OS设备驱动程序的责任,因此在Boot Loader 的执行全过程中可以不必响应任何中断) 2.内存设置(设置S3C2410的和内存相关的13个寄存器) 3.初始化调试指示灯(可选) 4.初始化UART,作为调试口(可选) 5.从NAND或NOR 复制代码到SDRAM 6.跳转到main,进入stage2 vivi的stage2实际完成的主要任务 step1:打印版本信息 step2:初始化GPIO step3: MMU初始化 step4:堆初始化 step5: MTD设备初始化 step6:存放vivi的私有参数 step7:添加vivi支持的命令 step8:根据用户选择进入vivi命令模式或启动内核 5. 什么是Bootloader的“启动加载模式”和“下载模式”? 启动加载(Boot loading)模式: BootLoader从目标机上某个固态存储设备上将操作系统加载到RAM中运 行,整个过程并没有用户的介入 在嵌入式产品发布时,BootLoader工作在此模式 下载(Down loading)模式: 开发人员可以使用各种命令,通过串口连接或网络连接等通信手段从主 机下载文件。比如:下载应用程序、数据文件、内核映像等 BootLoader的这种模式通常在系统更新时使用 6. vivi的命令是怎样实现的? 添加自定义命令的步骤-1: 首先构造一个user_command的实例,比如: user_command_t mytest_cmd = { “mytest\ command_mytest, NULL, “asdasd” }; 添加自定义命令的步骤-2: 然后实现命令的真正函数command_mytest void command_mytest(int argc, const char **argv) { if(argc == 2) { if((strncmp(argv[1],“help”,strlen(argv[1]))) ==0) { printk(“myTest Command Help\\n”); return; } } printk(“myTest Command exec\\n”); return; } 添加自定义命令的步骤-3 将命令加入到系统 在command.c中的 int init_builtin_cmds(void) 函数的最后加入:add_command(&mytest_cmd); 生成vivi镜像 make clean make menuconfig make 烧录:load flash vivi x 测试:进入vivi,执行命令:mytest 7. vivi传递给内核的参数“noinitrd root=/dev/bon/2 init=/linuxrc console=ttyS0”是什么意思? 内核启动所需要的参数: 根文件位置、init程序,内核控制台 代表默认从bon/2的位置启动根文件系统 root: 指定了根文件系统在FLASH分区中的位置 console:指定了内核启动后首选的控制台 init: 指定了linux内核启动完毕后调用的第一个用户态程序,即进程 号为2的进程。 8. 怎样把一个SD卡驱动程序编译到Linux内核中?描述一下详细步骤。 mmc/SD card driver 1、arch/arm/mach-s3c2440/mach-smdk2440.c platform结构中增加&s3c_device_sdi语句。 static struct platform_device *smdk2440_devices[] __initdata = { &s3c_device_usb, &s3c_device_lcd, &s3c_device_wdt, &s3c_device_i2c, &s3c_device_iis, &s3c_device_nand, &s3c_device_sdi, //add here }; 在make menuconfig时选上所有关于sd卡的信息,要想支持中文还得选上: Device Drivers ---> <*> MMC/SD/SDIO card support ---> --- MMC/SD/SDIO card support [ ] MMC debugging [ ] Allow unsafe resume (DANGEROUS) *** MMC/SD/SDIO Card Drivers *** <*> MMC block device driver [*] Use bounce buffer for simple hosts < > SDIO UART/GPS class support < > MMC host test driver *** MMC/SD/SDIO Host Controller Drivers *** < > Secure Digital Host Controller Interface support < > MMC/SD/SDIO over SPI <*> Samsung S3C SD/MMC Card Interface support File system--》 DOS/FAT/NT Filesystems--》 VFAT(Windows-95)fs support (437)Default codepage for FAT **把437改为936 Native language support --> [*]Simplified Chinese charset [*]NLS UTF-8 这样就可直接挂载了 mount -t vfat /dev/mmcblk0 /mnt 如果sd卡分区了 mount -t vfat /dev/mmcblk0p1 /mnt 直接读写sd就可以了 2、爱普板子SD卡少接两根引脚,分别是检测SD卡插拔的中断引脚和SD卡写保护的引 脚所以插上SD卡后要重启才能起作用 9. 你是怎样理解Linux内核配置编译机制的? 10. Linux内核源码各个子目录分别包含哪些内容? Linux内核源代码主要包含以下子目录: 1.arch 与体系结构相关的代码。对应于每个支持的体系结构,有一个相应的子目录如i386、arm等与之对应,相应目录下有对应的芯片与之对应 2.drivers 设备驱动代码,占整个内核代码量的一半以上 里面的每个子目录对应一类驱动程序,如: block:块设备、char:字符设备、mtd:存储类设备等 3.fs 文件系统代码,每个支持的文件系统有相应的子目录,如cramfs,yaffs,jffs2等 4.include 这里包括编译内核所需的大部分头文件 与平台无关的头文件放在include/linux子目录下 各类驱动或功能部件的头文件(/media、/mtd、/net等) 与平台相关的头文件/asm-arm、/asm-i386等 5.lib 与体系结构无关的内核库代码 与体系结构相关的内核库代码在/usr/*/lib下 6.init 内核初始化代码,其中的main.c中的start_kernel函数是系统引导起来后运行的第1个函数,这是研究内核开始工作的起点 7.ipc 内核进程间通信代码 8.mm 与体系结构无关的内存管理代码 与处理器体系结构相关的代码在/usr/*/mm下 9.kernel 内核管理的核心代码 10.net 网络部分代码,其每个目录对应于网络的一个方面 11.scripts 存放一些脚本文件,例如配置内核时用到的make menuconfig命令 12.Documentation 内核相关文档 13.Crypto 常用加密及散列算法,和一些压缩及CRC校验算法 11. 执行make menuconfig配置完Linux内核后,会生成两个文件.config和 include/autoconf.h,它们分别是什么作用? 12. 什么是根文件系统,你是怎样理解这一概念的? 根文件系统由各个目录组成,其它目录上的所有目录、文件的集合,也成为普通文件系统(内核镜像文件、内核启动后运行的第一个程序(init)、给用户提供操作界面的shell程序,应用程序所依赖的库等)。 13. 怎样制作一个cramfs格式的根文件系统?描述一下详细过程。 使用Busybox-1.16.1制作根文件系统 交叉编译器:arm-linux-4.3.2 一、 STEP 1 创建根目录: 在home目录下创建/home/rootfs文件夹,用作新构建的根文件系统的根目录 #cd /home #mkdir rootfs 创建根文件系统目录,主要包括以下目录 /dev /etc /lib /usr /var /proc /tmp /home /root /mnt /bin /sbin sys #mkdir dev etc lib usr var proc tmp home root mnt bin sbin sys 二、 STEP 2 构建 /bin、/sbin、linuxrc: 解压并进入busybox-1.16.1目录,执行 # make defconfig # make menuconfig Busybox Setting -----> Build Options -----> [*]Build BusyBox as a static binary (no shared libs) \\\\静态编译busybox 指定交叉编译器为 (/usr/local/arm/4.3.2/bin/arm-linux-)Cross Compiler prefix Installation Options -----> 选择上 Don’t use /usr Busybox Library Tuning---> [*]Username completion [*]Fancy shell prompts [*]Query cursor position from terminal 编译出的busybox的shell命令解释器支持显示当前路径及主机信息 保存退出 # make # make install 在busybox目录下会看见 _install目录,里面有/bin /sbin /usr linuxrc三个文件 (cp 复制时加参数-a)将这四个目录或文件拷到第一步所建的rootfs文件夹下。 三、 STEP 3 构建etc目录: 进入根文件系统rootfs的etc目录,执行如下操作: #cd /home/rootfs/etc 拷贝Busybox-1.2.0/examples/bootfloopy/etc/* 到当前目录下 # cp –r busybox-1.16.1/examples/bootfloopy/etc/* rootfs/etc inittab、fstab、profile、init.d/rcS 1. inittab: 用来作为linuxrc的解释脚本 2. init.d/rcS: inittab启动的第一个脚本,一般用来挂载系统必需的文件系统、必要的设备连接、设置IP地址、启动其他脚本等,默认仅有mount –a 3. fstab: 执行mount –a时,按照此文件挂载文件系统 4. profile:登陆完shell后自动执行此脚本,一般用来设置需要的环境变量 1) 修改inittab 把第二项改为::respawn:-/bin/login 把第三行tty2::askfirst:-/bin/sh 屏蔽掉 2) 拷贝虚拟机上的/etc/passwd, /etc/group, /etc/shadow到rootfs/etc 下。 # cp /etc/passwd rootfs/etc # cp /etc/group rootfs/etc # cp /etc/shadow roofs/etc 对以上三个文件修改,只保存与root相关的项,根据具体情况内容会有所不同。 修改passwd为root:x:0:0:root:/root:/bin/sh,即只保存与root相关项,而且最后改成/bin/ash 修改group为root:x:0:root 修改shadow为root:$1$x9yv1WlB$abJ2v9jOlOc9xW/y0QwPs.:14034:0:99999:7::: (注意这个值不一定一样,各人密码不一样,这个值也不一样) 登陆开发板时需输入用户名密码,同虚拟机相同 可以通过 passwd命令修改密码 或通过adduser增加新用户 3) 修改profile PATH=/bin:/sbin:/usr/bin:/usr/sbin 量 export LD_LIBRARY_PATH=/lib:/usr/lib 量 /bin/hostname sunplusedu USER=\LOGNAME=$USER HOSTNAME='/bin/hostname' PS1='[\%u@\\h \\W]# ' 显示主机名、当前路径等信息: \\\\可执行程序 环境变\\\\动态链接库 环境变 4) 修改 etc/init.d/rc.S文件 (注意mount –a 在下面) /bin/mount -n –t ramfs ramfs /var /bin/mount -n -t ramfs ramfs /tmp /bin/mount -n -t sysfs none /sys /bin/mount -n -t ramfs none /dev /bin/mkdir /var/tmp /bin/mkdir /var/modules /bin/mkdir /var/run /bin/mkdir /var/log /bin/mkdir -p /dev/pts /bin/mkdir -p /dev/shm /sbin/mdev -s /bin/mount -a echo /sbin/mdev > /proc/sys/kernel/hotplug 5) 修改etc/fstab文件,增加以下文件 none /dev/pts devpts mode=0622 0 0 tmpfs /dev/shm tmpfs defaults 0 0 四、 STEP 4 构建lib目录: 进入 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/lib目录 将以下动态库拷贝到rootfs/lib下 #cp *so* roofs/lib -a 进入以下目录 /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/armv4t/usr/lib 将以下动态库拷贝到rootfs/lib下 #cp ./libstdc++.so.* rootfs/lib -a 五、 使用nfs挂载验证 修改vivi启动参数,从虚拟机上启动根文件系统: param set linux_cmd_line \修改为实际的目录),rsize=1024,wsize=1024 ip=192.168.220.11(开发板地址可以随便填):192.168.220.xx(虚拟机的地 址):192.168.220.254:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0“ 或修改u-boot的参数: setenv bootargs ‘noinitrd root=/dev/nfs nfsroot=/home/rootfs/(修改为实际的目录),rsize=1024,wsize=1024 ip=192.168.220.11(开发板地址可以随便填):192.168.220.xx(虚拟机的地 址):192.168.220.254:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0’ 注意:vivi修改参数使用双引号,u-boot使用单引号 六、 SETP5下载验证 经过以上几步,一个最基本的linux根文件系统已经制作完毕 可以将制作好的根文件系统通过nfs、cramfs、yaffs工具进行验证 1)CRAMFS工具包主要有两个工具,分别是mkcramfs和cramfsck mkcramfs工具用来创建CRAMFS文件系统 # mkcramfs dirname outfile cramfsck工具用来进行CRAMFS文件系统的释放和检查 # cramfsck file -x dir 2)Yaffs工具包yaffs.tar.gz的使用: #tar zxvf yaffs.tar.gz #cd yaffs; #cd untils #make //会产生mkyaffsimage 用mkyaffsimage制作yaffs文件系统镜像: #mkyaffsimage usr usr.yaffs