生产者消费者内核模块调试

一、单生产者和单消费者

假设现在有一个牛奶生产厂家,它有一个经销商,并且由于资金不足,只有一个仓库。牛奶生产厂家首先生产一批牛奶,并放在仓库里,然后通知经销商来批发。经销商卖完牛奶后,打电话再订购下一批牛奶。牛奶生产厂家接到订单后,才开始生产下一批牛奶。

pc.c 代码如下:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/semaphore.h>
#include <linux/sched.h>
#include <asm/atomic.h>
#include <linux/delay.h>
#include <linux/kthread.h>

#define PRODUCT_NUMS 10

//信号量定义
static struct semaphore sem_producer;
static struct semaphore sem_consumer;

static char product[12];
static atomic_t num;
static int id=1;
static int consume_num=1;

//生产者线程
static int producer(void *p)
{
	char *product=(char *)p;
	int i;
	
	atomic_inc(&num);		//给num加1
	printk("producer [%d] strat ...\n",current->pid);
	for(i=0;i<PRODUCT_NUMS;i++)			//仓库最多存10批牛奶
	{
		down(&sem_producer);			//获取信号量sem_producer,当该信号量无法获取时,它将进入睡眠状态,直到信号量可用,它才继续执行
		snprintf(product,12,"2010-01-%d",id++);		
		printk("producer [%d] produce %s\n",current->pid,product);
		up(&sem_consumer);				//释放信号量sem_consumer
	}
	
	printk("producer [%d] exit ...\n",current->pid);
	return 0;
}

//消费者线程
static int consumer(void *p)
{
	char *product=(char *)p;
	
	printk("consumer [%d] start ... \n",current->pid);
	for(;;)
	{
		msleep(100);
		down_interruptible(&sem_consumer);		//获取信号量sem_consumer,进程在等待获取信号量的时候是可以被信号打断的
		if(consume_num >= PRODUCT_NUMS*atomic_read(&num))	//PRODUCT_NUMS*atomic_read(&num) 表示生产了多少批牛奶
			break;
		printk("consumer [%d] consume %s\n",current->pid,product);
		consume_num++;
		memset(product,'\0',12);
		up(&sem_producer);					//释放信号量sem_producer
	}
	
	printk("consume [%d] exit ...\n",current->pid);
	return 0;
}

static int procon_init(void)
{
	printk(KERN_INFO"show producer and consumer\n");		//KERN_INFO 表示内核提示信息
	sema_init(&sem_producer,1);	
	sema_init(&sem_consumer,0);	
	atomic_set(&num,0);										//原子操作sum=0
	//创建相应的内核线程
	kthread_run(producer,product,"producer");
	kthread_run(consumer,product,"consumer");
	
	return 0;
}

static void procon_exit(void)
{
	printk(KERN_INFO"exit producer and consumer\n");
}

module_init(procon_init);
module_exit(procon_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("producer and consumer Module");
MODULE_ALIAS("a simplest module");

snprintf()函数用于将格式化的数据写入字符串,其原型为:

/* 参数:
   str为要写入的字符串;
   n为要写入的字符的最大数目,超过n会被截断;
   format为格式化字符串,argument为其变量。
*/
int snprintf(char *str, int n, char * format [, argument, …]);

返回值:成功则返回参数str 字符串长度,失败则返回-1,错误原因存于errno 中。

memset()函数是一个初始化函数,作用是将某一块内存中的全部设置为指定的值。

/* 参数:
   s指向要填充的内存块;
   c是要被设置的值;
   n是要被设置该值的字符数。
*/
void *memset(void *s, int c, size_t n); 

返回类型是一个指向存储区s的指针。

semaphore结构体如下:

struct semaphore {
	raw_spinlock_t		lock;			//自旋锁
	unsigned int		count;			//资源数量
	struct list_head	wait_list;		//存放等待队列链表的地址
};

sema_inith()函数原型如下:

/* 参数:
   sem为信号量;
   val也就是semaphore结构体中的count;
*/
static inline void sema_init(struct semaphore *sem, int val);

kthread_create宏定义如下:

/*
 * kthread_create - 在当前节点上创建一个内核线程
 * @threadfn: 在线程中运行的函数
 * @data: threadfn()的数据指针
 * @namefmt: 线程名称的printf格式字符串
 * @arg...: namefmt的参数
 *
 * 注意:kthread_create()只是创建一个内核线程,但并没有启动
 */
#define kthread_create(threadfn, data, namefmt, arg...) \
	kthread_create_on_node(threadfn, data, NUMA_NO_NODE, namefmt, ##arg)

kthread_run宏定义如下:

/**
 * kthread_run - 创建并唤醒一个线程
 * @threadfn: 运行到signal_pending(current)为止的函数
 * @data: threadfn()的数据指针
 * @namefmt: 线程名称的printf格式字符串
 *
 * 调用wake_up_process()来启动线程
 */
#define kthread_run(threadfn, data, namefmt, ...)			   \
({									   \
	struct task_struct *__k						   \
		= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
	if (!IS_ERR(__k))						   \
		wake_up_process(__k);					   \
	__k;								   \
})

Makefile 代码如下:

obj-m += pc.o

CURRENT_PATH:=$(shell pwd)	#模块所在的当前所在路径
LINUX_KERNEL:=$(shell uname -r)	#linux内核代码的当前版本
LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL)	#linux内核的当前版本源码路径

all:
	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules	#编译模块
#				内核的路径		  当前目录编译完放哪   表明编译的是内核模块

clean:
	make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean	#清理模块

运行结果:

在这里插入图片描述

二、问答及多生产者-多消费者

在这里插入图片描述

(1)不可以,如果初始化信号量sem_producer为锁定的互斥信号量,sem_consumer为未锁定的互斥信号量,那么在这样的初始化状态下,生产者线程无法进行生产,会进入一个信号量等待的过程,而消费者线程会因为缺少产品在if条件语句处退出循环,结束运行,这样将会导致生产者线程永远等不到信号量,进而出现“死锁”的情况。

(2)(3)(4)思路:由于线程是并发执行的,所以可以把多个仓库看做成一个仓库,因此所有的情况都可以看成是“多生产者、多消费者、一个缓冲区”

#include <linux/init.h>
#include <linux/module.h>
#include <linux/semaphore.h>
#include <linux/sched.h>
#include <asm/atomic.h>
#include <linux/delay.h>
#include <linux/kthread.h>

int p=0,c=0,h=0,consume_num=0;
module_param(p,int,0);
module_param(c,int,0);
module_param(h,int,0);
module_param(consume_num,int,0);

#define PRODUCT_NUMS 10

//信号量定义
static struct semaphore sem_producer;
static struct semaphore sem_consumer;

static char product[12];
static atomic_t num;
static int id=1;

//生产者线程
static int producer(void *p)
{
	char *product=(char *)p;
	int i;
	
	if(atomic_read(&num)<p)
	{
        atomic_inc(&num);		//给num加1
        printk("producer [%d] strat ...\n",current->pid);
        for(i=0;i<PRODUCT_NUMS;i++)	
        {
            down(&sem_producer);			//获取信号量sem_producer,当该信号量无法获取时,它将进入睡眠状态,直到信号量可用,它才继续执行
            snprintf(product,12,"2010-01-%d",id++);		
            printk("producer [%d] produce %s\n",current->pid,product);
            up(&sem_consumer);				//释放信号量sem_consumer
        }

        printk("producer [%d] exit ...\n",current->pid);
        return 0;
	}
	
	return 0;
}

//消费者线程
static int consumer(void *p)
{
	char *product=(char *)p;
	
	printk("consumer [%d] start ... \n",current->pid);
	for(;;)
	{
		msleep(100);
		down_interruptible(&sem_consumer);		//获取信号量sem_consumer,进程在等待获取信号量的时候是可以被信号打断的
		if(consume_num >= PRODUCT_NUMS*atomic_read(&num))	//PRODUCT_NUMS*atomic_read(&num) 表示生产了多少批牛奶
			break;
		printk("consumer [%d] consume %s\n",current->pid,product);
		consume_num++;
		memset(product,'\0',12);
		up(&sem_producer);					//释放信号量sem_producer
	}
	
	printk("consume [%d] exit ...\n",current->pid);
	return 0;
}

static int procon_init(void)
{
	int a=1;
	int b=1;
	printk(KERN_INFO"show producer and consumer\n");		//KERN_INFO 表示内核提示信息
	sema_init(&sem_producer,p);
	if(p==c)
	{
		sema_init(&sem_consumer,0);
	}else if(p>c){
		sema_init(&sem_consumer,c);
	}else{	
		sema_init(&sem_consumer,c-p);
	}
	atomic_set(&num,0);										//原子操作sum=0
	//创建相应的内核线程
	for(a;a<=p;a++)
	{
		kthread_run(producer,product,"producer %d",a);
	}
	for(b;b<=c;b++)
	{
		kthread_run(consumer,product,"consumer %d",b);
	}
	
	return 0;
}

static void procon_exit(void)
{
	printk(KERN_INFO"exit producer and consumer\n");
}

module_init(procon_init);
module_exit(procon_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("producer and consumer Module");
MODULE_ALIAS("a simplest module");

“多个生产者、一个消费者、一个缓冲区”,执行指令sudo insmod pc.ko p=2 c=1 h=1 consume_num=1,运行结果:

在这里插入图片描述

“多个生产者、多个消费者、一个缓冲区”,执行指令sudo insmod pc.ko p=2 c=2 h=1 consume_num=2,运行结果:

在这里插入图片描述

“一个生产者、一个消费者、多个缓冲区”,执行指令sudo insmod pc.ko p=1 c=1 h=2 consume_num=1,运行结果:

在这里插入图片描述

“多个生产者、多个消费者、多个缓冲区”,执行指令sudo insmod pc.ko p=2 c=2 h=2 consume_num=2,运行结果:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_58538265/article/details/133916726
今日推荐