为自己这一段时间学的操作系统中关于Linux部分做一个系统性的练习,记录下自己的心得体会。
首先,这是所做的四个题目。
①编写一段程序,使用系统调用fork( )创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程输出不同的内容。试观察记录屏幕上的显示结果,并分析原因。
②修改上述程序,每一个进程循环显示一句话。子进程显示'daughter …'及'son ……',父进程显示 'parent ……',观察结果,分析原因。
③再调用exec( )用新的程序替换该子进程的内容 ,并利用wait( )来控制进程执行顺序。调用Exit()使子进程结束。
④利用linux的信号量机制实现生产者-消费者问题。(基于进程)
下面总结一下自己每一步的具体操作,给自己留个纪念,也算是抛砖引玉,给大家传授一点儿经验啦!
第一、二两题目:首先,创建.c文件,具体的方式,在我的第一篇博客—--Linux添加系统调用里面讲到过,也可以直接参考这一篇博客,https://blog.csdn.net/yuechuxuan/article/details/69989320比较详细的说明。编辑自己的.c文件,开始写实现代码。
//第一个题目:fork( )创建两个子进程两个子进程,运行观察输出结果
#include
#include
int main( )
{
int p1,p2;
while((p1=fork( ))==-1); //创建子进程 p1
if (p1==0)
printf("daughter\n");
else
{
while((p2=fork( ))==-1); //创建子进程 p2
if(p2==0)
printf("son\n");
else
printf("parent\n");
}
}
//修改后,第二题每一个进程循环显示一句话
#include
#include
int main( )
{
int p1,p2,i;
while((p1=fork( ))== -1); /*创建子进程 p1*/
if (p1==0)
for(i=0;i<10;i++)
printf("daughter %d\n",i);
else
{
while((p2=fork( ))== -1); /*创建子进程 p2*/
if(p2==0)
for(i=0;i<10;i++)
printf("son %d\n",i);
else
for(i=0;i<10;i++)
printf("parent %d\n",i);
}
}
这里注意,第一次创建编译过.c文件之后,下一次修改就可以直接打开你的.c文件,注释掉之前的代码,重新编辑保存即可。如图:
写完一段代码,在终端使用命令gcc fork.c -o fork(说明:此处为你自己命的文件名) 在shell中编译该程序,编译结束后,继续输入./fork就可以运行自己刚才所写的程序了。
第一道题目多次运行结果如下:
原因分析:本来从进程并发执行来看,各种情况都有可能。上面的三个进程没有同步措施,父进程与子进程的输出内容会叠加在一起。输出次序带有随机性。
第二道题目多次运行结果:
原因分析:由实验 1.1 可知各种情况都有可能,由于函数fork()创建进程所需的时间多于输出字符的时间,而且 printf( )在输出字符串时不会被中断,因此,字符串内部字符顺序输出不变。但由于进程并发执行的调度顺序和父子进程抢占处理机问题,输出字符串的顺序和先后随着执行的不同而发生变化。这与打印单字符的结果相同。
第三题:首先介绍一下,关于题目中的两个函数的具体作用和用法
(引用自:https://blog.csdn.net/bit_clearoff/article/details/55051580)
在Linux中,exec是一个函数族,它一共有6个函数,如下:
#include
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[]);
在这6个函数中,execve函数属于系统调用,而其他的五个函数都是在execve函数基础上经过包装而形成的库函数,他们的关系如下图:
exec函数族中的函数的作用是,在一个正在运行的进程内部根据函数所指定的文件名和路径找到可执行的文件,并将进程执行的程序替换为新程序,新程序从main处开始执行。其中这里的文件必须是二进制文件或Linux下可执行的脚本文件,另外,exec函数族的函数只是用磁盘上的一个新程序替换了当前进程的正文段、数据段、堆和栈,其它的并没有改变。
这里将在.c文件所在的目录下进行寻找,找到可执行文件。
#include
#include
#include
#include
void main( )
{
int pid;
pid=fork( ); //创建子进程
switch(pid)
{
case -1: //创建失败
printf("fork fail!\n");
exit(1);
case 0: // 子进程
execl("/bin/ls","ls","-1","-color",NULL);
printf("exec fail!\n");
exit(1);
default: //父进程
wait(NULL); //同步
printf("ls completed !\n");
exit(0);
}
}
同样的方法,在终端输入./fork
执行结果如下:
原因分析:程序在调用 fork( ) 建立一个子进程后,马上调用 wait( ) ,使父进程在子进程结束之前,一直处于睡眠状态。子进程用 exec( )装入命令 ls ,exec( )后,子进程的代码被 ls 的代码取代,这时子进程的 PC 指向 ls 的第1 条语句,开始执行 ls 的命令代码。 wait( ) 给我们提供了一种实现进程同步的简单方法。
根目录下文件情况:
运行完成。
第四题:参考https://blog.csdn.net/yaozhiyi/article/details/7561759
问题共需要三个信号量:
用full、empty和mutex分别表示仓库的库存的同步信号量、仓库为空的同步信号量和正在对仓库进行操作的互斥信号量。其初值分别为0、仓库的容量(程序中使用MAX_BUFFRT_SIZE表示)和0。生产者:p(empty) -> p(mute) -> v(mutex) -> v(full) 消费者:p(full) -> p(mutex) -> v(mutex) ->v(empty)。
具体流程图:
具体的实现:参考https://blog.csdn.net/qq_31490151/article/details/78984593
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_BUFFER_SIZE 10
#define SHM_MODE 0600
#define SEM_MODE 0600
#define SEM_FULL 0
#define SEM_EMPTY 1
#define MUTEX 2
/*
#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
// union semun is defined by including
#else
// according to X/OPEN we have to define it ourselves
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
};
#endif
union semun su;//sem union,用于初始化信号量
*/
struct my_buffer
{
int head;
int tail;
char str[MAX_BUFFER_SIZE];
int num; //缓冲区里字母数量
int is_empty;
};
const int N_CONSUMER = 2;//消费者数量
const int N_PRODUCER = 2;//生产者数量
const int N_BUFFER = 10;//缓冲区容量
const int N_WORKTIME = 10;//工作次数
int shm_id = -1;
int sem_id = -1;
pid_t child;
pid_t parent;
//得到10以内的一个随机数
int get_random()
{
int digit;
srand((unsigned)(getpid() + time(NULL)));
digit = rand() % 10;
return digit;
}
//得到A~Z的一个随机字母
char getRandChar()
{
char letter;
srand((unsigned)(getpid() + time(NULL)));
letter = (char)((rand() % 26) + 'A');
return letter;
}
//sem_id 表示信号量集合的 id
//sem_num 表示要处理的信号量在信号量集合中的索引
//P操作
void waitSem(int sem_id,int sem_num)
{
struct sembuf sb;
sb.sem_num = sem_num;
sb.sem_op = -1;//表示要把信号量减一
sb.sem_flg = SEM_UNDO;//
//第二个参数是 sembuf [] 类型的,表示数组
//第三个参数表示 第二个参数代表的数组的大小
if(semop(sem_id,&sb,1) < 0){
perror("waitSem failed");
exit(1);
}
}
//V操作
void sigSem(int sem_id,int sem_num)
{
struct sembuf sb;
sb.sem_num = sem_num;
sb.sem_op = 1;
sb.sem_flg = SEM_UNDO;
//第二个参数是 sembuf [] 类型的,表示数组
//第三个参数表示 第二个参数代表的数组的大小
if(semop(sem_id,&sb,1) < 0){
perror("sigSem failed");
exit(1);
}
}
//打印进程运行结果
void printTime()
{
//打印时间
time_t now;
struct tm *timenow; //实例化tm结构指针
time(&now);
timenow = localtime(&now);
printf("执行时间: %s ",asctime(timenow));
}
int main(int argc, char ** argv)
{
shm_id = shmget(IPC_PRIVATE,MAX_BUFFER_SIZE,SHM_MODE); //申请共享内存
if(shm_id < 0)
{
perror("create shared memory failed");
exit(1);
}
struct my_buffer *shmptr;
shmptr = shmat(shm_id, 0, 0); //将申请的共享内存附加到申请通信的进程空间
if (shmptr == (void*)-1)
{
perror("add buffer to using process space failed!\n");
exit(1);
}
if((sem_id = semget(IPC_PRIVATE,3,SEM_MODE)) < 0)
{ //创建三个信号量,SEM_EMPTY,SEM_FULL和MUTEX
perror("create semaphore failed! \n");
exit(1);
}
if(semctl(sem_id,SEM_FULL,SETVAL,0) == -1)
{ //将索引为0的信号量设置为0-->SEM_FULL
perror("sem set value error! \n");
exit(1);
}
if(semctl(sem_id,SEM_EMPTY,SETVAL,10) == -1)
{ //将索引为1的信号量设置为10-->SEM_EMPTY
perror("sem set value error! \n");
exit(1);
}
if(semctl(sem_id,MUTEX,SETVAL,1) == -1)
{ //将索引为3的信号量设置为1-->MUTEX
perror("sem set value error! \n");
exit(1);
}
shmptr -> head = 0;
shmptr -> tail = 0;
shmptr -> is_empty = 1;
shmptr -> num = 0;
for(int i = 0; i < N_PRODUCER; i++)
{
parent = fork();
if(parent < 0)
{
perror("the fork failed");
exit(1);
}
else if(parent == 0)
{
shmptr = shmat(shm_id, 0, 0); //将申请的共享内存附加到申请通信的进程空间
if (shmptr == (void*)-1)
{
perror("add buffer to using process space failed!\n");
exit(1);
}
int count = 0;
for(int j = 0; j < N_WORKTIME; j++)
{
waitSem(sem_id, SEM_EMPTY);
waitSem(sem_id, MUTEX);
sleep(get_random());
printf("-------------------------------------------------------------\n");
printf("我是第 %d 个生产者进程,PID = %d\n", i + 1, getpid());
/*生产产品*/
char c = getRandChar(); //随机获取字母
shmptr -> str[shmptr->tail] = c;
shmptr -> tail = (shmptr->tail + 1) % MAX_BUFFER_SIZE;
shmptr -> is_empty = 0; //写入新产品
shmptr -> num++;
/*打印输出结果*/
printTime(); //程序运行时间
int p;
printf("缓冲区数据(%d个):",shmptr -> num); //打印缓冲区中的数据
p = (shmptr->tail-1 >= shmptr->head) ? (shmptr->tail-1) : (shmptr->tail-1 + MAX_BUFFER_SIZE);
for (p; !(shmptr -> is_empty) && p >= shmptr -> head; p--)
{
printf("%c", shmptr -> str[p % MAX_BUFFER_SIZE]);
}
printf("\t 生产者 %d 放入 '%c'. \n", i + 1, c);
printf("-------------------------------------------------------------\n");
fflush(stdout);
sigSem(sem_id, MUTEX);
sigSem(sem_id, SEM_FULL);
}
//将共享段与进程之间解除连接
shmdt(shmptr);
exit(0);
}
}
for(int i = 0; i < N_CONSUMER; i++)
{
child = fork();
if(child < 0)//调用fork失败
{
perror("the fork failed");
exit(1);
}
else if(child == 0)
{
int count = 0;
shmptr = shmat(shm_id, 0, 0); //将申请的共享内存附加到申请通信的进程空间
if (shmptr == (void*)-1)
{
perror("add buffer to using process space failed!\n");
exit(1);
}
for(int j = 0; j < N_WORKTIME; j++)
{
waitSem(sem_id, SEM_FULL);
waitSem(sem_id, MUTEX);
sleep(get_random());
printf("-------------------------------------------------------------\n");
printf("我是第 %d 个消费者进程,PID = %d\n", i + 1, getpid());
/*消费数据*/
char lt = shmptr -> str[shmptr -> head];
shmptr -> head = (shmptr -> head + 1) % MAX_BUFFER_SIZE;
shmptr -> is_empty = (shmptr->head == shmptr->tail); //
shmptr -> num--;
/*打印输出结果*/
printTime(); //程序运行时间
int p;
printf("缓冲区数据(%d个):",shmptr -> num); //打印缓冲区中的数据
p = (shmptr -> tail - 1 >= shmptr -> head) ? (shmptr -> tail-1) : (shmptr -> tail - 1 + MAX_BUFFER_SIZE);
for (p; !(shmptr -> is_empty) && p >= shmptr -> head; p--)
{
printf("%c", shmptr -> str[p % MAX_BUFFER_SIZE]);
}
printf("\t 消费者 %d 取出 '%c'. \n", i + 1, lt);
printf("-------------------------------------------------------------\n");
fflush(stdout);
sigSem(sem_id,MUTEX);
sigSem(sem_id,SEM_EMPTY);
}
//将共享段与进程之间解除连接
shmdt(shmptr);
exit(0);
}
}
//主进程最后退出
while (wait(0) != -1);
//将共享段与进程之间解除连接
shmdt(shmptr);
//对共享内存区执行控制操作
shmctl(shm_id,IPC_RMID,0);//当cmd为IPC_RMID时,删除该共享段
shmctl(sem_id,IPC_RMID,0);
printf("主进程运行结束!\n");
fflush(stdout);
exit(0);
return 0;
}
最终运行结果:
以上就是4个综合题目的具体实现,其实每个题目都不复杂,只要搞懂了原理,耐住性子还是可以做出来的。不懂的话就要记得多查资料,努力去理解,希望每一个人都可以体会到Linux的别样美!