Linux常用函数和头文件(合集)

56 篇文章 1 订阅
22 篇文章 0 订阅


linux中fork()函数详解

转自http://blog.csdn.net/jason314/article/details/5640969

一、fork入门知识

     一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
    一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。

     我们来看一个例子:

/*
 *  fork_test.c
 *  version 1
 *  Created on: 2010-5-29
 *      Author: wangth
 */
#include <unistd.h>
#include <stdio.h> 
int main () 
{ 
	pid_t fpid; //fpid表示fork函数返回的值
	int count=0;
	fpid=fork(); 
	if (fpid < 0) 
		printf("error in fork!"); 
	else if (fpid == 0) {
		printf("i am the child process, my process id is %d/n",getpid()); 
		printf("我是爹的儿子/n");//对某些人来说中文看着更直白。
		count++;
	}
	else {
		printf("i am the parent process, my process id is %d/n",getpid()); 
		printf("我是孩子他爹/n");
		count++;
	}
	printf("统计结果是: %d/n",count);
	return 0;
}

     运行结果是:
    i am the child process, my process id is 5574
    我是爹的儿子
    统计结果是: 1
    i am the parent process, my process id is 5573
    我是孩子他爹
    统计结果是: 1

    在语句fpid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(fpid<0)……
    为什么两个进程的fpid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
    1)在父进程中,fork返回新创建子进程的进程ID;
    2)在子进程中,fork返回0;
    3)如果出现错误,fork返回一个负值;

    在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。

    引用一位网友的话来解释fpid的值为什么在父子进程中不同。“其实就相当于链表,进程形成了链表,父进程的fpid(p 意味point)指向子进程的进程id, 因为子进程没有子进程,所以其fpid为0.
    fork出错可能有两种原因:
    1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
    2)系统内存不足,这时errno的值被设置为ENOMEM。
    创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。
    每个进程都有一个独特(互不相同)的进程标识符(process ID),可以通过getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。
   
 fork执行完毕后,出现两个进程,


    有人说两个进程的内容完全一样啊,怎么打印的结果不一样啊,那是因为判断条件的原因,上面列举的只是进程的代码和指令,还有变量啊。
    执行完fork后,进程1的变量为count=0,fpid!=0(父进程)。进程2的变量为count=0,fpid=0(子进程),这两个进程的变量都是独立的,存在不同的地址中,不是共用的,这点要注意。可以说,我们就是通过fpid来识别和操作父子进程的。
    还有人可能疑惑为什么不是从#include处开始复制代码的,这是因为fork是把进程当前的情况拷贝一份,执行fork时,进程已经执行完了int count=0;fork只拷贝下一个要执行的代码到新的进程。

二、fork进阶知识

    先看一份代码:

/*
 *  fork_test.c
 *  version 2
 *  Created on: 2010-5-29
 *      Author: wangth
 */
#include <unistd.h>
#include <stdio.h>
int main(void)
{
   int i=0;
   printf("i son/pa ppid pid  fpid/n");
   //ppid指当前进程的父进程pid
   //pid指当前进程的pid,
   //fpid指fork返回给当前进程的值
   for(i=0;i<2;i++){
       pid_t fpid=fork();
       if(fpid==0)
    	   printf("%d child  %4d %4d %4d/n",i,getppid(),getpid(),fpid);
       else
    	   printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);
   }
   return 0;
}

    运行结果是:
    i son/pa ppid pid  fpid
    0 parent 2043 3224 3225
    0 child  3224 3225    0
    1 parent 2043 3224 3226
    1 parent 3224 3225 3227
    1 child     1 3227    0
    1 child     1 3226    0
 
    这份代码比较有意思,我们来认真分析一下:
    第一步:在父进程中,指令执行到for循环中,i=0,接着执行fork,fork执行完后,系统中出现两个进程,分别是p3224和p3225(后面我都用pxxxx表示进程id为xxxx的进程)。可以看到父进程p3224的父进程是p2043,子进程p3225的父进程正好是p3224。我们用一个链表来表示这个关系:
    p2043->p3224->p3225 
    第一次fork后,p3224(父进程)的变量为i=0,fpid=3225(fork函数在父进程中返向子进程id),代码内容为:


   for(i=0;i<2;i++){
       pid_t fpid=fork();//执行完毕,i=0,fpid=3225
       if(fpid==0)
    	   printf("%d child  %4d %4d %4d/n",i,getppid(),getpid(),fpid);
       else
    	   printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);
   }
   return 0;

    p3225(子进程)的变量为i=0,fpid=0(fork函数在子进程中返回0),代码内容为:

   for(i=0;i<2;i++){
       pid_t fpid=fork();//执行完毕,i=0,fpid=0
       if(fpid==0)
    	   printf("%d child  %4d %4d %4d/n",i,getppid(),getpid(),fpid);
       else
    	   printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid);
   }
   return 0;

    所以打印出结果:
    0 parent 2043 3224 3225
    0 child  3224 3225    0

    第二步:假设父进程p3224先执行,当进入下一个循环时,i=1,接着执行fork,系统中又新增一个进程p3226,对于此时的父进程,p2043->p3224(当前进程)->p3226(被创建的子进程)。
    对于子进程p3225,执行完第一次循环后,i=1,接着执行fork,系统中新增一个进程p3227,对于此进程,p3224->p3225(当前进程)->p3227(被创建的子进程)。从输出可以看到p3225原来是p3224的子进程,现在变成p3227的父进程。父子是相对的,这个大家应该容易理解。只要当前进程执行了fork,该进程就变成了父进程了,就打印出了parent。
    所以打印出结果是:
    1 parent 2043 3224 3226
    1 parent 3224 3225 3227
 
    第三步:第二步创建了两个进程p3226,p3227,这两个进程执行完printf函数后就结束了,因为这两个进程无法进入第三次循环,无法fork,该执行return 0;了,其他进程也是如此。
    以下是p3226,p3227打印出的结果:
    1 child     1 3227    0
    1 child     1 3226    0
 
    细心的读者可能注意到p3226,p3227的父进程难道不该是p3224和p3225吗,怎么会是1呢?这里得讲到进程的创建和死亡的过程,在p3224和p3225执行完第二个循环后,main函数就该退出了,也即进程该死亡了,因为它已经做完所有事情了。p3224和p3225死亡后,p3226,p3227就没有父进程了,这在操作系统是不被允许的,所以p3226,p3227的父进程就被置为p1了,p1是永远不会死亡的,至于为什么,这里先不介绍,留到“三、fork高阶知识”讲。
    总结一下,这个程序执行的流程如下:


     这个程序最终产生了3个子进程,执行过6次printf()函数。
    我们再来看一份代码:

/*
 *  fork_test.c
 *  version 3
 *  Created on: 2010-5-29
 *      Author: wangth
 */
#include <unistd.h>
#include <stdio.h>
int main(void)
{
   int i=0;
   for(i=0;i<3;i++){
       pid_t fpid=fork();
       if(fpid==0)
    	   printf("son/n");
       else
    	   printf("father/n");
   }
   return 0;

}

     它的执行结果是:
    father
    son
    father
    father
    father
    father
    son
    son
    father
    son
    son
    son
    father
    son 
    这里就不做详细解释了,只做一个大概的分析。
    for        i=0         1           2
              father     father     father
                                        son
                            son       father
                                        son
               son       father     father
                                        son
                            son       father
                                        son
    其中每一行分别代表一个进程的运行打印结果。
    总结一下规律,对于这种N次循环的情况,执行printf函数的次数为2*(1+2+4+……+2N-1)次,创建的子进程数为1+2+4+……+2N-1个。(感谢gao_jiawei网友指出的错误,原本我的结论是“执行printf函数的次数为2*(1+2+4+……+2N)次,创建的子进程数为1+2+4+……+2”,这是错的)
    网上有人说N次循环产生2*(1+2+4+……+2N)个进程,这个说法是不对的,希望大家需要注意。

    数学推理见http://202.117.3.13/wordpress/?p=81(该博文的最后)。
    同时,大家如果想测一下一个程序中到底创建了几个子进程,最好的方法就是调用printf函数打印该进程的pid,也即调用printf("%d/n",getpid());或者通过printf("+/n");来判断产生了几个进程。有人想通过调用printf("+");来统计创建了几个进程,这是不妥当的。具体原因我来分析。
    老规矩,大家看一下下面的代码:

/*
 *  fork_test.c
 *  version 4
 *  Created on: 2010-5-29
 *      Author: wangth
 */
#include <unistd.h>
#include <stdio.h>
int main() {
	pid_t fpid;//fpid表示fork函数返回的值
	//printf("fork!");
	printf("fork!/n");
	fpid = fork();
	if (fpid < 0)
		printf("error in fork!");
	else if (fpid == 0)
		printf("I am the child process, my process id is %d/n", getpid());
	else
		printf("I am the parent process, my process id is %d/n", getpid());
	return 0;
}

    执行结果如下:
    fork!
    I am the parent process, my process id is 3361
    I am the child process, my process id is 3362 
    如果把语句printf("fork!/n");注释掉,执行printf("fork!");
    则新的程序的执行结果是:
    fork!I am the parent process, my process id is 3298
    fork!I am the child process, my process id is 3299 
    程序的唯一的区别就在于一个/n回车符号,为什么结果会相差这么大呢?
    这就跟printf的缓冲机制有关了,printf某些内容时,操作系统仅仅是把该内容放到了stdout的缓冲队列里了,并没有实际的写到屏幕上。但是,只要看到有/n 则会立即刷新stdout,因此就马上能够打印了。
    运行了printf("fork!")后,“fork!”仅仅被放到了缓冲里,程序运行到fork时缓冲里面的“fork!”  被子进程复制过去了。因此在子进程度stdout缓冲里面就也有了fork! 。所以,你最终看到的会是fork!  被printf了2次!!!!
    而运行printf("fork! /n")后,“fork!”被立即打印到了屏幕上,之后fork到的子进程里的stdout缓冲里不会有fork! 内容。因此你看到的结果会是fork! 被printf了1次!!!!
    所以说printf("+");不能正确地反应进程的数量。
    大家看了这么多可能有点疲倦吧,不过我还得贴最后一份代码来进一步分析fork函数。

#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
   fork();
   fork() && fork() || fork();
   fork();
   return 0;
}

    问题是不算main这个进程自身,程序到底创建了多少个进程。
    为了解答这个问题,我们先做一下弊,先用程序验证一下,到此有多少个进程。

#include <stdio.h>
int main(int argc, char* argv[])
{
   fork();
   fork() && fork() || fork();
   fork();
   printf("+/n");
}

    答案是总共20个进程,除去main进程,还有19个进程。
    我们再来仔细分析一下,为什么是还有19个进程。
    第一个fork和最后一个fork肯定是会执行的。
    主要在中间3个fork上,可以画一个图进行描述。
    这里就需要注意&&和||运算符。
    A&&B,如果A=0,就没有必要继续执行&&B了;A非0,就需要继续执行&&B。
    A||B,如果A非0,就没有必要继续执行||B了,A=0,就需要继续执行||B。
    fork()对于父进程和子进程的返回值是不同的,按照上面的A&&B和A||B的分支进行画图,可以得出5个分支。

    

        加上前面的fork和最后的fork,总共4*5=20个进程,除去main主进程,就是19个进程了。

三、fork高阶知识

        这一块我主要就fork函数讲一下操作系统进程的创建、死亡和调度等。因为时间和精力限制,我先写到这里,下次找个时间我争取把剩下的内容补齐。


参考资料:

 

      http://blog.csdn.net/dog_in_yellow/archive/2008/01/13/2041079.aspx

      http://blog.chinaunix.net/u1/53053/showart_425189.html

      http://blog.csdn.net/saturnbj/archive/2009/06/19/4282639.aspx

      http://www.cppblog.com/zhangxu/archive/2007/12/02/37640.html

      http://www.qqread.com/linux/2010/03/y491043.html

      http://www.yuanma.org/data/2009/1103/article_3998.htm


-------------------------------------------------------------------------------------------------------------------------


        转自 http://blog.csdn.net/dlutbrucezhang/article/details/8629630


vfork()

        创建的子进程结束后需要运行exit()或exec父进程才会运行,否则就会造成死锁
        下面给出一个简单的vfork调用

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int global=4;

int main()
{
	pid_t pid;
	int vari=5;
	if((pid=vfork())<0)
	{
		printf("vfork error.\n");
		return 1;
	}
	else if(pid==0)
	{
		global++;
		vari--;
		printf("Child changed the vari and global \n");
		_exit(0);
	}
	else
		printf("Parent didn't changed the vari and global\n");
	printf("global =%d ,vari=%d \n",global,vari);
	return 0;
}



--------------------------------------------------------------------------------------------------------------------------


Exec函数族


        转自 http://baike.baidu.com/view/3292080.htm


UNIX环境高级编程对EXEC的解释

        exec函数族,顾名思义,就是一簇函数,他把当前进程映像替换成新的程序文件,而且该程序通常main函数开始执行!
        用fork函数创建子进程后,子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程完全由新程序代换,而新程序则从其 main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。有六种不同的exec函数可供使用,它们常常被统称为exec函数。这些exec函数都是UNIX进程控制原语。用fork可以创建新进程,用exec可以执行新的程序。exit函数和两个wait函数处理终止和等待终止。这些是我们需要的基本的进程控制原语。
        说是exec系统调用,实际上在Linux中,并不存在一个exec()的函数形式,exec指的是一组函数,一共有6个,分别是:

#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

        其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。


函数介绍

        exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件,如果不是可以执行的文件,那么就解释成为一个shell文件,sh **执行!
        上面6条函数看起来似乎很复杂,但实际上无论是作用还是用法都非常相似,只有很微小的差别。
        参数argc指出了运行该程序时命令行参数的个数,数组argv存放了所有的命令行参数,数组envp存放了所有的环境变量。环境变量指的是一组值,从用户登录后就一直存在,很多应用程序需要依靠它来确定系统的一些细节,我们最常见的环境变量是PATH,它指出了应到哪里去搜索应用程序,如 /bin;HOME也是比较常见的环境变量,它指出了我们在系统中的个人目录。环境变量一般以字符串"XXX=xxx"的形式存在,XXX表示变量名,xxx表示变量的值。
        值得一提的是,argv数组和envp数组存放的都是指向字符串的指针,这两个数组都以一个NULL元素表示数组的结尾。


        现在来看一下exec函数族,先把注意力集中在execve上:

int execve(const char *path, char *const argv[], char *const envp[]);

        execve第1个参数path是被执行应用程序的完整路径,第2个参数argv就是传给被执行应用程序的命令行参数,第3个参数envp是传给被执行应用程序的环境变量。

        在这里有点要注意,不管是arg0,还是argv[0]都必须是程序的可执行文件的名字,比如:
execl("/bin/echo", "echo", "executed by execl", NULL)中的echo;
execl("/bin/ls", "ls", "/azuo", "-la", (char *)0 )中的ls;
execlp("echo", "echo", "executed by execlp", NULL)中的echo;

        留心看一下这6个函数还可以发现,前3个函数都是以execl开头的,后3个都是以execv开头的,它们的区别在于,execv开头的函数是以"char *argv[]"这样的形式传递命令行参数,而execl开头的函数采用了我们更容易习惯的方式,把参数一个一个列出来,然后以一个NULL表示结束。这里的NULL的作用和argv数组里的NULL作用是一样的。
        这里建议使用 (char *)0 代替NULL。
        在全部6个函数中,只有execle和execve使用了char *envp[]传递环境变量,其它的4个函数都没有这个参数,这并不意味着它们不传递环境变量,这4个函数将把默认的环境变量不做任何修改地传给被执行的应用程序。而execle和execve会用指定的环境变量去替代默认的那些。
        还有2个以p结尾的函数execlp和execvp,咋看起来,它们和execl与execv的差别很小,事实也确是如此,除execlp和 execvp之外的4个函数都要求,它们的第1个参数path必须是一个完整的路径,如"/bin/ls";而execlp和execvp的第1个参数 file可以简单到仅仅是一个文件名,如 "ls",这两个函数可以自动到环境变量PATH制定的目录里去寻找。



--------------------------------------------------------------------------------------------------------------------------


system()与execv()函数使用详解


        转自 http://www.cnblogs.com/akira90/archive/2012/12/05/2802809.html


        在网上搜了很久都没有一个很好的解释,都只说了一方面system调用子进程后继续执行父进程,execv是调用一个新的进程,所以打算自己读读这两个执行文件源码,自己再找找其他不同:

相关函数: fork,execl,execle,execlp,execv,execvp
表头文件: #include<unistd.h>
定义函数: int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
 
函数说明: execve()用来执行参数filename字符串所代表的文件路径,第二个参数系利用数组指针来传递给执行文件,最后一个参数则为传递给执行文件的新环境变量数组。
返回值:   如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno 中。
错误代码:
 
 EACCES
. 欲执行的文件不具有用户可执行的权限。
. 欲执行的文件所属的文件系统是以noexec 方式挂上。
.欲执行的文件或script翻译器非一般文件。
 
 EPERM
.进程处于被追踪模式,执行者并不具有root权限,欲执行的文件具有SUID 或SGID 位。
.欲执行的文件所属的文件系统是以nosuid方式挂上,欲执行的文件具有SUID 或SGID 位元,但执行者并不具有root权限。
 
 E2BIG 参数数组过大
 ENOEXEC 无法判断欲执行文件的执行文件格式,有可能是格式错误或无法在此平台执行。
 EFAULT 参数filename所指的字符串地址超出可存取空间范围。
 ENAMETOOLONG 参数filename所指的字符串太长。
 ENOENT 参数filename字符串所指定的文件不存在。
 ENOMEM 核心内存不足
 ENOTDIR 参数filename字符串所包含的目录路径并非有效目录
 EACCES 参数filename字符串所包含的目录路径无法存取,权限不足
 ELOOP 过多的符号连接
 ETXTBUSY 欲执行的文件已被其他进程打开而且正把数据写入该文件中
 EIO I/O 存取错误
 ENFILE 已达到系统所允许的打开文件总数。
 EMFILE 已达到系统所允许单一进程所能打开的文件总数。
 EINVAL 欲执行文件的ELF执行格式不只一个PT_INTERP节区
 EISDIR ELF翻译器为一目录
 ELIBBAD ELF翻译器有问题。
 
 范例:
 #include<unistd.h>
 main()
 {
   char * argv[ ]={“ls”,”-al”,”/etc/passwd”,(char *)0};
   char * envp[ ]={“PATH=/bin”,0}
   execve(“/bin/ls”,argv,envp);
 }
 执行
 -rw-r--r-- 1 root root 705 Sep 3 13 :52 /etc/passwd


http://www.tutorialspoint.com/unix_system_calls/execve.htm
http://blog.tianya.cn/blogger/post_read.asp?BlogID=1285060&PostID=12814565
http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=blob;f=fs/exec.c#l1376

        下面是system执行文件代码:

相关函数: fork,execve,waitpid,popen
表头文件: #i nclude<stdlib.h>
定义函数: int system(const char * string);

函数说明: system()会调用fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数string字符串所代表的命令,此命>令执行完后随即返回原调用的进程。在调用system()期间SIGCHLD 信号会被暂时搁置,SIGINT和SIGQUIT 信号则会被忽略。

返回值: =-1:出现错误   
        =0:调用成功但是没有出现子进程   
        >0:成功退出的子进程的id
如果system()在调用/bin/sh时失败则返回127,其他失败原因返回-1。若参数string为空指针(NULL),则返回非零值>。 如果system()调用成功则最后会返回执行shell命令后的返回值,但是此返回值也有可能为 system()调用/bin/sh失败所返回的127,因此最好能再检查errno 来确认执行成功。

附加说明: 在编写具有SUID/SGID权限的程序时请勿使用system(),system()会继承环境变量,通过环境变量可能会造成系统安全的问题。

范例:
#include<stdlib.h>
main()
{
  system(“ls -al /etc/passwd /etc/shadow”);
}

执行结果:
-rw-r--r-- 1 root root 705 Sep 3 13 :52 /etc/passwd
-r--------- 1 root root 572 Sep 2 15 :34 /etc/shado

例2:
char tmp[];
sprintf(tmp,"/bin/mount -t vfat %s /mnt/usb",dev);
system(tmp);
其中dev是/dev/sda1。

system源码

#include 
#include 
#include 
#include 

int system(const char * cmdstring)
{
    pid_t pid;
    int status;

    if(cmdstring == NULL){
          
         return (1);
    }


    if((pid = fork())<0){

            status = -1;
    }
    else if(pid == 0){
        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
        -exit(127); //子进程正常执行则不会执行此语句
        }
    else{
            while(waitpid(pid, &status, 0) < 0){
                if(errno != EINTER){
                    status = -1;
                    break;
                }
            }
        }
        return status;
}

        先分析一下原理,然后再看上面的代码大家估计就能看懂了:   

        当system接受的命令为NULL时直接返回,否则fork出一个子进程,因为fork在两个进程:父进程和子进程中都返回,这里要检查返回的pid,fork在子进程中返回0,在父进程中返回子进程的pid,父进程使用waitpid等待子进程结束,子进程则是调用execl来启动一个程序代替自己,execl("/bin/sh", "sh", "-c", cmdstring,(char*)0)是调用shell,这个shell的路径是/bin/sh,后面的字符串都是参数,然后子进程就变成了一个shell进程,这个shell的参数是cmdstring,就是system接受的参数。在windows中的shell是command,想必大家很熟悉shell接受命令之后做的事了。
   
        如果上面的你没有看懂,那我再解释下fork的原理:当一个进程A调用fork时,系统内核创建一个新的进程B,并将A的内存映像复制到B的进程空间中,因为A和B是一样的,那么他们怎么知道自己是父进程还是子进程呢,看fork的返回值就知道,上面也说了fork在子进程中返回0,在父进程中返回子进程的pid。

        execl是编译器的函数(在一定程度上隐藏具体系统实现),在linux中它会接着产生一个linux系统的调用execve, 原型见下:
        int execve(const char * file,const char **argv,const char **envp);
        看到这里你就会明白为什么system()会接受父进程的环境变量,但是用system改变环境变量后,system一返回主函数还是没变,原因从system的实现可以看到,它是通过产生新进程实现的,从我的分析中可以看到父进程和子进程间没有进程通信,子进程自然改变不了父进程的环境变量。

        system调用最终依然是依靠execve()实现调用的,那么分析下execve:
 
        典型的用法,就是在shell中,执行一个文件,比如说,一个编译好的文件,叫做helloworld,那么,在shell下执行./helloworld的时候,shell就去fork()一个子进程,然后在子进程里面execve("./helloworld",NULL,NULL);
这样,就执行了这个文件!
        具体点:
        在shell中:
if(!fork())
{
        execve("./helloworld",NULL,NULL);
        exit(0);
}
        那么,这个execve函数,都做了什么,追一下内核看看!
        首先,利用参数(文件名),调用函数namei(filename),能取得这个文件名对应的i节点!
        然后把当前进程(子进程)的i节点置成上面取得的那个i节点。
        释放所有资源,释放内存页表并且修改LDT。
        凶狠的把中断压入的EIP的值都给改了,改成了从上面那个i节点读出的可执行文件的头部那个文件执行的头字段。
        更猛的是,把栈也给改了!
        好了,这下子跟父进程一点关系都没有了!
        中断返回后,程序从i节点指向的那个可执行程序开始执行!
        这里,我们注意到个问题!
        execve之后,原来的那些代码,都没了,也就是说,上面的那个exit(0),根本执行不到那!因为在execve调用中,代码就换成了那个i节点的了,以前的那些,都释放了!牛!
        于是,
#include <stdio.h>
#include <unistd.h>
int main()
{
        execve("./helloworld",NULL,NULL);
        printf("nothing!\n");
        return 0;
}
        这个程序输出什么?(假设helloworld程序输出hello world!)
        那么,这个程序输出的,就是hello world!
        你会想,为什么没有下面的nothing!啊?
        因为,execve调用中,这些代码都没有了,代码被替换成helloworld的了,而且,只执行helloworld就完了!
        那么,你会说,那怎么执行完helloworld后,继续做事啊?
        这样!
#include <stdio.h>
#include <unistd.h>
int main()
{
    if(!fork())
      execve("./helloworld",NULL,NULL);
    else
      printf("nothing!\n");
    return 0;
}
        执行一下,绝大多数会输出hello world!后,输出nothing!
        这个execve够变态!
 
        下面是找的一个比较好的解释,有时间再慢慢研究:
 //execve()系统中断调用函数。加载并执行子进程(其它程序)。
 // 该函数系统中断调用(int 0x80)功能号__NR_execve 调用的函数。
 // 参数:eip - 指向堆栈中调用系统中断的程序代码指针eip 处,参见kernel/system_call.s 程序
 // 开始部分的说明;tmp - 系统中断调用本函数时的返回地址,无用;
 // filename - 被执行程序文件名;argv - 命令行参数指针数组;envp - 环境变量指针数组。
 // 返回:如果调用成功,则不返回;否则设置出错号,并返回-1。
 int
 do_execve (unsigned long *eip, long tmp, char *filename,
        char **argv, char **envp)
 {
   struct m_inode *inode;    // 内存中I 节点指针结构变量。
   struct buffer_head *bh;    // 高速缓存块头指针。
   struct exec ex;        // 执行文件头部数据结构变量。
   unsigned long page[MAX_ARG_PAGES];    // 参数和环境字符串空间的页面指针数组。
   int i, argc, envc;
   int e_uid, e_gid;        // 有效用户id 和有效组id。
   int retval;            // 返回值。
   int sh_bang = 0;        // 控制是否需要执行脚本处理代码。
 // 参数和环境字符串空间中的偏移指针,初始化为指向该空间的最后一个长字处。
   unsigned long p = PAGE_SIZE * MAX_ARG_PAGES - 4;
 
 // eip[1]中是原代码段寄存器cs,其中的选择符不可以是内核段选择符,也即内核不能调用本函数。
   if ((0xffff & eip[1]) != 0x000f)
     panic ("execve called from supervisor mode");
 // 初始化参数和环境串空间的页面指针数组(表)。
   for (i = 0; i < MAX_ARG_PAGES; i++)    /* clear page-table */
     page[i] = 0;
 // 取可执行文件的对应i 节点号。
   if (!(inode = namei (filename)))    /* get executables inode */
     return -ENOENT;
 // 计算参数个数和环境变量个数。
   argc = count (argv);
   envc = count (envp);
 
 // 执行文件必须是常规文件。若不是常规文件则置出错返回码,跳转到exec_error2(第347 行)。
 restart_interp:
   if (!S_ISREG (inode->i_mode))
     {                /* must be regular file */
       retval = -EACCES;
       goto exec_error2;
     }
 // 检查被执行文件的执行权限。根据其属性(对应i 节点的uid 和gid),看本进程是否有权执行它。
   i = inode->i_mode;
   e_uid = (i & S_ISUID) ? inode->i_uid : current->euid;
   e_gid = (i & S_ISGID) ? inode->i_gid : current->egid;
   if (current->euid == inode->i_uid)
     i >>= 6;
   else if (current->egid == inode->i_gid)
     i >>= 3;
   if (!(i & 1) && !((inode->i_mode & 0111) && suser ()))
     {
       retval = -ENOEXEC;
       goto exec_error2;
     }
 // 读取执行文件的第一块数据到高速缓冲区,若出错则置出错码,跳转到exec_error2 处去处理。
   if (!(bh = bread (inode->i_dev, inode->i_zone[0])))
     {
       retval = -EACCES;
       goto exec_error2;
     }
 // 下面对执行文件的头结构数据进行处理,首先让ex 指向执行头部分的数据结构。
   ex = *((struct exec *) bh->b_data);    /* read exec-header *//* 读取执行头部分 */
 // 如果执行文件开始的两个字节为'#!',并且sh_bang 标志没有置位,则处理脚本文件的执行。
   if ((bh->b_data[0] == '#') && (bh->b_data[1] == '!') && (!sh_bang))
     {
 /*
 * This section does the #! interpretation.
 * Sorta complicated, but hopefully it will work. -TYT
 */
 /*
 * 这部分处理对'#!'的解释,有些复杂,但希望能工作。-TYT
 */
 
       char buf[1023], *cp, *interp, *i_name, *i_arg;
       unsigned long old_fs;
 
 // 复制执行程序头一行字符'#!'后面的字符串到buf 中,其中含有脚本处理程序名。
       strncpy (buf, bh->b_data + 2, 1022);
 // 释放高速缓冲块和该执行文件i 节点。
       brelse (bh);
       iput (inode);
 // 取第一行内容,并删除开始的空格、制表符。
       buf[1022] = '\0';
       if (cp = strchr (buf, '\n'))
     {
       *cp = '\0';
       for (cp = buf; (*cp == ' ') || (*cp == '\t'); cp++);
     }
 // 若该行没有其它内容,则出错。置出错码,跳转到exec_error1 处。
       if (!cp || *cp == '\0')
     {
       retval = -ENOEXEC;    /* No interpreter name found */
       goto exec_error1;
     }
 // 否则就得到了开头是脚本解释执行程序名称的一行内容。
       interp = i_name = cp;
 // 下面分析该行。首先取第一个字符串,其应该是脚本解释程序名,iname 指向该名称。
       i_arg = 0;
       for (; *cp && (*cp != ' ') && (*cp != '\t'); cp++)
     {
       if (*cp == '/')
         i_name = cp + 1;
     }
 // 若文件名后还有字符,则应该是参数串,令i_arg 指向该串。
       if (*cp)
     {
       *cp++ = '\0';
       i_arg = cp;
     }
 /*
 * OK, we've parsed out the interpreter name and
 * (optional) argument.
 */
 /*
 * OK,我们已经解析出解释程序的文件名以及(可选的)参数。
 */
 // 若sh_bang 标志没有设置,则设置它,并复制指定个数的环境变量串和参数串到参数和环境空间中。
       if (sh_bang++ == 0)
     {
       p = copy_strings (envc, envp, page, p, 0);
       p = copy_strings (--argc, argv + 1, page, p, 0);
     }
 /*
 * Splice in (1) the interpreter's name for argv[0]
 * (2) (optional) argument to interpreter
 * (3) filename of shell script
 *
 * This is done in reverse order, because of how the
 * user environment and arguments are stored.
 */
 /*
 * 拼接 (1) argv[0]中放解释程序的名称
 * (2) (可选的)解释程序的参数
 * (3) 脚本程序的名称
 *
 * 这是以逆序进行处理的,是由于用户环境和参数的存放方式造成的。
 */
 // 复制脚本程序文件名到参数和环境空间中。
       p = copy_strings (1, &filename, page, p, 1);
 // 复制解释程序的参数到参数和环境空间中。
       argc++;
       if (i_arg)
     {
       p = copy_strings (1, &i_arg, page, p, 2);
       argc++;
     }
 // 复制解释程序文件名到参数和环境空间中。若出错,则置出错码,跳转到exec_error1。
       p = copy_strings (1, &i_name, page, p, 2);
       argc++;
       if (!p)
     {
       retval = -ENOMEM;
       goto exec_error1;
     }
 /*
 * OK, now restart the process with the interpreter's inode.
 */
 /*
 * OK,现在使用解释程序的i 节点重启进程。
 */
 // 保留原fs 段寄存器(原指向用户数据段),现置其指向内核数据段。
       old_fs = get_fs ();
       set_fs (get_ds ());
 // 取解释程序的i 节点,并跳转到restart_interp 处重新处理。
       if (!(inode = namei (interp)))
     {            /* get executables inode */
       set_fs (old_fs);
       retval = -ENOENT;
       goto exec_error1;
     }
       set_fs (old_fs);
       goto restart_interp;
     }
 // 释放该缓冲区。
   brelse (bh);
 // 下面对执行头信息进行处理。
 // 对于下列情况,将不执行程序:如果执行文件不是需求页可执行文件(ZMAGIC)、或者代码重定位部分
 // 长度a_trsize 不等于0、或者数据重定位信息长度不等于0、或者代码段+数据段+堆段长度超过50MB、
 // 或者i 节点表明的该执行文件长度小于代码段+数据段+符号表长度+执行头部分长度的总和。
   if (N_MAGIC (ex) != ZMAGIC || ex.a_trsize || ex.a_drsize ||
       ex.a_text + ex.a_data + ex.a_bss > 0x3000000 ||
       inode->i_size < ex.a_text + ex.a_data + ex.a_syms + N_TXTOFF (ex))
     {
       retval = -ENOEXEC;
       goto exec_error2;
     }
 // 如果执行文件执行头部分长度不等于一个内存块大小(1024 字节),也不能执行。转exec_error2。
   if (N_TXTOFF (ex) != BLOCK_SIZE)
     {
       printk ("%s: N_TXTOFF != BLOCK_SIZE. See a.out.h.", filename);
       retval = -ENOEXEC;
       goto exec_error2;
     }
 // 如果sh_bang 标志没有设置,则复制指定个数的环境变量字符串和参数到参数和环境空间中。
 // 若sh_bang 标志已经设置,则表明是将运行脚本程序,此时环境变量页面已经复制,无须再复制。
   if (!sh_bang)
     {
       p = copy_strings (envc, envp, page, p, 0);
       p = copy_strings (argc, argv, page, p, 0);
 // 如果p=0,则表示环境变量与参数空间页面已经被占满,容纳不下了。转至出错处理处。
       if (!p)
     {
       retval = -ENOMEM;
       goto exec_error2;
     }
     }
 /* OK, This is the point of no return */
 /* OK,下面开始就没有返回的地方了 */
 // 如果原程序也是一个执行程序,则释放其i 节点,并让进程executable 字段指向新程序i 节点。
   if (current->executable)
     iput (current->executable);
   current->executable = inode;
 // 清复位所有信号处理句柄。但对于SIG_IGN 句柄不能复位,因此在322 与323 行之间需添加一条
 // if 语句:if (current->sa[I].sa_handler != SIG_IGN)。这是源代码中的一个bug。
   for (i = 0; i < 32; i++)
     current->sigaction[i].sa_handler = NULL;
 // 根据执行时关闭(close_on_exec)文件句柄位图标志,关闭指定的打开文件,并复位该标志。
   for (i = 0; i < NR_OPEN; i++)
     if ((current->close_on_exec >> i) & 1)
       sys_close (i);
   current->close_on_exec = 0;
 // 根据指定的基地址和限长,释放原来程序代码段和数据段所对应的内存页表指定的内存块及页表本身。
   free_page_tables (get_base (current->ldt[1]), get_limit (0x0f));
   free_page_tables (get_base (current->ldt[2]), get_limit (0x17));
 // 如果“上次任务使用了协处理器”指向的是当前进程,则将其置空,并复位使用了协处理器的标志。
   if (last_task_used_math == current)
     last_task_used_math = NULL;
   current->used_math = 0;
 // 根据a_text 修改局部表中描述符基址和段限长,并将参数和环境空间页面放置在数据段末端。
 // 执行下面语句之后,p 此时是以数据段起始处为原点的偏移值,仍指向参数和环境空间数据开始处,
 // 也即转换成为堆栈的指针。
   p += change_ldt (ex.a_text, page) - MAX_ARG_PAGES * PAGE_SIZE;
 // create_tables()在新用户堆栈中创建环境和参数变量指针表,并返回该堆栈指针。
   p = (unsigned long) create_tables ((char *) p, argc, envc);
 // 修改当前进程各字段为新执行程序的信息。令进程代码段尾值字段end_code = a_text;令进程数据
 // 段尾字段end_data = a_data + a_text;令进程堆结尾字段brk = a_text + a_data + a_bss。
   current->brk = ex.a_bss +
     (current->end_data = ex.a_data + (current->end_code = ex.a_text));
 // 设置进程堆栈开始字段为堆栈指针所在的页面,并重新设置进程的用户id 和组id。
   current->start_stack = p & 0xfffff000;
   current->euid = e_uid;
   current->egid = e_gid;
 // 初始化一页bss 段数据,全为零。
   i = ex.a_text + ex.a_data;
   while (i & 0xfff)
     put_fs_byte (0, (char *) (i++));
 // 将原调用系统中断的程序在堆栈上的代码指针替换为指向新执行程序的入口点,并将堆栈指针替换
 // 为新执行程序的堆栈指针。返回指令将弹出这些堆栈数据并使得CPU 去执行新的执行程序,因此不会
 // 返回到原调用系统中断的程序中去了。
   eip[0] = ex.a_entry;        /* eip, magic happens :-) *//* eip,魔法起作用了 */
   eip[3] = p;            /* stack pointer *//* esp,堆栈指针 */
   return 0;
 exec_error2:
   iput (inode);
 exec_error1:
   for (i = 0; i < MAX_ARG_PAGES; i++)
     free_page (page[i]);
   return (retval);
 }


--------------------------------------------------------------------------------------------------------------------------


进程终止和等待

转自 http://blog.csdn.net/dlutbrucezhang/article/details/8629892


进程结束

1.在Linux中任何让一个进程结束

进程退出表示进程即将结束。在Linux中进程退出分为了正常退出和异常退出两种。

1>正常退出

a. 在main()函数中执行return 。

b.调用exit()函数

c.调用_exit()函数

2>异常退出

a.调用about函数

b.进程收到某个信号,而该信号使程序终止。

不管 是哪种退出方式,系统最终都会执行内核中的同一代码。这段代码用来关闭进程所用已打开的文件描述符,释放它所占用的内存和其他资源。

 

 

3.比较以上几种退出方式的不同点

(1)exit和return 的区别:

a.exit是一个函数,有参数。exit执行完后把控制权交给系统

b.return是函数执行完后的返回。renturn执行完后把控制权交给调用函数。

(2)exit和abort的区别:

a.exit是正常终止进程

b.about是异常终止。


exit()和_exit()函数

exit()和_exit()的学习

1>exit和_exit函数都是用来终止进程的。

当程序执行到exit或_exit时,系统无条件的停止剩下所有操作,清除包括PCB在内的各种数据结构,并终止本进程的运行。

2>exit在头文件stdlib.h中声明,而_exit()声明在头文件unistd.h中声明。 exit中的参数exit_code为0代表进程正常终止,若为其他值表示程序执行过程中有错误发生。

3>exit()和_exit()的区别: 

a._exit()执行后立即返回给内核,而exit()要先执行一些清除操作,然后将控制权交给内核。

b. 调用_exit函数时,其会关闭进程所有的文件描述符,清理内存以及其他一些内核清理函数,但不会刷新流(stdin, stdout, stderr  ...).   exit函数是在_exit函数之上的一个封装,其会调用_exit,并在调用之前先刷新流。


exit()函数与_exit()函数最大区别就在于exit()函数在调用exit系统之前要检查文件的打开情况,把文件缓冲区的内容写回文件。由于Linux的标准函数库中,有一种被称作“缓冲I/O”的操作,其特征就是对应每一个打开的文件,在内存中都有一片缓冲区。每次读文件时,会连续的读出若干条记录,这样在下次读文件时就可以直接从内存的缓冲区读取;同样,每次写文件的时候也仅仅是写入内存的缓冲区,等满足了一定的条件(如达到了一定数量或遇到特定字符等),再将缓冲区中的内容一次性写入文件。这种技术大大增加了文件读写的速度,但也给编程代来了一点儿麻烦。比如有一些数据,认为已经写入了文件,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内,这时用_exit()函数直接将进程关闭,缓冲区的数据就会丢失。因此,要想保证数据的完整性,就一定要使用exit()函数。


几个要点:
1.不管进程如何终止,最后都会执行内核中的同一段代码:为相应进程关闭所有打开描述符,释放内存等等。
2.若父进程在子进程之前终止了,则子进程的父进程将变为init进程,其PID为1保证每个进程都有父进程。
3.当子进程先终止,父进程如何知道子进程的终止状态?事实上,内核为每个终止子进程保存了终止状态等信息,父进程调用wait等函数,就可获取该信息。
4.当父进程调用wait等函数后,内核将释放终止进程所使用的所有内存,关闭其打开的所有文件。
5.对于已经终止、但是其父进程尚未对其调用wait等函数的进程,被称为僵尸进程(即已经结束,但尚未释放资源的)。
6.对于父进程先终止,而被init领养的进程会是僵尸进程吗?init对每个终止的子进程,都会调用wait函数,获取其终止状态信息。
综上所述,子进程调用exit后,父进程必须调用wait。

进程等待

系统中的僵尸进程都要由wait系统调用来回收,下面就通过实战看一看wait的具体用法:

wait的函数原型是:

#include <sys/types.h> /* 提供类型pid_t的定义 */

#include <sys/wait.h>

pid_t wait(int *status);

进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程, wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就象下面这样:

pid = wait(NULL);

如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。


下面给出两个示例

1.不同类型结束进程的状态字示例

[cpp]  view plain copy
  1. #include <sys/types.h>  
  2. #include <sys/wait.h>  
  3. #include <stdio.h>  
  4. /* 处理并打印状态字的子函数*/  
  5. void h_exit(int status)  
  6. {  
  7.         if(WIFEXITED(status))  
  8.             printf("normal termination, exit status=%d \n", WEXITSTATUS(status));  
  9.         else if(WIFSIGNALED(status))  
  10.         {  
  11.   
  12.         printf("abnormal termination, signal number =%d %s\n", WTERMSIG(status),  
  13.         #ifdef WCOREDUMP  
  14.             WCOREDUMP(status) ? " )" : "(core file generated)");  
  15.         #else  
  16.             ") ");  
  17.         #endif  
  18.         }  
  19. }  
  20.     /*主函数。示范三种结束进程的不同方式,并调用h_exit函数处理返回状态字*/  
  21. int main()  
  22. {  
  23.         pid_t pid;  
  24.         int status;  
  25.         /*子程序正常退出 */  
  26.         if((pid=fork())<0)  
  27.         {  
  28.             printf("fork error \n");  
  29.             exit(0);  
  30.         }  
  31.         else if (pid==0)  
  32.             exit(7);  
  33.         if(wait(&status)!=pid)      /*等待子进程*/  
  34.         {  
  35.             printf("wait error \n");  
  36.             exit(0);  
  37.         }  
  38.         h_exit(status);         /*打印状态 */  
  39.         /*子进程abort终止 */  
  40.         if((pid=fork())<0)  
  41.         {  
  42.             printf("fork errof\n");  
  43.             exit(0);  
  44.         }  
  45.         else if(pid==0)         /*子进程*/  
  46.             abort();            /*产生信号SIGABRT终止进程*/  
  47.         if(wait(&status)!=pid)      /*等待子进程*/  
  48.         {  
  49.             printf("wait error.\n");  
  50.             exit(0);  
  51.         }  
  52.         h_exit(status);         /*打印状态*/  
  53.         /* 子进程除零终止 */  
  54.         if((pid=fork())<0)  
  55.         {  
  56.             printf("fork errof\n");  
  57.             exit(0);  
  58.         }  
  59.         else if(pid==0)         /*子进程 */  
  60.             status /=0;         /*除数为0产生SIGFPE */  
  61.         if(wait(&status)!=pid)  
  62.         {  
  63.             printf("wait error.\n");  
  64.             exit(0);  
  65.         }  
  66.         h_exit(status);         /*打印状态*/  
  67.   
  68.         exit(0);  
  69. }  
2.waitpid的使用

[cpp]  view plain copy
  1. #include <sys/types.h>  
  2. #include <sys/wait.h>  
  3. #include <stdio.h>  
  4. int main()  
  5. {  
  6.     pid_t pid;  
  7.   
  8.     if((pid=fork())<0)  
  9.     {  
  10.         printf("fork error.\n");  
  11.         exit(0);  
  12.     }  
  13.     else if(pid==0)  
  14.     {  
  15.         if((pid=fork())<0)  
  16.         {  
  17.             printf("fork error.\n");  
  18.             exit(0);  
  19.         }  
  20.         else if(pid>0)  
  21.             exit(0);  
  22.         sleep(2);  
  23.         printf("second child, parent pid=%d \n", getppid());  
  24.         exit(0);  
  25.     }  
  26.   
  27.     if(waitpid(pid, NULL, 0)!=pid)  
  28.     {  
  29.         printf("waitpid error.\n");  
  30.         exit(0);  
  31.     }  
  32.     exit(0);  
  33. }  

说明:

WIFEXITED(status)如果子进程正常结束则为非0 值。

WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。


WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真
WTERMSIG(status) 取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏。


WIFSTOPPED(status) 如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。
WSTOPSIG(status) 取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED 来判断后才使用此宏。


wait函数

wait函数的原型为:pid_t wait(int *status)

  当进程退出时,它向父进程发送一个SIGCHLD信号,默认情况下总是忽略SIGCHLD信号,此时进程状态一直保留在内存中,直到父进程使用wait函数收集状态信息,才会清空这些信息.

  用wait来等待一个子进程终止运行称为回收进程.

  当父进程忘了用wait()函数等待已终止的子进程时,子进程就会进入一种无父进程的状态,此时子进程就是僵尸进程.

  wait()要与fork()配套出现,如果在使用fork()之前调用wait(),wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID.

如果先终止父进程,子进程将继续正常进行,只是它将由init进程(PID 1)继承,当子进程终止时,init进程捕获这个状态.


waitpid函数

1)waitpid的概述:

  .waitpid函数的原型为pid_t waitpid(pid_t pid,int *status,int options)

  .从本质上讲,系统调用waitpid是wait的封装,waitpid只是多出了两个可由用户控制的参数pid和options,为编程提供了灵活性.

  2)waitpid的参数说明:

  参数pid的值有以下几种类型:

  pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去.

  pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样.

  pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬.

  pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值.

  参数options的值有以下几种类型:

  如果使用了WNOHANG参数,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去.

  如果使用了WUNTRACED参数,则子进程进入暂停则马上返回,但结束状态不予以理会.

  Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如:

  ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);

  如果我们不想使用它们,也可以把options设为0,如:ret=waitpid(-1,NULL,0);

  waitpid的返回值比wait稍微复杂一些,一共有3种情况:

3)waitpid的返回值:

  当正常返回的时候waitpid返回收集到的子进程的进程ID;

  如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;

  如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

  当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD.






--------------------------------------------------------------------------------------------------------------------------


函数stat,lstat,fstat的使用


转自 http://hi.baidu.com/fdiiprrbbebklpq/item/7dc7a78d97965b54e63d19bb



1、函数都是获取文件(普通文件,目录,管道,socket,字符,块()的属性  
      函数原型#include <sys/stat.h>

      int stat(const char *restrict pathname, struct stat *restrict buf);
      提供文件名字,获取文件对应属性。
      int fstat(int filedes, struct stat *buf);
      通过文件描述符获取文件对应的属性。
      int lstat(const char *restrict pathname, struct stat *restrict buf);
      连接文件描述命,获取文件属性。

      一眼就能看出来fstat的第一个参数是和另外两个不一样的,对!fstat区别于另外两个系统调用的地方在于,fstat系统调用接受的是 一个“文件描述符”,而另外两个则直接接受“文件全路径”。文件描述符是需要我们用open系统调用后才能得到的,而文件全路经直接写就可以了。

      stat 和lstat的区别:当文件是一个符号链接时,lstat返回的是该符号链接本身的信息;而stat返回的是该链接指向的文件的信息。(似乎有些晕吧,这样记,lstat比stat多了一个l,因此它是有本事处理符号链接文件的,因此当遇到符号链接文件时,lstat当然不会放过。而 stat系统调用没有这个本事,它只能对符号链接文件睁一只眼闭一只眼,直接去处理链接所指文件喽)


2 文件对应的属性

   struct stat {
        mode_t     st_mode;       //文件对应的模式,文件,目录等
        ino_t      st_ino;       //inode节点号
        dev_t      st_dev;        //设备号码
        dev_t      st_rdev;       //特殊设备号码
        nlink_t    st_nlink;      //文件的连接数
        uid_t      st_uid;        //文件所有者
        gid_t      st_gid;        //文件所有者对应的组
        off_t      st_size;       //普通文件,对应的文件字节数
        time_t     st_atime;      //文件最后被访问的时间
        time_t     st_mtime;      //文件内容最后被修改的时间
        time_t     st_ctime;      //文件状态改变时间
        blksize_t st_blksize;    //文件内容对应的块大小
        blkcnt_t   st_blocks;     //伟建内容对应的块数量
      };
      可以通过上面提供的函数,返回一个结构体,保存着文件的信息


3 获取文件属性的几个封装函数

///
// 功能说明 : 判断是否link文件
// 输入参数 : 
// 输出参数 : VOID
// 返回值   : YCPP_BOOL
// 使用说明 : 
///
YCPP_BOOL IsLink ( const char * szDirName )
{
        struct stat st;
        return ( lstat (szDirName, &st) == 0 && S_ISLNK (st.st_mode) );
}
///
// 功能说明 : 判断是否block文件
// 输入参数 : 
// 输出参数 : VOID
// 返回值   : YCPP_BOOL
// 使用说明 : 
///
YCPP_BOOL IsBlk ( const char * szDirName )
{
        struct stat st;
        return ( lstat (szDirName, &st) == 0 && S_ISBLK (st.st_mode) );
}
///
// 功能说明 : 判断是否FIFO文件
// 输入参数 : 
// 输出参数 : VOID
// 返回值   : YCPP_BOOL
// 使用说明 : 
///
YCPP_BOOL IsFifo ( const char * szDirName )
{
        struct stat st;
        return ( lstat (szDirName, &st) == 0 && S_ISFIFO (st.st_mode) );
}
///
// 功能说明 : 判断是否目录文件
// 输入参数 : 
// 输出参数 : VOID
// 返回值   : YCPP_BOOL
// 使用说明 : 
///
YCPP_BOOL IsDir ( const char * szDirName )
{
        struct stat st;
        return ( lstat (szDirName, &st) == 0 && S_ISDIR (st.st_mode) );
}
///
// 功能说明 : 判断是否设备文件
// 输入参数 : 
// 输出参数 : VOID
// 返回值   : YCPP_BOOL
// 使用说明 : 
///
YCPP_BOOL IsDevice ( const char * szDirName )
{
        struct stat st;
        return ( lstat (szDirName, &st) == 0 && S_ISCHR (st.st_mode) );
}
///
// 功能说明 : 判断是否普通文件
// 输入参数 : 
// 输出参数 : VOID
// 返回值   : YCPP_BOOL
// 使用说明 : 
///
YCPP_BOOL IsFile ( const char * szDirName )
{
        struct stat st;
        return ( lstat (szDirName, &st) == 0 && S_ISREG (st.st_mode) );
}
///
// 功能说明 : 判断是否可读
// 输入参数 : 
// 输出参数 : VOID
// 返回值   : YCPP_BOOL
// 使用说明 : 
///
YCPP_BOOL canAccess ( const char *szFileName )
{
        if( access(szFileName, R_OK) == 0 )
                return YCPP_TRUE;
        return YCPP_FALSE;
}
///
// 功能说明 : 判断是否可修改
// 输入参数 : 
// 输出参数 : VOID
// 返回值   : YCPP_BOOL
// 使用说明 : 
///
YCPP_BOOL canModify ( const char *szFileName )
{
        if ( access(szFileName, W_OK | R_OK) == 0 )
                return YCPP_TRUE;
        return YCPP_FALSE;
}
///
// 功能说明 : 获取文件上次修改时间
// 输入参数 : 
// 输出参数 : VOID
// 返回值   : YCPP_RET
// 使用说明 : 
///
YCPP_RET FileLastMTime ( const char *szFileName )
{
        struct stat st;
        memset ( &st, 0, sizeof(struct stat) );
        if ( lstat (szFileName, &st) == 0 )
                return st.st_mtime;
        
        printf ( "%s FileLastMTime error %s\n", szFileName, strerror(errno) );
        return -1;
}
///
// 功能说明 : 获取文件大小,以字节为单位
// 输入参数 : 
// 输出参数 : VOID
// 返回值   : YCPP_RET 
// 使用说明 : 
///
YCPP_RET FileSize ( const char *szFileName )
{
        struct stat st;
        
        memset ( &st, 0, sizeof(struct stat) );
        if ( lstat (szFileName, &st) == 0 )
                return st.st_size;
        
        printf ( "%s FileSize error %s\n", szFileName, strerror(errno) );
        return -1;
}




------------------------------------------------------------------------------------------------------------------------------



编程:Linux中常用C/C++一些头文件的作用

原:http://www.linuxidc.com/Linux/2009-07/20783.htm


1、 Linux中一些头文件的作用:
<assert.h>:ANSI C。提供断言,assert(表达式)
<glib.h>:GCC。GTK,GNOME的基础库,提供很多有用的函数,如有数据结构操作函数。使用glib只需要包含<glib.h>
<dirent.h>:GCC。文件夹操作函数。struct dirent,struct DIR,opendir(),closedir(),readdir(),readdir64()等


<ctype.h>:ANSI C。字符测试函数。isdigit(),islower()等
<errno.h>:ANSI C。查看错误代码errno是调试程序的一个重要方法。当linuc C api函数发生异常时,一般会将errno变量(需include errno.h)赋一个整数值,不同的值表示不同的含义,可以通过查看该值推测出错的原因。在实际编程中用这一招解决了不少原本看来莫名其妙的问题。比较 麻烦的是每次都要去linux源代码里面查找错误代码的含义,现在把它贴出来,以后需要查时就来这里看了。来自linux 2.4.20-18的内核代码中的/usr/include/asm/errno.h
<getopt.h>:处理命令行参数。getopt()


2、
-------------------------
linux常用头文件如下:
POSIX标准定义的头文件
<dirent.h>        目录项
<fcntl.h>         文件控制
<fnmatch.h>    文件名匹配类型
<glob.h>    路径名模式匹配类型
<grp.h>        组文件
<netdb.h>    网络数据库操作
<pwd.h>        口令文件
<regex.h>    正则表达式
<tar.h>        TAR归档值
<termios.h>    终端I/O
<unistd.h>    符号常量
<utime.h>    文件时间
<wordexp.h>    字符扩展类型
-------------------------
<arpa/inet.h>    INTERNET定义
<net/if.h>    套接字本地接口
<netinet/in.h>    INTERNET地址族
<netinet/tcp.h>    传输控制协议定义
------------------------- 
<sys/mman.h>    内存管理声明
<sys/select.h>    Select函数
<sys/socket.h>    套接字借口
<sys/stat.h>    文件状态
<sys/times.h>    进程时间
<sys/types.h>    基本系统数据类型
<sys/un.h>    UNIX域套接字定义
<sys/utsname.h>    系统名
<sys/wait.h>    进程控制
------------------------------
POSIX定义的XSI扩展头文件
<cpio.h>    cpio归档值 
<dlfcn.h>    动态链接
<fmtmsg.h>    消息显示结构
<ftw.h>        文件树漫游
<iconv.h>    代码集转换使用程序
<langinfo.h>    语言信息常量
<libgen.h>    模式匹配函数定义
<monetary.h>    货币类型
<ndbm.h>    数据库操作
<nl_types.h>    消息类别
<poll.h>    轮询函数
<search.h>    搜索表
<strings.h>    字符串操作
<syslog.h>    系统出错日志记录
<ucontext.h>    用户上下文
<ulimit.h>    用户限制
<utmpx.h>    用户帐户数据库 
-----------------------------
<sys/ipc.h>    IPC(命名管道)
<sys/msg.h>    消息队列
<sys/resource.h>资源操作
<sys/sem.h>    信号量
<sys/shm.h>    共享存储
<sys/statvfs.h>    文件系统信息
<sys/time.h>    时间类型
<sys/timeb.h>    附加的日期和时间定义
<sys/uio.h>    矢量I/O操作
------------------------------
POSIX定义的可选头文件
<aio.h>        异步I/O
<mqueue.h>    消息队列
<pthread.h>    线程
<sched.h>    执行调度
<semaphore.h>    信号量
<spawn.h>     实时spawn接口
<stropts.h>    XSI STREAMS接口
<trace.h>     事件跟踪

3、 C/C++头文件一览
C
#include <assert.h>    //设定插入点
#include <ctype.h>     //字符处理
#include <errno.h>     //定义错误码
#include <float.h>     //浮点数处理
#include <iso646.h>        //对应各种运算符的宏
#include <limits.h>    //定义各种数据类型最值的常量
#include <locale.h>    //定义本地化C函数
#include <math.h>     //定义数学函数
#include <setjmp.h>        //异常处理支持
#include <signal.h>        //信号机制支持
#include <stdarg.h>        //不定参数列表支持
#include <stddef.h>        //常用常量
#include <stdio.h>     //定义输入/输出函数
#include <stdlib.h>    //定义杂项函数及内存分配函数
#include <string.h>    //字符串处理
#include <time.h>     //定义关于时间的函数
#include <wchar.h>     //宽字符处理及输入/输出
#include <wctype.h>    //宽字符分类

传统C++
#include <fstream.h>    //改用<fstream>
#include <iomanip.h>    //改用<iomainip>
#include <iostream.h>   //改用<iostream>
#include <strstrea.h>   //该类不再支持,改用<sstream>中的stringstream
————————————————————————————————

标准C++ 
#include <algorithm>    //STL 通用算法
#include <bitset>     //STL 位集容器
#include <cctype>          //字符处理
#include <cerrno>      //定义错误码
#include <cfloat>     //浮点数处理
#include <ciso646>         //对应各种运算符的宏
#include <climits>     //定义各种数据类型最值的常量
#include <clocale>     //定义本地化函数
#include <cmath>      //定义数学函数
#include <complex>     //复数类
#include <csignal>         //信号机制支持
#include <csetjmp>         //异常处理支持
#include <cstdarg>         //不定参数列表支持
#include <cstddef>         //常用常量
#include <cstdio>      //定义输入/输出函数
#include <cstdlib>     //定义杂项函数及内存分配函数
#include <cstring>     //字符串处理
#include <ctime>      //定义关于时间的函数
#include <cwchar>      //宽字符处理及输入/输出
#include <cwctype>     //宽字符分类
#include <deque>      //STL 双端队列容器
#include <exception>    //异常处理类
#include <fstream>     //文件输入/输出
#include <functional>   //STL 定义运算函数(代替运算符)
#include <limits>      //定义各种数据类型最值常量
#include <list>      //STL 线性列表容器
#include <locale>          //本地化特定信息
#include <map>       //STL 映射容器
#include <memory>          //STL通过分配器进行的内存分配
#include <new>             //动态内存分配
#include <numeric>         //STL常用的数字操作
#include <iomanip>     //参数化输入/输出
#include <ios>       //基本输入/输出支持
#include <iosfwd>     //输入/输出系统使用的前置声明
#include <iostream>     //数据流输入/输出
#include <istream>     //基本输入流
#include <iterator>        //STL迭代器
#include <ostream>     //基本输出流
#include <queue>      //STL 队列容器
#include <set>       //STL 集合容器
#include <sstream>     //基于字符串的流
#include <stack>      //STL 堆栈容器
#include <stdexcept>    //标准异常类
#include <streambuf>    //底层输入/输出支持
#include <string>     //字符串类
#include <typeinfo>        //运行期间类型信息
#include <utility>     //STL 通用模板类
#include <valarray>        //对包含值的数组的操作
#include <vector>     //STL 动态数组容器
————————————————————————————————

C99增加的部分
#include <complex.h>   //复数处理
#include <fenv.h>    //浮点环境
#include <inttypes.h>  //整数格式转换
#include <stdbool.h>   //布尔环境
#include <stdint.h>   //整型环境
#include <tgmath.h>   //通用类型数学宏





------------------------------------------------------------------------------------------------------------------------------


pthread_create函数应用 


转自 http://www.cnblogs.com/maliqian/archive/2011/12/16/2290815.html


1. pthread_create函数

函数简介

       pthread_create是UNIX环境创建线程函数

头文件

#include<pthread.h>

函数声明

int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict_attr,void*(*start_rtn)(void*),void *restrict arg);

返回值

       若成功则返回0,否则返回出错编号

参数

       第一个参数为指向线程标识符的指针。

       第二个参数用来设置线程属性。

       第三个参数是线程运行函数的起始地址。

       最后一个参数是运行函数的参数。

另外

       在编译时注意加上-lpthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库


2. pthread_join函数

函数简介

       函数pthread_join用来等待一个线程的结束。

函数原型为:

extern int pthread_join __P (pthread_t __th, void **__thread_return);

参数:

第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。如果执行成功,将返回0,如果失败则返回一个错误号。


3. 例子:

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
struct member
{
        int num;
        char *name;
};     
//结构体后的分号勿漏
void *create(void *arg)       
//有void* 型参数传入,不能直接void
{
        struct member *temp;
        temp=(struct member *)arg;      
//结构体变量之间不能直接赋值,但可以通过指针赋地址
        printf("member->num:%d\n",temp->num);
        printf("member->name:%s\n",temp->name);
        sleep(1);
        return (void *)8;     
//这个很有特色,返回一个指向void的数据类型的值,这个值作为后面的exit code
}
int main(int agrc,char* argv[])
{
        pthread_t tidp;
        struct member *b;
        void* a;
        b=(struct member *)malloc(sizeof(struct member));           
//先分配内存空间撒~
        b->num=1;
        b->name="mlq";              
//字符串赋值,其他好用简便的方法有:   char *p = NULL;   p = new char [256];
        if((pthread_create(&tidp,NULL,create,(void*)b))==-1)     /
//void * 为“无类型指针”,void *  可以指向任何类型的数据
        {
                printf("create error!\n");
                return 1;
        }
        if(pthread_join(tidp,&a))                   
//调用pthread_join函数,等待线程结束再继续往下执行,要不然主进程和下面的线程并行执行
        {
                printf("thread is not exit...\n");
                return -2;
        }
        printf("thread is exit ,code is %d\n",(int)a);       //不知为啥这里是(int)a,,a不是指针来的么
        return 0;
}



==============================================================================



Linux Pipe

转自 http://blog.ddup.us/?p=285 && http://www.armfans.net/thread-1559-1-4.html



       在一个多进程操作系统所提供的运行环境下,可以通过两种不同的途径或者说采用两种不同的策略,来建立起复杂的大型应用系统。
       一种途径就是通过一个孤立的,大型的,复杂的进程提供所需的全部服务;
       另外一种途径就是通过由若干相互联系的,小型的。相对简单的进程构成的组合来提供所需的功能。


       早期的操作系统往往倾向与前者,而Unix以及其衍生的各种操作系统往往倾向于后者。相比之下,后者有着各种好处:
       1. 模块化.
       2. 各个进程都得到保护,在相当程度上排除了相互干扰的可能性.
       3. 灵活性更强。
       当然这种好处也是要付出代价的,也有缺点,但是相比之下,这种途径的优点远远超出了其缺点。

       Unix向应用软件提供了一些进程间通信的手段,早期的Unix提供了:管道(pipe),信号(signal),跟踪(trace)。在linux中进行了扩展,包括:pipe、fifo、共享内存,信号量,消息队列,共享文件等等。其中pipe和fifo使用最广泛,二者的区别为pipe为匿名管道,只能用在有父子关系的进程间通信,而fifo可以通过文件系统中的一个文件取得,所以不受上述限制。作为父子进程间通信的通道,pipe同样可以看作是一个先进先出的队列。


建立过程:

       这里我们只谈管道:父进程与子进程,或者两个兄弟进程之间,可以通过系统调用建立起一个单向的通信管道。但是,这种管道只能由父进程来建立,所以对于子进程来说是静态的,与生俱来的。管道两端的进程各自将该管道视作一个文件。一个进程往通道中写的内容由另一个进程从通道读出,通过通道传递的内容遵循“先入先出”(FIFO)的规则。每个通道都是单向的,需要双向通信时要建立起两个通道。

       下面说一说进程间管道的建立,在这之前我们要说到fork()函数,在Linux系统中一个新的进程是由一个已经存在的进程“复制”出来的,而不是“创造”出来的(而所谓的“创建”实际上就是复制)。

       管道机制的主体是系统调用pipe(),但是由pipe()所建立的管道的两端都在同一个进程中,这样的管道起不到进程间通信的作用,但很明显可以被用于线程之间通讯。所以如果要在进程间进行通讯的话,必须和fork()配合,才能在父子进程间或者两个子进程之间建立起进程间的通信管道。

       下面就介绍一下怎样将管道用于进程间通信:
       (1)  进程A创建了一个管道,创建完成时代表管道两端的两个已打开文件都在进程A中。(可实现线程之间通讯)
       (2)  进程A通过fork()创建出进程B,在fork()的过程中进程A的打开文件表按原样复制到进程B中。
       (3) 进程A关闭管道的读端,而进程B关闭管道的写段。于是,管道的写段在进程A中而读端在进程B中,成为了父子进程之间的通信管道。
       (4) 进程A又通过frok()创建进程C,而后关闭其管道写段而与管道脱离关系,使得管道的写段在进程C中而读端在进程B中,成为两个兄弟进程之间的管道。

       人们在认识到管道机制也存在一些缺点和不足。由于管道是一种“无名”,“无形”的文件,它可以通过fork()的过程创建于“近亲” 的进程之间,而不能成为可以在任意两个进程之间建立通信的机制,更不可能成为一种一般的,通用的进程间通信模型,同时,管道机制的这种缺点本身强烈的暗示着人们,只要用“有名”,“有形”的文件来实现管道,就能克服这种缺点。所以有了管道之后,“命名管道”的出现时必然的。
       为了实现“命名管道”,在“普通文件”,“块设备文件”,“字符设备文件”之外,又设立了一种文件类型,称为FIFO文件。


PIPE基本用法

       pipe的使用很简单,原型如下:

#include <unistd.h>
int pipe(int pipefd[2]);

       典型的使用方法如下:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
 
int main()
{
    int pfd[2];
    int ret = pipe(pfd);
    if (ret != 0) {
        perror("pipe");
        exit(-1);
    }
 
    pid_t pid = fork();
    if (pid == 0) {
        close(pfd[1]);
        char rdbuf[1024];
        read(pfd[0], rdbuf, sizeof(rdbuf));
        return 0;
    } else if (pid > 0) {
        close(pfd[0]);
        char wrbuf[] = "abcd";
        write(pfd[1], wrbuf, sizeof(wrbuf));
        pid_t cpid = waitpid(-1, NULL, 0);
    } else {
        perror("fork");
    }
    return 0;
}

       从代码中可以看出,调用pipe之后,pfd即为一对相关联的管道,其中pfd[0]对应的是数据输入端,pfd[1]对应数据输出端。当父进程想要给子进程发送数据时,直接将数据write到pfd[1]中即可,对应的子进程从pfd[0]中read。


PIPE缓冲区限制

       从上边的父子进程间关系可以看到,这是操作系统中典型的1:1生产者消费者模型。管道作为联系生产者与消费者的缓冲区同样会涉及到两个问题:

       1)缓冲区的大小问题。 

       2)缓冲区读写的互斥问题。

       将上述代码中父进程的write一行去掉,即可测试出在管道为空的情况下,子进程读管道会阻塞(默认是阻塞的,亦可以将其设置为非阻塞,这样返回的将是-1,同时errno设为EAGAIN)。

       为了测试缓冲区的大小,再次修改上述代码,使得子进程不再读取数据,同时父进程一直向其中写入数据,代码如下:

    pid_t pid = fork();
    if (pid == 0) {
        close(pfd[1]);
        pause();
        return 0;
    } else if (pid > 0) {
        close(pfd[0]);
        pause();
        char buf[] = "a";
        for (int i = 0; ; i++) {
            write(pfd[1], buf, sizeof(buf[0]));
            printf("write pipe %d, total %d\n", i+1, (i+1)*sizeof(buf[0]));
        }
        pid_t cpid = waitpid(-1, NULL, 0);
    } else {
        perror("fork");
    }

       修改之后的父进程每次向管道中写入一个字符,并将已经写入的字节数打印出来。当程序停住时,在Linux2.6.27上显示的结果为:
write pipe 65536, total 65536

       由此可以看出,此时pipe的缓冲区大小为64KB。相同的程序在MacOSX中显示为:
write pipe 16384, total 16384

       可以看出MacOSX的pipe默认缓冲区大小16KB。

       已知了pipe默认缓冲区的大小了,那么自然就会想这个缓冲区大小是不是可以人工设定呢?搜索一番之后,发现好多人都说这个值是限定死了的,在内核代码中固定就是64K。后来下了一份linux 3.0代码发现这个默认值已经是可以改的了。相关代码在include/linux/pipe_fs_i.h中:
#define PIPE_DEF_BUFFERS    16
…
…
/* for F_SETPIPE_SZ and F_GETPIPE_SZ */
long pipe_fcntl(struct file *, unsigned int, unsigned long arg);

       看到PIPE_DEF_BUFFERS就是默认的pipe缓冲区大小,不过是以页为单位的,默认的页大小为4K,所以默认的pipe缓冲区大小即为64KB。但是在最下边又有个函数pipe_fcntl,同时有两个常量F_SETPIPE_SZ, F_GETPIPE_SZ。看来应该是用来修改默认缓冲区大小的。

       果然这个特性是在2.6.35的内核中加入的。发行说明可以见这里,相应的commit log中看到有相应的Commit。这样就可以通过使用fcntl配合上边两个常量指令更改pipe缓冲区大小。同时在/proc/sys/fs/pipe-max-size中可以方便查看pipe缓冲区最大值。

       至此已经知道了linux默认的pipe缓冲区为64KB,同时在2.6.35之后的内核可以使用fcntl更改缓冲区大小。还剩另一个问题,就是原子读写问题。


PIPE的原子访问

       同样在include/linux/pipe_fs_i.h中,有如下代码:

/* Differs from PIPE_BUF in that PIPE_SIZE is the length of the actual
   memory allocation, whereas PIPE_BUF makes atomicity guarantees.  */
#define PIPE_SIZE       PAGE_SIZE

       看到定义了PIPE_SIZE这个常量,大小为一个页面大小(默认是4K)。从注释中可以看到这个PIPE_SIZE值即为PIPE最大可保证的原子操作字节数。同样在Write Man Page中写到:
Write requests of {PIPE_BUF} bytes or less shall not be interleaved with data from other processes doing writes on the same pipe. 
Writes of greater than {PIPE_BUF} bytes may have data interleaved, on arbitrary boundaries, with writes by other processes, whether or not the O_NONBLOCK flag of the file status flags is set.

       这个PIPE_BUF常量在/usr/include/linux/limits.h中定义:
#define PIPE_BUF        4096    /* # bytes in atomic write to a pipe */

       另外也可以通过ulimit -p查看这个限制,不过这是个只读值,不能修改的。ulimit -a输出如下,其中的pipe size 即是:
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 81920
max locked memory       (kbytes, -l) 32
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 10240
cpu time               (seconds, -t) unlimited
max user processes              (-u) 1024
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited


参考:
           http://pubs.opengroup.org/onlinepubs/9699919799/functions/write.html
           http://stackoverflow.com/questions/4624071/pipe-buffer-size-is-4k-or-64k
           http://home.gna.org/pysfst/tests/pipe-limit.html
           http://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer
           http://stackoverflow.com/questions/4739348/is-it-possible-to-change-the-size-of-a-named-pipe-on-linux























评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值