1.资源继承
fork()函数,分叉函数
调用fork()函数,可以生成当前程序的副本(子进程)。 子进程有复制来自父进程的堆栈段和数据空间(包括流缓冲区)。之后由系统调度算法决定执行哪一个进程先执行(包括显示中终端命令行的进程也会参与系统资源的争夺)。
如在下面的两个程序中,就因为一个'\n',而使两个程序出现的结果,有别于以往的认识。
程序一:
#include<unistd.h>
#include<stdio.h>
#include <sys/types.h>
int main()
{
printf("最初该进程的id为:%d\n",getpid());
printf("Before fork...");
pid_t pid = fork();
if(pid == 0)
{
printf("此时调用子进程,id为:%d, 该子进程的父进程id为:%d\n",getpid(),getppid());
}
else
{
printf("此时调用父进程,id为:%d, 子进程的id为:%d\n",getpid(),pid);
}
return 0;
}
运行结果:
不知道你们注意到没有,子进程它的父进程竟然与最开始的进程的id不一样,原因就是因为显示终端命令的进程也参与了系统资源的竞争,此时子进程没能竞争上,所以他(3754进程)的父进程就变成了2170(显示终端命令的进程)。
程序二:
#include<unistd.h>
#include<stdio.h>
#include <sys/types.h>
int main()
{
printf("最初该进程的id为:%d\n",getpid());
printf("Before fork...\n");
pid_t pid = fork();
if(pid == 0)
{
printf("此时调用子进程,id为:%d, 该子进程的父进程id为:%d\n",getpid(),getppid());
}
else
{
printf("此时调用父进程,id为:%d, 子进程的id为:%d\n",getpid(),pid);
}
return 0;
}
运行结果:
可以看到在程序二中,Before fork... 只出现了一次。这是因为printf的输出机制,当printf中没有'\n'时,只是先将内容放入标准输出队列缓冲区中,所以在程序一,子进程也拥有了(继承但不是共享)父进程缓冲区的内容。而当遇到了'\n', 此时会刷新标准输出队列(stdout),因此,在程序二中,也就只有一个Before fork...
2.进程并发执行和并发控制
(1)并发执行
程序三:
#include<unistd.h>
#include<stdio.h>
int main()
{
int p1,p2;
while ((p1 = fork()) == -1); //创建子进程p1
if (p1 == 0) //执行子进程p1
printf("BBB\t");
else
{
while((p2 = fork()) == -1); //创建子进程p2
if (p2 == 0) //执行子进程2
printf("CCC\t");
else
printf("AAA\t"); //父进程执行
}
return 0;
}
运行结果:
三个进程随机调度,理论上还有其他的结果BBB AAA CCC等。
(2)并发控制
lockf函数
int lockf(int fd, int cmd, off_t len);
/*
fd代表文件描述符(0为写入(stdin),1为输出(stdout))
cmd操作控制值(0~3),0代表开锁,1代表加锁
len代表要锁定或解锁的连续字节数。0表示从调用lockf()后开始锁定
*/
程序四:
#include<unistd.h>
#include<stdio.h>
int main()
{
int p1, p2, i;
while ((p1 = fork()) == -1);//创建子进程p1
if (p1 == 0)//执行子进程p1,输出100个son
for (i = 0; i < 100; i++) printf("son%d\n",i);
else
{
while ((p2 = fork()) == -1);//创建子进程p2
if (p2 == 0)//执行子进程p2,输出100个daughter
for (i = 0; i < 100; i++) printf("daughter%d\n",i);
else
for (i = 0; i < 1000; i++) //父进程执行,输出1000个children
printf("children%d\n",i);
}
return 0;
}
运行结果:
因为结果太长,这里只截取了一部分,为了使效果更加明显,输出了1000个children单词和100个son和100个daughter单词。可以看到程序四和程序三只是输出的次数和内容不同,总体框架是一样的,但在程序四中,进程之间的调度会给人一种很乱的感觉。某个进程的单词插到了另外进程的单词中了。这是因为在调用父进程输出children时, 此时使用的是外设(打印到屏幕上),父进程会进入阻塞状态,cpu进程数少一个,就要调度其他的进程,比如调用了son这个进程,那么它也要使用输出的外设,而此时这个外设(打印到屏幕上)我们并没有给它加锁,因此它会去和children竞争这个设备,同理daughter也一样,结果就是正如我们所看到的一样。在程序五中,加上了锁,此时其他进程,只能等待当前外设的解锁。
程序五
#include<unistd.h>
#include<stdio.h>
int main()
{
int p1, p2, i;
while ((p1 = fork()) == -1); //创建子进程p1
if (p1 == 0)//执行子进程p1,输出20个son
{
lockf(1,1,0);
for (i = 0; i < 20; i++) printf("son%d\n",i);
lockf(1,0,0);
}
else
{
while ((p2 = fork()) == -1); //创建子进程p2
if (p2 == 0)//执行子进程p2,输出100个daughter
{
lockf(1,1,0);
for (i = 0; i < 20; i++) printf("daughter%d\n",i);
lockf(1,0,0);
}
else{
lockf(1,1,0);
for (i = 0; i < 20; i++) //父进程执行,输出1000个children
printf("children%d\n",i);
lockf(1,0,0);
}
}
return 0;
}
运行结果:
可以看到单词不会乱串了。
最后,补充一点exit(0),只是造成该进程终止,不一定是整个程序的结束(如果这个程序只有一个进程的话)。
附上一点实用命令:
1. gcc code.c -o code #编译链接并形成可执行文件code
2. vim 下代码格式化(代码格式化参考:https://blog.csdn.net/qachenzude/article/details/25511875):
(1)gg到第一行
(2)shift+v 转到可视模式
(3)shift+g 全选
(4) 按下 =
3. linux shell 重复执行n次同一命令: seq n|xargs -i cmd (效果:执行cmd命令n次, 顺序执行)