第一部分:进程管理
- 创建进程
在Linux系统中,我们一般使用系统调用fork()函数来创建进程,为此,我们在工作目录下创建一个测试文件processTest.c:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
int i = 100;
pid = fork();
if(pid == -1)
{
printf("Creat fork error!!!\n");
exit(1);
}
else if(pid)
{
i++;
printf("The father i = %d.\n",i);
printf("The father return %d.\n",pid);
printf("The father pid is %d.\n",getpid());
printf("The father ppid is %d.\n",getppid());
while(1);
}
else
{
i++;
printf("\nThe child i = %d.\n",i);
printf("The child return %d.\n",pid);
printf("The child pid is %d.\n",getpid());
printf("The child ppid is %d.\n",getppid());
while(1);
}
return 0;
}
这个测试程序可以很好地帮助我们理解fork()函数的原理以及Linux进程创建的机制。
首先,pid可以给fork函数返回一个值,一般为一个正实数或零,如果返回-1则说明发生错误。
然后,我们打印父进程基本信息,即父进程的pid和父进程的父进程的ppid,这里用while(1);因为父、子进程是并发了,父进程退出了就看不到打印信息了,后面的子进程也是同理。
写好代码后,我们使用命令gcc processTest.c -o processTest.exe进行手动编译并生成可执行文件processTest.exe,然后运行命令./processTest.exe,可以看到如下运行结果:
我们可以看到子进程的pid是0,而子进程的ppid即是父进程的pid
- 进程软中断通信
Linux中的信号一个正实数,它定义在系统头文件<signal.h>中,它一般用来通知某进程有中断异常,我们这个实验就是来了解信号软中断的基本机制。
在本实验当中,我们要创建2个进程,通过系统调用signal()捕捉键盘上的中断信号,捕捉到中断信号后,父进程用系统调用kill( )向两个子进程发出信号,子进程捕捉到信号后分别输出信息后终止。
现在我们创建一个测试文件interrupt.c,代码如下:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>
void stop(), waiting();
int wait_mark;
void stop(){
wait_mark = 0;
}
void waiting(){
while(wait_mark == 1);
}
int main(){
int p1, p2;
while((p1 = fork()) == -1);
if(p1 == 0){
wait_mark = 1;
signal(SIGINT, SIG_IGN);
signal(16, stop);
waiting();
printf("\nchild process p1 is killed by parent!\n");
sleep(3);
exit(0);
}else{
while((p2 = fork()) == -1);
if(p2 == 0){
wait_mark = 1;
signal(SIGINT, SIG_IGN);
signal(17, stop);
waiting();
printf("\nchild process p2 is killed by parent!\n");
sleep(3);
exit(0);
}else{
wait_mark = 1;
signal(SIGINT, stop);
waiting();
kill(p1, 16);
kill(p2, 17);
wait(0);
wait(0);
printf("\nparent process is killed !\n");
exit(0);
}
}
}
可以看到,我们让子进程1接受父进程软中断信号17然后转向stop()函数,而如果等待标记不为0那么就一直等待父进程的信号,其中非常重要的一部就是signal(SIGINT, SIG_IGN),它实现了屏蔽ctrl+c的键盘中断信号。
分别运行编译和执行命令:
gcc interrupt.c -o interrupt.exe
./ interrupt.exe
运行结果:
- 进程共享内存通信
共享内存通信即让两个进程访问同一块内存空间并以此传递数据,为了达到这一目的,Linux也内置了相应的头文件,比如sys/shm.h,我们需要实现将其include进来。
首先,我们现在工作目录下建立一个写文件,命名为shmread.c:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/shm.h>
#include "shmdata.h"
int main()
{
int running = 1;
void *shm = NULL;
struct shared_use_st *shared;
int shmid;
shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
if(shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
shm = shmat(shmid, 0, 0);
if(shm == (void*)-1)
{
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("\nMemory attached at %X\n", (int)shm);
shared = (struct shared_use_st*)shm;
shared->written = 0;
while(running)
{
if(shared->written != 0)
{
printf("You wrote: %s", shared->text);
sleep(rand() % 3);
shared->written = 0;
if(strncmp(shared->text, "end", 3) == 0)
running = 0;
}
else
sleep(1);
}
if(shmdt(shm) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
if(shmctl(shmid, IPC_RMID, 0) == -1)
{
fprintf(stderr, "shmctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
从上述代码可以看出,我们首先创建共享内存空间:
shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
然后用一个while循环来一直读取内存中的数据,当written标志不为0的时候,我们进行读操作,并且如果读到的数据是end,则退出,读取完成之后,我们先将共享内存从当前进程分离,然后将其删除。
写者的程序采用相同的机制,代码如下:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/shm.h>
#include "shmdata.h"
int main()
{
int running = 1;
void *shm = NULL;
struct shared_use_st *shared = NULL;
char buffer[BUFSIZ + 1];
int shmid;
shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666|IPC_CREAT);
if(shmid == -1)
{
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
shm = shmat(shmid, (void*)0, 0);
if(shm == (void*)-1)
{
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attached at %X\n", (int)shm);
shared = (struct shared_use_st*)shm;
while(running)
{
while(shared->written == 1)
{
sleep(1);
printf("Waiting...\n");
}
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
strncpy(shared->text, buffer, TEXT_SZ);
shared->written = 1;
if(strncmp(buffer, "end", 3) == 0)
running = 0;
}
if(shmdt(shm) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
sleep(2);
exit(EXIT_SUCCESS);
}
shmwrite取得共享内存并连接到自己的地址空间中。检查共享内存中的written,是否为0,若不是,表示共享内存中的数据还没有被完,则等待其他进程读取完成,并提示用户等待。若共享内存的written为0,表示没有其他进程对共享内存进行读取,则提示用户输入文本,并再次设置共享内存中的written为1,表示写完成,其他进程可对共享内存进行读操作。
分别编译shmread和shmwrite文件得到两个可执行文件,然后使用命令./shmread.exe &来进行读操作,可以看到执行的结果是我们已经从成功实现了进程与1一块共享内存区域的关联。
然后执行写操作命令./shmwrite.exe,可以看到我们也关联了另一块内存区域,这个时候输入一段text进行测试,即可以进行读写通信,然后输入end命令即可终结通信过程。
- 进程信号量通信
信号量是操作系统内部通信的一种原子操作,主要由两种操作signal()和wait()组成,本实验以经典的生产者消费者模型为例,使用信号量操作来模拟和实现进程间的通信。
我们在工作目录下建立mutex.c文件:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#define true 1
int product_id = 0;
int consumer_id = 0;
int N;
int producerNum;
int consumerNum;
typedef int semaphore;
typedef int item;
item* buffer;
int in = 0;
int out = 0;
int proCount = 0;
semaphore mutex = 1, empty , full = 0, proCmutex = 1;
void * producer(void * a){
int id = ++product_id;
while(true){
int flag = 0;
while(empty <= 0){
printf("生产者%d:缓冲区已满!阻塞中……\n",id);
flag =1;
sleep(1);
}
if(flag == 1)
printf("生产者%d因缓冲区有空位唤醒!\n",id);
flag = 0;
while(proCmutex <= 0){printf("生产者%d生产阻塞中……\n",id);flag = 1;sleep(1);};
proCmutex--;
if(flag == 1)
printf("生产者%d生产唤醒!\n",id);
proCount++;
printf("生产者%d:生产一个产品ID%d!\n",id,proCount);
flag = 0;
while(mutex <= 0){printf("生产者%d装入阻塞中……\n",id);sleep(1);flag=1;};
mutex--;
if(flag == 1)
printf("生产者%d装入唤醒,装入产品ID%d,缓冲区位置为%d!\n",id,proCount,in);
else
printf("生产者%d装入产品ID%d,缓冲区位置为%d!\n",id,proCount,in);
empty--;
buffer[in] = proCount;
in = (in + 1) % N;
full++;
mutex++;
proCmutex++;
sleep(1);
}
}
void * consumer(void *b){
int id = ++consumer_id;
while(true){
int flag = 0;
while(full <= 0){
printf("\t\t\t\t消费者%d:缓冲区为空!阻塞中……\n",id);
flag = 1;
sleep(1);
}
full--;
if(flag ==1)
printf("\t\t\t\t消费者%d因缓冲区有产品唤醒!\n",id);
flag = 0;
while(mutex <= 0){printf("\t\t\t\t消费者%d消费阻塞中……\n",id);sleep(1);};
mutex--;
if(flag == 1)
printf("\t\t\t\t消费者%d消费唤醒!\n",id);
int nextc = buffer[out];
buffer[out] = 0;//消费完将缓冲区设置为0
empty++;
printf("\t\t\t\t消费者%d:消费一个产品ID%d,缓冲区位置为%d\n",id, nextc,out);
out = (out + 1) % N;
mutex++;
sleep(1);
}
}
int main()
{
int tempnum;
printf("请输入生产者数目:\n");
scanf("%d",&tempnum);
producerNum = tempnum;
printf("请输入消费者数目:\n");
scanf("%d",&tempnum);
consumerNum = tempnum;
printf("请输入缓冲区大小:\n");
scanf("%d",&tempnum);
N = tempnum;
empty = N;
buffer = (item*)malloc(N*sizeof(item));
for(int i=0;i<N;i++)
{
buffer[i]=0;
}
pthread_t threadPool[producerNum+consumerNum];
int i;
for(i = 0; i < producerNum; i++){
pthread_t temp;
if(pthread_create(&temp, NULL, producer, NULL) == -1)
{
printf("ERROR, fail to create producer%d\n", i);
exit(1);
}
threadPool[i] = temp;
}
for(i = 0; i < consumerNum; i++){
pthread_t temp;
if(pthread_create(&temp, NULL, consumer, NULL) == -1){
printf("ERROR, fail to create consumer%d\n", i);
exit(1);
}
threadPool[i+producerNum] = temp;
}
void * result;
for(i = 0; i < producerNum+consumerNum; i++){
if(pthread_join(threadPool[i], &result) == -1){
printf("fail to recollect\n");
exit(1);
}
}
return 0;
}
如上所示首先我们在该文件中先定义好生产者,消费者的ID和数目,缓冲区的大小,商品的类型和大小,缓冲区首尾指针,然后我们定义4个信号量:分别为互斥信号量mutex,资源信号量empty和full,以及生产者互斥信号量promutex。
紧接着,我们在生产者函数中,先判断缓冲区是否已满,完成产品数量自增,然后申请资源,阻塞进程,互斥信号量mutex自减,同时更新缓冲区的大小,即缓冲区非空白大小自加,然后mutex自加释放资源,实现互斥。
生产者的实现原理大致相同,不再赘述。
编号程序之后,执行命令cc mutex.c -o mutex.exe编译mutex.c并生成可执行文件mutex.exe,这里需要注意pthread线程操作函数库不是Linux系统默认的库,编译时需要指定使用线程库libpthread.a,所以在使用pthread_create创建线程时,在gcc编译时要加-lpthread参数选项,即执行命令cc mutex.c -o mutex.exe -lpthread,然后运行命令./ mutex.exe执行mutex.exe文件,然后我们设置生产者消费者初始数目均为5,缓冲区大小为10,执行算法
运行结果:
最后按ctrl+c可退出程序