Linux的进程间通信(IPC,InterProcess Communication)通信方法有管道、消息队列、共享内存、信号量、套接口等。
管道分为有名管道和无名管道,无名管道只能用于亲属进程之间的通信,而有名管道则可用于无亲属关系的进程之间。
#define INPUT 0
#define OUTPUT 1
void main()
{
int file_descriptors[2];
/*定义子进程号 */
pid_t pid;
char buf[BUFFER_LEN];
int returned_count;
/*创建无名管道*/
pipe(file_descriptors);
/*创建子进程*/
if ((pid = fork()) == - 1)
{
printf("Error in fork\n");
exit(1);
}
/*执行子进程*/
if (pid == 0)
{
printf("in the spawned (child) process...\n");
/*子进程向父进程写数据,关闭管道的读端*/
close(file_descriptors[INPUT]);
write(file_descriptors[OUTPUT], "test data", strlen("test data"));
exit(0);
}
else
{
/*执行父进程*/
printf("in the spawning (parent) process...\n");
/*父进程从管道读取子进程写的数据,关闭管道的写端*/
close(file_descriptors[OUTPUT]);
returned_count = read(file_descriptors[INPUT], buf, sizeof(buf));
printf("%d bytes of data received from spawned process: %s\n",
returned_count, buf);
}
}
上述程序中,无名管道以
int pipe(int filedis[2]);
方式定义,参数filedis返回两个文件描述符filedes[0]为读而打开,filedes[1]为写而打开,filedes[1]的输出是filedes[0]的输入;
在Linux系统下,有名管道可由两种方式创建(假设创建一个名为“fifoexample”的有名管道):
(1)mkfifo("fifoexample","rw");
(2)mknod fifoexample p
mkfifo是一个函数,mknod是一个系统调用,即我们可以在shell下输出上述命令。
有名管道创建后,我们可以像读写文件一样读写之:
/* 进程一:读有名管道*/
void main()
{
FILE *in_file;
int count = 1;
char buf[BUFFER_LEN];
in_file = fopen("pipeexample", "r");
if (in_file == NULL)
{
printf("Error in fdopen.\n");
exit(1);
}
while ((count = fread(buf, 1, BUFFER_LEN, in_file)) > 0)
printf("received from pipe: %s\n", buf);
fclose(in_file);
}
/* 进程二:写有名管道*/
void main()
{
FILE *out_file;
int count = 1;
char buf[BUFFER_LEN];
out_file = fopen("pipeexample", "w");
if (out_file == NULL)
{
printf("Error opening pipe.");
exit(1);
}
sprintf(buf, "this is test data for the named pipe example\n");
fwrite(buf, 1, BUFFER_LEN, out_file);
fclose(out_file);
}
消息队列用于运行于同一台机器上的进程间通信,与管道相似;
共享内存通常由一个进程创建,其余进程对这块内存区进行读写。得到共享内存有两种方式:映射/dev/mem设备和内存映像文件。前一种方式
不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的是实际的物理内存;常用的方式是通过shmXXX函数族来实现共享内存:
int shmget(key_t key, int size, int flag); /* 获得一个共享存储标识符 */
该函数使得系统分配size大小的内存用作共享内存;
void *shmat(int shmid, void *addr, int flag); /* 将共享内存连接到自身地址空间中*/
shmid为shmget函数返回的共享存储标识符,addr和flag参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实
际地址。此后,进程可以对此地址进行读写操作访问共享内存。
本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:
(1)测试控制该资源的信号量;
(2)若此信号量的值为正,则允许进行使用该资源,进程将进号量减1;
(3)若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1);
(4)当进程不再使用一个信号量控制的资源时,信号量值加1,如果此时有进程正在睡眠等待此信号量,则唤醒此进程。
下面是一个使用信号量的例子,该程序创建一个特定的IPC结构的关键字和一个信号量,建立此信号量的索引,修改索引指向的信号量的值,最
后清除信号量:
#include <stdio.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>
void main()
{
key_t unique_key; /* 定义一个IPC关键字*/
int id;
struct sembuf lock_it;
union semun options;
int i;
unique_key = ftok(".", 'a'); /* 生成关键字,字符'a'是一个随机种子*/
/* 创建一个新的信号量集合*/
id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666);
printf("semaphore id=%d\n", id);
options.val = 1; /*设置变量值*/
semctl(id, 0, SETVAL, options); /*设置索引0的信号量*/
/*打印出信号量的值*/
i = semctl(id, 0, GETVAL, 0);
printf("value of semaphore at index 0 is %d\n", i);
/*下面重新设置信号量*/
lock_it.sem_num = 0; /*设置哪个信号量*/
lock_it.sem_op = - 1; /*定义操作*/
lock_it.sem_flg = IPC_NOWAIT; /*操作方式*/
if (semop(id, &lock_it, 1) == - 1)
{
printf("can not lock semaphore.\n");
exit(1);
}
i = semctl(id, 0, GETVAL, 0);
printf("value of semaphore at index 0 is %d\n", i);
/*清除信号量*/
semctl(id, 0, IPC_RMID, 0);
}
套接字通信并不为Linux所专有,在所有提供了TCP/IP协议栈的操作系统中几乎都提供了socket,而所有这样操作系统,对套接字的编程方法几
乎是完全一样的。
4.小节
本章讲述了Linux进程的概念,并以多个实例讲解了进程控制及进程间通信方法,理解这一章的内容可以说是理解Linux这个操作系统的关键。
Linux下的C编程实战(四)
――“线程”控制与“线程”通信编程
1.Linux“线程”
笔者曾经在《基于嵌入式操作系统VxWorks的多任务并发程序设计》(《软件报》2006年第5~12期)中详细叙述了进程和线程的区别,
并曾经说明Linux是一种“多进程单线程”的操作系统。Linux本身只有进程的概念,而其所谓的“线程”本质上在内核里仍然是进程。大家知
道,进程是资源分配的单位,同一进程中的多个线程共享该进程的资源(如作为共享内存的全局变量)。Linux中所谓的“线程”只是在被创建
的时候“克隆”(clone)了父进程的资源,因此,clone出来的进程表现为“线程”,这一点一定要弄清楚。因此,Linux“线程”这个概念只有
在打冒号的情况下才是最准确的,可惜的是几乎没有书籍留心去强调这一点。
Linux内核只提供了轻量进程的支持,未实现线程模型,但Linux尽最大努力优化了进程的调度开销,这在一定程度上弥补无线程的缺陷
。Linux用一个核心进程(轻量进程)对应一个线程,将线程调度等同于进程调度,交给核心完成。
目前Linux中最流行的线程机制为LinuxThreads,所采用的就是线程-进程“一对一”模型,调度交给核心,而在用户级实现一个包括信号处理
在内的线程管理机制。LinuxThreads由Xavier Leroy (Xavier.Leroy@inria.fr)负责开发完成,并已绑定在GLIBC中发行,它实现了一种
BiCapitalized面向Linux的Posix 1003.1c “pthread”标准接口。Linuxthread可以支持Intel、Alpha、MIPS等平台上的多处理器系统。
按照POSIX 1003.1c 标准编写的程序与Linuxthread 库相链接即可支持Linux平台上的多线程,在程序中需包含头文件pthread. h,在编译链接
时使用命令:
gcc -D -REENTRANT -lpthread xxx. c
其中-REENTRANT宏使得相关库函数(如stdio.h、errno.h中函数) 是可重入的、线程安全的(thread-safe),-lpthread则意味着链接库目录下的
libpthread.a或libpthread.so文件。使用Linuxthread库需要2.0以上版本的Linux内核及相应版本的C库(libc 5.2.18、libc 5.4.12、libc 6)
。
2.“线程”控制
线程创建
进程被创建时,系统会为其创建一个主线程,而要在进程中创建新的线程,则可以调用pthread_create:
pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(start_routine)(void*), void *arg);
start_routine为新线程的入口函数,arg为传递给start_routine的参数。
每个线程都有自己的线程ID,以便在进程内区分。线程ID在pthread_create调用时回返给创建线程的调用者;一个线程也可以在创建后使用
pthread_self()调用获取自己的线程ID:
pthread_self (void) ;
线程退出
线程的退出方式有三:
(1)执行完成后隐式退出;
(2)由线程本身显示调用pthread_exit 函数退出;
pthread_exit (void * retval) ;
(3)被其他线程用pthread_cance函数终止:
pthread_cance (pthread_t thread) ;
在某线程中调用此函数,可以终止由参数thread 指定的线程。
如果一个线程要等待另一个线程的终止,可以使用pthread_join函数,该函数的作用是调用pthread_join的线程将被挂起直到线程ID为参数
thread的线程终止:
pthread_join (pthread_t thread, void** threadreturn);
3.线程通信
线程互斥
互斥意味着“排它”,即两个线程不能同时进入被互斥保护的代码。Linux下可以通过pthread_mutex_t 定义互斥体机制完成多线程的互斥操作
,该机制的作用是对某个需要互斥的部分,在进入时先得到互斥体,如果没有得到互斥体,表明互斥部分被其它线程拥有,此时欲获取互斥体
的线程阻塞,直到拥有该互斥体的线程完成互斥部分的操作为止。
下面的代码实现了对共享全局变量x 用互斥体mutex 进行保护的目的:
int x; // 进程中的全局变量
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL); //按缺省的属性初始化互斥体变量mutex
pthread_mutex_lock(&mutex); // 给互斥体变量加锁
… //对变量x 的操作
phtread_mutex_unlock(&mutex); // 给互斥体变量解除锁
线程同步
同步就是线程等待某个事件的发生。只有当等待的事件发生线程才继续执行,否则线程挂起并放弃处理器。当多个线程协作时,相互作用的任
务必须在一定的条件下同步。
Linux下的C语言编程有多种线程同步机制,最典型的是条件变量(condition variable)。pthread_cond_init用来创建一个条件变量,其函数原
型为:
pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr);
pthread_cond_wait和pthread_cond_timedwait用来等待条件变量被设置,值得注意的是这两个等待调用需要一个已经上锁的互斥体mutex,这
是为了防止在真正进入等待状态之前别的线程有可能设置该条件变量而产生竞争。pthread_cond_wait的函数原型为:
pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex);
pthread_cond_broadcast用于设置条件变量,即使得事件发生,这样等待该事件的线程将不再阻塞:
pthread_cond_broadcast (pthread_cond_t *cond) ;
pthread_cond_signal则用于解除某一个等待线程的阻塞状态:
pthread_cond_signal (pthread_cond_t *cond) ;
pthread_cond_destroy 则用于释放一个条件变量的资源。
在头文件semaphore.h 中定义的信号量则完成了互斥体和条件变量的封装,按照多线程程序设计中访问控制机制,控制对资源的同步访问,提
供程序设计人员更方便的调用接口。
sem_init(sem_t *sem, int pshared, unsigned int val);
这个函数初始化一个信号量sem 的值为val,参数pshared 是共享属性控制,表明是否在进程间共享。
sem_wait(sem_t *sem);
调用该函数时,若sem为无状态,调用线程阻塞,等待信号量sem值增加(post )成为有信号状态;若sem为有状态,调用线程顺序执行,但信号
量的值减一。
sem_post(sem_t *sem);
调用该函数,信号量sem的值增加,可以从无信号状态变为有信号状态。
4.实例
下面我们还是以著名的生产者/消费者问题为例来阐述Linux线程的控制和通信。一组生产者线程与一组消费者线程通过缓冲区发生联系。生产
者线程将生产的产品送入缓冲区,消费者线程则从中取出产品。缓冲区有N 个,是一个环形的缓冲池。
#include <stdio.h>
#include <pthread.h>
#define BUFFER_SIZE 16 // 缓冲区数量
struct prodcons
{
// 缓冲区相关数据结构
int buffer[BUFFER_SIZE]; /* 实际数据存放的数组*/
pthread_mutex_t lock; /* 互斥体lock 用于对缓冲区的互斥操作 */
int readpos, writepos; /* 读写指针*/
pthread_cond_t notempty; /* 缓冲区非空的条件变量 */
pthread_cond_t notfull; /* 缓冲区未满的条件变量 */
};
/* 初始化缓冲区结构 */
void init(struct prodcons *b)
{
pthread_mutex_init(&b->lock, NULL);
pthread_cond_init(&b->notempty, NULL);
pthread_cond_init(&b->notfull, NULL);
b->readpos = 0;
b->writepos = 0;
}
/* 将产品放入缓冲区,这里是存入一个整数*/
void put(struct prodcons *b, int data)
{
pthread_mutex_lock(&b->lock);
/* 等待缓冲区未满*/
if ((b->writepos + 1) % BUFFER_SIZE == b->readpos)
{
pthread_cond_wait(&b->notfull, &b->lock);
}
/* 写数据,并移动指针 */
b->buffer[b->writepos] = data;
b->writepos++;
if (b->writepos > = BUFFER_SIZE)
b->writepos = 0;
/* 设置缓冲区非空的条件变量*/
pthread_cond_signal(&b->notempty);
pthread_mutex_unlock(&b->lock);
}
/* 从缓冲区中取出整数*/
int get(struct prodcons *b)
{
int data;
pthread_mutex_lock(&b->lock);
/* 等待缓冲区非空*/
if (b->writepos == b->readpos)
{
pthread_cond_wait(&b->notempty, &b->lock);
}
/* 读数据,移动读指针*/
data = b->buffer[b->readpos];
b->readpos++;
if (b->readpos > = BUFFER_SIZE)
b->readpos = 0;
/* 设置缓冲区未满的条件变量*/
pthread_cond_signal(&b->notfull);
pthread_mutex_unlock(&b->lock);
return data;
}
/* 测试:生产者线程将1 到10000 的整数送入缓冲区,消费者线
程从缓冲区中获取整数,两者都打印信息*/
#define OVER ( - 1)
struct prodcons buffer;
void *producer(void *data)
{
int n;
for (n = 0; n < 10000; n++)
{
printf("%d --->\n", n);
put(&buffer, n);
} put(&buffer, OVER);
return NULL;
}
void *consumer(void *data)
{
int d;
while (1)
{
d = get(&buffer);
if (d == OVER)
break;
printf("--->%d \n", d);
}
return NULL;
}
int main(void)
{
pthread_t th_a, th_b;
void *retval;
init(&buffer);
/* 创建生产者和消费者线程*/
pthread_create(&th_a, NULL, producer, 0);
pthread_create(&th_b, NULL, consumer, 0);
/* 等待两个线程结束*/
pthread_join(th_a, &retval);
pthread_join(th_b, &retval);
return 0;
}
5.WIN32、VxWorks、Linux线程类比
目前为止,笔者已经创作了《基于嵌入式操作系统VxWorks的多任务并发程序设计》(《软件报》2006年5~12期连载)、《深入浅出Win32多线
程程序设计》(天极网技术专题)系列,我们来找出这两个系列文章与本文的共通点。
看待技术问题要瞄准其本质,不管是Linux、VxWorks还是WIN32,其涉及到多线程的部分都是那些内容,无非就是线程控制和线程通信,它们的
许多函数只是名称不同,其实质含义是等价的,下面我们来列个三大操作系统共同点详细表单:
事项
WIN32
VxWorks
Linux
线程创建
CreateThread
taskSpawn
pthread_create
线程终止
执行完成后退出;线程自身调用ExitThread 函数即终止自己;被其他线程调用函数TerminateThread函数
执行完成后退出;由线程本身调用exit退出;被其他线程调用函数taskDelete终止
执行完成后退出;由线程本身调用pthread_exit 退出;被其他线程调用函数pthread_cance终止
获取线程ID
GetCurrentThreadId
taskIdSelf
pthread_self
创建互斥
CreateMutex
semMCreate
pthread_mutex_init
获取互斥
WaitForSingleObject、
WaitForMultipleObjects
semTake
pthread_mutex_lock
释放互斥
ReleaseMutex
semGive
phtread_mutex_unlock
创建信号量
CreateSemaphore
semBCreate、semCCreate
sem_init
等待信号量
WaitForSingleObject
semTake
sem_wait
释放信号量
ReleaseSemaphore
semGive
sem_post
6.小结
本章讲述了Linux下多线程的控制及线程间通信编程方法,给出了一个生产者/消费者的实例,并将Linux的多线程与WIN32、VxWorks多
线程进行了类比,总结了一般规律。鉴于多线程编程已成为开发并发应用程序的主流方法,学好本章的意义也便不言自明。
Linux下的C编程实战(五)
――驱动程序设计
1.引言
设备驱动程序是操作系统内核和机器硬件之间的接口,它为应用程序屏蔽硬件的细节,一般来说,Linux的设备驱动程序需要完成如下功能:
(1)初始化设备;
(2)提供各类设备服务;
(3)负责内核和设备之间的数据交换;
(4)检测和处理设备工作过程中出现的错误。
妙不可言的是,Linux下的设备驱动程序被组织为一组完成不同任务的函数的集合,通过这些函数使得Windows的设备操作犹如文件一般。在应
用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。本系列文章的第2章文件系统编程中已经看
到了这些函数的真面目,它们就是open ()、close ()、read ()、write () 等。
Linux主要将设备分为二类:字符设备和块设备(当然网络设备及USB等其它设备的驱动编写方法又稍有不同)。这两类设备的不同点在于:在
对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,而块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求
能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作。块设备主要针对磁盘等慢速设备。以字符设备的驱
动较为简单,因此本章主要阐述字符设备的驱动编写。
2.驱动模块函数
init 函数用来完成对所控设备的初始化工作,并调用register_chrdev() 函数注册字符设备。假设有一字符设备“exampledev”,则其init
函数为:
void exampledev_init(void)
{
if (register_chrdev(MAJOR_NUM, " exampledev ", &exampledev_fops))
TRACE_TXT("Device exampledev driver registered error");
else
TRACE_TXT("Device exampledev driver registered successfully");
…//设备初始化
}
其中,register_chrdev函数中的参数MAJOR_NUM为主设备号,“exampledev”为设备名,exampledev_fops为包含基本函数入口点的结构体,类
型为file_operations。当执行exampledev_init时,它将调用内核函数register_chrdev,把驱动程序的基本入口点指针存放在内核的字符设备
地址表中,在用户进程对该设备执行系统调用时提供入口地址。
file_operations结构体定义为:
struct file_operations
{
int (*lseek)();
int (*read)();
int (*write)();
int (*readdir)();
int (*select)();
int (*ioctl)();
int (*mmap)();
int (*open)();
void(*release)();
int (*fsync)();
int (*fasync)();
int (*check_media_change)();
void(*revalidate)();
};
大多数的驱动程序只是利用了其中的一部分,对于驱动程序中无需提供的功能,只需要把相应位置的值设为NULL。对于字符设备来说,要提供
的主要入口有:open ()、release ()、read ()、write ()、ioctl ()。
open()函数 对设备特殊文件进行open()系统调用时,将调用驱动程序的open () 函数:
int open(struct inode * inode ,struct file * file);
其中参数inode为设备特殊文件的inode (索引结点) 结构的指针,参数file是指向这一设备的文件结构的指针。open()的主要任务是确定硬件
处在就绪状态、验证次设备号的合法性(次设备号可以用MINOR(inode-> i - rdev) 取得)、控制使用设备的进程数、根据执行情况返回状态码
(0表示成功,负数表示存在错误) 等;
release()函数 当最后一个打开设备的用户进程执行close ()系统调用时,内核将调用驱动程序的release () 函数:
void release (struct inode * inode ,struct file * file) ;
release 函数的主要任务是清理未结束的输入/输出操作、释放资源、用户自定义排他标志的复位等。
read()函数 当对设备特殊文件进行read() 系统调用时,将调用驱动程序read() 函数:
void read(struct inode * inode ,struct file * file ,char * buf ,int count) ;
参数buf是指向用户空间缓冲区的指针,由用户进程给出,count 为用户进程要求读取的字节数,也由用户给出。
read() 函数的功能就是从硬设备或内核内存中读取或复制count个字节到buf 指定的缓冲区中。在复制数据时要注意,驱动程序运行在内核中
,而buf指定的缓冲区在用户内存区中,是不能直接在内核中访问使用的,因此,必须使用特殊的复制函数来完成复制工作,这些函数在<asm/
segment.h>中定义:
void put_user_byte (char data_byte ,char * u_addr) ;
void put_user_word (short data_word ,short * u_addr) ;
void put_user_long(long data_long ,long * u_addr) ;
void memcpy_tofs (void * u_addr ,void * k_addr ,unsigned long cnt) ;
参数u_addr为用户空间地址,k_addr 为内核空间地址,cnt为字节数。
write( ) 函数 当设备特殊文件进行write () 系统调用时,将调用驱动程序的write () 函数:
void write (struct inode * inode ,struct file * file ,char * buf ,int count) ;
write ()的功能是将参数buf 指定的缓冲区中的count 个字节内容复制到硬件或内核内存中,和read() 一样,复制工作也需要由特殊函数来完
成:
unsigned char_get_user_byte (char * u_addr) ;
unsigned char_get_user_word (short * u_addr) ;
unsigned char_get_user_long(long * u_addr) ;
unsigned memcpy_fromfs(void * k_addr ,void * u_addr ,unsigned long cnt) ;
ioctl() 函数 该函数是特殊的控制函数,可以通过它向设备传递控制信息或从设备取得状态信息,函数原型为:
int ioctl (struct inode * inode ,struct file * file ,unsigned int cmd ,unsigned long arg);
参数cmd为设备驱动程序要执行的命令的代码,由用户自定义,参数arg 为相应的命令提供参数,类型可以是整型、指针等。
同样,在驱动程序中,这些函数的定义也必须符合命名规则,按照本文约定,设备“exampledev”的驱动程序的这些函数应分别命名为
exampledev_open、exampledev_ release、exampledev_read、exampledev_write、exampledev_ioctl,因此设备“exampledev”的基本入口点
结构变量exampledev_fops 赋值如下:
struct file_operations exampledev_fops {
NULL ,
exampledev_read ,
exampledev_write ,
NULL ,
NULL ,
exampledev_ioctl ,
NULL ,
exampledev_open ,
exampledev_release ,
NULL ,
NULL ,
NULL ,
NULL
} ;
3.内存分配
由于Linux驱动程序在内核中运行,因此在设备驱动程序需要申请/释放内存时,不能使用用户级的malloc/free函数,而需由内核级的函数
kmalloc/kfree () 来实现,kmalloc()函数的原型为:
void kmalloc (size_t size ,int priority);
参数size为申请分配内存的字节数;参数priority说明若kmalloc()不能马上分配内存时用户进程要采用的动作:GFP_KERNEL 表示等待,即等
kmalloc()函数将一些内存安排到交换区来满足你的内存需要,GFP_ATOMIC 表示不等待,如不能立即分配到内存则返回0 值;函数的返回值指
向已分配内存的起始地址,出错时,返回0。
kmalloc ()分配的内存需用kfree()函数来释放,kfree ()被定义为:
# define kfree (n) kfree_s( (n) ,0)
其中kfree_s () 函数原型为:
void kfree_s (void * ptr ,int size);
参数ptr为kmalloc()返回的已分配内存的指针,size是要释放内存的字节数,若为0 时,由内核自动确定内存的大小。
4.中断
许多设备涉及到中断操作,因此,在这样的设备的驱动程序中需要对硬件产生的中断请求提供中断服务程序。与注册基本入口点一样,驱动程
序也要请求内核将特定的中断请求和中断服务程序联系在一起。在Linux中,用request_irq()函数来实现请求:
int request_irq (unsigned int irq ,void( * handler) int ,unsigned long type ,char * name);
参数irq为要中断请求号,参数handler为指向中断服务程序的指针,参数type 用来确定是正常中断还是快速中断(正常中断指中断服务子程序
返回后,内核可以执行调度程序来确定将运行哪一个进程;而快速中断是指中断服务子程序返回后,立即执行被中断程序,正常中断type 取值
为0 ,快速中断type 取值为SA_INTERRUPT),参数name是设备驱动程序的名称。
5.实例
笔者最近设计了一块采用三星S3C2410 ARM处理器的电路板(ARM处理器广泛应用于手机、PDA等嵌入式系统),板上包含四个用户可编程的发光
二极管(LED),这些LED连接在ARM处理器的可编程I/O口(GPIO)上。下图给出了ARM中央处理器与LED的连接原理:
<!--[if !vml]--><!--[endif]--> <!--[if !vml]--><!--[endif]-->
我们在ARM处理器上移植Linux操作系统,现在来编写这些LED的驱动:
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/spinlock.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <asm/hardware.h>
#define DEVICE_NAME "leds" /*定义led 设备的名字*/
#define LED_MAJOR 231 /*定义led 设备的主设备号*/
static unsigned long led_table[] =
{
/*I/O 方式led 设备对应的硬件资源*/
GPIO_B10, GPIO_B8, GPIO_B5, GPIO_B6,
};
/*使用ioctl 控制led*/
static int leds_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
unsigned long arg)
{
switch (cmd)
{
case 0:
case 1:
if (arg > 4)
{
return -EINVAL;
}
write_gpio_bit(led_table[arg], !cmd);
default:
return -EINVAL;
}
}
static struct file_operations leds_fops =
{
owner: THIS_MODULE, ioctl: leds_ioctl,
};
static devfs_handle_t devfs_handle;
static int __init leds_init(void)
{
int ret;
int i;
/*在内核中注册设备*/
ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &leds_fops);
if (ret < 0)
{
printk(DEVICE_NAME " can't register major number\n");
return ret;
}
devfs_handle = devfs_register(NULL, DEVICE_NAME, DEVFS_FL_DEFAULT, LED_MAJOR,
0, S_IFCHR | S_IRUSR | S_IWUSR, &leds_fops, NULL);
/*使用宏进行端口初始化,set_gpio_ctrl 和write_gpio_bit 均为宏定义*/
for (i = 0; i < 8; i++)
{
set_gpio_ctrl(led_table[i] | GPIO_PULLUP_EN | GPIO_MODE_OUT);
write_gpio_bit(led_table[i], 1);
}
printk(DEVICE_NAME " initialized\n");
return 0;
}
static void __exit leds_exit(void)
{
devfs_unregister(devfs_handle);
unregister_chrdev(LED_MAJOR, DEVICE_NAME);
}
module_init(leds_init);
module_exit(leds_exit);
使用命令方式编译led 驱动模块:
#arm-linux-gcc -D__KERNEL__ -I/arm/kernel/include
-DKBUILD_BASENAME=leds -DMODULE -c -o leds.o leds.c
以上命令将生成leds.o 文件,把该文件复制到板子的/lib目录下,使用以下命令就可以安装leds驱动模块:
#insmod /lib/ leds.o
删除该模块的命令是:
#rmmod leds
6.小结
本章讲述了Linux设备驱动程序的入口函数及驱动程序中的内存申请、中断等,并给出了一个通过ARM处理器的GPIO口控制LED的驱动实例。TEST