2007年4月29日星期日

深入理解C语言指针的奥秘

指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。 要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的 类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区。让我们分别说明。
  先声明几个指针放着做例子:
  例一:
  (1)int*ptr;
  (2)char*ptr;
  (3)int**ptr;
  (4)int(*ptr)[3];
  (5)int*(*ptr)[4];
  
  指针的类型
  从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的类型:
  (1)int*ptr;//指针的类型是int*
  (2)char*ptr;//指针的类型是char*
  (3)int**ptr;//指针的类型是int**
  (4)int(*ptr)[3];//指针的类型是int(*)[3]
  (5)int*(*ptr)[4];//指针的类型是int*(*)[4]
  怎么样?找出指针的类型的方法是不是很简单?
  指针所指向的类型
  当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
  从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:
  (1)int*ptr;//指针所指向的类型是int
  (2)char*ptr;//指针所指向的的类型是char
  (3)int**ptr;//指针所指向的的类型是int*
  (4)int(*ptr)[3];//指针所指向的的类型是int()[3]
  (5)int*(*ptr)[4];//指针所指向的的类型是int*()[4]
  在指针的算术运算中,指针所指向的类型有很大的作用。
  指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C越来越熟悉时,你会发现,把与指针搅和在一起的"类型"这个概念分成"指针的 类型"和"指针所指向的类型"两个概念,是精通指针的关键点之一。我看了不少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起书来前 后矛盾,越看越糊涂。
指针的值,或者叫指针所指向的内存区或地址
  指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序里,所有类型的指针的值都是一个32位整数,因为 32位程序里内存地址全都是32位长。 指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为si zeof(指针所指向的类型)的一片内 存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值 是这块内存区域的首地址。
  指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。
  以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?
  指针本身所占据的内存区
  指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里,指针本身占据了4个字节的长度。
  指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。
  指针的算术运算
指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的。例如:
  例二:
  1、chara[20];
  2、int*ptr=a;
  ...
 ...
  3、ptr++;
  在上例中,指针ptr的类型是int*,它指向的类型是int,它被初始化为指向整形变量a。接下来的第3句中,指针ptr被加了1,编译器是这样处 理的:它把指针ptr的值加上了sizeof(int),在32位程序中,是被加上了4。由于地址是用字节做单位的,故ptr所指向的地址由原来的变量a 的地址向高地址方向增加了4个字节。
由于char类型的长度是一个字节,所以,原来ptr是指向数组a的第0号单元开始的四个字节,此时指向了数组a中从第4号单元开始的四个字节。
  我们可以用一个指针和一个循环来遍历一个数组,看例子:
  例三:
intarray[20];
int*ptr=array;
...
//此处略去为整型数组赋值的代码。
...
for(i=0;i<20;i++)
{
 (*ptr)++;
 ptr++;
}
  这个例子将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1,所以每次循环都能访问数组的下一个单元。

  再看例子:

  例四:

  1、chara[20];
  2、int*ptr=a;
  ...
  ...
  3、ptr+=5;
  在这个例子中,ptr被加上了5,编译器是这样处理的:将指针ptr的值加上5乘sizeof(int),在32位程序中就是加上了5乘4=20。由 于地址的单位是字节,故现在的ptr所指向的地址比起加5后的ptr所指向的地址来说,向高地址方向移动了20个字节。在这个例子中,没加5前的ptr指 向数组a的第0号单元开始的四个字节,加5后,ptr已经指向了数组a的合法范围之外了。虽然这种情况在应用上会出问题,但在语法上却是可以的。这也体现 出了指针的灵活性。

如果上例中,ptr是被减去5,那么处理过程大同小异,只不过ptr的值是被减去5乘sizeof(int),新的ptr指向的地址将比原来的ptr所指向的地址向低地址方向移动了20个字节。
  总结一下,一个指针ptrold加上一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指 向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值增加了n乘sizeof(ptrold所指向的类型)个字节。就是说, ptrnew所指向的内存区将比ptrold所指向的内存区向高地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。
  一个指针ptrold减去一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和 ptrold所指向的类型也相同。ptrnew的值将比ptrold的值减少了n乘sizeof(ptrold所指向的类型)个字节,就是说, ptrnew所指向的内存区将比ptrold所指向的内存区向低地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。
运算符&和*
这里&是取地址运算符,*是...书上叫做"间接运算符"。
  &a的运算结果是一个指针,指针的类型是a的类型加个*,指针所指向的类型是a的类型,指针所指向的地址嘛,那就是a的地址。
  *p的运算结果就五花八门了。总之*p的结果是p所指向的东西,这个东西有这些特点:它的类型是p指向的类型,它所占用的地址是p所指向的地址。
  例五:
inta=12;
intb;
int*p;
int**ptr;
p=&a;
//&a的结果是一个指针,类型是int*,指向的类型是int,指向的地址是a的地址。
*p=24;
//*p的结果,在这里它的类型是int,它所占用的地址是p所指向的地址,显然,*p就是变量a。
ptr=&p;
//&p的结果是个指针,该指针的类型是p的类型加个*,在这里是int **。该指针所指向的类型是p的类型,这里是int*。该指针所指向的地址就是指针p自己的地址。
*ptr=&b;
//*ptr是个指针,&b的结果也是个指针,且这两个指针的类型和所指向的类型是一样的,所以用&b来给*ptr赋值就是毫无问题的了。
**ptr=34;
//*ptr的结果是ptr所指向的东西,在这里是一个指针,对这个指针再做一次*运算,结果就是一个int类型的变量。
  指针表达式
一个表达式的最后结果如果是一个指针,那么这个表达式就叫指针表式。
  下面是一些指针表达式的例子:
  例六:
inta,b;
intarray[10];
int*pa;
pa=&a;//&a是一个指针表达式。
int**ptr=&pa;//&pa也是一个指针表达式。
*ptr=&b;//*ptr和&b都是指针表达式。
pa=array;
pa++;//这也是指针表达式。
例七:
char*arr[20];
char**parr=arr;//如果把arr看作指针的话,arr也是指针表达式
char*str;
str=*parr;//*parr是指针表达式
str=*(parr+1);//*(parr+1)是指针表达式
str=*(parr+2);//*(parr+2)是指针表达式
  由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,指针指向的内存区,指针自身占据的内存。

  好了,当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一个左值,否则就不是一个左值。
  在例七中,&a不是一个左值,因为它还没有占据明确的内存。*ptr是一个左值,因为*ptr这个指针已经占据了内存,其实*ptr就是指针pa,既然pa已经在内存中有了自己的位置,那么*ptr当然也有了自己的位置。
  数组和指针的关系
  数组的数组名其实可以看作一个指针。看下例:
  例八:
intarray[10]={0,1,2,3,4,5,6,7,8,9},value;
...
...
value=array[0];//也可写成:value=*array;
value=array[3];//也可写成:value=*(array+3);
value=array[4];//也可写成:value=*(array+4);
上例中,一般而言数组名array代表数组本身,类型是int[10],但如果把array看做指针的话,它指向数组的第0个单元,类型是int*,所指 向的类型是数组单元的类型即int。因此*array等于0就一点也不奇怪了。同理,array+3是一个指向数组第3个单元的指针,所以*(array +3)等于3。其它依此类推。

  例九:
char*str[3]={
 "Hello,thisisasample!",
 "Hi,goodmorning.",
 "Helloworld"
};
chars[80];
strcpy(s,str[0]);//也可写成strcpy(s,*str);
strcpy(s,str[1]);//也可写成strcpy(s,*(str+1));
strcpy(s,str[2]);//也可写成strcpy(s,*(str+2));
上例中,str是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指针数组名str当作一个指针的话,它指向数组的第0号单元,它的类型是char**,它指向的类型是char*。
*str也是一个指针,它的类型是char*,它所指向的类型是char,它指向的地址是字符串"Hello,thisisasample!"的第一个字 符的地址,即'H'的地址。 str+1也是一个指针,它指向数组的第1号单元,它的类型是char**,它指向的类型是char*。

  *(str+1)也是一个指针,它的类型是char*,它所指向的类型是char,它指向 "Hi,goodmorning."的第一个字符'H',等等。

  下面总结一下数组的数组名的问题。声明了一个数组TYPEarray[n],则数组名称array就有了两重含义:第一,它代表整个数组,它的 类型是TYPE[n];第二 ,它是一个指针,该指针的类型是TYPE*,该指针指向的类型是TYPE,也就是数组单元的类型,该指针指向的内存区就是数 组第0号单元,该指针自己占有单独的内存区,注意它和数组第0号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++的表达式是错误 的。
  在不同的表达式中数组名array可以扮演不同的角色。
  在表达式sizeof(array)中,数组名array代表数组本身,故这时sizeof函数测出的是整个数组的大小。
在表达式*array中,array扮演的是指针,因此这个表达式的结果就是数组第0号单元的值。sizeof(*array)测出的是数组单元的大小。
  表达式array+n(其中n=0,1,2,....。)中,array扮演的是指针,故array+n的结果是一个指针,它的类型是TYPE*,它指向的类型是TYPE,它指向数组第n号单元。故sizeof(array+n)测出的是指针类型的大小。
例十
intarray[10];
int(*ptr)[10];
ptr=&array;:
上例中ptr是一个指针,它的类型是int(*)[10],他指向的类型是int[10] ,我们用整个数组的首地址来初始化它。在语句ptr=&array中,array代表数组本身。

  本节中提到了函数sizeof(),那么我来问一问,sizeof(指针名称)测出的究竟是指针自身类型的大小呢还是指针所指向的类型的大小?答案是前者。例如:
int(*ptr)[10];
  则在32位程序中,有:
sizeof(int(*)[10])==4
sizeof(int[10])==40
sizeof(ptr)==4
实际上,sizeof(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。
指针和结构类型的关系
可以声明一个指向结构类型对象的指针。
  例十一:
structMyStruct
{
 inta;
 intb;
 intc;
}
MyStructss={20,30,40};
//声明了结构对象ss,并把ss的三个成员初始化为20,30和40。
MyStruct*ptr=&ss;
//声明了一个指向结构对象ss的指针。它的类型是MyStruct*,它指向的类型是MyStruct。
int*pstr=(int*)&ss;
//声明了一个指向结构对象ss的指针。但是它的类型和它指向的类型和ptr是不同的。
  请问怎样通过指针ptr来访问ss的三个成员变量?
  答案:
ptr->a;
ptr->b;
ptr->c;
  又请问怎样通过指针pstr来访问ss的三个成员变量?
  答案:
*pstr;//访问了ss的成员a。
*(pstr+1);//访问了ss的成员b。
*(pstr+2)//访问了ss的成员c。
  虽然我在我的MSVC++6.0上调式过上述代码,但是要知道,这样使用pstr来访问结构成员是不正规的,为了说明为什么不正规,让我们看看怎样通过指针来访问数组的各个单元:
  例十二:
intarray[3]={35,56,37};
int*pa=array;
  通过指针pa访问数组array的三个单元的方法是:
*pa;//访问了第0号单元
*(pa+1);//访问了第1号单元
*(pa+2);//访问了第2号单元

从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。
  所有的C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间没有空隙。但在存放结构对象的各个成员时,在某 种编译环境下,可能会需要字对齐或双字对齐或者是别的什么对齐,需要在相邻两个成员之间加若干个"填充字节",这就导致各个成员之间可能会有若干个字节的 空隙。
  所以,在例十二中,即使*pstr访问到了结构对象ss的第一个成员变量a,也不能保证*(pstr+1)就一定能访问到结构成员b。因为成员a和成 员b之间可能会有若干填充字节,说不定*(pstr+1)就正好访问到了这些填充字节呢。这也证明了指针的灵活性。要是你的目的就是想看看各个结构成员之 间到底有没有填充字节,嘿,这倒是个不错的方法。
过指针访问结构成员的正确方法应该是象例十二中使用指针ptr的方法。
  指针和函数的关系
  可以把一个指针声明成为一个指向函数的指针。intfun1(char*,int);
int(*pfun1)(char*,int);
pfun1=fun1;
....
....
inta=(*pfun1)("abcdefg",7);//通过函数指针调用函数。
可以把指针作为函数的形参。在函数调用语句中,可以用指针表达式来作为实参。
  例十三:
intfun(char*);
inta;
charstr[]="abcdefghijklmn";
a=fun(str);
...
...
intfun(char*s)
{
intnum=0;
for(inti=0;i{
num+=*s;s++;
}
returnnum;
}
  这个例子中的函数fun统计一个字符串中各个字符的ASCII码值之和。前面说了,数组的名字也是一个指针。在函数调用中,当把str作为实参传递给 形参s后,实际是把str的值传递给了s,s所指向的地址就和str所指向的地址一致,但是str和s各自占用各自的存储空间。在函数体内对s进行自加1 运算,并不意味着同时对str进行了自加1运算。
指针类型转换
当我们初始化一个指针或给一个指针赋值时,赋值号的左边是一个指针,赋值号的右边是一个指针表达式。在我们前面所举的例子中,绝大多数情况下,指针的类型和指针表达式的类型是一样的,指针所指向的类型和指针表达式所指向的类型是一样的。
  例十四:
  1、floatf=12.3;
  2、float*fptr=&f;
  3、int*p;
   在上面的例子中,假如我们想让指针p指向实数f,应该怎么搞?是用下面的语句吗?

  p=&f;

  不对。因为指针p的类型是int*,它指向的类型是int。表达式&f的结果是一个指针,指针的类型是float*,它指向的类型是 float。两者不一致,直接赋值的方法是不行的。至少在我的MSVC++6.0上,对指针的赋值语句要求赋值号两边的类型一致,所指向的类型也一致,其 它的编译器上我没试过,大家可以试试。为了实现我们的目的,需要进行"强制类型转换":
p=(int*)&f;
如果有一个指针p,我们需要把它的类型和所指向的类型改为TYEP*TYPE, 那么语法格式是:
  (TYPE*)p;
  这样强制类型转换的结果是一个新指针,该新指针的类型是TYPE*,它指向的类型是TYPE,它指向的地址就是原指针指向的地址。而原来的指针p的一切属性都没有被修改。
  一个函数如果使用了指针作为形参,那么在函数调用语句的实参和形参的结合过程中,也会发生指针类型的转换。
  例十五:
voidfun(char*);
inta=125,b;
fun((char*)&a);
...
...
voidfun(char*s)
{
charc;
c=*(s+3);*(s+3)=*(s+0);*(s+0)=c;
c=*(s+2);*(s+2)=*(s+1);*(s+1)=c;
}
}
注意这是一个32位程序,故int类型占了四个字节,char类型占一个字节。函数fun的作用是把一个整数的四个字节的顺序来个颠倒。注意到了吗?在函 数调用语句中,实参&a的结果是一个指针,它的类型是int*,它指向的类型是int。形参这个指针的类型是char*,它指向的类型是 char。这样,在实参和形参的结合过程中,我们必须进行一次从int*类型到char*类型的转换。结合这个例子,我们可以这样来想象编译器进行转换的 过程:编译器先构造一个临时指针char*temp, 然后执行temp=(char*)&a,最后再把temp的值传递给s。所以最后的结果 是:s的类型是char*,它指向的类型是char,它指向的地址就是a的首地址。

  我们已经知道,指针的值就是指针指向的地址,在32位程序中,指针的值其实是一个32位整数。那可不可以把一个整数当作指针的值直接赋给指针呢?就象下面的语句:
unsignedinta;
TYPE*ptr;//TYPE是int,char或结构类型等等类型。
...
...
a=20345686;
ptr=20345686;//我们的目的是要使指针ptr指向地址20345686(十进制

ptr=a;//我们的目的是要使指针ptr指向地址20345686(十进制)
编译一下吧。结果发现后面两条语句全是错的。那么我们的目的就不能达到了吗?不,还有办法:
unsignedinta;
TYPE*ptr;//TYPE是int,char或结构类型等等类型。
...
...
a=某个数,这个数必须代表一个合法的地址;
ptr=(TYPE*)a;//呵呵,这就可以了。
严格说来这里的(TYPE*)和指针类型转换中的(TYPE*)还不一样。这里的(TYPE*)的意思是把无符号整数a的值当作一个地址来看待。上面强调了a的值必须代表一个合法的地址,否则的话,在你使用ptr的时候,就会出现非法操作错误。

  想想能不能反过来,把指针指向的地址即指针的值当作一个整数取出来。完 全可以。下面的例子演示了把一个指针的值当作一个整数取出来,然后再把这个整数当作一个地址赋给一个指针:
  例十六:
inta=123,b;
int*ptr=&a;
char*str;
b=(int)ptr;//把指针ptr的值当作一个整数取出来。
str=(char*)b;//把这个整数的值当作一个地址赋给指针str。
  现在我们已经知道了,可以把指针的值当作一个整数取出来,也可以把一个整数值当作地址赋给一个指针。
  指针的安全问题
看下面的例子:
  例十七:
chars='a';
int*ptr;
ptr=(int*)&s;
*ptr=1298;
  指针ptr是一个int*类型的指针,它指向的类型是int。它指向的地址就是s的首地址。在32位程序中,s占一个字节,int类型占四个字节。最 后一条语句不但改变了s所占的一个字节,还把和s相临的高地址方向的三个字节也改变了。这三个字节是干什么的?只有编译程序知道,而写程序的人是不太可能 知道的。也许这三个字节里存储了非常重要的数据,也许这三个字节里正好是程序的一条代码,而由于你对指针的马虎应用,这三个字节的值被改变了!这会造成崩 溃性的错误。
  让我们再来看一例:
  例十八:
  1、chara;
  2、int*ptr=&a;
  ...
  ...
  3、ptr++;
  4、*ptr=115;
  该例子完全可以通过编译,并能执行。但是看到没有?第3句对指针ptr进行自加1运算后,ptr指向了和整形变量a相邻的高地址方向的一块存储区。这 块存储区里是什么?我们不知道。有可能它是一个非常重要的数据,甚至可能是一条代码。而第4句竟然往这片存储区里写入一个数据!这是严重的错误。所以在使 用指针时,程序员心里必须非常清楚:我的指针究竟指向了哪里。在用指针访问数组的时候,也要注意不要超出数组的低端和高端界限,否则也会造成类似的错误。
  在指针的强制类型转换:ptr1=(TYPE*)ptr2中,如果sizeof(ptr2的类型)大于sizeof(ptr1的类型),那么在使用指 针ptr1来访问ptr2所指向的存储区时是安全的。如果sizeof(ptr2的类型)小于sizeof(ptr1的类型),那么在使用指针ptr1来 访问ptr2所指向的存储区时是不安全的。至于为什么,读者结合例十七来想一想,应该会明白的。

2007年4月25日星期三

easy linux

linux目录架构
/ 根目录
/bin 常用的命令 binary file 的目錄
/boot 存放系统启动时必须读取的档案,包括核心 (kernel) 在内
/boot/grub/menu.lst GRUB设置
/boot/vmlinuz 内核
/boot/initrd 核心解壓縮所需 RAM Disk
/dev 系统周边设备
/etc 系统相关设定文件
/etc/DIR_COLORS 设定颜色
/etc/HOSTNAME 设定用户的节点名
/etc/NETWORKING 只有YES标明网络存在
/etc/host.conf 文件说明用户的系统如何查询节点名
/etc/hosts 设定用户自已的IP与名字的对应表
/etc/hosts.allow 设置允许使用inetd的机器使用
/etc/hosts.deny 设置不允许使用inetd的机器使用
/etc/hosts.equiv 设置远端机不用密码
/etc/inetd.conf 设定系统网络守护进程inetd的配置
/etc/gateways 设定路由器
/etc/protocols 设定系统支持的协议
/etc/named.boot 设定本机为名字服务器的配置文件
/etc/sysconfig/network-scripts/ifcfg-eth0 设置IP
/etc/resolv.conf 设置DNS
/etc/X11 X Window的配置文件,xorg.conf 或 XF86Config 這兩個 X Server 的設定檔
/etc/fstab 记录开机要mount的文件系统
/etc/inittab 设定系统启动时init进程将把系统设置成什么样的runlevel
/etc/issue 记录用户登录前显示的信息
/etc/group 设定用户的组名与相关信息
/etc/passwd 帐号信息
/etc/shadow 密码信息
/etc/sudoers 可以sudo命令的配置文件
/etc/securetty 设定哪些终端可以让root登录
/etc/login.defs 所有用户登录时的缺省配置
/etc/exports 设定NFS系统用的
/etc/init.d/ 所有服務的預設啟動 script 都是放在這裡的,例如要啟動或者關閉
/etc/xinetd.d/ 這就是所謂的 super daemon 管理的各項服務的設定檔目錄
/etc/modprobe.conf 内核模块额外参数设定
/etc/syslog.conf 日志设置文件
/home 使用者家目录
/lib 系统会使用到的函数库
/lib/modules kernel 的相关模块
/var/lib/rpm rpm套件安装处
/lost+found 系統不正常產生錯誤時,會將一些遺失的片段放置於此目錄下
/mnt 外设的挂载点
/media 与/mnt类似
/opt 主机额外安装的软件
/proc 虚拟目录,是内存的映射
/proc/version 内核版本
/proc/sys/kernel 系统内核功能
/root 系统管理员的家目录
/sbin 系统管理员才能执行的指令
/srv 一些服務啟動之後,這些服務所需要取用的資料目錄
/tmp 一般使用者或者是正在執行的程序暫時放置檔案的地方
/usr 最大的目录,存许应用程序和文件
/usr/X11R6: X-Window目录
/usr/src: Linux源代码
/usr/include:系统头文件
/usr/openwin 存放SUN的OpenWin
/usr/man 在线使用手册
/usr/bin 使用者可執行的 binary file 的目錄
/usr/local/bin 使用者可執行的 binary file 的目錄
/usr/lib 系统会使用到的函数库
/usr/local/lib 系统会使用到的函数库
/usr/sbin 系统管理员才能执行的指令
/usr/local/sbin 系统管理员才能执行的指令
/var 日志文件
/var/log/secure 記錄登入系統存取資料的檔案,例如 pop3, ssh, telnet, ftp 等都會記錄在此檔案中
/var/log/wtmp 記錄登入者的訊息資料, last
/var/log/messages 幾乎系統發生的錯誤訊息
/var/log/boot.log 記錄開機或者是一些服務啟動的時候,所顯示的啟動或關閉訊息
/var/log/maillog 紀錄郵件存取或往來( sendmail 與 pop3 )的使用者記錄
/var/log/cron 記錄 crontab 這個例行性服務的內容
/var/log/httpd, /var/log/news, /var/log/mysqld.log, /var/log/samba, /var/log/procmail.log:
分別是幾個不同的網路服務的記錄檔

一些常用的基本命令:
uname -a 查看内核版本
ls -al 显示所有文件的属性
pwd 显示当前路径
cd - 返回上一次目录 cd ~ 返回主目录
date s 设置时间、日期
cal 显示日历 cal 2006
bc 计算器具
man & info 帮助手册
locale 显示当前字体 locale -a 所有可用字体 /etc/sysconfig/i18n设置文件
LANG=en 使用英文字体
sync 将数据同步写入硬盘
shutdonw -h now & half & poweroff 关机
reboot 重启
startx & init 5 进入图形介面
/work & ?work 向上、下查找文档内容
chgrp 改变档案群组 chgrp testing install.log
chown 改变所属人 chown root:root install.log
chmod 改变属性 chmod 777 install.log read=4 write=2 execute=1
cp 复制 cp filename
rm 删除文件 rm -rf filename 强制删除文件
rmdir 删除文件夹
mv 移动 mv 123.txt 222.txt 重命名
mkdir 创建文件夹
touch 创建文件 更新当前时间
cat 由第一行开始显示 cat |more 分页
nl 在内容前加行号
more & less 一面一面翻动
head -n filename 显示第N行内容
tail -n filename 显示后N行内容
od 显示非纯文档
df -h 显示分区空间
du 显示目录或文件的大小
fdisk 分区设置 fdisk -l /dev/hda 显示硬盘分区状态
mkfs 建立各种文件系统 mkfs -t ext3 /dev/ram15
fsck 检查和修复LINUX档案
ln 硬链接 ln -s 软件链接
whereis 查找命令
locate 查找
find 查找 find / -name "***.***"
which 查看工具
whoami 显示当前用户
gcc -v 查看GCC版本
chattr +i filename 禁止删除 chattr -i filename 取消禁止
lsattr 显示隐藏档属性
updatedb 更新资料库
mke2fs 格式化 mkfs -t ext3
dd if=/etc/passwd of=/tmp/passwd.bak 备份
mount 列出系统所有的分区
mount -t iso9660 /dev/cdrom /mnt/cdrom 挂载光盘
mount -t vfat /dev/fd0 /mnt/floppy 挂载软盘
mount -t vfat -o iocharset=utf8,umask=000 /dev/hda2 /mnt/hda2 挂载fat32分区
mount -t ntfs -o nls=utf8,umask=000 /dev/hda3 /mnt/hda3 挂载ntfs分区
Linux-NTFS Project: http://linux-ntfs.sourceforge.net/
umount /mnt/hda3 缷载
ifconfig 显示或设置网络设备
service network restart 重启网卡
ifdown eth0 关闭网卡
ifup eth0 开启网卡
clear 清屏
history 历史记录 !55 执行第55个指令
stty 设置终端 stty -a
fdisk /mbr 删除GRUB
at 僅進行一次的工作排程
crontab 循環執行的例行性命令 [e]编辑,[l]显示,[r]删除任务
& 后台运行程序 tar -zxvf 123.tar.gz & --------->后台运行
jobs 观看后台暂停的程序 jobs -l
fg 将后台程序调到前台 fg n ------>n是数字,可以指定进行那个程序
bg 让工作在后台运行
kill 结束进程 kill -9 PID [9]强制结束,[15]正常结束,[l]列出可用的kill信号
ps aux 查看后台程序
top 查看后台程序 top -d 2 每两秒更新一次 top -d 2 -p10604 观看某个PID
top -b -n 2 > /tmp/top.txt ----->將 top 的資訊進行 2 次,然後將結果輸出到 /tmp/top.txt
pstree 以树状图显示程序 [A]以 ASCII 來連接, [u]列出PID, [p]列出帐号
killall 要刪除某個服務 killall -9 httpd
free 显示内存状态 free -m -------->以M为单位显示
uptime 显示目前系统开机时间
netstat 显示网络状态 netstat -tulnp------>找出目前系統上已在監聽的網路連線及其 PID
dmesg 显示开机信息 demsg | more
nice 设置优先权 nice -n -5 vi & ----->用 root 給一個 nice 植為 -5 ,用於執行 vi
renice 调整已存在优先权
runlevel 显示目前的runlevel
depmod 分析可载入模块的相依性
lsmod 显示已载入系统的模块
modinfo 显示kernel模块的信息
insmod 载入模块
modprobe 自动处理可载入模块
rmmod 删除模块
chkconfig 检查,设置系统的各种服务 chkconfig --list ----->列出各项服务状态
ntsysv 设置系统的各种服务
cpio 备份文件

压缩命令:
*.Z compress 程式壓縮的檔案;
*.bz2 bzip2 程式壓縮的檔案;
*.gz gzip 程式壓縮的檔案;
*.tar tar 程式打包的資料,並沒有壓縮過;
*.tar.gz tar 程式打包的檔案,其中並且經過 gzip 的壓縮
compress filename 压缩文件 加[-d]解压 uncompress
gzip filename 压缩 加[-d]解压 zcat 123.gz 查看压缩文件内容
bzip2 -z filename 压缩 加[-d]解压 bzcat filename.bz2 查看压缩文件内容
tar -cvf /home/123.tar /etc 打包,不压缩
tar -xvf 123.tar 解开包
tar -zxvf /home/123.tar.gz 以gzip解压
tar -jxvf /home/123.tar.bz2 以bzip2解压
tar -ztvf /tmp/etc.tar.gz 查看tar内容
cpio -covB > [file|device] 份份
cpio -icduv < [file|device] 还原

vi一般用法
一般模式 编辑模式 指令模式
h 左 a,i,r,o,A,I,R,O :w 保存
j 下 进入编辑模式 :w! 强制保存
k 上 dd 删除光标当前行 :q! 不保存离开
l 右 ndd 删除n行 :wq! 保存后离开
0 移动到行首 yy 复制当前行 :e! 还原原始档
$ 移动到行尾 nyy 复制n行 :w filename 另存为
H 屏幕最上 p,P 粘贴 :set nu 设置行号
M 屏幕中央 u 撤消 :set nonu 取消行号
L 屏幕最下 [Ctrl]+r 重做上一个动作 ZZ 保存离开
G 档案最后一行 [ctrl]+z 暂停退出 :set nohlsearch 永久地关闭高亮显示
/work 向下搜索 :sp 同时打开两个文档
?work 向上搜索 [Ctrl]+w 两个文档设换
gg 移动到档案第一行 :nohlsearch 暂时关闭高亮显示

认识SHELL
alias 显示当前所有的命令别名 alias lm="ls -al" 命令别名 unalias lm 取消命令别名
type 类似which
exprot 设置或显示环境变量
exprot PATH="$PATH":/sbin 添加/sbin入PATH路径
echo $PATH 显示PATH路径
bash 进入子程序
name=yang 设定变量
unset name 取消变量
echo $name 显示变量的内容
myname="$name its me" & myname='$name its me' 单引号时$name失去变量内容
ciw=/etc/sysconfig/network-scripts/ 设置路径
env 列出所有环境变量
echo $RANDOM 显示随意产生的数
set 设置SHELL
PS1='[\u@\h \w \A #\#]\$ ' 提示字元的設定
[root@linux ~]# read [-pt] variable -----------读取键盘输入的变量
參數:
-p :後面可以接提示字元!
-t :後面可以接等待的『秒數!』
declare 声明 shell 变量
ulimit -a 显示所有限制资料
ls /tmp/yang && echo "exist" || echo "not exist"
意思是說,當 ls /tmp/yang 執行後,若正確,就執行echo "exist" ,若有問題,就執行echo "not exist"
echo $PATH | cut -d ':' -f 5 以:为分隔符,读取第5段内容
export | cut -c 10-20 读取第10到20个字节的内容
last | grep 'root' 搜索有root的一行,加[-v]反向搜索
cat /etc/passwd | sort 排序显示
cat /etc/passwd | wc 显示『行、字数、字节数』
正规表示法
[root@test root]# grep [-acinv] '搜尋字串' filename
參數說明:
-a :將 binary 檔案以 text 檔案的方式搜尋資料
-c :計算找到 '搜尋字串' 的次數
-i :忽略大小寫的不同,所以大小寫視為相同
-n :順便輸出行號
-v :反向選擇,亦即顯示出沒有 '搜尋字串' 內容的那一行!
grep -n 'the' 123.txt 搜索the字符 -----------搜尋特定字串
grep -n 't[ea]st' 123.txt 搜索test或taste两个字符---------利用 [] 來搜尋集合字元
grep -n '[^g]oo' 123.txt 搜索前面不为g的oo-----------向選擇 [^]
grep -n '[0-9]' 123.txt 搜索有0-9的数字
grep -n '^the' 123.txt 搜索以the为行首-----------行首搜索^
grep -n '^[^a-zA-Z]' 123.txt 搜索不以英文字母开头
grep -n '[a-z]$' 123.txt 搜索以a-z结尾的行---------- 行尾搜索$
grep -n 'g..d' 123.txt 搜索开头g结尾d字符----------任意一個字元 .
grep -n 'ooo*' 123.txt 搜索至少有两个oo的字符---------重複字元 *
sed 文本流编辑器 利用脚本命令来处理文本文件
awd 模式扫描和处理语言
nl 123.txt | sed '2,5d' 删除第二到第五行的内容
diff 比较文件的差异
cmp 比较两个文件是否有差异
patch 修补文件
pr 要打印的文件格式化

帐号管理
/etc/passwd 系统帐号信息
/etc/shadow 帐号密码信息 经MD5 32位加密
在密码栏前面加『 * 』『 ! 』禁止使用某帐号
/etc/group 系统群组信息
/etc/gshadow
newgrp 改变登陆组
useradd & adduser 建立新用户 ---------> useradd -m test 自动建立用户的登入目录
useradd -m -g pgroup test --------->指定所属级
/etc/default/useradd 相关设定
/etc/login.defs UID/GID 有關的設定
passwd 更改密码 -----------> passwd test
usermod 修改用户帐号
userdel 删除帐号 ----------->userdel -r test
chsh 更换登陆系统时使用的SHELL [-l]显示可用的SHELL;[-s]修改自己的SHELL
chfn 改变finger指令显示的信息
finger 查找并显示用户信息
id 显示用户的ID -----------> id test
groupadd 添加组
groupmod 与usermod类似
groupdel 删除组
su test 更改用户 su - 进入root,且使用root的环境变量
sudo 以其他身份来执行指令
visudo 编辑/etc/sudoers 加入一行『 test ALL=(ALL) ALL 』
%wheel ALL = (ALL) ALL 系统里所有wheel群组的用户都可用sudo
%wheel ALL = (ALL) NOPASSWD: ALL wheel群组所有用户都不用密码NOPASSWD
User_Alias ADMPW = vbird, dmtsai, vbird1, vbird3 加入ADMPW组
ADMPW ALL = NOPASSWD: !/usr/bin/passwd, /usr/bin/passwd [A-Za-z]*, \
!/usr/bin/passwd root 可以更改使用者密码,但不能更改root密码 (在指令前面加入 ! 代表不可)
PAM (Pluggable Authentication Modules, 嵌入式模組)
who & w 看谁在线
last 最近登陆主机的信息
lastlog 最近登入的時間 读取 /var/log/lastlog
talk 与其他用户交谈
write 发送信息 write test [ctrl]+d 发送
mesg 设置终端机的写入权限 mesg n 禁止接收 mesg y
wall 向所有用户发送信息 wall this is q test
mail 写mail
/etc/default/useradd 家目录默认设置
quota 显示磁盘已使用的空间与限制 quota -guvs ----->秀出目前 root 自己的 quota 限制值
quota -vu 查询
quotacheck 检查磁盘的使用空间与限制 quotacheck -avug ----->將所有的在 /etc/mtab 內,含有 quota 支援的 partition 進行掃瞄
[-m] 强制扫描
quota一定要是独立的分区,要有quota.user和quota.group两件文件,在/etc/fstab添加一句:
/dev/hda3 /home ext3 defaults,usrquota,grpquota 1 2
chmod 600 quota* 设置完成,重启生效
edquota 编辑用户或群组的quota [u]用户,[g]群组,[p]复制,[t]设置宽限期限
edquota -a yang edquota -p yang -u young ----->复制
quotaon 开启磁盘空间限制 quotaon -auvg -------->啟動所有的具有 quota 的 filesystem
quotaoff 关闭磁盘空间限制 quotaoff -a -------->關閉了 quota 的限制
repquota -av 查閱系統內所有的具有 quota 的 filesystem 的限值狀態
Quota 從開始準備 filesystem 的支援到整個設定結束的主要的步驟大概是:
1、設定 partition 的 filesystem 支援 quota 參數:
由於 quota 必須要讓 partition 上面的 filesystem 支援才行,一般來說, 支援度最好的是 ext2/ext3 ,
其他的 filesystem 類型鳥哥我是沒有試過啦! 啟動 filesystem 支援 quota 最簡單就是編輯 /etc/fstab ,
使得準備要開放的 quota 磁碟可以支援 quota 囉;
2、建立 quota 記錄檔:
剛剛前面講過,整個 quota 進行磁碟限制值記錄的檔案是 aquota.user/aquota.group,
要建立這兩個檔案就必須要先利用 quotacheck 掃瞄才行喔!
3、編輯 quota 限制值資料:
再來就是使用 edquota 來編輯每個使用者或群組的可使用空間囉;
4、重新掃瞄與啟動 quota :
設定好 quota 之後,建議可以再進行一次 quotacheck ,然後再以 quotaon 來啟動吧!

开机流程简介
1、載入 BIOS 的硬體資訊,並取得第一個開機裝置的代號;
2、讀取第一個開機裝置的 MBR 的 boot Loader (亦即是 lilo, grub, spfdisk 等等) 的開機資訊;
3、載入 Kernel 作業系統核心資訊, Kernel 開始解壓縮,並且嘗試驅動所有硬體裝置;
4、Kernel 執行 init 程式並取得 run-level 資訊;
5、init 執行 /etc/rc.d/rc.sysinit 檔案;
6、啟動核心的外掛模組 (/etc/modprobe.conf);
7、init 執行 run-level 的各個批次檔( Scripts );
8、init 執行 /etc/rc.d/rc.local 檔案;
9、執行 /bin/login 程式,並等待使用者登入;
10、登入之後開始以 Shell 控管主機。
在/etc/rc.d/rc3.d內,以S开头的为开机启动,以K开头的为关闭,接着的数字代表执行顺序
GRUB vga设定
彩度\解析度 640x480 800x600 1024x768 1280x1024 bit
256 769 771 773 775 8 bit
32768 784 787 790 793 15 bit
65536 785 788 791 794 16 bit
16.8M 786 789 792 795 32 bit

./configure 检查系统信息 ./configure --help | more 帮助信息
make clean 清除之前留下的文件
make 编译
make install 安装
rpm -q ----->查询是否安装 rpm -ql ------>查询该套件所有的目录
rpm -qi ----->查询套件的说明资料 rpm -qc[d] ----->设定档与说明档
rpm -ivh ---->安装 rpm -V -------->查看套件有否更动过
rpm -e ------>删除 rpm -Uvh ------->升级安装
--nodeps ----->强行安装 --test ----->测试安装

EPSON(爱普生)嵌入式Linux工程师的学习总结

EPSON(爱普生)嵌入式Linux工程师的学习总结
AKAE(亚嵌教育)学习总结
-2006.9.3 Liyang
转眼间在AKAE的学习即将结束了,回忆二个月前一个刚迈出校园的我,和现在的我,可以说已经完全的改变。
自 己接触电脑应该是在小学,第一次在妈妈工作的地方,看到了DOS,现在还记得在那个黑黑的屏幕下编出第一个批处理程序的喜悦而且并在为什么这个机器竟然会 做这么人性化的事情上困惑了很多年,有可能现在才真的知道为什么吧。不过在那时就有了以后要成为一个电脑高手的想法(当然,当时肯定不知道黑客这个词 了)。原来在大学,在同学里自认为已经是个高手了,可以帮人家解决一下WINDOWS上的一下小问题,帮同学免费的在网吧上网,在院里可以和老师一块做项 目。但是真正发觉自己只是在电脑业余爱好者的行列中的时候是在大四时,参加了计算机那些科班出身的人的一个项目。做完项目后,大家都在找工作了,我心里非 常明确的告诉自己不能做一个肤浅的“高级程序员”。自己想了解更多电脑背后那些神秘的东西,当时知道LINUX给了我一个机会,自己又是一个自动化出身的 学生,所以当时看到了AKAE,心中一亮,更坚定了自己不去加入那些盲目去招聘会的大军里了。终于在七月份,我毕业了,也如愿的来到了由中国一些开源项目 领头羊们带领的AKAE(亚嵌教育)。
下 面进入了第一周的学习,是LINUX的一些入门操作,这周对我最大的改变是,现在我感觉如果用鼠标去右键新建一个东西好麻烦,好慢。如果偶尔在WIN下, 写些东西,经常不由的ESC->:->w,而不是CTRL+S了。我知道,我已经对WIN上的很多东西陌生了,对LINUX又已经是爱不释手 了。
第 二周的学习,是一些C语言的编程。看看现在自己编的CODE,又看了看在学校自己编过的一些程序,一个.c叫一个程序,最惨的还有一个.c+一个main 叫一个程序。又看看在AKAE编的code,一个程序,不,应该说是一个小项目吧,有一个或多个文件夹,里面有README,加了(#ifndef, #define,#endif)的头文件,和规范的编码风格的.c,里面还有一些#ifdef DEBUG等语句的代码。现在还有点不敢相信二个月的我的变化了。
第 三周,是对自己编程的升华了,都知道UNIX和C的关系,那么UNIX环境的编程无疑是对C语言的深入理解。在这周里,我学到了一直非常想得到的但书本上 不会讲的东西。例如,一个a.out的形成一步一步是怎么过来的,它在运行时的堆栈的样子,在内存中的布局是什么样子。站在了这种角度再去编程,再去 debug,爽的感觉可能只有自己能够体会的到吧。
真正对思想的改变还是对UNIX编程的学习,IO编程,进程,信号量,线程,还有网络的编程中,我学到了很多操作系统级的知识,和网络协议的知识。当然更重要的是,在AKAE众多武林高手的指点下,自己也有勇气并可以看懂一些传说中的葵花宝典级的书了。
增 加内功后,自己知道了UNIX编程的名言“尽量提供机制,而不提供策略”。自己编程是站在用户态呢,还是内核态。当在用户态编程时,我的程序的进程上下文 是什么样的,自己调用的一些标准库函数和系统调用时,他们是如何陷入内核态执行的。现在编程时脑子里就会有这么一个立体的画面,爽。(谁都喜欢知道自己到 底在做什么,自己做得事情后面真正在发生什么。)
UNIX编 程的另一大特点莫过于同步了,想一想在同一时刻,你的程序在同时做着很多不同的事情,即使只有一个处理器,也可以让你感觉到一种并发的效果,这样有很多问 题会得到简化,很多程序更加高效合理。(例如像交互这种“慢”系统调用) 当自己真的可以真正的理解和考虑到同步的精髓时,自己可能又会小骄傲一下吧。现在只是知道在多进程,线程(LINUX里好像线程也是用进程实现的吧)编程 时,如何通过信号量来同步每个进程,如何用锁的机制来保护共享资源。加锁的顺序和技巧,避免死锁。当然信号更是个很广很有用的概念,以后要通过实践继续总 结。
还有AKAE的大作业制度真的很好,让我体验到了网络编程的乐趣,几个人用自己编的程序聊天的感觉,又是一种从未有过的喜悦。
第 四周我们进入了ARM嵌入式体系结构的学习中,有人说,其实计算机就二条命脉:处理器+操作系统。 在前三周在自己不知不觉的编程中,学到了自己一直希望得到的操作系统的知识后,这周AKAE又一次及时的给出了另一条计算机世界的命脉-处理器的体系架 构。原来在学校只是学过微机原理,现在看到了精简指令集架构的美,知道了2/8原则的成功思想,无奈那些老牌的CISC的处理器要兼顾它以前的东西。当了 解了一些“大脑”-处理器是怎么干活的时候,例如:异常,中断,内存管理,流水线,等知识后,感觉自己离计算机这个世界又进了一步。
这周的试验更是有趣,看着自己编的跑马灯,听着蜂鸣器发出的音乐(虽然音质上。。。),不过这都是自己的成果呀,一个字--“享受”。
第五周进入了实用的嵌入式LINUX的应用的学习了,通过这周的学习和具体的试验,让我真正的理论联系了实践。自己建立交叉编译环境,了解BOOTLOADER,自己编译内核自己做文件系统,完全的DIY的计算机出来了。
第 六周对C进行了一些强化的编程训练,也算是前一段的总结和让我看到变化的一周。而且在这周里,最重要的是我知道我自己是一名程序员了,我编出来的程序不是 为了兴趣自己用了,我的用户可能是一些街上的大妈,所以我不得不站在它们的角度去提供一些功能,和做出一些出错处理。对出错处理这方面感觉收益非浅。自己 在编程时也对内存这个“雷区”更加小心了。
李明老师(AKAE教学总监)提出的状态机的编程思路使我内功又一次的提高,因为李老师刻意的反复提出,我知道了它的重要性,上网一查更知道这个属于编译原理的概念是一个应用非常广泛的思想。谢谢老师们还教给了我们他们的宝贵经验和方法。
第 七周,不敢相信自己要开始编写设备驱动了,不过这是真的,现在可以编写一个简单的字符设备驱动了,还有一些关于硬件中断的驱动例程。脑子里有了字符设备, 块设备,网络设备的编程逻辑,现在头脑中就可以呈现出内核态的一些样子。站在内核态编程是一种挑战,也是一种升华吧。以后一定会在工作的实践中去进一步的 尝试。
第八周,内核,对就是这个LINUX最宝贵的东西,这个计算机技术发烧友的珍宝。不过它也是最复杂的了。AKAE的高手给我指明了一条学习内核的明路,让我不会走进一个森林而迷路。以后内核肯定是要深入的研究的,这个摆在自己面前最大最好的东西不能浪费了!
说 实话,这几个月自己在AKAE学到了太多太多,“外功”,“内功”同时大增。更重要的是我还学到了看到了另一种精神,看到了AKAE的老师们那种对计算机 技术的执著,对中国开源的贡献,他们的实力可以说比外面一些这个CEO那个高级架构师强很多,但是他们依然朴素的生活,抱着共同的理想和目标。他们才是真 正搞技术的,他们是中国程序员的骄傲。这个是我在AKAE学到的最大的财富了,让我以后更加坚定的钻研技术,以后我要更加努力的学习,争取加入他们这个大 家庭,做一些有意义的事情。
作者就职于EPSON(中国)公司,AKAE 2006年学员。

Linux系统调用列表

以下是Linux系统调用的一个列表,包含了大部分常用系统调用和由系统调用派生出的的函数。这可能是你在互联网上所能看到的唯一一篇中文注释的Linux系统调用列表,即使是简单的字母序英文列表,能做到这么完全也是很罕见的。

按照惯例,这个列表以man pages第2节,即系统调用节为蓝本。按照笔者的理解,对其作了大致的分类,同时也作了一些小小的修改,删去了几个仅供内核使用,不允许用户调用的系统调用,对个别本人稍觉不妥的地方作了一些小的修改,并对所有列出的系统调用附上简要注释。

其 中有一些函数的作用完全相同,只是参数不同。(可能很多熟悉C++朋友马上就能联想起函数重载,但是别忘了Linux核心是用C语言写的,所以只能取成不 同的函数名)。还有一些函数已经过时,被新的更好的函数所代替了(gcc在链接这些函数时会发出警告),但因为兼容的原因还保留着,这些函数我会在前面标 上“*”号以示区别。

一、进程控制:

fork 创建一个新进程
clone 按指定条件创建子进程
execve 运行可执行文件
exit 中止进程
_exit 立即中止当前进程
getdtablesize 进程所能打开的最大文件数
getpgid 获取指定进程组标识号
setpgid 设置指定进程组标志号
getpgrp 获取当前进程组标识号
setpgrp 设置当前进程组标志号
getpid 获取进程标识号
getppid 获取父进程标识号
getpriority 获取调度优先级
setpriority 设置调度优先级
modify_ldt 读写进程的本地描述表
nanosleep 使进程睡眠指定的时间
nice 改变分时进程的优先级
pause 挂起进程,等待信号
personality 设置进程运行域
prctl 对进程进行特定操作
ptrace 进程跟踪
sched_get_priority_max 取得静态优先级的上限
sched_get_priority_min 取得静态优先级的下限
sched_getparam 取得进程的调度参数
sched_getscheduler 取得指定进程的调度策略
sched_rr_get_interval 取得按RR算法调度的实时进程的时间片长度
sched_setparam 设置进程的调度参数
sched_setscheduler 设置指定进程的调度策略和参数
sched_yield 进程主动让出处理器,并将自己等候调度队列队尾
vfork 创建一个子进程,以供执行新程序,常与execve等同时使用
wait 等待子进程终止
wait3 参见wait
waitpid 等待指定子进程终止
wait4 参见waitpid
capget 获取进程权限
capset 设置进程权限
getsid 获取会晤标识号
setsid 设置会晤标识号


二、文件系统控制

1、文件读写操作

fcntl 文件控制
open 打开文件
creat 创建新文件
close 关闭文件描述字
read 读文件
write 写文件
readv 从文件读入数据到缓冲数组中
writev 将缓冲数组里的数据写入文件
pread 对文件随机读
pwrite 对文件随机写
lseek 移动文件指针
_llseek 在位地址空间里移动文件指针
dup 复制已打开的文件描述字
dup2 按指定条件复制文件描述字
flock 文件加/解锁
poll I/O多路转换
truncate 截断文件
ftruncate 参见truncate
umask 设置文件权限掩码
fsync 把文件在内存中的部分写回磁盘


2、文件系统操作

access 确定文件的可存取性
chdir 改变当前工作目录
fchdir 参见chdir
chmod 改变文件方式
fchmod 参见chmod
chown 改变文件的属主或用户组
fchown 参见chown
lchown 参见chown
chroot 改变根目录
stat 取文件状态信息
lstat 参见stat
fstat 参见stat
statfs 取文件系统信息
fstatfs 参见statfs
readdir 读取目录项
getdents 读取目录项
mkdir 创建目录
mknod 创建索引节点
rmdir 删除目录
rename 文件改名
link 创建链接
symlink 创建符号链接
unlink 删除链接
readlink 读符号链接的值
mount 安装文件系统
umount 卸下文件系统
ustat 取文件系统信息
utime 改变文件的访问修改时间
utimes 参见utime
quotactl 控制磁盘配额


三、系统控制

ioctl I/O总控制函数
_sysctl 读/写系统参数
acct 启用或禁止进程记账
getrlimit 获取系统资源上限
setrlimit 设置系统资源上限
getrusage 获取系统资源使用情况
uselib 选择要使用的二进制函数库
ioperm 设置端口I/O权限
iopl 改变进程I/O权限级别
outb 低级端口操作
reboot 重新启动
swapon 打开交换文件和设备
swapoff 关闭交换文件和设备
bdflush 控制bdflush守护进程
sysfs 取核心支持的文件系统类型
sysinfo 取得系统信息
adjtimex 调整系统时钟
alarm 设置进程的闹钟
getitimer 获取计时器值
setitimer 设置计时器值
gettimeofday 取时间和时区
settimeofday 设置时间和时区
stime 设置系统日期和时间
time 取得系统时间
times 取进程运行时间
uname 获取当前UNIX系统的名称、版本和主机等信息
vhangup 挂起当前终端
nfsservctl 对NFS守护进程进行控制
vm86 进入模拟8086模式
create_module 创建可装载的模块项
delete_module 删除可装载的模块项
init_module 初始化模块
query_module 查询模块信息
*get_kernel_syms 取得核心符号,已被query_module代替


四、内存管理

brk 改变数据段空间的分配
sbrk 参见brk
mlock 内存页面加锁
munlock 内存页面解锁
mlockall 调用进程所有内存页面加锁
munlockall 调用进程所有内存页面解锁
mmap 映射虚拟内存页
munmap 去除内存页映射
mremap 重新映射虚拟内存地址
msync 将映射内存中的数据写回磁盘
mprotect 设置内存映像保护
getpagesize 获取页面大小
sync 将内存缓冲区数据写回硬盘
cacheflush 将指定缓冲区中的内容写回磁盘


五、网络管理

getdomainname 取域名
setdomainname 设置域名
gethostid 获取主机标识号
sethostid 设置主机标识号
gethostname 获取本主机名称
sethostname 设置主机名称


六、socket控制

socketcall socket系统调用
socket 建立socket
bind 绑定socket到端口
connect 连接远程主机
accept 响应socket连接请求
send 通过socket发送信息
sendto 发送UDP信息
sendmsg 参见send
recv 通过socket接收信息
recvfrom 接收UDP信息
recvmsg 参见recv
listen 监听socket端口
select 对多路同步I/O进行轮询
shutdown 关闭socket上的连接
getsockname 取得本地socket名字
getpeername 获取通信对方的socket名字
getsockopt 取端口设置
setsockopt 设置端口参数
sendfile 在文件或端口间传输数据
socketpair 创建一对已联接的无名socket


七、用户管理

getuid 获取用户标识号
setuid 设置用户标志号
getgid 获取组标识号
setgid 设置组标志号
getegid 获取有效组标识号
setegid 设置有效组标识号
geteuid 获取有效用户标识号
seteuid 设置有效用户标识号
setregid 分别设置真实和有效的的组标识号
setreuid 分别设置真实和有效的用户标识号
getresgid 分别获取真实的,有效的和保存过的组标识号
setresgid 分别设置真实的,有效的和保存过的组标识号
getresuid 分别获取真实的,有效的和保存过的用户标识号
setresuid 分别设置真实的,有效的和保存过的用户标识号
setfsgid 设置文件系统检查时使用的组标识号
setfsuid 设置文件系统检查时使用的用户标识号
getgroups 获取后补组标志清单
setgroups 设置后补组标志清单


八、进程间通信

ipc 进程间通信总控制调用


1、信号

sigaction 设置对指定信号的处理方法
sigprocmask 根据参数对信号集中的信号执行阻塞/解除阻塞等操作
sigpending 为指定的被阻塞信号设置队列
sigsuspend 挂起进程等待特定信号
signal 参见signal
kill 向进程或进程组发信号
*sigblock 向被阻塞信号掩码中添加信号,已被sigprocmask代替
*siggetmask 取得现有阻塞信号掩码,已被sigprocmask代替
*sigsetmask 用给定信号掩码替换现有阻塞信号掩码,已被sigprocmask代替
*sigmask 将给定的信号转化为掩码,已被sigprocmask代替
*sigpause 作用同sigsuspend,已被sigsuspend代替
sigvec 为兼容BSD而设的信号处理函数,作用类似sigaction
ssetmask ANSI C的信号处理函数,作用类似sigaction


2、消息

msgctl 消息控制操作
msgget 获取消息队列
msgsnd 发消息
msgrcv 取消息


3、管道

pipe 创建管道


4、信号量

semctl 信号量控制
semget 获取一组信号量
semop 信号量操作


5、共享内存

shmctl 控制共享内存
shmget 获取共享内存
shmat 连接共享内存
shmdt 拆卸共享内存

参考资料

  • Linux man pages

  • Advanced Programming in the UNIX Environment, W. Richard Stevens, 1993

一道微软亚洲工程院C语言笔试题的解答

题目:
struct S
{
int i;
int * p;
};

void main()
{
struct S s;
int * p = &s.i;

p[0] = 4;
p[1] = 3;

s.p = p;

s.p[1] = 1;
s.p[0] = 2;
}


问程序会在哪一行死掉。

分析:这道题有点难度。如果你对指针掌握的不错的话,仔细分析,相信最终还是可以迎刃而解的。下面就来逐条分析

struct S s;
int * p = &s.i;/*取成员s.i的地址*/

p[0] = 4;;/*设置成员s.i为4。因为指针p指向i地址,p[0]指向i*/
p[1] = 3;/*设置成员s.p为3。因为p[0]指向s.i,p[1]指向指针s.p*/

s.p = p;/*重新设置指针s.p为s.i的地址*/

s.p[1] = 1;/*置s.p指针为1。因前面s.p指向s.i的地址,固s.p[1]指向s.p*/
s.p[0] = 2;/*因为s.p指针已经通过前面被设置为1,即非法地址,所以s.p[0]想通过s.p去访问s.i显然是非法的。*/

在内存中的对应关系:
 | s.i | s.p |
| p[0] | p[1] |
| s.p[0] | s.p[1] |
后话:这里出题者故意将s.p[0]和s.p[1]的访问次序对调,就是想通过设置s.p[1]搞死s.p[0]的相关操作。如果先执行s.p[0] = 2,然后执行s.p[1] = 1,那么所有代码都正常通过。
答案:程序执行最后一句s.p[0] = 2死掉。

高手详解:sscanf函数的高级用法

sscanf是一个很好用的函数,利用它可以从字符串中取出整数、浮点数和字符串等等。它的使用方法简单,特别对于整数和浮点数来说。但新手可能并不知道处理字符串时的一些高级用法,这里做个简要说明吧。

  1. 常见用法。

以下是引用片段:
  char str[512] = {0};
  sscanf("123456 ", "%s", str);
  printf("str=%s\n", str);


  2. 取指定长度的字符串。如在下例中,取最大长度为4字节的字符串。

以下是引用片段:
  sscanf("123456 ", "%4s", str);
  printf("str=%s\n", str);


  3. 取到指定字符为止的字符串。如在下例中,取遇到空格为止字符串。

以下是引用片段:
  sscanf("123456 abcdedf", "%[^ ]", str);
  printf("str=%s\n", str);


  4. 取仅包含指定字符集的字符串。如在下例中,取仅包含1到9和小写字母的字符串。

以下是引用片段:
  sscanf("123456abcdedfBCDEF", "%[1-9a-z]", str);
  printf("str=%s\n", str);


  5. 取到指定字符集为止的字符串。如在下例中,取遇到大写字母为止的字符串。

以下是引用片段:
  sscanf("123456abcdedfBCDEF", "%[^A-Z]", str);
  printf("str=%s\n", str);

c语言的试题和技巧

1引言
 本文的写作目的并不在于提供C/C++程序员求职面试指导,而旨在从技术上分析面试题的
内涵。文中的大多数面试题来自各大论坛,部分试题解答也参考了网友的意见。
  许多面试题看似简单,却需要深厚的基本功才能给出完美的解答。企业要求面试者写
一个最简单的strcpy函数都可看出面试者在技术上究竟达到了怎样的程度,我们能真正写
好一个strcpy函数吗?我们都觉得自己能,可是我们写出的strcpy很可能只能拿到10分中
的2分。读者可从本文看到strcpy函数从2分到10分解答的例子,看看自己属于什么样的层
次。此外,还有一些面试题考查面试者敏捷的思维能力。
分析这些面试题,本身包含很强的趣味性;而作为一名研发人员,通过对这些面试题
的深入剖析则可进一步增强自身的内功。
  2.找错题
  试题1:
void test1()
{
 char string[10];
 char* str1 = "0123456789";
 strcpy( string, str1 );
}
  试题2:
void test2()
{
 char string[10], str1[10];
 int i;
 for(i=0; i<10; address =" strDest;" p =" (char" str =" NULL;" str =" NULL;" str =" GetMemory();" p =" (char" str =" NULL;" str =" (char">

char *str = NULL;
GetMemory( str );
  后的str仍然为NULL;
  试题5中
char p[] = "hello world";
return p;
  的p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放。这是许多程序
员常犯的错误,其根源在于不理解变量的生存期。

  试题6的GetMemory避免了试题4的问题,传入GetMemory的参数为字符串指针的指针,
但是在GetMemory中执行申请内存及赋值语句

*p = (char *) malloc( num );
  后未判断内存是否申请成功,应加上:
if ( *p == NULL )
{
 ...//进行申请内存失败处理
}
  试题7存在与试题6同样的问题,在执行
char *str = (char *) malloc(100);
  后未进行内存是否申请成功的判断;另外,在free(str)后未置str为空,导致可能变
成一个“野”指针,应加上:
str = NULL;
  试题6的Test函数中也未对malloc的内存进行释放。
  剖析:
  试题4~7考查面试者对内存操作的理解程度,基本功扎实的面试者一般都能正确的回
答其中50~60的错误。但是要完全解答正确,却也绝非易事。
 对内存操作的考查主要集中在:
  (1)指针的理解;
  (2)变量的生存期及作用范围;
  (3)良好的动态内存申请和释放习惯。
  再看看下面的一段程序有什么错误:
swap( int* p1,int* p2 )
{
 int *p;
 *p = *p1;
 *p1 = *p2;
 *p2 = *p;
}
  在swap函数中,p是一个“野”指针,有可能指向系统区,导致程序运行的崩溃。在V
C++中DEBUG运行时提示错误“Access Violation”。该程序应该改为:
swap( int* p1,int* p2 )
{
 int p;
 p = *p1;
 *p1 = *p2;
 *p2 = p;
}
  3.内功题
  试题1:分别给出BOOL,int,float,指针变量 与“零值”比较的 if 语句(假设变
量名为var)
  解答:
  BOOL型变量:if(!var)
   int型变量: if(var==0)
   float型变量:   const float EPSINON = 0.00001;
  if ((x >= - EPSINON) && (x <= EPSINON)   指针变量:  if(var==NULL)   剖析:   考查对0值判断的“内功”,BOOL型变量的0判断完全可以写成if(var==0),而int型变 量也可以写成if(!var),指针变量的判断也可以写成if(!var),上述写法虽然程序都能正 确运行,但是未能清晰地表达程序的意思。   一般的,如果想让if判断一个变量的“真”、“假”,应直接使用if(var)、if(!var ),表明其为“逻辑”判断;如果用if判断一个数值型变量(short、int、long等),应该用 if(var==0),表明是与0进行“数值”上的比较;而判断指针则适宜用if(var==NULL),这 是一种很好的编程习惯。   浮点型变量并不精确,所以不可将float变量用“==”或“!=”与数字比较,应该设 法转化成“>=”或“<=”形式。如果写成if (x == 0.0),则判为错,得0分。   试题2:以下为Windows NT下的32位C++程序,请计算sizeof的值 void Func ( char str[100] ) {  sizeof( str ) = ? } void *p = malloc( 100 ); sizeof ( p ) = ?   解答: sizeof( str ) = 4 sizeof ( p ) = 4   剖析:   Func ( char str[100] )函数中数组名作为函数形参时,在函数体内,数组名失去了 本身的内涵,仅仅只是一个指针;在失去其内涵的同时,它还失去了其常量特性,可以作 自增、自减等操作,可以被修改。   数组名的本质如下:  (1)数组名指代一种数据结构,这种数据结构就是数组;  例如: char str[10]; cout << least =" MIN(*p++," n="2,移位后应该是“hiabcdefgh”" n =" strlen(" n =" strlen(" str =" NULL);" operate ="(const" str="="NULL)" m_data =" new" m_data =" '\0';" length =" strlen(str);" m_data =" new" length =" strlen(other.m_data);" m_data =" new" operate ="(const" this ="="" length =" strlen(" m_data =" new" a =" 1;" b ="="" sum =" 0;" i="1;">