fork和lockf应用

来源:互联网 发布:阿里云智能家居 编辑:程序博客网 时间:2024/05/18 03:13

**

fork和lockf应用

**
实验目的
进一步认识并发执行的实质;分析进程争用资源的现象
实验过程
一,将每个进程输出一个字符改为每个进程输出一句话。

/*shiyan1_test2.c*Created on 2015-10-07*Author:Wanglin*output a sentence*/# include<stdio.h># include<unistd.h>int main(){int p1,p2;p1=fork();if(!p1) printf("I am the first child\n");else{        p2=fork();        if(!p2) printf("I am the second child\n");        else printf("I am the parent\n");}return 0;}

编译并运行shiyan1_test2.c
gcc shiyan1_test2.c ./a.out
输出如下:

[root@localhost os]# gcc shiyan3_test2.c[root@localhost os]# ./a.out I am the parent[root@localhost os]# I am the second childI am the first child./a.out I am the parent[root@localhost os]# I am the second childI am the first child

系统中一共有4个进程,shiyan1_test2.c中有一个父进程,和父进程2次调用fork创建的2个子进程,另外一个进程是linux的shell进程,shell就是解析用户输入命令的终端,也就是和用户交互的这个黑框。父进程输出”I am the parent\n”,第一个子进程输出”I am the first child\n”,第二个子进程输出”I am the second child\n”,shell进程以命令提示符的形式输出,[root@localhost os]#,代表在这个shell进程中,用户是超级用户root,主机名为本地主机localhost,当前路径是在os文件夹中,#是root用户的专用命令提示符,普通用户是美元符号$。
运行多次,会发现上述4句话的输出顺序是不固定的,也就是说进程调度的顺序是会变化的,并且不一定和代码中创建的顺序一致,我们可以看到shiyan1_test2.c三句话的先后顺序本应该是”I am the first child\n”,”I am the second child\n”,”I am the parent\n”,而实际运行顺序并非如此,进程调度的顺序主要由操作系统调度程序决定,而不是用户编写的代码决定。
二, 在父进程fork之前,输出一句话,这句话后面不加“\n”或加“\n”。
我们先来运行不带有“\n”的情况:

/*shiyan1_test3.c*Created on 2015-10-07*Author Wanglin*print a sentence without '\n'or with '\n' befork the parent fork*/# include<stdio.h># include<unistd.h>int main(){int p1,p2;printf("I am the sentence befork the parent fork ");//printf("I am the sentence befork the parent fork\n");p1=fork();if(!p1) printf("b\n");else{        p2=fork();        if(!p2) printf("c\n");        else printf("a\n");}return 0;}

编译并运行shiyan1_test3.c
输出结果如下:

[root@localhost os]# ./a.out            I am the sentence befork the parent fork a[root@localhost os]# I am the sentence befork the parent fork cI am the sentence befork the parent fork b

可以看到4个进程分别输出:
进程1:I am the sentence befork the parent fork a
进程2:[root@localhost os]#
进程3:I am the sentence befork the parent fork c
进程4:I am the sentence befork the parent fork b
即除了shell进程外,每个进程都打印了这句话。我们来分析一下代码,表面上看起来,子进程应该从fork()处继续往下执行,并不会执行打印I am the sentence befork the parent fork这句话,而事实并非如此。在父进程第1次调用fork()前,执行printf,由于输出时,我们要将内存中的数据输出到磁盘中,而磁盘读取速度远小于内存,为了解决磁盘与内存速度不匹配的问题,系统会先将要printf的内容存到磁盘缓冲区buffer,也就是将I am the sentence befork the parent fork这句话写入buffer。而子进程会继承父进程的buffer内容,于是在子进程中也会打印这句话,第1个子进程会输出,第2个子进程也会输出。
接下来,我们加上“\n”:

/*shiyan3_test4.c*Created on 2015-10-07*Author:Wanglin *Version 1.0* print a sentence with '\n' befork the parent fork*/# include<stdio.h># include<unistd.h>int main(){int p1,p2;printf("I am the sentence befork the parent fork \n");p1=fork();if(!p1) printf("b\n");else{        p2=fork();        if(!p2) printf("c\n");        else printf("a\n");    }return 0;}

编译运行shiyan_test4.c
输出结果:

[root@localhost os]# ./a.out            I am the sentence befork the parent fork a[root@localhost os]# cb

4个进程分别输出:
进程1:a
进程2:[root@localhost os]#
进程3:c
进程3:b
I am the sentence befork the parent fork这句话只被输出了一次,这是因为系统检测到“\n”会自动清空缓冲区,于是子进程继承父进程时,缓冲区里面没有要打印这句话的信息,于是在2个子进程中只分别输出c和b。
要清空缓冲区,还可以用fflush(stdout)函数,用以清空系统标准输出流。修改shiyan_test4.c

/*shiyan3_test4.c*Created on 2015-10-07*Author:Wanglin*Version 2.0* using fflush(stdout) to clear the buffer*/# include<stdio.h># include<unistd.h>int main(){int p1,p2;printf("I am the sentence befork the parent fork ");fflush(stdout);p1=fork();if(!p1) printf("b\n");else{        p2=fork();        if(!p2) printf("c\n");        else printf("a\n");}return 0;}
[root@localhost os]# ./a.out            I am the sentence befork the parent fork a[root@localhost os]# cb

句子只被输出一次, fflush(stdout); //清空输出流;stdout是系统自动生成的指针标准输出流,与之对应的还有标准输入流stdin,清空输入流可以用fflush(stdin);

三,在程序中使用系统调用lockf来给每一个进程加锁,实现进程之间的互斥。
1.lockf放在循环内部,执行休眠
我们在shiyan_test4.c的基础上修改代码,分别将 a, b,c替换为 parent,daughter和son。子进程1输出5次daughter,并用lockf(1,1,0)给stdout加锁,lockf(1,0,0)解锁,解锁后令进程休眠3秒,即sleep(3);子进程2输出5次son,同理用lockf加锁和解锁, 解锁后也休眠3秒。parent不做处理,和shiyan_test4.c一致。

/*shiyan1_test5.c *Created on 2015-10-07 *Author:wanglin *输出多条语句,flock,sleep(3) */# include<stdio.h># include<unistd.h>int main(){int p1,p2;p1=fork();if(!p1)        {    int i;    for(i=0;i<5;++i){    lockf(1,1,0);    printf("daughter %d\n",i);    lockf(1,0,0);    sleep(3);    }    }else{    p2=fork();    if(!p2)    {    int j;    for(j=0;j<5;++j)    {    lockf(1,1,0);    printf("son %d\n",j);    lockf(1,0,0);    sleep(3);    }    }        else    printf("parent \n");   }return 0;}

编译shiyan_test5.c并运行
gcc shiyan_test5.c
./a.out
运行结果如下:

[root@localhost os]# ./a.out            parent [root@localhost os]# son 0daughter 0son 1daughter 1son 2daughter 2son 3daughter 3son 4daughter 4

可以看到子进程1和子进程2交替执行,5句daughter没有连续输出,5句son也没有连续输出。lockf()的基本功能是实现资源的互斥访问,即同一时刻同一个资源只允许一个进程访问,本次实验中的资源即系统的标准输出stdout,互斥的结果为同一时刻只可以有一个进程执行输出,并且不可以被打断。daughter进程先用lockf(1,1,0)锁上stdout,执行输出之后,立即用lockf(1,0,0)释放stdout,而此时进程没有立即进入下一次循环,而是休眠了3秒,也就是说没有立即又给stdout加锁,在休眠的这段时间,son可以获得stdout资源,于是son执行输出;同理,son加锁,输出,释放锁之后,和daughter一样,也执行了休眠,于是daughter可以获得stdout资源,于是就出现了两个进程交替执行输出的情况。
多运行几次,还会出现以下结果,不是严格的交替执行,但是都可以说明进程的输出循环会被打断,不会连续输出5次。

2.接下来我们继续修改代码,不执行休眠

/*shiyan1_test5.c *Created on 2015-10-07 *Author:wanglin *输出多条语句,flock,sleep(3) */# include<stdio.h># include<unistd.h>int main(){int p1,p2;p1=fork();if(!p1)        {    int i;    for(i=0;i<5;++i){    lockf(1,1,0);    printf("daughter %d\n",i);    lockf(1,0,0);    //sleep(3);    }    }else{    p2=fork();    if(!p2)    {    int j;    for(j=0;j<5;++j)    {    lockf(1,1,0);    printf("son %d\n",j);    lockf(1,0,0);    //sleep(3);    }    }        else    printf("parent \n");   }return 0;}

再次编译并运行,输出结果如下

多执行几次,会发现5句daughter和5句son的输出都是连续的,son,daughter,parent和shell的顺序则有可能改变。这比较容易理解,拿daughter进程来解释,daughter进程开始循环,加锁,输出,然后释放锁,不进行休眠,立即进入下一次for循环,紧接着又加锁,也就是说释放锁之后理解又加锁,间隔时间极其短暂,忽略两次循环的切换时间,stdout资源相当于一直处于加锁状态,其他进程不可能获得stdout资源,必须等daughter5次循环完毕之后,才可以获得,同理,son进程运行的时候也是一样,循环不会被其他进程打断。
3,将lockf放在循环外面

编译运行,输出结果如下

./a.out parent [root@localhost os]# son 0son 1son 2son 3son 4daughter 0daughter 1daughter 2daughter 3daughter 4

我们可以推断出不论运行多少次,son和daughter的输出都是连续的。有了前面两个例子,这个例子就很容易理解了,在循环外部给stdout加上锁,整个循环过程中其他进程都不可以获得stdout资源,必须等该进程循环完毕,释放资源后其他进程才可以获得stdout,并执行输出。
同样,我们还可以继续修改代码,但总而言之,资源被锁住的时候,只允许为该资源上锁的进程访问,其他进程均不可以再访问。本例中的资源是标准输入输出,而文件的读取,程序脚本的运行,也可以用lockf来进行互斥访问,在实际应用中,lockf有在互斥访问方面具有举足轻重的作用。
五,当父进程fork子进程后,父进程和子进程如何执行程序的?
在父进程中,fork返回新创建子进程的进程ID;父进程紧接着fork语句的下一句话执行;子进程中,fork返回0;子进程会继承父进程大部分的资源,包括缓冲区等,只有少部分不会继承,每个进程都有唯一的进程id号,可以利用getpid()获得进程id,getppid()获得父进程的id; 如果出现错误,fork返回一个负值;表明子进程创建失败。

1 0