一、单生产者和单消费者
假设现在有一个牛奶生产厂家,它有一个经销商,并且由于资金不足,只有一个仓库。牛奶生产厂家首先生产一批牛奶,并放在仓库里,然后通知经销商来批发。经销商卖完牛奶后,打电话再订购下一批牛奶。牛奶生产厂家接到订单后,才开始生产下一批牛奶。
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,运行结果: