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

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

                                       筑波大学 システム情報系 
                                       新城 靖
                                       <yas@cs.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/os2-2018/2019-01-25
あるいは、次のページから手繰っていくこともできます。
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-4.20/include/linux/fs.h
1777:	struct file_operations {
1778:	        struct module *owner;
1779:	        loff_t (*llseek) (struct file *, loff_t, int);
1780:	        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
1781:	        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
1782:	        ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
1783:	        ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
1784:	        int (*iterate) (struct file *, struct dir_context *);
1785:	        int (*iterate_shared) (struct file *, struct dir_context *);
1786:	        __poll_t (*poll) (struct file *, struct poll_table_struct *);
1787:	        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
1788:	        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
1789:	        int (*mmap) (struct file *, struct vm_area_struct *);
1790:	        unsigned long mmap_supported_flags;
1791:	        int (*open) (struct inode *, struct file *);
1792:	        int (*flush) (struct file *, fl_owner_t id);
1793:	        int (*release) (struct inode *, struct file *);
1794:	        int (*fsync) (struct file *, loff_t, loff_t, int datasync);
1795:	        int (*fasync) (int, struct file *, int);
1796:	        int (*lock) (struct file *, int, struct file_lock *);
1797:	        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
1798:	        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
1799:	        int (*check_flags)(int);
1800:	        int (*flock) (struct file *, int, struct file_lock *);
1801:	        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
1802:	        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
1803:	        int (*setlease)(struct file *, long, struct file_lock **, void **);
1804:	        long (*fallocate)(struct file *file, int mode, loff_t offset,
1805:	                          loff_t len);
1806:	        void (*show_fdinfo)(struct seq_file *m, struct file *f);
1807:	#ifndef CONFIG_MMU
1808:	        unsigned (*mmap_capabilities)(struct file *);
1809:	#endif
1810:	        ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
1811:	                        loff_t, size_t, unsigned int);
1812:	        loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
1813:	                                   struct file *file_out, loff_t pos_out,
1814:	                                   loff_t len, unsigned int remap_flags);
1815:	        int (*fadvise)(struct file *, loff_t, loff_t, int);
1816:	} __randomize_layout;
主な手続きの意味

◆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引数は、コマンドにより 使われないことともある。

◆alloc_chrdev_region()

デバイスドライバで動的にメジャー番号とマイナー番号を割り割り当てるには、 alloc_chrdev_region() が使える。

◆device_create()

デバイスドライバで、mknod コマンドをを使わず/dev/ の下にデバイス・ファ イルを作成するには、device_create() が使える。(/dev の他に、/sys 以下に もディレクトリが作られる。)

■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 [←]
Tue Jan 22 21:39:16 JST 2019
# hwclock --show [←]
Tue Jan 22 21:39:19 2019  -0.961309 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:	}
実行例
$ make rtc-read-time [←]
cc     rtc-read-time.c   -o rtc-read-time
$ su [←]
Password: 
# ./rtc-read-time [←]
2019-01-22 21:42:54
# ./rtc-read-time ; hwclock --show; date [←]
2019-01-22 21:42:55
Tue Jan 22 21:42:56 2019  -0.203576 seconds
Tue Jan 22 21:42:56 JST 2019
# ./rtc-read-time ; hwclock --show; date [←]
2019-01-22 21:42:59
Tue Jan 22 21:43:00 2019  -0.672310 seconds
Tue Jan 22 21:43:00 JST 2019
# []
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-4.20/drivers/char/rtc.c
 803:	static const struct file_operations rtc_fops = {
 804:	        .owner          = THIS_MODULE,
 805:	        .llseek         = no_llseek,
 806:	        .read           = rtc_read,
 807:	#ifdef RTC_IRQ
 808:	        .poll           = rtc_poll,
 809:	#endif
 810:	        .unlocked_ioctl = rtc_ioctl,
 811:	        .open           = rtc_open,
 812:	        .release        = rtc_release,
 813:	        .fasync         = rtc_fasync,
 814:	};
 815:	
 816:	static struct miscdevice rtc_dev = {
 817:	        .minor          = RTC_MINOR,
 818:	        .name           = "rtc",
 819:	        .fops           = &rtc_fops,
 820:	};

 847:	static int __init rtc_init(void)
 848:	{
...
 951:	        if (misc_register(&rtc_dev)) {
...
 958:	                return -ENODEV;
 959:	        }
...
1025:	        (void) init_sysctl();
1026:	
1027:	        printk(KERN_INFO "Real Time Clock Driver v" RTC_VERSION "\n");
1028:	
1029:	        return 0;
1030:	}

linux-4.20/include/uapi/linux/major.h
  10:	#define UNNAMED_MAJOR           0
  11:	#define MEM_MAJOR               1
  12:	#define RAMDISK_MAJOR           1
  13:	#define FLOPPY_MAJOR            2
  14:	#define PTY_MASTER_MAJOR        2
  15:	#define IDE0_MAJOR              3
  16:	#define HD_MAJOR                IDE0_MAJOR
  17:	#define PTY_SLAVE_MAJOR         3
  18:	#define TTY_MAJOR               4
  19:	#define TTYAUX_MAJOR            5
  20:	#define LP_MAJOR                6
  21:	#define VCS_MAJOR               7
  22:	#define LOOP_MAJOR              7
  23:	#define SCSI_DISK0_MAJOR        8
  24:	#define SCSI_TAPE_MAJOR         9
  25:	#define MD_MAJOR                9
  26:	#define MISC_MAJOR              10
...

linux-4.20/include/linux/miscdevice.h
  15:	#define PSMOUSE_MINOR           1
  16:	#define MS_BUSMOUSE_MINOR       2       /* unused */
...
  27:	#define RTC_MINOR               135
...
  59:	#define VHOST_VSOCK_MINOR       241
  60:	#define MISC_DYNAMIC_MINOR      255
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
# []
2019年 coins で動いている Linux では、別のドライバが動いている。 ソースは、drivers/rtc/rtc-cmos.c にある。
# uname -a [←]
Linux pentas-compc 2.6.32-504.12.2.el6.x86_64 #1 SMP Wed Mar 11 22:03:14 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
# dmesg | grep Real [←]
# dmesg | grep cmos [←]
rtc_cmos 00:04: RTC can wake from S4
rtc_cmos 00:04: rtc core: registered rtc_cmos as rtc0
rtc_cmos 00:04: setting system clock to 2018-10-29 09:36:36 UTC (1540805796)
# []

◆rtc_open()

linux-4.20/drivers/char/rtc.c

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

 717:	static int rtc_open(struct inode *inode, struct file *file)
 718:	{
 719:	        spin_lock_irq(&rtc_lock);
 720:	
 721:	        if (rtc_status & RTC_IS_OPEN)
 722:	                goto out_busy;
 723:	
 724:	        rtc_status |= RTC_IS_OPEN;
...
 727:	        spin_unlock_irq(&rtc_lock);
 728:	        return 0;
 729:	
 730:	out_busy:
 731:	        spin_unlock_irq(&rtc_lock);
 732:	        return -EBUSY;
 733:	}

◆rtc_release()

linux-4.20/drivers/char/rtc.c
 740:	static int rtc_release(struct inode *inode, struct file *file)
 741:	{
...
 770:	
 771:	        spin_lock_irq(&rtc_lock);
...
 773:	        rtc_status &= ~RTC_IS_OPEN;
 774:	        spin_unlock_irq(&rtc_lock);
 775:	
 776:	        return 0;
 777:	}
rtc_open() で立てた rtc_status の RTC_IS_OPEN ビットは、close() システ ム・コールで呼ばれる rtc_release() ( struct file_operations rtc_fops の .release) で、落とされる。

◆rtc_ioctl()とrtc_do_ioctl()

linux-4.20/drivers/char/rtc.c
 705:	static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 706:	{
 707:	        long ret;
 708:	        ret = rtc_do_ioctl(cmd, arg, 0);
 709:	        return ret;
 710:	}

 385:	static int rtc_do_ioctl(unsigned int cmd, unsigned long arg, int kernel)
 386:	{
 387:	        struct rtc_time wtime;
 388:	
...
 405:	        switch (cmd) {
...
 467:	        case RTC_ALM_READ:      /* Read the present alarm time */
...
 478:	        case RTC_ALM_SET:       /* Store a time into the alarm */
...
 527:	        case RTC_RD_TIME:       /* Read the time/date from RTC  */
 528:	        {
 529:	                memset(&wtime, 0, sizeof(struct rtc_time));
 530:	                rtc_get_rtc_time(&wtime);
 531:	                break;
 532:	        }
 533:	        case RTC_SET_TIME:      /* Set the RTC */
 534:	        {
 535:	                struct rtc_time rtc_tm;
 536:	                unsigned char mon, day, hrs, min, sec, leap_yr;
 537:	                unsigned char save_control, save_freq_select;
 538:	                unsigned int yrs;
...
 546:	                if (copy_from_user(&rtc_tm, (struct rtc_time __user *)arg,
 547:	                                   sizeof(struct rtc_time)))
 548:	                        return -EFAULT;
 549:	
 550:	                yrs = rtc_tm.tm_year + 1900;
 551:	                mon = rtc_tm.tm_mon + 1;   /* tm_mon starts at zero */
 552:	                day = rtc_tm.tm_mday;
 553:	                hrs = rtc_tm.tm_hour;
 554:	                min = rtc_tm.tm_min;
 555:	                sec = rtc_tm.tm_sec;
 556:	
 557:	                if (yrs < 1970)
 558:	                        return -EINVAL;
 559:	
 560:	                leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400));
 561:	
 562:	                if ((mon > 12) || (day == 0))
 563:	                        return -EINVAL;
 564:	
 565:	                if (day > (days_in_mo[mon] + ((mon == 2) && leap_yr)))
 566:	                        return -EINVAL;
 567:	
 568:	                if ((hrs >= 24) || (min >= 60) || (sec >= 60))
 569:	                        return -EINVAL;
 570:	
 571:	                yrs -= epoch;
 572:	                if (yrs > 255)          /* They are unsigned */
 573:	                        return -EINVAL;
 574:	
 575:	                spin_lock_irq(&rtc_lock);
...
 618:	                CMOS_WRITE(yrs, RTC_YEAR);
 619:	                CMOS_WRITE(mon, RTC_MONTH);
 620:	                CMOS_WRITE(day, RTC_DAY_OF_MONTH);
 621:	                CMOS_WRITE(hrs, RTC_HOURS);
 622:	                CMOS_WRITE(min, RTC_MINUTES);
 623:	                CMOS_WRITE(sec, RTC_SECONDS);
...
 628:	                spin_unlock_irq(&rtc_lock);
 629:	                return 0;
 630:	        }
...
 698:	        default:
 699:	                return -ENOTTY;
 700:	        }
 701:	        return copy_to_user((void __user *)arg,
 702:	                            &wtime, sizeof wtime) ? -EFAULT : 0;
 703:	}

◆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-4.20/drivers/char/rtc.c
1183:	static void rtc_get_rtc_time(struct rtc_time *rtc_tm)
1184:	{
...
1211:	        spin_lock_irqsave(&rtc_lock, flags);
1212:	        rtc_tm->tm_sec = CMOS_READ(RTC_SECONDS);
1213:	        rtc_tm->tm_min = CMOS_READ(RTC_MINUTES);
1214:	        rtc_tm->tm_hour = CMOS_READ(RTC_HOURS);
1215:	        rtc_tm->tm_mday = CMOS_READ(RTC_DAY_OF_MONTH);
1216:	        rtc_tm->tm_mon = CMOS_READ(RTC_MONTH);
1217:	        rtc_tm->tm_year = CMOS_READ(RTC_YEAR);
1218:	        /* Only set from 2.6.16 onwards */
1219:	        rtc_tm->tm_wday = CMOS_READ(RTC_DAY_OF_WEEK);
...
1245:	        rtc_tm->tm_year += epoch - 1900;
1246:	        if (rtc_tm->tm_year <= 69)
1247:	                rtc_tm->tm_year += 100;
1248:	
1249:	        rtc_tm->tm_mon--;
1250:	}

◆入出力の方法

x86 CMOS RTC は、前者。

◆outb()とinb()

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

unsigned char inb(unsigned short 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-4.20/arch/x86/include/asm/mc146818rtc.h
  12:	#define RTC_PORT(x)     (0x70 + (x))
...
  93:	#define CMOS_READ(addr) rtc_cmos_read(addr)
  94:	#define CMOS_WRITE(val, addr) rtc_cmos_write(val, addr)

linux-4.20/arch/x86/kernel/rtc.c
 126:	unsigned char rtc_cmos_read(unsigned char addr)
 127:	{
 128:	        unsigned char val;
 129:	
 130:	        lock_cmos_prefix(addr);
 131:	        outb(addr, RTC_PORT(0));
 132:	        val = inb(RTC_PORT(1));
 133:	        lock_cmos_suffix(addr);
 134:	
 135:	        return val;
 136:	}
...
 139:	void rtc_cmos_write(unsigned char val, unsigned char addr)
 140:	{
 141:	        lock_cmos_prefix(addr);
 142:	        outb(addr, RTC_PORT(0));
 143:	        outb(val, RTC_PORT(1));
 144:	        lock_cmos_suffix(addr);
 145:	}

linux-4.20/include/linux/mc146818rtc.h
  50:	#define RTC_SECONDS             0
  51:	#define RTC_SECONDS_ALARM       1
  52:	#define RTC_MINUTES             2
  53:	#define RTC_MINUTES_ALARM       3
  54:	#define RTC_HOURS               4
  55:	#define RTC_HOURS_ALARM         5
...
  59:	#define RTC_DAY_OF_WEEK         6
  60:	#define RTC_DAY_OF_MONTH        7
  61:	#define RTC_MONTH               8
  62:	#define RTC_YEAR                9
...
  66:	#define RTC_REG_A               10
  67:	#define RTC_REG_B               11
  68:	#define RTC_REG_C               12
...
 104:	#define RTC_INTR_FLAGS  RTC_REG_C

◆outb()とinb()の実装(インライン関数)

次のファイルにある定義は、読みやすい。
linux-4.20/arch/x86/boot/boot.h
  43:	static inline void outb(u8 v, u16 port)
  44:	{
  45:	        asm volatile("outb %0,%1" : : "a" (v), "dN" (port));
  46:	}
  47:	static inline u8 inb(u16 port)
  48:	{
  49:	        u8 v;
  50:	        asm volatile("inb %1,%0" : "=a" (v) : "dN" (port));
  51:	        return v;
  52:	}

◆asm()文

C言語のプログラムの中にアセンブリ言語のプログラムを混ぜて書く方法。
asm ( "アセンブラの命令列"
 : 出力オペランド(省略可)
 : 入力オペランド(省略可)
 : 破壊するレジスタ(省略可) )

◆outb()とinb()の実装(マクロ)

次の定義を使うこともある。C言語のプリプロセッサで次のように組み立てられ るinline 関数として定義されている。
linux-4.20/arch/x86/include/asm/io.h
 274:	#define BUILDIO(bwl, bw, type)                                          \
 275:	static inline void out##bwl(unsigned type value, int port)              \
 276:	{                                                                       \
 277:	        asm volatile("out" #bwl " %" #bw "0, %w1"                       \
 278:	                     : : "a"(value), "Nd"(port));                       \
 279:	}                                                                       \
 280:	                                                                        \
 281:	static inline unsigned type in##bwl(int port)                           \
 282:	{                                                                       \
 283:	        unsigned type value;                                            \
 284:	        asm volatile("in" #bwl " %w1, %" #bw "0"                        \
 285:	                     : "=a"(value) : "Nd"(port));                       \
 286:	        return value;                                                   \
 287:	}                                                                       \
...
 334:	BUILDIO(b, b, char)
 335:	BUILDIO(w, w, short)
 336:	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
$ []

■割り込み

◆割り込みの必要性

◆割り込みとは

◆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 handler(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-4.20/include/linux/rtc.h
 105:	static int rtc_has_irq = 1;

 135:	static DECLARE_WAIT_QUEUE_HEAD(rtc_wait);

 847:	static int __init rtc_init(void)
 848:	{
...
 862:	        irq_handler_t rtc_int_handler_ptr;
...
 915:	                rtc_has_irq = 0;
...
 934:	                rtc_int_handler_ptr = rtc_interrupt;
...
 937:	        if (request_irq(RTC_IRQ, rtc_int_handler_ptr, 0, "rtc", NULL)) {
 938:	                /* Yeah right, seeing as irq 8 doesn't even hit the bus. */
 939:	                rtc_has_irq = 0;
 940:	                printk(KERN_ERR "rtc: IRQ %d is not free.\n", RTC_IRQ);
 941:	                rtc_release_region();
 942:	
 943:	                return -EIO;
 944:	        }
...
1029:	        return 0;
1030:	}

 231:	static irqreturn_t rtc_interrupt(int irq, void *dev_id)
 232:	{
 240:	        spin_lock(&rtc_lock);
 241:	        rtc_irq_data += 0x100;
 242:	        rtc_irq_data &= ~0xff;
...
 251:	                rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);
..
 257:	        spin_unlock(&rtc_lock);
 258:	
 259:	        wake_up_interruptible(&rtc_wait);
 260:	
...
 263:	        return IRQ_HANDLED;
 264:	}

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

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

◆x86 Interrupt Descriptor Table (IDT)

x86 IDT は、ハードウェア・レベルの割り込みハンドラの一覧表を保持する。 その先頭番地は、IDTR レジスタに保存される。
linux-4.20/arch/x86/kernel/traps.c
 929:	void __init trap_init(void)
 930:	{
...
 934:	        idt_setup_traps();
...
 955:	}

linux-4.20/arch/x86/include/asm/irq_vectors.h
  45:	#define IA32_SYSCALL_VECTOR             0x80

 109:	#define NR_VECTORS                       256

linux-4.20/arch/x86/kernel/idt.c
 171:	gate_desc idt_table[IDT_ENTRIES] __page_aligned_bss;

  74:	static const __initconst struct idt_data def_idts[] = {
  75:	        INTG(X86_TRAP_DE,               divide_error),
  76:	        INTG(X86_TRAP_NMI,              nmi),
  77:	        INTG(X86_TRAP_BR,               bounds),
  78:	        INTG(X86_TRAP_UD,               invalid_op),
  79:	        INTG(X86_TRAP_NM,               device_not_available),
...
 105:	        SYSG(IA32_SYSCALL_VECTOR,       entry_INT80_32),
 107:	};

 265:	void __init idt_setup_traps(void)
 266:	{
 267:	        idt_setup_from_table(idt_table, def_idts, ARRAY_SIZE(def_idts), true);
 268:	}

linux-4.20/arch/x86/entry/entry_32.S
1228:	ENTRY(divide_error)
1229:	        ASM_CLAC
1230:	        pushl   $0                              # no error code
1231:	        pushl   $do_divide_error
1232:	        jmp     common_exception
1233:	END(divide_error)

1410:	ENTRY(nmi)
...
1435:	        call    do_nmi

 989:	ENTRY(entry_INT80_32)
...
1002:	        call    do_int80_syscall_32
...
1026:	        INTERRUPT_RETURN

linux-4.20/arch/x86/include/asm/irqflags.h
 151:	#define INTERRUPT_RETURN                iret

linux-4.20/arch/x86/entry/common.c
 345:	__visible void do_int80_syscall_32(struct pt_regs *regs)
 346:	{
...
 349:	        do_syscall_32_irqs_on(regs);
 350:	}

 304:	static __always_inline void do_syscall_32_irqs_on(struct pt_regs *regs)
 305:	{
...
 307:	        unsigned int nr = (unsigned int)regs->orig_ax;
...
 323:	        if (likely(nr < IA32_NR_syscalls)) {
 324:	                nr = array_index_nospec(nr, IA32_NR_syscalls);
 334:	                regs->ax = ia32_sys_call_table[nr](
 335:	                        (unsigned int)regs->bx, (unsigned int)regs->cx,
 336:	                        (unsigned int)regs->dx, (unsigned int)regs->si,
 337:	                        (unsigned int)regs->di, (unsigned int)regs->bp);
...
 339:	        }
...
 342:	}

linux-4.20/arch/x86/include/asm/syscall.h
  33:	#define ia32_sys_call_table sys_call_table

◆x86 do_IRQ

x86 Linux では、Interrupt Descriptor Table に従い、 irq_entries_start() が呼ばれる。 それは、common_interrupt を経由して、 do_IRQ() を呼ぶ。

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

◆handle_IRQ_event()

linux-4.20/kernel/irq/handle.c
 198:	irqreturn_t handle_irq_event(struct irq_desc *desc)
 199:	{
 200:	        irqreturn_t ret;
...
 206:	        ret = handle_irq_event_percpu(desc);
...
 211:	}

 184:	irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
 185:	{
 186:	        irqreturn_t retval;
...
 189:	        retval = __handle_irq_event_percpu(desc, &flags);
...
 195:	        return retval;
 196:	}

 137:	irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
 138:	{
 139:	        irqreturn_t retval = IRQ_NONE;
 140:	        unsigned int irq = desc->irq_data.irq;
 141:	        struct irqaction *action;
...
 145:	        for_each_action_of_desc(desc, action) {
 146:	                irqreturn_t res;
...
 149:	                res = action->handler(irq, action->dev_id);
...
 156:	                switch (res) {
...
 170:	                case IRQ_HANDLED:
 171:	                        *flags |= action->flags;
 172:	                        break;
 173:	
 174:	                default:
 175:	                        break;
 176:	                }
 177:	
 178:	                retval |= res;
 179:	        }
 180:	
 181:	        return retval;
 182:	}

linux-4.20/kernel/irq/internals.h
 156:	#define for_each_action_of_desc(desc, act)                      \
 157:	        for (act = desc->action; act; act = act->next)

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

◆割り込みコンテキスト(アトミック・コンテキスト)

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

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

割り込みコンテキストでは、このうようなことはできない。 速やかに終了すべきである。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);
    // 割り込みハンドラの終了を待つ。

■課題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() やポインタを操作して直接ユーザ空間のメ モリにアクセスすると問題がある。701 行目から 702 行目を、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 (成功) を返すようにしている。

なお、__user は、ユーザ空間のアドレスを意味し、エラー・チェックに使われ る、Cプリプロセッサで空の文字列に展開されることもある。この問題では空の 文字列に展開されると考えなさい。

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

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

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

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

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

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