fork和lockf应用

**

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 child
I am the first child
./a.out 
I am the parent
[root@localhost os]# I am the second child
I 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 c
I 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]# c
b

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]# c
b

句子只被输出一次, 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 0
daughter 0
son 1
daughter 1
son 2
daughter 2
son 3
daughter 3
son 4
daughter 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 0
son 1
son 2
son 3
son 4
daughter 0
daughter 1
daughter 2
daughter 3
daughter 4

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

  • 36
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值