いままでの内容をつかうと、ユーザプロセスで割り込み処理を行うことも可能になります。
題材としておもしろいので、ここで取り上げます。
具体的には、select を使います。selectで寝かせたところに、割り込みハンドル関数内で wakeup をしかけます。
たったこれだけで、ユーザプロセスで割り込みを拾うことが可能になってしまいます。
// モジュール側: intsel.c
// gcc -c intsel.c -Wall -Wstrict-prototypes -O -pipe -m486
// mknod /dev/ocrtest c 60 0; chmod a+rw /dev/ocrtest
#define MODULE
#define __KERNEL__
#include
#include
#include
#include
#include
#include
#if LINUX_VERSION_CODE >= 0x020100
#include
#include
#else
static inline unsigned long
copy_to_user(void *to, const void *from, unsigned long n)
{
memcpy_tofs(to,from,n);
return 0;
}
static inline unsigned long
copy_from_user(void *to, const void *from, unsigned long n)
{
memcpy_fromfs(to,from,n);
return 0;
}
#endif
static int irq=7; // 割り込み番号
static int devmajor=60;
static char *devname="intsel";
#if LINUX_VERSION_CODE > 0x20115
MODULE_PARM(irq, "i");
MODULE_PARM(devmajor, "i");
MODULE_PARM(devname, "s");
#endif
// ファイルごとの管理領域
typedef struct
{
unsigned long f_version; // 識別用 f_version
struct wait_queue *wait; // 休眠待機用領域
int sleep_mode; // 0: 起きてる 1: 待ち
unsigned long long wtime; // 最後に起きた時間
unsigned long long rtime; // 最後にioctlした呼んだ時間=リセット
} FileInfo;
#define MAXFILE 3
static FileInfo fi[MAXFILE];
// 時間取得関数:
unsigned long long get_time(void)
{
#ifdef USE_RDTSC // RDTSC 直読み
unsigned int h,l;
/* read Pentium cycle counter */
__asm__(".byte 0x0f,0x31" : "=a" (l),"=d" (h));
return ((unsigned long long int)h<<32)|l;
#else // gettimeofday 使用
struct timeval tv;
do_gettimeofday(&tv);
return (unsigned long long)(tv.tv_sec)*1000000+tv.tv_usec;
#endif
}
// Linux 2.0/2.2 共通
static int intsel_open(struct inode * inode, struct file * file)
{
int i;
printk("intsel_open: (%ld)\n",file->f_version);
for(i=0;if_version;
fi[i].sleep_mode=0;
fi[i].wait=NULL;
file->private_data=fi+i; // プライベートデータに構造体ポインタ設定
MOD_INC_USE_COUNT;
return 0;
}
// Linux 2.1 以降帰り値 int (事実上共通でも可: カーネル内で返り値使わず)
#if LINUX_VERSION_CODE >= 0x020100
static int intsel_close(struct inode * inode, struct file * file)
#else
static void intsel_close(struct inode * inode, struct file * file)
#endif
{
printk("intsel_close: (%ld)\n",file->f_version);
MOD_DEC_USE_COUNT;
((FileInfo *)(file->private_data))->f_version=0;
((FileInfo *)(file->private_data))->sleep_mode=0;
((FileInfo *)(file->private_data))->wtime=0;
((FileInfo *)(file->private_data))->rtime=0;
#if LINUX_VERSION_CODE >= 0x020100
return 0;
#endif
}
// poll / select : 2.2 では poll. 2.0 では select
#if LINUX_VERSION_CODE >= 0x020100
static unsigned int intsel_poll(struct file *file,
struct poll_table_struct *ptab)
{
if(((FileInfo *)(file->private_data))->rtime // リセット後
<((FileInfo *)(file->private_data))->wtime) // 起こされた
return POLLIN|POLLRDNORM; // 読み込みOK
poll_wait(file,&(((FileInfo *)(file->private_data))->wait),ptab);
((FileInfo *)(file->private_data))->sleep_mode=1;
return 0; // おやすみなさい (^^;
}
#else // 2.0 系用
static int intsel_select(struct inode *inode, struct file *file,
int sel_type, select_table *stab)
{
if(sel_type==SEL_IN)
{
if(((FileInfo *)(file->private_data))->rtime // リセット後
<((FileInfo *)(file->private_data))->wtime) // 起こされた
return 1; // 読み込みOK
((FileInfo *)(file->private_data))->sleep_mode=1;
select_wait(&(((FileInfo *)(file->private_data))->wait),stab);
return 0;
}
return 0;
}
#endif
// ioctl: Linux 2.0/2.2共通
static int intsel_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
((FileInfo *)(file->private_data))->rtime=get_time(); // リセット
switch(cmd)
{
case 1: // 起こされ時間の記録
copy_to_user((void *)arg,
&(((FileInfo *)(file->private_data))->wtime),8);
return 0;
default:
return 0;
}
}
#if LINUX_VERSION_CODE >= 0x020100
static struct file_operations intsel_fops = { // Linux 2.2.10 より
NULL, // loff_t llseek(struct file *, loff_t, int)
NULL, // ssize_t read(struct file *, char *, size_t, loff_t *)
NULL, // ssize_t write(struct file *, const char *, size_t, loff_t *)
NULL, // int readdir(struct file *, void *, filldir_t)
intsel_poll, // u. int poll(struct file *, struct poll_table_struct *)
intsel_ioctl, // int ioctl(struct inode *, struct file *, u.int, u.long)
NULL, // int mmap(struct file *, struct vm_area_struct *)
intsel_open, // int open(struct inode *, struct file *)
NULL, // int flush(struct file *)
intsel_close, // int release(struct inode *, struct file *)
NULL, // int fsync(struct file *, struct dentry *)
NULL, // int fasync(int, struct file *, int)
NULL, // int check_media_change(kdev_t dev)
NULL, // int revalidate(kdev_t dev)
NULL, // int lock(struct file *, int, struct file_lock *)
};
#else
static struct file_operations intsel_fops = { // Linux 2.0.36 より
NULL, // int lseek(struct inode *, struct file *, off_t, int)
NULL, // int read(struct inode *, struct file *, char *, int)
NULL, // int write(struct inode *, struct file *, const char *, int)
NULL, // int readdir(struct inode *, struct file *, void *, filldir_t)
intsel_select,// int select(struct inode *, struct file *, int, select_table *)
intsel_ioctl, // int ioctl(struct inode *, struct file *, u.int, unsigned long)
NULL, // int mmap(struct inode *, struct file *, struct vm_area_struct *)
intsel_open, // int open(struct inode *, struct file *)
intsel_close, // void release(struct inode *, struct file *)
NULL, // int fsync(struct inode *, struct file *)
NULL, // int fasync(struct inode *, struct file *, int)
NULL, // int check_media_change(kdev_t dev)
NULL, // int revalidate(kdev_t dev)
};
#endif
// 割り込みハンドル関数
static void intsel_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
int i;
unsigned long long wtime=get_time();
//printk("interrupted irq:%d dev_id:%s jiffies:%ld\n",
// irq,(char *)dev_id,jiffies);
for(i=0;i
// プロセス側: intsel_user.c
#include
#include
#include
#include
#include
#include
#include
// 時間取得関数:
unsigned long long get_time(void)
{
#ifdef USE_RDTSC // RDTSC 直読み
unsigned int h,l;
/* read Pentium cycle counter */
__asm__(".byte 0x0f,0x31" : "=a" (l),"=d" (h));
return ((unsigned long long int)h<<32)|l;
#else // gettimeofday 使用
struct timeval tv;
gettimeofday(&tv,NULL);
return (unsigned long long)(tv.tv_sec)*1000000+tv.tv_usec;
#endif
}
#ifdef USE_RDTSC // 1ミリ秒あたりの数字
#define TickPerMSEC 366000.0 // CPU動作周波数
#else
#define TickPerMSEC 1000.0 // 1ミリ/1マイクロ秒
#endif
int main(void)
{
int fd,r;
unsigned long long utime,wtime;
struct timeval tv;
fd_set fds;
fd=open("/dev/intsel",O_RDWR);
if(fd<0)
{ printf("cannnot open /dev/intsel\n"); return 1; }
while(1)
{
FD_ZERO(&fds); // select の準備
FD_SET(fd,&fds);
tv.tv_sec=1; tv.tv_usec=0; // タイムアウト1秒
r=select(fd+1,&fds,NULL,NULL,&tv); // 読みselect
if(r<0)
{ printf("select returned with signal or error\n"); break; }
if(r==0)
{ putchar('.'); fflush(stdout); continue; }
if((r==1)&&(FD_ISSET(fd,&fds)))
{
utime=get_time();
ioctl(fd,1,&wtime);
printf("\ninterrupt at %lld, selected at %lld\n",wtime,utime);
printf("difference: %lf [ms] (%lld ticks)\n",
(utime-wtime)/TickPerMSEC, utime-wtime);
}
}
close(fd);
return 0;
}
% gcc -O -o intsel_user intsel_user.c
% gcc -c intsel.c -Wall -Wstrict-prototypes -O -pipe -m486
# ./insdev intsel /dev/intsel
% ./intsel_user
...... <- べつのところで # ./lpint
interrupt at 954497906049005, selected at 954497906049044
difference: 0.039000 [ms] (39 ticks)
.....
interrupt at 954497911336106, selected at 954497911336150
difference: 0.044000 [ms] (44 ticks)
....
割り込みはなにを使ってもいいのですが、ここでは手頃なところでパラレルポートを使いました(なぜか、割り込みちゃんと発生しないことがありましたが)。
原理的には、上で述べたように、select で寝かせたものを 割り込みハンドラで wakeup します。
その判定には最後に ioctl したときとの時間で判断しています。
ioctl がリセットの役目です。
ロジックの組み方次第では、リセット動作を不要にできますが、ここでは単純化のためにこうしています。
ioctl はコマンドが1の場合に、unsigned long long 型で割り込み発生時刻を返すようにしています。
ユーザ空間のプロセスである、intsel_user では、selectから抜けたときに時刻とこの時刻から、割り込み発生からプロセスにCPUが移るまでの時間をおおまかに算出しています。
Linux 2.0.36 + PentiumII 233M で60マイクロ秒くらい、Linux 2.2.10 + Celeron 366MHz で30マイクロ秒くらいの性能が調子がいいときには出ます。
ただし、所詮はプロセスなので、他のプロセスの動作具合いや、カーネルの動作具合いによって1ミリ秒近くまで遅れることがありますし、最悪数十ミリ秒遅れることも有り得るでしょう。
(プロセスがスワップアウトされてしまっては、当然こんなものでは済みません)。
割り込みの性能にもよりますが、わりと応答性はいいとおもいます。いかがでしょうか?
時間の測定には gettimeofday の値を使用しています(ドライバでもdo_gettimeofdayを使用)。
もし、いちいちシステムコールなんて時間のかかるものつかえるか!という場合には、両プログラムを-DUSE_RDTSC オプションつきでコンパイルすれば、Pentium以降のCPUのみですが、CPUのクロックを数える命令を使用するようになります。
なお、その場合には TickPerMSEC の値をCPUの動作周波数をもとに正しく設定しないと、いい加減な時間を表示してしまいますのでご注意を。例では 366MHz にしてあります。
なお、この例のように、複数のものをカーネルに登録するとき(ここではドライバの関数テーブルと割り込み)、登録時にひとつでも転んだ場合にのこりを解放することを忘れてはいけません。