linux进程控制

来源:互联网 发布:js数组删除元素 编辑:程序博客网 时间:2024/05/16 16:07

 
1. Linux进程概述
进程是程序的执行实例,它是Linux的基本调度单位。一个进程由如下元素组成:
l         程序的当前上下文,即程序的当前执行状态;
l         程序的当前执行目录
l         程序访问的文件和目录
l         程序的访问权限,比如它的文件模式和所有权
l         内存和其他分配给进程的系统资源
 
内核使用进程来控制对CPU和其他系统资源的访问,并且使用进程来决定在CPU上运行哪个程序,运行多久以及采用什么特性运行它。内核的调度器负责在所有的进程间分配CPU执行时间,称为时间片(timeslice),它轮流在每个进程分得的时间片用完后从进程那里抢回控制权。
 
1.1. 进程标识
OS会为每个进程分配一个唯一的整型ID,做为进程的标识号(pid)。进程除了自身的ID外,还有父进程ID(ppid),所有进程的祖先进程是同一个进程,它叫做init进程,ID为1,init进程是内核自举后的一个启动的进程。init进程负责引导系统、启动守护(后台)进程并且运行必要的程序。
进程的pid和ppid可以分别通过函数getpid()和getppid()获得。
示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
    int main()
{
                  printf("pid:%d ppid:%d/n",getpid(),getppid());
return 0;
}
1.2. 进程的用户ID与组ID(进程的运行身份)
              进程在运行过程中,必须具有一类似于用户的身份,以便进行进程的权限控制,缺省情况下,哪个登录用户运行程序,该程序进程就具有该用户的身份。例如,假设当前登录用户为gotter,他运行了ls程序,则ls在运行过程中就具有gotter的身份,该ls进程的用户ID和组ID分别为gotter和gotter所属的组。这类型的ID叫做进程的真实用户ID和真实组ID。真实用户ID和真实组ID可以通过函数getuid()和getgid()获得。
              与真实ID对应,进程还具有有效用户ID和有效组ID的属性,内核对进程的访问权限检查时,它检查的是进程的有效用户ID和有效组ID,而不是真实用户ID和真实组ID。缺省情况下,用户的(有效用户ID和有效组ID)与(真实用户ID和真实组ID)是相同的。有效用户id和有效组id通过函数geteuid()和getegid()获得。
 
示例      
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
printf("uid:%d gid:%d euid:%d egid:%d/n",
getuid(),getgid(),geteuid(),getegid());
return 0;
}
 
 
 
开始时登录用户为ghaha(uid为500):
shell>id
uid=500(ghaha) gid=500(ghaha) groups=500(ghaha)
 
编译生成可执行文件a.out,程序文件的属性可能为:
-rwxrwxr-x    1 ghaha    ghaha       12132 Oct 7 09:26 a.out
 
执行结果可能为:
shell>a.out
uid:500 gid:500 euid:500 egid:500
 
现在将a.out的所有者可执行属性改为s
shell>chmod u+s a.out
shell>ll
-rwsrwxr-x    1 ghaha    ghaha       12132 Oct 7 09:26 a.out
 
此时改另外一个用户gotter登录并运行程序a.out
shell>id
uid=502(gotter) gid=502(gotter) groups=502(gotter)
shell>a.out
uid:502 gid:502 euid:500 egid:502
 
可以看到,进程的有效用户身份变为了ghaha,而不是gotter了,这是因为文件a.out的访问权限的所有者可执行为设置了s的属性,设置了改属性以后,用户运行a.out时,a.out进程的有效用户身份将不再是运行a.out的用户,而是a.out文件的所有者。
 
s权限最常见的例子是
/usr/bin/passwd程序,它的权限位为
 
shell>ll /usr/bin/passwd
-r-s--x--x    1 root     root        16336 Feb 13 2003 /usr/bin/passwd
 
说明任何一个用户运行该程序时,该程序的有效身份都将是root,这样passwd程序才有权限读取/etc/passwd文件的信息。
 
2. 进程的创建
Linux下有四类创建子进程的函数:
       system(),fork(),exec*(),popen()
 
2.1. system函数
原型:
       #include <stdlib.h>
       int system(const char *string);
system函数通过调用shell程序/bin/sh–c来执行string所指定的命令,该函数在内部是通过调用execve(“/bin/sh”,..)函数来实现的。通过system创建子进程后,原进程和子进程各自运行,相互间关联较少。如果system调用成功,将返回0。
 
示例:
 
#include <stdio.h>
#include <stdlib.h>
int main()
{
       system("ls -l");
       return 0;
}
 
2.2. fork函数
原型:
    #include <unistd.h>
pid_t fork(void);
 
fork函数创建子进程的过程为:在进程调用fork时,系统根据现有的进程的特性几乎是完全意义的复制出一个新的子进程,复制的内容包括原进程的当时内存空间的代码、变量、对象等所有内存状态,真实和有效uid和gid,环境、资源限制、打开的文件等。通过这种复制方式创建出子进程后,原有进程和子进程都从函数fork返回,各自继续往下运行,但是原进程的fork返回值于子进程的fork返回值不同,在原进程中,fork返回子进程的pid,而在进程中,fork返回0,如果fork返回负值,表示创建子进程失败。
 
示例:
 
// TestFork.cpp {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
#include <stdlib.h>
#include <unistd.h>
int main()
{
    printf("Parent process id:%d/n",getpid());
    pid_t iRet=fork();
    if(iRet<0){
        printf("Create child process fail!/n");
    }else if(iRet==0){
        printf("I'm child process,and id:%d ppid:%d/n",
                getpid(),getppid());
    }else{
        printf("Create child process success,child id:%d/n",iRet);
    }
    return 0;
}
// TestFork.cpp }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
 
shell>g++ TestFork..cpp
shell>a.out
Parent process id:6059
I'm child process,and id:6060 ppid:6059
Create child process success,child id:6060
 
2.3. exec函数族
exec*由一组函数组成,原型:
#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[]);
      
       exec函数族的工作过程与fork完全不同,fork是在复制一份原进程,而exec函数是用exec的第一个参数指定的程序在现有进程空间中运行,并且用新进程完全覆盖掉现有进程空间。
       path是可包括执行文件名的全路径名,而不是可执行文件所在的路径
       file既可以是全路径名,也可以是可执行文件名
       arg是可执行文件的命令行参数,可以有许多个,但通常的的一个参数应该是可执行文件的全路径名,注意最后一个参数必须为NULL。
       argv是传递参数的另外一种形式,它是一个字符串的数组,一般形式为:
              char *argv[]={“full path”,”param1”,”param2”,...NULL};
       envp也是一个字符串数组,它指定新进程的环境变量,一般形式为:
       char *envp[]={“name1=val1”,”name2=val2”,...NULL};
       对有参数envp的函数调用,新进程中的全局变量environ指针指向的环境变量数组将会被envp中的内容替代。
 
       示例:
 
       //GetArg.cpp{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
    for(int i=0;i<argc;i++)
        printf("arg%d:%s/n",i,argv[i]);
    char **ppEnv=environ;
    while(ppEnv && *ppEnv)
        printf("%s/n",*ppEnv++);
   return 0;
}
       //GetArg.cpp}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
子进程程序GetArg.cpp,编译后的可执行文件存放到./exe/GetArg.exe
 
exec测试程序
//TestExec.cpp{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
       #include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
    char *arrArg[]={"GetArg.exe","abc",NULL};
    char *arrEnv[]={"env1=111","env2=222",NULL};
    int iRet;
    //
    iRet=system("ls -l");
    //iRet=execl("ls","-l",NULL);//error!
    //iRet=execl("/bin/ls","ls","-l",NULL);
    //iRet=execl("/bin/ls","-l",NULL);//incorrect!
    //iRet=execl("./exe/GetArg.exe","GetArg.exe","abc",NULL);
    //iRet=execl("./exe/GetArg.exe GetArg.exe abc",NULL);//error!
    //iRet=execl("./exe/GetArg.exe","GetArg.exe abc",NULL);
    //iRet=execl("./exe","GetArg.exe","abc",NULL); //error!
    //iRet=execlp("./exe/GetArg.exe","GetArg.exe","abc",NULL);
    //iRet=execlp("./exe","GetArg.exe","abc",NULL);//error!
    //iRet=execlp("ls","ls","-l",NULL);
    //iRet=execle("./exe/GetArg.exe","abc",arrEnv);//????
    //iRet=execv("./exe/GetArg.exe",arrArg);
    //iRet=execve("./exe/GetArg.exe",arrArg,arrEnv);
    printf("iRet: %d/n",iRet);
    return 0;
}
       // TestExec.cpp}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} 
2.4. popen函数
popen函数类似于system函数,它是一种无需fork和exec就能执行外部程序的简易方法。与system的不同之处在于它使用管道工作。原型为:
#include <stdio.h>
    FILE *popen(const char *command, const char *type);
    int pclose(FILE *stream);
 
command为可执行文件的全路径和执行参数;
type可选参数为”r”或”w”,如果为”w”,则popen返回的文件流做为新进程的标准输入流,即stdin,如果为”r”,则popen返回的文件流做为新进程的标准输出流。
pclose等待新进程的结束,而不是杀新进程。
 
示例:
 
新进程源程序GetLine.cpp,可执行文件GetLine.exe
//GetLine.cpp{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
 
int main(int argc,char *argv[])
{
    char szBuf[32];
    fgets(szBuf,sizeof(szBuf),stdin);
    fputs(szBuf,stdout);
    sleep(3);
    return 0;
}
//GetLine.cpp}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
 
测试程序TestPopen.cpp
//TestPopen{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
    FILE *pStream=popen("GetLine.exe","w");
    if(pStream){
        fputs("My god!/n",pStream);
        pclose(pStream);
    }else{
        perror("popen fail!");
    }
    return 0;
}
//TestPopen}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
3. 进程控制与终止
3.1. 进程的控制
一个进程创建子进程后,如果子进程先退出,系统不会自动清理掉子进程的环境,而必须由父进程调用wait或waitpid函数来完成清理工作,如果父进程不做清理工作,则已经退出的子进程将成为僵尸进程(defunct),在系统中如果存在的僵尸进程过多,将会影响系统的性能,所以必须对僵尸进程进行处理。
       另一种情况,如果父进程先于子进程退出,则子进程成为孤儿进程,孤儿进程退出后,它的清理工作有祖先进程init自动处理。
       函数原型:
#include <sys/types.h>
        #include <sys/wait.h>
 
        pid_t wait(int *status);
        pid_t waitpid(pid_t pid, int *status, int options);
      
       wait和waitpid都等待一个已经退出的子进程,并进行清理工作;
wait函数随机地等待一个已经退出的子进程,并返回该子进程的pid;
waitpid等待指定pid的子进程;
status参数是传出参数,存放子进程的退出代码;
options的可选项有2个:WNOHANG和WUNTRACED,两参数可用|合成。WNOHANG表示无论子进程是否退出都将立即返回,WUNTRACED极少使用。
 
示例:
 
//{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
       #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
 
void SignChildPsExit(int iSignNo)
{
    pid_t pid;
    int iExitCode;
    pid=wait(&iExitCode);
    printf("SignNo:%d child %d exit/n",iSignNo,pid);
}
 
int main()
{
    signal(SIGCHLD,SignChildPsExit);
    printf("Parent process id:%d/n",getpid());
    pid_t iRet=fork();
    if(iRet<0){
        printf("Create child process fail!/n");
        return -1;
}
//}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
 
3.2. 进程的终止
进程的终止有5种方式:
 
l         main函数的自然返回;
l         调用exit函数
l         调用_exit函数
l         调用abort函数
l         接收到能导致进程终止的信号
 
前3种方式为正常的终止,后2种为非正常终止。但是无论哪种方式,进程终止时都将执行相同的关闭打开的文件,释放占用的内存等资源。只是后两种终止会导致程序有些代码不会正常的执行,比如对象的析构、atexit函数的执行等。
 
kill函数
原型为:
#include <sys/types.h>
#include <signal.h>
 
int kill(pid_t pid, int sig);
 
kill函数给进程pid发送信号sig,可发送的信号可以通过shell命令kill –l查看,接收信号的进程如果不对信号进行处理,将会被自动终止。
 
 
4. 进程间打开文件的继承
4.1. 用fork继承打开的文件
fork以后的子进程自动继承了父进程的打开的文件,继承以后,父进程关闭调打开的文件不会对子进程造成影响。
 
示例:
 
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
   
int main()
{
    char szBuf[32]={0};
    int iFile=open("./a.txt",O_RDONLY);
    if(fork()>0){//parent process
        close(iFile);
        return 0;
    }
    //child process
    sleep(3);//wait for parent process closing fd
    if(read(iFile,szBuf,sizeof(szBuf)-1)<1){
        perror("read fail");
    }else{
        printf("string:%s/n",szBuf);
    }
    close(iFile);
    return 0;
}
4.2. 用exec*继承打开的文件
用exec*创建的子进程照样可以继承已经打开的文件。
示例:
// Parent2.cpp {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
 
int main()
{
    char szBuf[32]={0};
    if(fork()>0)//parent process
        return 0;
    //child process
    close(STDOUT_FILENO);
    int iFile=open("./a.txt",O_WRONLY|O_CREAT,0666);
    if(iFile!=STDOUT_FILENO){
        perror("open a.txt fail");
        return -1;
    }
   lseek(iFile,0,SEEK_END);
    execl("./Child2.exe",NULL);
    perror("execl fail!"); //must never run here!
    return 0;
}
//Parent2.cpp}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
 
//Child2.cpp{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
#include <stdio.h>
#include <unistd.h>
int main()
{
    printf("hello/n");
    return 0;
}
//Child2.cpp}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
4.3. 用管道(FIFO)传递打开的文件的信息
如果用exec*创建子进程,虽然子进程可以自动继承父进程打开的文件,但是因为exec*后,子进程完全替换掉了父进程的空间,所以比较难获取打开的文件描述符,在这种情况下,必须采用进程间的通讯机制,让子进程知道自己继承了一些什么已经打开的文件。
 
示例,用FIFO传递打开的文件的信息:
//Parent3.cpp{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
int main()
{
    char szBuf[32]={0};
    int fdFifo,fdFile;
    fdFile=open("./a.txt",O_WRONLY|O_CREAT,0666);
    if(fdFile<0){
        perror("open a.txt fail");
        return -1;
    }
    lseek(fdFile,0,SEEK_END);
    if(mkfifo("MyFifo",0666)<0 && errno != EEXIST){
        perror("create fifo fail");
    return -2;      
    }
    if(fork()>0){//parent process
        close(fdFile);
        if((fdFifo=open("MyFifo",O_WRONLY))<0){
            perror("open fifo fail");
            return -3;
        }
        write(fdFifo,&fdFile,sizeof(fdFile));
        close(fdFifo);
        //unlink("MyFifo");
        return 0;
    }
    //child process
    execl("./Child3.exe",NULL);
    perror("execl fail!"); //must never run here!
    perror("execl fail!"); //must never run here!
    return 0;
}
//Parent3.cpp}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
 
//Child3.cpp{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
 
int main()
{
    int fdFifo,fdFile;
    char szBuf[]="written by child3/n";
    fdFifo=open("MyFifo",O_RDONLY);
    if(fdFifo<0){
        perror("Child3 open fifo fail");
        return -1;
    }
    if(read(fdFifo,&fdFile,sizeof(fdFile))<1){
        perror("read fifo fail"); 
        return -2;
    }
    close(fdFifo);
    write(fdFile,szBuf,strlen(szBuf));
    close(fdFile);
    return 0;
}
//Child3.cpp}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
5. 进程间通信(Interprocess Communication,IPC)
5.1. 无名管道(PIPE)
pipe可以看作一个形似管子的文件,管子的一端写数据进去,另外一段读数据出来。无名管道通常用于有继承关系的两进程间传递数据,因为pipe是半工的,所以只能是一个进程传递数据给另外一个进程。
管道函数原型:
#include <unistd.h>
int pipe(int fds[2]);
管道在程序中用一对文件描述符表示,其中一个文件描述符有可读属性,一个有可写的属性。
函数pipe用于创建一个无名管道,如果成功,fds[0]存放可写的文件描述符,fds[1]存放可写文件描述符,并且返回0,否则返回-1。
通过调用pipe获取这对打开的文件描述符后,一个进程就可以往fds[0]中写数据,而另一个进程就可以从fds[1]中读数据出来了。当然两进程间必须有继承关系,才能继承这对打开的文件描述符。
管道不象真正的物理文件,不是持久的,即两进程终止后,管道也自动消失了。
示例:
 
#include <stdio.h>
#include <string.h>
#include <unistd.h>
 
int main()
{
    int fds[2];
    int iRet;
    char szBuf[32];
    //
    pipe(fds);
    printf("fds[0]:%d fds[1]:%d/n",
            fds[0],fds[1]);
    if(fork()==0){
        close(fds[1]);
        iRet=read(fds[0],szBuf,sizeof(szBuf));
        if(iRet>0){
            szBuf[iRet]=0;
            printf("ps %d read:%s/n",getpid(),szBuf);
        }else{
            printf("read ret:%d/n",iRet);
        }
        close(fds[0]);
    }else{
       close(fds[0]);
        strcpy(szBuf,"Hello,child ps.");
        iRet=write(fds[1],szBuf,strlen(szBuf));
        printf("ps %d wrote:%s/n",getpid(),szBuf);
        close(fds[1]);
    }
    return 0;
}
 
管道两端的关闭是有先后顺序的,如果先关闭写端,则从另一端读数据时,read函数将返回0,表示管道已经关闭;
但是如果先关闭写端,则往另一端写数据是,将会使写数据的进程接收到SIGPIPE信号,如果写进程不对该信号进行处理,将导致写进程终止,如果写进程处理了该信号,则写数据的write函数返回一个负值,表示管道已经关闭。
示例:
 
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
 
int main()
{
    int fds[2];
    int iRet;
    char szBuf[32];
    //
    /*注释掉这部分将导致写进程被信号SIGPIPE终止
    sigset_t setSig;
    sigemptyset(&setSig);
    sigaddset(&setSig,SIGPIPE);
    sigprocmask(SIG_BLOCK,&setSig,NULL);
    */
    pipe(fds);
    if(fork()==0){
        close(fds[1]);
        close(fds[0]);
    }else{
        close(fds[0]);
        usleep(1*1000);
       strcpy(szBuf,"Hello,child ps.");
        iRet=write(fds[1],szBuf,strlen(szBuf));
        if(iRet<0){
            printf("wrote ret:%d/n");
        }else{
            printf("ps %d wrote:%s/n",getpid(),szBuf);
        }
        close(fds[1]);
    }
    return 0;
}
5.2. 命名管道(FIFO)
与无名管道PIPE工作方式类似的,还有另一种管道:命名管道(FirstIn,FirstOut)。两者区别在于:PIPE不是持久的,即使用PIPE的所有进程终止后,PIPE自动消失;而FIFO对应于一个物理文件,是持久的,并且多个无继承进程可以根据该物理文件打开并使用同一个FIFO。
5.2.1. 创建、删除FIFO文件
创建FIFO文件与创建普通文件很类似,只是创建后的文件用于FIFO。
原型:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数pathname为要创建的FIFO文件的全路径名;
参数mode为文件访问权限
如果创建成功,则返回0,否则-1。
 
示例:
//MkFifo.cpp{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
 
int main(int argc,char *argv[])
{
    mode_t iRight=0666;
    if(argc!=2){
        puts("Usage: MkFifo.exe {filename}");
        return -1;
    }
    if(mkfifo(argv[1],iRight)<0){
        perror("mkfifo fail");
        return -2;
    }
    return 0;
}
//MkFifo.cpp}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
shell>MkFifo.exe MyFifo.dat
shell>ll
total 16
-rw-rw-r--    1 ghaha    ghaha         296 Oct 29 08:40 MkFifo.cpp
prw-rw-r--    1 ghaha    ghaha           0 Oct 29 08:43 MyFifo.dat
-rwxrwxr-x    1 ghaha    ghaha       11939 Oct 29 08:41 MkFifo.exe
shell>MkFifo MyFifo.dat//第二次创建将报文件存在的错误。
mkfifo fail: File exists
 
删除文件的函数原型为:
#include <unistd.h>
int unlink(const char *pathname);
 
5.2.2. 打开、关闭FIFO文件
对FIFO类型的文件的打开/关闭跟普通文件一样,都是使用open函数。如果打开时使用O_WRONLY选项,则打开FIFO的写入端,如果使用O_RDONLY选项,则打开FIFO的读取端,写入端和读取端都可以被几个进程同时打开。
如果以读取方式打开FIFO,并且还没有其他进程以写入方式打开FIFO,open函数将被阻塞;同样,如果以写入方式打开FIFO,并且还没其他进程以读取方式FIFO,open函数也将被阻塞。但是,如果open函数中包含O_NONBLOCK选项,则上述两种情况下调用open都不被阻塞。
与PIPE相同,关闭FIFO时,如果先关读取端,将导致继续往FIFO中写数据的进程接收SIG_PIPE的信号。
5.2.3. 读写FIFO
可以采用与普通文件相同的读写方式读写FIFO,
示例:
//WriteFifo.cpp{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
 
int main(int argc,char *argv[])
{
    char szBuf[128];
    int iRet,fdFifo;
    puts("open for writing.");
    fdFifo=open("MyFifo.pip",O_WRONLY);
    if(fdFifo<0){
        perror("open fifo fail");
        return -1;
    }
    puts("begin write/n");
    srand(time(NULL));
    for(int i=0;i<10;i++){
        sprintf(szBuf,"ps %d write %d",getpid(),i);
        printf("%s/n",szBuf);
        iRet=write(fdFifo,szBuf,strlen(szBuf));
        if(iRet<0){
            perror("write fifo fail");
            return -2;
        }
        int iuSec=int(1000*1000*(double(rand())/RAND_MAX));
        usleep(iuSec);
    }
    close(fdFifo);
    return 0;
}
//WriteFifo.cpp}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
 
//ReadFifo.cpp{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
 
int main(int argc,char *argv[])
{
    char szBuf[128];
    int iRet,fdFifo;
    puts("open for reading.");
    fdFifo=open("MyFifo.pip",O_RDONLY);
    if(fdFifo<0){
        perror("open fifo fail");
        return -1;
    }
    puts("begin read/n");
    while(true){
        usleep(int(1000*1000*(double(rand())/RAND_MAX)));
        iRet=read(fdFifo,szBuf,sizeof(szBuf));
        if(iRet<1){
            break;
        }else{
            szBuf[iRet]=0;
            printf("read :%s/n",szBuf);
        }
    }
    close(fdFifo);
    return 0;
}
//ReadFifo.cpp}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
 
5.3. 共享内存 
共享内存也是进程间(进程间不需要有继承关系)交流信息的一种常用手段。
通常情况下,OS通过内存映射与页交换技术,使进程的内存空间映射到不同的物理内存,这样能保证每个进程运行的独立性,不至于受其它进程的影响。但是,可以通过共享内存的方式,使不同进程的虚拟内存映射到同一块物理内存,这样,一个进程往这块物理内存的更新数据,另外的进程可以立即看到这块物理内存的修改。
原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
key_t ftok(const char *pathname, int proj_id);
int shmget(key_t key, int size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
函数ftok用于创建一个关键字,可以用该关键字关联一个共享内存段。
              参数pathname为一个全路径文件名,并且该文件必须可访问。
              参数proj_id通常传入一非0字符
              通过pathname和proj_id组合可以创建唯一的key
              如果调用成功,返回一关键字,否则返回-1
函数shmget用于创建或打开一共享内存段,该内存段由函数的第一个参数标识。
              参数key是一个与共享内存段相关联关键字,如果事先已经存在一个与指定关键字关联的共享内存段,则直接返回该内存段的标识。key的值既可以用ftok产生,也可以是IPC_RPIVATE,表示总是创建新的共享内存段;
              参数size指定共享内存段的大小,通常是内存页面大小(当前intel处理器为4k)的倍数;
              参数shmflg是一掩码合成值,可以是访问权限值与(IPC_CREAT或IPC_EXCL)的合成。IPC_CREAT表示如果不存在该内存段,则创建它。IPC_EXCL表示如果该内存段存在,则函数返回失败结果(-1)。
              如果调用成功,返回内存段标识,否则返回-1
       函数shmat将共享内存段映射到进程空间的某一地址。
              参数shmid是共享内存段的标识;
              参数shmaddr和shmflg通常为0
              如果调用成功,返回映射后的进程空间地址,否则返回(char *)-1。
函数shmdt用于将共享内存段与进程空间分离。
              参数shmaddr通常应该是shmget的成功返回值。
函数shmctl可以删除共享内存段。
              参数shmid是共享内存段标识;
              参数cmd是对共享内存段的操作方式,可选为IPC_STAT,IPC_SET,IPC_RMID
              参数buf是表示共享内存段的信息结构体数据。
              例如shmctl(kshareMem,IPC_RMID,NULL)表示删除调共享内存段kHareMem
 
示例:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
 
bool IsStr(const char *pszBuf,int iMaxSize)
{
       while(iMaxSize-->0 && *pszBuf++);
       return *(pszBuf-1)==0;
}
 
int main(int argc, char *argv[])
{
       const int BUF_SIZE=4000;   
       FILE *pStream;                  
       key_t kShareMem;       
       int iShareMem;                    
       char *pszBuf;              
       //
       if(argc!=2 || strcmp(argv[1],"read")!=0
          && strcmp(argv[1],"write")!=0){
              printf("Usage:ShareMem <read|write>/n");
              return -1;
       }
       pStream=fopen("./a.bin","ab");
       if(pStream)
              fclose(pStream);
       kShareMem=ftok("./a.bin",'a');
       if(kShareMem==-1){
              perror("ftok fail.");
              return -2;
       }
       iShareMem=shmget(kShareMem,BUF_SIZE,0666|IPC_CREAT);
       if(iShareMem<0){
              perror("shmget fail.");
              return -3;
       }
       pszBuf=(char *)shmat(iShareMem,0,0);
       if(pszBuf==(char *)-1){
              perror("shmat fail.");
              return -4;
       }
       if(strcmp(argv[1],"write")==0){
              while(fgets(pszBuf,256,stdin));           
       }else{
              char szOld[256]={0};
              while(true){
                     if(IsStr(pszBuf,256) &&
                     strcmp(szOld,pszBuf)!=0){
                            strcpy(szOld,pszBuf);
                            printf("%s",szOld);
                     }
                     usleep(100*1000);
              }
       }
       shmdt(pszBuf);
       return 0;
}
 
5.4. 消息队列
       消息队列与FIFO很相似,都是一个队列结构,都可以有多个进程往队列里面写信息,多个进程从队列中读取信息。但FIFO需要读、写的两端事先都打开,才能够开始信息传递工作。而消息队列可以事先往队列中写信息,需要时再打开读取信息。
5.4.1. 初始化消息队列
与共享内存相似,可以用msgget创建或者打开一个消息队列。
原型:
              #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>
    int msgget(key_t key, int msgflg);
 
       参数key是唯一标识一个消息队列的关键字,如果为IPC_PRIVATE(值为0),表示创建一个只由调用进程使用的消息队列,非0值的key表示创建一个可以被多个进程共享的消息队列;
              参数msgflg指明队列的访问权限和创建标志,创建标志的可选值为IPC_CREAT和IPC_EXCL如果单独指定IPC_CREAT,msgget要么返回新创建的消息队列id,要么返回具有相同key值的消息队列id;如果IPC_EXCL和IPC_CREAT同时指明,则要么创建新的消息队列,要么当队列存在时,调用失败并返回-1。
 
示例:
 
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
 
int main()
{
    key_t key;             //消息队列关键字
    int iMode;              //消息队列读写权限
    int iFlag;         //创建标志位
    int iMsgQueId;       //消息队列ID
    //输入关键字值
    printf("Enter the disired key in hex =");
    scanf("%x",&key);
    //输入读写权限
    printf("Enter the operation permission mode in octal =");
    scanf("%o",&iMode);
    //输入创建标志
    printf("Enter create flags:/n"
              "No flags =0/n"
              "IPC_CREAT =1 /n"
              "IPC_CREAT and IPC_EXCL =2/n");
    printf("Flags = ");
    scanf("%d",&iFlag);
    //输出各参数值
    printf("key=0x%x,mode=0%o,flags=%d/n",
                  key,iMode,iFlag);
 
    if(iFlag==0){
           iFlag=iMode|0;
    }else if(iFlag==1){
           iFlag=iMode|IPC_CREAT;
    }else{
           iFlag=iMode|IPC_CREAT|IPC_EXCL;
    }
    printf("IPC_CREAT:%x,iFlag:%x/n",IPC_CREAT,iFlag);
    //创建或打开消息队列
    if((iMsgQueId=msgget(key,iFlag))==-1){
           perror("msgget fail");
           return -1;
    }else{
           puts("msgget succeed.");
           return 0;
    }
}
 
5.4.2. 发送与接收消息
发送和接收消息的函数原型:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long msgtyp, int msgflg);
 
参数msgid指明消息队列的ID;
参数msgbuf是消息结构体:
       struct msgbuf {
    long mtype;         /* type of message */
    char mtext[1];      /* message text */
};   
              字段mtype是用户自己指定的消息类型,该结构体第2个成员仅仅是一种说明性的结构,实际上用户可以使用任何类型的数据;
参数gsz是消息体的大小,每个消息体最大不要超过4K;
参数msgflg可以为0或IPC_NOWAIT,如果设置IPC_NOWAIT,则msgsnd和msgrcv都不会阻塞,此时如果队列满并调用msgsnd,或队列空时调用msgrcv,将返回错误;
参数msgtyp有3种选项:
              msgtyp==0     接收队列中的第1个消息;
              msgtyp>0      接收对列中的第1个类型等于msgtyp的消息
              msgtyp<0              接收其类型小于或等于msgtyp绝对值的第1个最低类型消息
 
示例:
 
///////////msgsnd.cpp/////////////////////////////
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
 
struct MY_MSG{
       long lType;
       char szInfo[BUFSIZ];
};          
 
int main()
{
       MY_MSG msg;
       int iMsgQueId;
       //
       iMsgQueId=msgget(1234,0666|IPC_CREAT);
       if(iMsgQueId==-1){
              perror("msgget fail");
              return -1;
       }
       msg.lType=1;
       while(true){
              printf("Enter some info:");
              fgets(msg.szInfo,BUFSIZ,stdin);
              if(msgsnd(iMsgQueId,&msg,BUFSIZ,0)==-1){
                     perror("msgsnd fail");
                     return -2;
              }
              if(memcmp(msg.szInfo,"end",3)==0){
                     break;
              }
       }
       return 0;
              }
 
 
///////////msgrcv.cpp/////////////////////////////
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
 
struct MY_MSG{
       long lType;
       char szInfo[BUFSIZ];
};          
 
int main()
{
       MY_MSG msg;
       int iMsgQueId;
       //
       iMsgQueId=msgget(1234,0666|IPC_CREAT);
       if(iMsgQueId==-1){
              perror("msgget fail");
              return -1;
       }
       msg.lType=1;
       while(true){
              if(msgrcv(iMsgQueId,&msg,BUFSIZ,0,0)==-1){
                     perror("msgsnd fail");
                     return -2;
              }
              printf("Get:%s",msg.szInfo);
              if(memcmp(msg.szInfo,"end",3)==0){
                     break;
              }
       }
       return 0;
              }
 
 
6. 守护进程(Daemon)
守护进程(Daemon)是在后台运行不受终端控制的进程。比如各种网络服务器,如果web服务器,ftp服务器等。
想要脱离所有终端的原因是守护进程可能是从终端启动,在这之后这个终端要能用来执行其它任务,或者关闭掉终端仍不影响已经启动的守护进程,简单一点的办法是启动的时候在命令行末尾加一个&符号,例如 nohup myserver.out&,但是也可以写代码将程序转成守护进程。
变为守护进程可按照如下几步进行:
1、 在父进程重执行fork,并让父进程退出去,从而断开程序与终端的关联;
2、 在子进程中调用setsid();使新进程成为新的进程组组长和新会话期的领导;
3、 让根目录成为进程的工作目录,因为如果守护进程是从一个可挂接的文件系统中启动的话,如果不修改工作目录,在守护进程运行时,该文件系统就不能卸载;
4、 修改umask为0,避免在守护进程中创建的文件受到原有父进程的umask属性的影响;
5、 尽可能地关闭不需要的文件描述符。
 
示例:
 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
 
void BeDaemon()
{
       const int MAXFD=64;
       if(fork()!=0)   //脱离控制终端
              exit(0);
       setsid();          //成为新进程组组长和新会话领导
       chdir("/");              //设置工作目录为根目录
       umask(0);              //重设文件访问权限掩码
       for(int i=0;i<MAXFD;i++) //尽可能关闭所有从父进程继承来的文件
              close(i);
}
 
int main()
{
       int iLoop=0;
       BeDaemon(); //成为守护进程
       while(true){
              sleep(1);
       }
       return 0;
              }
原创粉丝点击