デバイスドライバと割り込み処理、inb()とoutb()

					2014年01月16日
情報科学類 オペレーティングシステム II

                                       筑波大学 システム情報工学研究科 
                                       コンピュータサイエンス専攻, 電子・情報工学系
                                       新城 靖
                                       <yas@cs.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2013/2014-01-16
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~yas/
http://www.cs.tsukuba.ac.jp/~yas/

■今日の大事な話

■デバイス・ドライバ

デバイス・ドライバとは、 オペレーティング・システム・カーネル内で動作するモジュールで、ディスク やキーボード等のデバイス(周辺装置)を管理し、入出力を制御する。 「オブジェクト指向」よりも、デバイスドライバの考え方が古い。

◆Unix系OSのデバイス・ドライバの種類

◆メジャー番号とマイナー番号

ブロック型と文字型のデバイス・ドライバは、メジャー番号とマイナー番号の 2つの番号で区別される。利用者プログラムからそれらをアクセスする時には、 /dev 以下のファイルをアクセスする。
$ df / [←]
Filesystem           1K-blocks      Used Available Use% Mounted on
/dev/sda2            232431456  13088380 207345736   6% /
$ ls -l /dev/sda2 [←]
brw-r----- 1 root disk 8, 2 Jan 24 12:00 /dev/sda2
$ []
ls -l で見ると、ブロック型は、b、文字型は、c で始まる。メ ジャー番号は、デバイスの種類、マイナー番号は、同じ種類で、細かい違い (上の例では、パーティション)等を意味する。

メジャー番号は、静的に決めうちにすることもあるが、 alloc_chrdev_region() を呼び、動的に割り当てられることもできる。 使われているメジャー番号は、/proc/devices に現れる。 /dev/ の下にあるブロック型と文字型のファイルは、mknod コマンド (make node) で作ることができる。

# mknod b /dev/ファイル名 メジャー番号 マイナー番号 [←]
# mknod c /dev/ファイル名 メジャー番号 マイナー番号 [←]
最近の Linux では、起動時に自動的に mknod が行われるので、手で mknod コマンド を打つ必要性はあまりない。

文字型デバイスの登録

まず、struct file_operations 構造体とその内部の関数群を定義する。その構 造体を、struct cdev に設定する。最後に、struct cdev を、cdev_add() で登 録する。
struct file_operations my_fops = { .... };
struct cdev *my_cdevp = cdev_alloc();
my_cdev->ops = &my_fops; 
my_cdev->owner = &my_fops; 
struct file_operations my_fops = { .... };
struct cdev my_cdev ;
cdev_init(&my_cdev,&my_fops);
cdev_add(&my_cdev,num, count)
register_chrdev() という関数で登録することもできる。以前はこの方法が主。
int register_chrdev(unsigned int major, const char *name,
    struct file_operations *fops);

◆file_operations構造体

file_operations構造体は、文字型でバイスのデバイス・ドライバのインタフェー スを定めている構造体。インタフェースは、関数の集合として定義されている。
linux-3.12.6/include/linux/fs.h

1526:	struct file_operations {
1527:	        struct module *owner;
1528:	        loff_t (*llseek) (struct file *, loff_t, int);
1529:	        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
1530:	        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
1531:	        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
1532:	        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
1533:	        int (*iterate) (struct file *, struct dir_context *);
1534:	        unsigned int (*poll) (struct file *, struct poll_table_struct *);
1535:	        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
1536:	        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
1537:	        int (*mmap) (struct file *, struct vm_area_struct *);
1538:	        int (*open) (struct inode *, struct file *);
1539:	        int (*flush) (struct file *, fl_owner_t id);
1540:	        int (*release) (struct inode *, struct file *);
1541:	        int (*fsync) (struct file *, loff_t, loff_t, int datasync);
1542:	        int (*aio_fsync) (struct kiocb *, int datasync);
1543:	        int (*fasync) (int, struct file *, int);
1544:	        int (*lock) (struct file *, int, struct file_lock *);
1545:	        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
1546:	        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
1547:	        int (*check_flags)(int);
1548:	        int (*flock) (struct file *, int, struct file_lock *);
1549:	        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
1550:	        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
1551:	        int (*setlease)(struct file *, long, struct file_lock **);
1552:	        long (*fallocate)(struct file *file, int mode, loff_t offset,
1553:	                          loff_t len);
1554:	        int (*show_fdinfo)(struct seq_file *m, struct file *f);
1555:	};
主な手続きの意味

◆inode 構造体

Linux カーネル内で、1つのファイルを表現する構造体。

デバイス・ファイルでは、open() 等で自分のメジャー番号とマイナー番号を取 り出すために使われることがある。

◆file構造体

開いたファイルを表現するために使われる。
    int fd1 = open("file1");
    int fd2 = open("file1");
ファイル名 "file1" で表現されるファイルの inode 構造体は、1 個でも、 file 構造体は、2 個割り当てられる。

◆ioctl()システム・コール

デバイスには、それぞれ特殊な操作方法がある。read(), write() には合わな いものがある。

このような特殊な操作は、システム・コールを増やすのではなく、全部 ioctl() システム・コールという特殊なシステム・コールを用いる。
int ioctl(int d, int request, ...);
コマンドは、デバイス・ドライバごとにことなる。第3引数は、コマンドにより 使われないことともある。

■x86 CMOS Real-Time Clock

CMOS RTC (Real-Time Clock) とは、バッテリ・バックアップされた時計。 CMOS や Real Time という名前が紛らわしいので、固有名詞と思うと良い。 次の2つの機能がある。
TOD (time of day) clock
時刻を、year/month/day hour:minute:second という形式で持つ。 秒以下は読めない。 PCの主電源を切っていても、この時計はマザーボード上のバッテリで進み続け る。 設定された日時に割り込みを起こす機能がある(alarm)。
定期的な割込み用
2Hz から 8192Hz の範囲で、2 の冪乗の周期で定期的な割込み(periodic interrupt)を起こせる。
Linux は、起動時に TOD clock を読んで、自身が管理する時計を初期化する。 以後、普段はこのハードウェアを読むことはしない。 Linuxが管理する時計は、ソフトウェア的に変更できる(date コマンド、 settimeofday() システム・コール)。 Linux では、1秒間に1回、内部的な時刻をこのハードウェアに保存している。

ハードウェアの内容は、hwclock コマンドで参照できる。
$ date [←]
Wed Jan 15 16:19:09 JST 2014
$ hwclock --show [←]
Wed Jan 15 16:19:11 2014  -0.571417 seconds
$ []

◆rtc-read-time.c

/dev/rtc は、CMOS RTC に対応した文字型デバイスのファイルである。これを 開いて ioctl() でアクセスすると、CMOS RTC の内容が読める。
   1:	
   2:	/*
   3:	        ~yas/syspro/time/rtc-read-time.c -- Read CMOS Realtime Clock in Linux
   4:	        Created on: 2011/01/28 17:12:36
   5:	*/
   6:	
   7:	
   8:	#include <sys/types.h>  /* open() */
   9:	#include <sys/stat.h>   /* open() */
  10:	#include <fcntl.h>      /* open() */
  11:	#include <sys/ioctl.h>  /* ioctl() */
  12:	#include <unistd.h>     /* close() */
  13:	#include <stdio.h>      /* printf() */
  14:	#include <stdlib.h>     /* exit() */
  15:	#include <linux/rtc.h>  /*  RTC_RD_TIME */
  16:	
  17:	#define RTC_DEVICE_FILE "/dev/rtc"
  18:	
  19:	main()
  20:	{
  21:	    int fd;
  22:	    struct rtc_time t1 ;
  23:	        if( (fd = open( RTC_DEVICE_FILE, O_RDONLY ))< 0 )
  24:	        {
  25:	            perror("open");
  26:	            exit( 1 );
  27:	        }
  28:	        if( ioctl( fd, RTC_RD_TIME, &t1 ) < 0 )
  29:	        {
  30:	            perror("ioctl(RTC_RD_TIME)");
  31:	            exit( 2 );
  32:	        }
  33:	        printf("%04d-%02d-%02d %02d:%02d:%02d\n",
  34:	                t1.tm_year+1900, t1.tm_mon+1, t1.tm_mday,
  35:	                t1.tm_hour, t1.tm_min, t1.tm_sec );
  36:	        close( fd );
  37:	}
実行例
$ rm rtc-read-time [←]
$ make rtc-read-time [←]
cc     rtc-read-time.c   -o rtc-read-time
$ ./rtc-read-time  [←]
2014-01-15 16:22:30
$ date [←]
Wed Jan 15 16:22:32 JST 2014
$ ./rtc-read-time ; date; hwclock --show [←]
2014-01-15 16:22:34
Wed Jan 15 16:22:34 JST 2014
Wed Jan 15 16:22:35 2014  -0.976230 seconds
$ []
RTC_RD_TIME を含めて、/dev/rtc に対する ioctl() では、次のようなコマン ドが使える。詳しくは、man rtc を参照。
/dev/rtc に対する ioctl() のコマンド
コマンド 説明
RTC_RD_TIME RTCのTODを読む(read)
RTC_SET_TIME RTCのTODに値をセットする
RTC_ALM_READ,RTC_ALM_SET RTCのalarmを読む/セットする
RTC_IRQP_READ alarmによる定期的な割り込みの(periodic interrupt)の周波数を読む/セットする
RTC_AIE_ON, RTC_AIE_OFF alarmの割り込みを許可する/禁止する
RTC_UIE_ON, RTC_UIE_OFF clockの更新後との割り込みを許可する/禁止する
RTC_PIE_ON, RTC_PIE_OFF 定期的な割り込みを許可する/禁止する
RTC_EPOCH_READ, RTC_EPOCH_SET RTCのepoch (起点となる年月日) を読む/書く

◆drivers/char/rtc.c

CMOS RTC のデバイスドライバは、drivers/char/rtc.c にある。 カーネルの起動時に rtc_init() という関数が呼ばれる。
linux-3.12.6/drivers/char/rtc.c

 899:	static const struct file_operations rtc_fops = {
 900:	        .owner          = THIS_MODULE,
 901:	        .llseek         = no_llseek,
 902:	        .read           = rtc_read,
 903:	#ifdef RTC_IRQ
 904:	        .poll           = rtc_poll,
 905:	#endif
 906:	        .unlocked_ioctl = rtc_ioctl,
 907:	        .open           = rtc_open,
 908:	        .release        = rtc_release,
 909:	        .fasync         = rtc_fasync,
 910:	};
 911:	
 912:	static struct miscdevice rtc_dev = {
 913:	        .minor          = RTC_MINOR,
 914:	        .name           = "rtc",
 915:	        .fops           = &rtc_fops,
 916:	};
...
 953:	static int __init rtc_init(void)
 954:	{
...
1058:	        if (misc_register(&rtc_dev)) {
...
1065:	                return -ENODEV;
1066:	        }
...
1132:	        (void) init_sysctl();
1133:	
1134:	        printk(KERN_INFO "Real Time Clock Driver v" RTC_VERSION "\n");
1135:	
1136:	        return 0;
1137:	}

linux-3.12.6/include/linux/miscdevice.h
  13:	#define PSMOUSE_MINOR           1
  14:	#define MS_BUSMOUSE_MINOR       2
...
  24:	#define RTC_MINOR               135
dmesg コマンドを使うと、printk() の結果を画面に表示してくれる。
$ dmesg | grep Real [←]
Real Time Clock Driver v1.12ac
$ []
dmesg コマンドの古いものは、(syslogd の働きで) /var/log/messages* 等の ファイルに保存される。
# grep Real /var/log/messages.2  [←]
...
Jan 24 12:00:40 windell50 kernel: Real Time Clock Driver v1.12ac
# []

◆rtc_open()

linux-3.12.6/drivers/char/rtc.c

 191:	static unsigned long rtc_status;        /* bitmapped status byte.       */

 730:	static int rtc_open(struct inode *inode, struct file *file)
 731:	{
 732:	        spin_lock_irq(&rtc_lock);
 733:	
 734:	        if (rtc_status & RTC_IS_OPEN)
 735:	                goto out_busy;
 736:	
 737:	        rtc_status |= RTC_IS_OPEN;
...
 740:	        spin_unlock_irq(&rtc_lock);
 741:	        return 0;
 742:	
 743:	out_busy:
 744:	        spin_unlock_irq(&rtc_lock);
 745:	        return -EBUSY;
 746:	}

◆rtc_release()

linux-3.12.6/drivers/char/rtc.c

 753:	static int rtc_release(struct inode *inode, struct file *file)
 754:	{
...
 766:	        spin_lock_irq(&rtc_lock);
...
 786:	        rtc_status &= ~RTC_IS_OPEN;
 787:	        spin_unlock_irq(&rtc_lock);
 788:	
 789:	        return 0;
 790:	}
rtc_open() で立てた rtc_status の RTC_IS_OPEN ビットは、close() システ ム・コールで呼ばれる rtc_release() ( struct file_operations rtc_fops の .release) で、落とされる。

◆rtc_ioctl()とrtc_do_ioctl()

linux-3.12.6/drivers/char/rtc.c

 718:	static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 719:	{
 720:	        long ret;
 721:	        ret = rtc_do_ioctl(cmd, arg, 0);
 722:	        return ret;
 723:	}

 398:	static int rtc_do_ioctl(unsigned int cmd, unsigned long arg, int kernel)
 399:	{
 400:	        struct rtc_time wtime;
...
 418:	        switch (cmd) {
...
 480:	        case RTC_ALM_READ:      /* Read the present alarm time */
...
 491:	        case RTC_ALM_SET:       /* Store a time into the alarm */
...
 540:	        case RTC_RD_TIME:       /* Read the time/date from RTC  */
 541:	        {
 542:	                memset(&wtime, 0, sizeof(struct rtc_time));
 543:	                rtc_get_rtc_time(&wtime);
 544:	                break;
 545:	        }
 546:	        case RTC_SET_TIME:      /* Set the RTC */
 547:	        {
 548:	                struct rtc_time rtc_tm;
 549:	                unsigned char mon, day, hrs, min, sec, leap_yr;
 550:	                unsigned char save_control, save_freq_select;
 551:	                unsigned int yrs;
...
 559:	                if (copy_from_user(&rtc_tm, (struct rtc_time __user *)arg,
 560:	                                   sizeof(struct rtc_time)))
 561:	                        return -EFAULT;
 562:	
 563:	                yrs = rtc_tm.tm_year + 1900;
 564:	                mon = rtc_tm.tm_mon + 1;   /* tm_mon starts at zero */
 565:	                day = rtc_tm.tm_mday;
 566:	                hrs = rtc_tm.tm_hour;
 567:	                min = rtc_tm.tm_min;
 568:	                sec = rtc_tm.tm_sec;
...
 631:	                CMOS_WRITE(yrs, RTC_YEAR);
 632:	                CMOS_WRITE(mon, RTC_MONTH);
 633:	                CMOS_WRITE(day, RTC_DAY_OF_MONTH);
 634:	                CMOS_WRITE(hrs, RTC_HOURS);
 635:	                CMOS_WRITE(min, RTC_MINUTES);
 636:	                CMOS_WRITE(sec, RTC_SECONDS);
 637:	
 638:	                CMOS_WRITE(save_control, RTC_CONTROL);
 639:	                CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT);
 640:	
 641:	                spin_unlock_irq(&rtc_lock);
 642:	                return 0;
 643:	        }
 711:	        default:
 712:	                return -ENOTTY;
 713:	        }
 714:	        return copy_to_user((void __user *)arg,
 715:	                            &wtime, sizeof wtime) ? -EFAULT : 0;
 716:	}

◆copy_from_user()とcopy_to_user()

デバイス・ドライバが動作するカーネル空間とユーザ空間では、「基本的に」 異なるアドレス空間を持っている。たとえば、同じ 1000 番地でも、別の内容 が含まれていることがある。

カーネル空間とユーザ空間でデータをコピーする時には、次のような特殊な関 数を使う必要がある。

unsigned long
copy_to_user(void __user *to, const void *from, unsigned long n)
unsigned long
copy_from_user(void *to, const void __user *from, unsigned long n)

これらの関数は、コピーの途中でページフォールトが発生した時にもうまくコ ピーできる(ページインの処理でプロセスがスリープすることがある)。また、 引数の番地が有効かどうかをチェックする。

Linux x86 アーキテクチャでは、カーネル空間とユーザ空間が一部重なってい ることがある。この場合、カーネルでmemcpy() を使ったり、直接ポインタを操 作してもユーザ空間がアクセスできてしまうが、それは誤りである。

◆rtc_get_rtc_time()

linux-3.12.6/drivers/char/rtc.c

1295:	static void rtc_get_rtc_time(struct rtc_time *rtc_tm)
1296:	{
1297:	        unsigned long uip_watchdog = jiffies, flags;
1298:	        unsigned char ctrl;
...
1323:	        spin_lock_irqsave(&rtc_lock, flags);
1324:	        rtc_tm->tm_sec = CMOS_READ(RTC_SECONDS);
1325:	        rtc_tm->tm_min = CMOS_READ(RTC_MINUTES);
1326:	        rtc_tm->tm_hour = CMOS_READ(RTC_HOURS);
1327:	        rtc_tm->tm_mday = CMOS_READ(RTC_DAY_OF_MONTH);
1328:	        rtc_tm->tm_mon = CMOS_READ(RTC_MONTH);
1329:	        rtc_tm->tm_year = CMOS_READ(RTC_YEAR);
1330:	        /* Only set from 2.6.16 onwards */
1331:	        rtc_tm->tm_wday = CMOS_READ(RTC_DAY_OF_WEEK);
...
1336:	        ctrl = CMOS_READ(RTC_CONTROL);
1337:	        spin_unlock_irqrestore(&rtc_lock, flags);
1338:	
1339:	        if (!(ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD) {
1340:	                rtc_tm->tm_sec = bcd2bin(rtc_tm->tm_sec);
1341:	                rtc_tm->tm_min = bcd2bin(rtc_tm->tm_min);
1342:	                rtc_tm->tm_hour = bcd2bin(rtc_tm->tm_hour);
1343:	                rtc_tm->tm_mday = bcd2bin(rtc_tm->tm_mday);
1344:	                rtc_tm->tm_mon = bcd2bin(rtc_tm->tm_mon);
1345:	                rtc_tm->tm_year = bcd2bin(rtc_tm->tm_year);
1346:	                rtc_tm->tm_wday = bcd2bin(rtc_tm->tm_wday);
1347:	        }
...
1357:	        rtc_tm->tm_year += epoch - 1900;
1358:	        if (rtc_tm->tm_year <= 69)
1359:	                rtc_tm->tm_year += 100;
1360:	
1361:	        rtc_tm->tm_mon--;
1362:	}

◆入出力の方法

x86 CMOS RTC は、前者。

◆outb()とinb()

Linux における入出力命令を使った入出力は、inb() と outb() で行われる。
outb(unsigned char value, int port)
  ポート番号 port に 1 バイトの value を出力する

unsigned char inb(int port)
  ポート番号 port から 1 バイトの value を入力してその値を返す
1 バイト 8 ビットではなくて 2 バイト 16 ビット 単位のもの (inw(), outw()) や4 バイト 32 ビット単位のもの( inl(), outl() ) もある。

◆CMOS RTCの入出力ポート

x86 CMOS RTC は、ポート番号 0x70 と 0x71 の入出力ポートを使う。
0x70
インデックス用ポート。out のみ
0x71
データ用ポート。in と out
データを読む方法
  1. インデックス用ポートに読みたい日付やデータのインデックス (RTC_SECONDS, RTC_MINUTES, RTC_HOURS, RTC_DAY_OF_MONTH, RTC_MONTH, RTC_YEAR)を out する。
  2. 直後にデータ用ポートらか in する
データを書く方法
  1. インデックス用ポートに読みたい日付やデータのインデックス を out する。
  2. 直後にデータ用ポートにデータを out する

◆rtc_cmos_read()

linux-3.12.6/arch/x86/include/asm/mc146818rtc.h
  12:	#define RTC_PORT(x)     (0x70 + (x))
  13:	#define RTC_ALWAYS_BCD  1       /* RTC operates in binary mode */
...
  93:	#define CMOS_READ(addr) rtc_cmos_read(addr)
  94:	#define CMOS_WRITE(val, addr) rtc_cmos_write(val, addr)

linux-3.12.6/arch/x86/kernel/rtc.c
 115:	/* Routines for accessing the CMOS RAM/RTC. */
 116:	unsigned char rtc_cmos_read(unsigned char addr)
 117:	{
 118:	        unsigned char val;
 119:	
 120:	        lock_cmos_prefix(addr);
 121:	        outb(addr, RTC_PORT(0));
 122:	        val = inb(RTC_PORT(1));
 123:	        lock_cmos_suffix(addr);
 124:	
 125:	        return val;
 126:	}
...
 129:	void rtc_cmos_write(unsigned char val, unsigned char addr)
 130:	{
 131:	        lock_cmos_prefix(addr);
 132:	        outb(addr, RTC_PORT(0));
 133:	        outb(val, RTC_PORT(1));
 134:	        lock_cmos_suffix(addr);
 135:	}

linux-3.12.6/include/linux/mc146818rtc.h
  43:	#define RTC_SECONDS             0
  44:	#define RTC_SECONDS_ALARM       1
  45:	#define RTC_MINUTES             2
  46:	#define RTC_MINUTES_ALARM       3
  47:	#define RTC_HOURS               4
  48:	#define RTC_HOURS_ALARM         5
...
  52:	#define RTC_DAY_OF_WEEK         6
  53:	#define RTC_DAY_OF_MONTH        7
  54:	#define RTC_MONTH               8
  55:	#define RTC_YEAR                9
...
  59:	#define RTC_REG_A               10
  60:	#define RTC_REG_B               11
...
  86:	#define RTC_CONTROL     RTC_REG_B
...
  92:	# define RTC_DM_BINARY 0x04     /* all time/date values are BCD if clear */

◆asm()文

C言語のプログラムの中にアセンブリ言語のプログラムを混ぜて書く方法。
asm ( アセンブラの命令列
 : 出力オペランド(省略可)
 : 入力オペランド(省略可)
 : 破壊するレジスタ(省略可) )
volatile をつけると、コンパイラによる最適化(命令の順番の入れ替え等)を 抑止できる。

◆outb()とinb()の実装

Linux のソースコードの中に outb() や inb() の関数定義を探しても見つからない。 それらは、C言語のプリプロセッサで次のように inline 関数として定義される。
linux-3.12.6/arch/x86/include/asm/io.h

 268:	#define BUILDIO(bwl, bw, type)                                          \
 269:	static inline void out##bwl(unsigned type value, int port)              \
 270:	{                                                                       \
 271:	        asm volatile("out" #bwl " %" #bw "0, %w1"                       \
 272:	                     : : "a"(value), "Nd"(port));                       \
 273:	}                                                                       \
 274:	                                                                        \
 275:	static inline unsigned type in##bwl(int port)                           \
 276:	{                                                                       \
 277:	        unsigned type value;                                            \
 278:	        asm volatile("in" #bwl " %w1, %" #bw "0"                        \
 279:	                     : "=a"(value) : "Nd"(port));                       \
 280:	        return value;                                                   \
 281:	}                                                                       \
...
 308:	BUILDIO(b, b, char)
 309:	BUILDIO(w, w, short)
 310:	BUILDIO(l, , int)
Cプリプロセッサを通し、文字列の結合を行うと、次のようになる。
static inline unsigned char inb(int port) {
    unsigned char value;
    asm volatile("inb %w1, %b0" : "=a"(value) : "Nd"(port));
    return value;
}

static inline void outb(unsigned char value, int port) {
    asm volatile("outb %b0, %w1" : : "a"(value), "Nd"(port));
}

◆/proc/ioports

/proc/ioports を見ると、使われている入出力ポートがわかる。
$ cat /proc/ioports [←]
0000-001f : dma1
0020-0021 : pic1
0040-0043 : timer0
0050-0053 : timer1
0060-0060 : keyboard
0064-0064 : keyboard
0070-0077 : rtc
0080-008f : dma page reg
...
ff80-ff9f : 0000:00:1d.0
  ff80-ff9f : uhci_hcd
$ []

◆BCD(Binary Coded Decimal)

BCD では、10進数1桁を4ビット、10進数n桁をn*4ビットで表現する。たとえば、 12(10進2桁) は、8ビットの数0x12 (バイナリなら 16+2==18)で表現する。
意味 バイナリ(16進表記,%x) 標準的なバイナリの意味(10進表記,%d)
0 0x00 0
1 0x01 1
2 0x02 2
3 0x03 3
4 0x04 4
5 0x05 5
6 0x06 6
7 0x07 7
8 0x08 8
9 0x09 9
10 0x10 16
11 0x11 17
12 0x12 18
13 0x13 19
14 0x14 20
15 0x15 21
16 0x16 22
17 0x17 23
18 0x18 24
19 0x19 25
20 0x20 32
30 0x30 48
40 0x40 64
50 0x50 80
60 0x60 96
70 0x70 112
80 0x80 128
90 0x90 144
99 0x99 153
100 0x100 256

◆bcd2bin()

bcd2bin() は、BCD で表現された数(0から99まで)を普通のバイナリに変換する。 bin2bcd() は、その逆を行う。
linux-3.6.8/lib/bcd.c
   1:	#include <linux/bcd.h>
   2:	#include <linux/export.h>
   3:	
   4:	unsigned bcd2bin(unsigned char val)
   5:	{
   6:	        return (val & 0x0f) + (val >> 4) * 10;
   7:	}
   8:	EXPORT_SYMBOL(bcd2bin);
   9:	
  10:	unsigned char bin2bcd(unsigned val)
  11:	{
  12:	        return ((val / 10) << 4) + val % 10;
  13:	}
  14:	EXPORT_SYMBOL(bin2bcd);

■割り込み

◆割り込みの必要性

◆割り込みとは

◆x86の割り込みコントローラ

◆Intel 8259 PIC

8259、8259、CPU

図? x86 の Intel 8259

◆APIC

Local APIC、Core、I/O APIC

図? x86 の APIC

APIC は、次のような割り込み信号を受け取る。

◆例外

例外(exceptions) とは、ハードウェア・デバイスとは無関係に、 CPU の命令実行の途中で生じる。

例:

多くの CPU アーキテクチャでは、例外を割り込みと同じように扱う。 x86 では、システム・コールの処理も、割り込みと同じように扱う。

◆割り込みハンドラ

割り込みハンドラ(interrupt handlers)とは、割り込みが生じた時にそれに応 答するために実行される関数。割り込みサービスルーチン(interrupt service routine, ISR)とも呼ばれる。

例:

Linux では、割り込みハンドラは、C言語の関数。普通の関数との違い。

◆割り込み記述子テーブル

割り込みハンドラは、割り込み記述子テーブル(interrupt descriptor tables)や 割り込みベクタテーブル(interrupt vector table)と呼ばれる表に登録されて いる。(CPUの違い、同じCPU x86でもモードの違いで、テーブルの名前が異なる。)

割り込み番号、割り込み記述子テーブル、割り込みハンドラ

図? 割り込み記述子テーブルと割り込みハンドラ

◆割り込みの前半部分と後半部分

次の2つを両立させたい Linux では、これを両立するために、割り込みの処理を2つに分ける。 例: ネットワークカードによるメッセージの受信

◆割り込み番号の共有

割り込み信号線が足りない時に、1つの割り込み番号を、複数のデバイスで共有 することがある。

デバイス5個、OR回路、PIC、CPU

図? PICの線が不足した時の対応

■Linuxにおける割り込みハンドラの登録

Linux で、割り込みの前半部を登録するには、request_irq() を使う。 登録を解除するには、free_irq() を使う。

◆request_irq()

CPUアーキテクチャに独立した形で割り込みハンドラを登録するには、 request_irq() を用いる。
include/linux/interrupt.h

typedef irqreturn_t (*irq_handler_t)(int, void *);

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
            const char *name, void *dev)
主なフラグ
IRQF_DISABLED
割り込みハンドラの実行中、「全ての」割り込みを禁止する。 このフラグがセットされていなければ、自分自身だけ禁止され、 他の割り込みは許可された状態で動作する。
IRQF_SAMPLE_RANDOM
乱数生成器のために割り込みを利用してもよい。
IRQF_TIMER
システム・タイマのために利用する。
IRQF_SHARED
複数のデバイスで同じ割り込み番号を共有できる。

◆free_irq()

割り込みハンドラが不要になった時には、free_irq() で取り除く。
void free_irq(unsigned int, void *dev)

◆irq_handler_t handler

割り込みハンドラは、次のような関数である。
irqreturn_t irq_handler_t(int irq, void *dev)
IRQ_NONE
割り込みは、自分自身のものではなかった。
IRQ_HANDLED
割り込みをきちんと処理した。
全割り込みハンドラが IRQ_NONE を返せば、なんらかのトラブルを意味する。 複数の割り込みハンドラが IRQ_HANDLED を返すことは正常。

◆/proc/interrupts

/proc/interrupts は、割り込みの回数を保持している。
$ cat /proc/interrupts  [←]
           CPU0       CPU1       
  0:    4208761      38584    IO-APIC-edge  timer
  1:          0          3    IO-APIC-edge  i8042
  7:          0          0    IO-APIC-edge  parport0
  8:          1          2    IO-APIC-edge  rtc
  9:          0          0   IO-APIC-level  acpi
 12:          3          1    IO-APIC-edge  i8042
 50:       5380      86508         PCI-MSI  ahci
 74:        346          0         PCI-MSI  HDA Intel
 98:        294      28232         PCI-MSI  eth1
169:        130      57006   IO-APIC-level  uhci_hcd:usb3
177:          0          0   IO-APIC-level  uhci_hcd:usb4, uhci_hcd:usb7
217:        358     149530   IO-APIC-level  ehci_hcd:usb1, uhci_hcd:usb5
225:          0          0   IO-APIC-level  ehci_hcd:usb2, uhci_hcd:usb6
233:          0          0   IO-APIC-level  uhci_hcd:usb8
NMI:          0          0 
LOC:    4246864    4246863 
ERR:          0
MIS:          0
$ []

◆x86 CMOS Real-Time Clock rtc_interrupt()

x86 CMOS Real-Time Clock での割り込みハンドラの例。
linux-3.12.6/drivers/char/rtc.c

  96:	static unsigned long rtc_port;
  97:	static int rtc_irq;
...
 191:	static unsigned long rtc_status;        /* bitmapped status byte.       */
 192:	static unsigned long rtc_freq;          /* Current periodic IRQ rate    */
 193:	static unsigned long rtc_irq_data;      /* our output to the world      */
...
 953:	static int __init rtc_init(void)
 954:	{
...
1000:	        if (request_irq(rtc_irq, rtc_interrupt, IRQF_SHARED, "rtc",
1001:	                        (void *)&rtc_port)) {
1002:	                rtc_has_irq = 0;
1003:	                printk(KERN_ERR "rtc: cannot register IRQ %d\n", rtc_irq);
1004:	                return -EIO;
1005:	        }
...
1137:	}

 239:	static irqreturn_t rtc_interrupt(int irq, void *dev_id)
 240:	{
...
 248:	        spin_lock(&rtc_lock);
 249:	        rtc_irq_data += 0x100;
 250:	        rtc_irq_data &= ~0xff;
...
 259:	                rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);
...
 262:	        if (rtc_status & RTC_TIMER_ON)
 263:	                mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);
 264:	
 265:	        spin_unlock(&rtc_lock);
...
 268:	        spin_lock(&rtc_task_lock);
 269:	        if (rtc_callback)
 270:	                rtc_callback->func(rtc_callback->private_data);
 271:	        spin_unlock(&rtc_task_lock);
...
 276:	        return IRQ_HANDLED;
 277:	}

■Linuxにおける割り込みハンドラの実行

ハードウェア依存のコードからハードウェア独立のコードを呼び出す。

◆x86 Interrupt Descriptor Table (IDT)

x86 IDT は、ハードウェア・レベルの割り込みハンドラの一覧表を保持する。 その先頭番地は、IDTR レジスタに保存される。
linux-3.12.6/arch/x86/include/asm/irq_vectors.h
  51:	# define SYSCALL_VECTOR                 0x80
...
 127:	#define NR_VECTORS                       256

linux-3.12.6/arch/x86/kernel/traps.c
  78:	gate_desc idt_table[NR_VECTORS] __page_aligned_bss;
...
 728:	void __init trap_init(void)
 729:	{
...
 740:	        set_intr_gate(X86_TRAP_DE, &divide_error);
 741:	        set_intr_gate_ist(X86_TRAP_NMI, &nmi, NMI_STACK);
...
 775:	        set_system_trap_gate(SYSCALL_VECTOR, &system_call);
 799:	}

◆x86 do_IRQ

x86 Linux では、Interrupt Descriptor Table に従い、do_IRQ() が呼ばれる。 引数には、x86 のレジスタを表現した構造体へのポインタが渡される。これに より、割り込みが発生した時のレジスタの値がわかる。割り込みハンドラが有 効で、割り込み番号に登録されていれば、最終的には、handle_IRQ_event() と いう、CPU とは独立の割り込みハンドラが呼ばれる。

◆handle_IRQ_event()

linux-3.12.6/kernel/irq/handle.c

 182:	irqreturn_t handle_irq_event(struct irq_desc *desc)
 183:	{
 184:	        struct irqaction *action = desc->action;
 185:	        irqreturn_t ret;
 186:	
 187:	        desc->istate &= ~IRQS_PENDING;
 188:	        irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
...
 191:	        ret = handle_irq_event_percpu(desc, action);
...
 194:	        irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
 195:	        return ret;
 196:	}
linux-3.12.6/kernel/irq/handle.c

 132:	irqreturn_t
 133:	handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
 134:	{
 135:	        irqreturn_t retval = IRQ_NONE;
 136:	        unsigned int flags = 0, irq = desc->irq_data.irq;
 137:	
 138:	        do {
 139:	                irqreturn_t res;
...
 142:	                res = action->handler(irq, action->dev_id);
...
 149:	                switch (res) {
...
 163:	                case IRQ_HANDLED:
 164:	                        flags |= action->flags;
 165:	                        break;
 166:	
 167:	                default:
 168:	                        break;
 169:	                }
 170:	
 171:	                retval |= res;
 172:	                action = action->next;
 173:	        } while (action);
...
 179:	        return retval;
 180:	}

■割り込みハンドラのプログラミングの注意点

◆割り込みコンテキスト

割り込みハンドラが実行されるのは、「割り込みコンテキスト」。 普通のシステム・コールの処理が実行される「プロセス・コンテキスト」とは 異なる。

プロセス・コンテキストでできること。

割り込みコンテキストでは、このうようなことはできない。 速やかに終了すべきである。busy loop はできるが、あまり やらない方がよい。他の割り込みは、実行される可能性もある。

◆割り込みの許可・禁止

マクロをつかって保存したり回復したりする。
unsigned long flags;

local_irq_save(flags); /* 割り込み禁止 */
...
local_irq_restore(flags); /* 割り込み許可 (save の時の状態にもどる) */
単一CPUの x86 では、cli() と sti() で割り込みの禁止と許可を設定する方法 があった。それそれ同名の CPU の命令を実行して、全ての割り込みを禁止/許 可する。マルチプロセッサ(マルチコア含む)では、1つのCPU で割り込みを禁止 しても、他の CPU では許可されていることがあるので、cli()/sti() の方法は 使えない。

特定の割り込み番号の割り込みを禁止する方法もある。

void disable_irq(unsigned ing irq);
    // 全CPUの割り込みを禁止する

void disable_irq_nosync(unsigned ing irq);
    // 同上。ただし、割り込みハンドラの終了を待たない。

void enable_irq(unsigned ing irq);
    // 割り込みを許可する。

void synchronize_irq(unsigned ing irq);
    // 割り込みハンドラの終了を待つ。

■GNU global

大きなソースコードを読むには、grepやgrep -r よりも GNU GLOBAL が便利。
gtags
ソースコードを解析して索引ファイル (GTAGS, GRTAGS, GPATH という名前) を作る。一度だけやれば十分。
global id
引数で与えられた id を定義しているファイルの名前を表示する。
global -x id
引数で与えられた id を定義しているファイルの名前とその行を表示する。
global -r id
引数で与えられた id を利用しているファイルの名前を表示する。
global -rx id
引数で与えられた id を利用しているファイルの名前とその行を表示する。
実行例
$ pwd [←]
/home/prof/yas/coins/os2-2013/linux-3.12.6
$ ls -l G* [←]
-rw-r--r--  1 yas  prof    9551872  1 15 22:10 GPATH
-rw-r--r--  1 yas  prof  310272000  1 15 22:10 GRTAGS
-rw-r--r--  1 yas  prof  121479168  1 15 22:10 GTAGS
$ global file_operations [←]
include/linux/fs.h
$ global -x file_operations [←]
file_operations  1526 include/linux/fs.h struct file_operations {
$ global -xr file_operations|head -3 [←]
file_operations   138 arch/alpha/kernel/srm_env.c static const struct file_operations srm_env_proc_fops = {
file_operations    42 arch/arc/kernel/arc_hostlink.c static const struct file_operations arc_hl_fops = {
file_operations   299 arch/arc/kernel/troubleshoot.c static const struct file_operations tlb_stats_file_ops = {
$ []

■課題3 デバイスドライバと割り込み処理、inb()とoutb()

★問題(301) CMOS RTC のデバイスドライバ

rtc-read-time.cのプログラムでシステム・コー ルが実行されると、CMOS RTC のデバイス・ドライバに含まれる関数が実行され る。次のシステム・コールが実行された時に、どんな関数が実行されるかを答 えなさい。 drivers/char/rtc.c に含まれる関数の名前で答えなさい。

★問題(302) copy_to_user()とmemcpy()

関数 rtc_do_ioctl() は、copy_to_user() を呼び出している。これは正しいプログラムであるが、も しも copy_to_user() を memcpy() やポインタを操作して直接ユーザ空間のメ モリにアクセスすると問題がある。714 行目から 715 行目を、memcpy() を使っ て「間違ったプログラム」に書き換えなさい。ただし、copy_to_user() は必ず 成功するものとして、エラー処理を省略しなさい。以下の「/*空欄*/」を埋め なさい。
     memcpy( /*空欄(a)*/,/*空欄(b)*/,/*空欄(c)*/ );
     return 0;
なお、memcpy() のインタフェースは、次のようになっている。
void * memcpy(void *destination, const void *source, size_t len);
sourceは、コピー元、destination は、コピー先、len は長さ(バイト数)であ る。結果として destination を返す。

C言語の 3 項演算子(?と:)は、次のような意味である。

    条件 ? 式1 : 式2
条件」が成り立つ(非0)なら、「式1」の値、成り立たな ければ、「式2」の値になる。この課題では、「間違ったプログラム」 を書く課題であり、return 0;と常に 0 (成功) を返すようにしている。

★問題(303) x86 CMOS RTCからの時間データの入力

次のプログラムは、x86 CMOS RTC ハードウェアから時間(hours)データを読み 出し変数hh に入れるプログラムの一部である。/*空欄(a)*/と/*空欄(b)*/を埋 めて完成させなさい。
    unsigned char hh;
    outb( /*空欄(a)*/, 0x70 );
    hh = inb( /*空欄(b)*/ );

★問題(304) 割り込みの利用

キーボード、マウス、ネットワーク・カード等のデバイスからの入力では割り 込みが多く使われている。割り込みを使う方法と割り込みを使わない方法を対 比して、割り込みを使う方法の利点を説明しなさい。

★問題(305) x86 CMOS Real-Time Clock の割り込みハンドラ

x86 CMOS Real-Time Clock の割り込みハンドラを関数名で答えなさい。 その関数の引数と結果を、簡単に説明しなさい。
Last updated: 2014/01/15 22:17:16
Yasushi Shinjo / <yas@cs.tsukuba.ac.jp>