• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            小默

            ldd3讀書筆記

            Google 筆記本
            ldd3讀書筆記

            最近編輯過的 2011年1月3日 
            Q:implicit declaration of function 'NIPQUAD'
            A:


             Using NIPQUAD() with NIPQUAD_FMT, %d.%d.%d.%d or %u.%u.%u.%u
            can be replaced with %pI4

            -		dprintf("SRC: %u.%u.%u.%u. Mask: %u.%u.%u.%u. Target: %u.%u.%u.%u.%s\n",
            -			NIPQUAD(src_ipaddr),
            -			NIPQUAD(arpinfo->smsk.s_addr),
            -			NIPQUAD(arpinfo->src.s_addr),
            +		dprintf("SRC: %pI4. Mask: %pI4. Target: %pI4.%s\n",
            +			&src_ipaddr,
            +			&arpinfo->smsk.s_addr,
            +			&arpinfo->src.s_addr,
             			arpinfo->invflags & ARPT_INV_SRCIP ? " (INV)" : "");



            網(wǎng)卡驅(qū)動分析

            ---
            1 找到網(wǎng)卡對應(yīng)驅(qū)動模塊,測試

            用lshw查看網(wǎng)卡對應(yīng)的驅(qū)動。我的是pcnet32。
             *-network
                            description: Ethernet interface
                            product: 79c970 [PCnet32 LANCE]
                            vendor: Hynix Semiconductor (Hyundai Electronics)
                            physical id: 1
                            bus info: pci@0000:02:01.0
                            logical name: eth1
                            version: 10
                            serial: 00:0c:29:b5:16:0d
                            width: 32 bits
                            clock: 33MHz
                            capabilities: bus_master rom ethernet physical logical
                            configuration: broadcast=yes driver=pcnet32 driverversion=1.35 ip=192.168.159.132 latency=64 link=yes maxlatency=255 mingnt=6 multicast=yes
                            resources: irq:19 ioport:2000(size=128) memory:dc400000-dc40ffff

            lsmod看到對應(yīng)模塊:
            $lsmod

            到 /usr/src/kernels/linux-2.6.36/drivers/net 下,ls可以看到 pcnet32.c 和對應(yīng)編譯好的.ko模塊。
            $ ls | grep pcnet32

            rmmod右上角網(wǎng)絡(luò)標(biāo)識圖成了叉叉。
            insmod pcnet32.ko 網(wǎng)絡(luò)標(biāo)識圖重新連上。
            (難道每次手動停止/啟動網(wǎng)絡(luò)連接,實際就是調(diào)用了卸載/加載模塊?囧)

            --
            2  嘗試編譯驅(qū)動
            把pcnet32.c拷貝出來。
            掃了眼net下面的Makefile,300行,嚇?biāo)牢银B。
            然后寫了個4行的Makefile,居然編譯插入運行一些正常,囧翻了。。。

            --
            3  分析代碼

            --
            4  分析在網(wǎng)絡(luò)模塊堆疊中的位置,數(shù)據(jù)流向

            --
            5  還沒有想好。。改下代碼?
            2.6.20內(nèi)核以后的skbuff.h頭文件中將struct sk_buff結(jié)構(gòu)體修改了,h中包含有傳輸層的報文頭,nh中包含有網(wǎng)絡(luò)層的報文頭,而mac中包含的是鏈路層的報文頭。
            linux-2.6.20以后的內(nèi)核頭文件sk_buff.h中這三個成員提取到單獨的變量對應(yīng)關(guān)系如下:
            h-->transport_header;
            nh-->network_header;
            mac-->mac_header;
            struct net


            以前查過一次,還有一點印象
            struct net是一個網(wǎng)絡(luò)名字空間namespace,在不同的名字空間里面可以有自己的轉(zhuǎn)發(fā)信息庫,有自己的一套net_device等等。
            默認情況下都是使用init_net這個全局變量
            Linux 系統(tǒng)內(nèi)核空間與用戶空間通信的實現(xiàn)與分析
            https://www.ibm.com/developerworks/cn/linux/l-netlink/

            推薦使用netlink套接字實現(xiàn)中斷環(huán)境和用戶態(tài)進程通信。

            CPU運行狀態(tài)有4種:處理硬中斷;處理軟中斷;運行于內(nèi)核態(tài),但有進程上下文;運行于用戶態(tài)進程。
            前三種狀態(tài),CPU運行于內(nèi)核空間。

            當(dāng)CPU運行進程上下文時,可以被阻塞。這時可以通過 copy_from_user()/copy_to_user() 實現(xiàn)內(nèi)核態(tài)和用戶態(tài)數(shù)據(jù)的拷貝。

            當(dāng)CPU運行于硬/軟中斷環(huán)境下時,不可以被阻塞。這時有2種方法實現(xiàn)和用戶態(tài)的通信。
            第1種方法:用一個和進程上下文相關(guān)的內(nèi)核線程,完成從用戶態(tài)接收數(shù)據(jù)的任務(wù);再通過一個臨界區(qū)將數(shù)據(jù)傳給中斷過程。缺點:中斷過程不能實時接收來自用戶態(tài)進程的數(shù)據(jù)。
            第2中方法:使用netlink套接字。用一個軟中斷調(diào)用用戶實現(xiàn)指定的接收函數(shù);再通過臨界區(qū)將數(shù)據(jù)傳給中斷過程。因為使用軟中斷來接收數(shù)據(jù),所以可以保證數(shù)據(jù)接收的實時性。
            Q:軟中斷不可以被阻塞,怎么從用戶空間接收數(shù)據(jù)。

            netlink套接字用法:


            --

            IMP2

            --Makefile

            obj-m += imp2_k.o
            KDIR := /lib/modules/$(shell uname -r)/build

            all:
            make -C $(KDIR) M=$(shell pwd) modules
            gcc -o imp2_u imp2_u.c
            clean:
            make -C $(DIR) M=$(shell pwd) clean
            rm -f *odule* imp2_k

            --imp2.h

            #ifndef _IMP2_H_
            #define _IMP2_H_

            #define IMP2_U_PID 0  /*用戶空間進程ID*/
            #define IMP2_K_MSG 1  /*內(nèi)核發(fā)出的消息*/
            #define IMP2_CLOSE 2  /*用戶空間進程關(guān)閉*/

            #define NL_IMP2  31  /*自定義的netlink協(xié)議類型*/

            /*nlmsghdr 后的數(shù)據(jù)*/
            struct packet_info{
            __u32 src;
            __u32 dest;
            };

            #endif

            --imp2_u.c

            #include <unistd.h>
            #include <stdio.h>
            #include <linux/types.h>
            #include <string.h>
            #include <sys/socket.h>
            #include <arpa/inet.h>
            #include <asm/types.h>
            #include <linux/netlink.h>
            #include <signal.h>
            #include "imp2.h"

            struct msg_to_kernel{
            struct nlmsghdr hdr;
            };

            struct u_packet_info{
            struct nlmsghdr hdr;
            struct packet_info icmp_info;
            };

            static int skfd;

            /*信號SIGINT的處理函數(shù)-告訴內(nèi)核用戶端關(guān)閉*/
            static void sig_int(int signo)
            {
            struct sockaddr_nl kpeer;
            struct msg_to_kernel message;

            /*內(nèi)核端地址*/
            memset(&kpeer, 0, sizeof(kpeer));
            kpeer.nl_family = AF_NETLINK;
            kpeer.nl_pid = 0;
            kpeer.nl_groups = 0;

            /*要傳遞的消息*/
            memset(&message, 0, sizeof(message));
            message.hdr.nlmsg_len = NLMSG_LENGTH(0);
            message.hdr.nlmsg_flags = 0;
            message.hdr.nlmsg_type = IMP2_CLOSE;
            message.hdr.nlmsg_pid = getpid();

            /*sendto - send a message on a socket*/
            sendto(skfd, /*int sockfd*/
               &message,  /*const void *buf*/
               &message.hdr.nlmsg_len,  /*size_t len*/
               0,  /*int flags*/
               (struct sockaddr *)(&kpeer), /*const struct sockaddr *dest_addr*/
               sizeof(kpeer)); /*socklen_t addrlen*/

            close(skfd);

            exit(0);
            }

            int main(void)
            {
            struct sockaddr_nl local;
            struct sockaddr_nl kpeer;
            int kpeerlen;
            struct msg_to_kernel message;
            struct u_packet_info info;
            int sendlen = 0;
            int rcvlen = 0;
            struct in_addr addr;

            /*創(chuàng)建套接字,設(shè)置用戶端地址,綁定套接字和地址*/
            skfd = socket(PF_NETLINK, SOCK_RAW, NL_IMP2);
            /*netlink套接字,用戶層端地址*/
            local.nl_family = AF_NETLINK;
            local.nl_pid = getpid();
            local.nl_groups = 0;
            if(bind(skfd, (struct sockaddr *)&local, sizeof(local)) != 0){
            printf("bind() error\n");
            return -1;
            }

            /*設(shè)置SIGINT信號的處理函數(shù)為sig_int*/
            signal(SIGINT, sig_int);

            /*netlink套接字,內(nèi)核端地址*/
            memset(&kpeer, 0, sizeof(kpeer));
            kpeer.nl_family = AF_NETLINK;
            kpeer.nl_pid = 0;
            kpeer.nl_groups = 0;

            /*把用戶端進程的pid傳遞給內(nèi)核*/
            memset(&message, 0, sizeof(message));
            message.hdr.nlmsg_len = NLMSG_LENGTH(0);
            message.hdr.nlmsg_flags = 0;
            message.hdr.nlmsg_type = IMP2_U_PID;
            message.hdr.nlmsg_pid = local.nl_pid;
            sendto(skfd, &message, message.hdr.nlmsg_len, 0,
            (struct sockaddr *)&kpeer, sizeof(kpeer));

            /*循環(huán)接收內(nèi)核發(fā)來的數(shù)據(jù)*/
            while(1){
            kpeerlen = sizeof(struct sockaddr_nl);
            rcvlen = recvfrom(skfd, &info, sizeof(struct u_packet_info),
            0, (struct sockaddr *)&kpeer, &kpeerlen);

            addr.s_addr = info.icmp_info.src;
            printf("src: %s, ", inet_ntoa(addr));
            addr.s_addr = info.icmp_info.dest;
            printf("dest: %s\n", inet_ntoa(addr));
            }

            return 0;
            }

            -- imp2_k.c

            /* 從netfilter的NF_IP_PRE_ROUTING點截獲ICMP數(shù)據(jù)報,記錄源地址和目錄地址
             * 使用netlink套接字將信息傳遞給用戶進程,再由用戶進程打印到終端上
             */
             
            #ifndef __KERNEL__
            #define __KERNEL__
            #endif

            #ifndef MODULE
            #define MODULE
            #endif

            #include <linux/module.h>
            #include <linux/kernel.h>
            #include <linux/init.h>
            #include <linux/types.h>
            #include <linux/netdevice.h>
            #include <linux/skbuff.h>
            #include <linux/netfilter_ipv4.h>
            #include <linux/inet.h>
            #include <linux/in.h>
            #include <linux/ip.h>
            #include <linux/netlink.h>
            #include <linux/spinlock.h>
            #include <linux/semaphore.h>
            #include <net/sock.h>
            #include "imp2.h"

            DECLARE_MUTEX(receive_sem);

            static struct sock *nlfd;

            struct{
            __u32 pid;
            rwlock_t lock;
            }user_proc;

            /*netlink的接收回調(diào)函數(shù),負責(zé)接收用戶層端的數(shù)據(jù)。運行在軟中斷環(huán)境*/
            static void kernel_receive(struct sk_buff *_skb)
            {
            struct sk_buff *skb = NULL;
            struct nlmsghdr *nlh = NULL;

            if(down_trylock(&receive_sem))
            return;

            /*檢查數(shù)據(jù)合法性*/
            skb = skb_get(_skb);

            if(skb->len >= sizeof(struct nlmsghdr)){
            nlh = (struct nlmsghdr *)skb->data;
            if((nlh->nlmsg_len >= sizeof(struct nlmsghdr)) && (skb->len >= nlh->nlmsg_len)){
            /*用戶層端進程id*/
            if(nlh->nlmsg_type == IMP2_U_PID){
            write_lock_bh(&user_proc.lock);
            user_proc.pid = nlh->nlmsg_pid;
            write_unlock_bh(&user_proc.lock);
            }
            /*用戶端進程關(guān)閉,置pid為0*/
            else if(nlh->nlmsg_type == IMP2_CLOSE){
            write_lock_bh(&user_proc.lock);
            if(nlh->nlmsg_pid == user_proc.pid)
            user_proc.pid = 0;
            write_unlock_bh(&user_proc.lock);
            }
            }
            }
            kfree_skb(skb);
            up(&receive_sem);
            }

            /*給netlink的用戶端發(fā)送數(shù)據(jù)*/
            static int send_to_user(struct packet_info *info)
            {
            int ret;
            int size;
            unsigned char *old_tail;
            struct sk_buff *skb;
            struct nlmsghdr *nlh;
            struct packet_info *packet;

            size = NLMSG_SPACE(sizeof(*info)); /*字節(jié)對齊后的數(shù)據(jù)報長度*/

            /*開辟一個新的套接字緩沖*/
            skb = alloc_skb(size, GFP_ATOMIC);
            old_tail = skb->tail;

            /*填寫數(shù)據(jù)報相關(guān)信息*/
            nlh = NLMSG_PUT(skb, 0, 0, IMP2_K_MSG, size-sizeof(*nlh));

            /*將struct packet_info作為nlmsg的數(shù)據(jù)*/
            packet = NLMSG_DATA(nlh);
            memset(packet, 0, sizeof(struct packet_info));
            packet->src = info->src;
            packet->dest = info->dest;

            /*計算經(jīng)過字節(jié)對其后nlmsg的長度*/
            nlh->nlmsg_len = skb->tail - old_tail;
            NETLINK_CB(skb).dst_group = 0;

            /*發(fā)送數(shù)據(jù)*/
            read_lock_bh(&user_proc.lock);
            ret = netlink_unicast(nlfd, skb, user_proc.pid, MSG_DONTWAIT);
            read_unlock_bh(&user_proc.lock);

            return ret;

            /*若發(fā)送失敗,則撤銷套接字*/
            nlmsg_failure:
            if(skb)
            kfree_skb(skb);

            return -1;
            }

            /* netfilter NF_IP_PRE_ROUTING點的掛接函數(shù)
             * 截獲ip數(shù)據(jù)報
             */
            static unsigned int get_icmp(unsigned int hook,
            struct sk_buff *pskb,
            const struct net_device *in,
            const struct net_device *out,
            int (*okfn)(struct sk_buff *))
            {
            struct iphdr *iph = NULL;
            struct packet_info info;

            iph = ip_hdr(pskb);
            /*如果傳輸層協(xié)議是ICMP*/
            if(iph->protocol == IPPROTO_ICMP){
            read_lock_bh(&user_proc.lock);
            if(user_proc.pid != 0){
            read_unlock_bh(&user_proc.lock);
            info.src = iph->saddr;
            info.dest = iph->daddr;
            send_to_user(&info); /*將數(shù)據(jù)發(fā)送給netlink的用戶層端*/
            }
            else
            read_unlock_bh(&user_proc.lock);
            }

            return NF_ACCEPT;
            }

            /*給netfilter框架的NF_IP_PRE_ROUTING點上掛接函數(shù)get_icmp()*/
            static struct nf_hook_ops imp2_ops = {
            .hook = get_icmp, /*hook被調(diào)用時執(zhí)行的回調(diào)函數(shù)*/
            .pf = PF_INET, /*協(xié)議族,<linux/socket.h>。ipv4使用PF_INET*/
            .hooknum = 0, /*安裝的這個函數(shù),對應(yīng)具體的hook類型*/
            .priority = NF_IP_PRI_FILTER - 1,
            };

            static int __init init(void)
            {
            rwlock_init(&user_proc.lock);

            /* 創(chuàng)建netlink套接字,指明接收數(shù)據(jù)的回調(diào)函數(shù)kernel_receive()
             * 這里協(xié)議NL_IMP2是自定義的*/
            nlfd = netlink_kernel_create(&init_net, NL_IMP2, 0, kernel_receive, NULL, THIS_MODULE);
            if(!nlfd){
            printk("cannot create a netlink socket\n");
            return -1;
            }

            /*注冊netfilter hook函數(shù)*/
            return nf_register_hook(&imp2_ops);
            }

            static void __exit fini(void)
            {
            /*釋放套接字所占資源*/
            if(nlfd){
            sock_release(nlfd->sk_socket);
            }
            /*注銷netfilter hook函數(shù)*/
            nf_unregister_hook(&imp2_ops);
            }

            module_init(init);
            module_exit(fini);

            at the beginning

            # 一個驅(qū)動程序的角色是提供機制,而不是策略 2010-11-15 09:26 小默

            “一個驅(qū)動程序的角色是提供機制,而不是策略。”--ldd3

            機制:提供什么能力
            策略:如何使用這些能力
            機制和策略由軟件不同部分,或完全不同的軟件實現(xiàn)。

            比如第一次實習(xí)時:
            我們這邊負責(zé)寫驅(qū)動,只關(guān)注實現(xiàn)什么功能,怎么實現(xiàn)這樣功能,這是機制。我們可以直接在設(shè)備管理器中安裝卸載,或者用命令行安裝卸載使用等,隨意,也就是開發(fā)過程完全不考慮策略。
            等開發(fā)進行到了一定階段,又招了另外一名同學(xué)負責(zé)界面,這是策略。用戶怎么使用這個驅(qū)動,操作界面是怎樣的,是由他來負責(zé)的。

            不知道是不是這個意思O(∩_∩)O~~  回復(fù)  更多評論 刪除評論  修改評論

            # makefile寫法 2010-11-15 17:14 小默

            對于單個.c文件的hello world例子:
            obj-m := hello.o
            從目標(biāo)文件hello.o簡歷一個模塊hello.ko

            如果模塊來自兩個源文件file1.c和file2.c:
            obj-m := module.o
            module-objs := file1.o file2.o

            上面的命令,必須在內(nèi)核系統(tǒng)上下建立文中被調(diào)用

            -----

            在任意當(dāng)前工作目錄中,需要在makefile中指明源碼樹的路徑。

            ifneq ($(KERNELRELEASE),)
            obj-m := hello.o
            else
            KERNELDIR ?= /lib/modules/$(shell uname -r)/build //TODO 不是在源碼樹中么/(ㄒoㄒ)/~~
            PWD := $(shell pwd)
            default:
            $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
            endif

            創(chuàng)建一個模塊需要調(diào)用兩次上面的makefile
            第一次:沒有設(shè)置變量KERNELRELEASE,通過已安裝模塊目錄中的符號鏈接指回內(nèi)核建立樹;然后運行default中的make命令,第二次使用makefile
            第二次:已經(jīng)設(shè)置了變量KERNELRELEASE,直接obj-m := hello.o創(chuàng)建模塊  回復(fù)  更多評論 刪除評論  修改評論

            # 加載和卸載模塊 2010-11-15 17:57 小默

            modprobe實現(xiàn)和insmod一樣加載模塊到內(nèi)核的功能
            不同的是,加載前會檢查模塊中是否有當(dāng)前內(nèi)核中沒有定義的symbol,如果有,在模塊搜索路徑中尋找其它模塊是否含有上面的symbol,有的話,自動加載關(guān)聯(lián)模塊
            insmod對于這種情況,會報錯unresolved symbols

            查看當(dāng)前加載模塊:
            lsmod
            cat /proc/modules
              回復(fù)  更多評論 刪除評論  修改評論

            # 如果你的模塊需要輸出符號給其他模塊使用 2010-11-15 18:12 小默

            EXPORT_SYMBOL(name);
            EXPORT_SYMBOL_GPL(name);  回復(fù)  更多評論 刪除評論  修改評論

            # 模塊參數(shù) 2010-11-15 18:40 小默

            說10次hello,Mom
            # insmod hellop howmany=10 whom="Mom"

            --

            static char *whom = "world"; //必須給默認值
            static int howmany = 1;
            module_param(howmany, int, S_IRUGO);
            module_param(whom, charp, S_IRUGO);

            --

            S_IRUGO 允許所有人讀
            S_IRUGO | S_IWUSR 允許所有人讀,允許root改變參數(shù)

            如果參數(shù)被sysfs修改,不會通知模塊。不要使參數(shù)可寫,除非準(zhǔn)備好檢測參數(shù)改變。

            --
            數(shù)組參數(shù):
            module_param_array(name, type, num, perm);   回復(fù)  更多評論 刪除評論  修改評論

            # 設(shè)備主次編號 2010-11-16 13:24 小默

            $ ls -l /dev
            ...
            crw-rw---- 1 vcsa tty 7, 132 Nov 15 17:16 vcsa4
            crw-rw---- 1 vcsa tty 7, 133 Nov 15 17:16 vcsa5
            crw-rw---- 1 vcsa tty 7, 134 Nov 15 17:16 vcsa6
            crw-rw---- 1 root root 10, 63 Nov 15 17:16 vga_arbiter
            drwxr-xr-x 2 root root 80 Nov 15 17:16 vg_colorfulgreen
            crw-rw-rw- 1 root root 1, 5 Nov 15 17:16 zero
            ...

            輸出第一列是c的是字符設(shè)備,第一列b塊設(shè)備

            修改日期前的兩個數(shù)字。
            第一個是主設(shè)備編號:標(biāo)識設(shè)備相連的驅(qū)動
            第二個是次設(shè)備編號:決定引用哪個設(shè)備

            -------
            設(shè)備編號的內(nèi)部表示

            dev_t 在<linux/types.h>中定義,32位,12位主編號,20位次編號。

            獲得一個dev_t的主或次編號:<linux/kdev_t.h>
            MAJOR(dev_t dev);
            MINOR(dev_t dev);

            將主次編號轉(zhuǎn)換成dev_t:
            MKDEV(int major, int minor);

            --------
            分配和釋放設(shè)備編號

            建立一個字符驅(qū)動時,做的第一件事就是獲取一個或多個設(shè)備編號使用:
            <linux/fs.h>
            int register_chrdev_region(dev_t first, unsigned int count, char *name);
            first要分配的起始設(shè)備編號
            count請求的連續(xù)設(shè)備編號的總數(shù)
            name連接到這個編號范圍的設(shè)備的名字,會出現(xiàn)在/proc/devices和sysfs中
            成功返回0,出錯返回負的錯誤碼。

            如果事先不知道使用哪個設(shè)備編號,使用下面函數(shù),內(nèi)核會分配一個主設(shè)備編號:
            int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
            dev是一個輸出參數(shù),返回分配范圍的第一個數(shù)
            firstminor請求第一個要用的次編號,常是0

            設(shè)備編號的釋放:
            void unregister_chrdev_region(dev_t first, unsigned int count);  回復(fù)  更多評論 刪除評論  修改評論

            # scull安裝腳本 2010-11-16 13:57 小默

            腳本scull_load:
            1 #!/bin/sh
            2 module="scull"
            3 device="scull"
            4 mode="664"

            6 # invoke insmod with all arguments we got
            7 # and use a pathname, as newer modutils don't look in. by default
            8 /sbin/insmod ./$module.ko $* || exit 1 # 插入模塊,使用獲取的所有參數(shù)($*)

            10 # remove stale nodes刪除無效的節(jié)點,不能刪除device0,device1,device2...阿。。TODO
            11 rm -f /dev/${device}[0-3]
            12 
            13 major=$(awk "\\$2==\"$module\" {print \\$1}" /proc/devices) #TODO 沒有搞明白
            14 mknod /dev/${device}0 c $major 0 #創(chuàng)建4個虛擬設(shè)備
            15 mknod /dev/${device}1 c $major 1
            16 mknod /dev/${device}2 c $major 2
            17 mknod /dev/${device}3 c $major 3
            18 
            19 # give appropriate group/permissions, and change the group.
            20 # Not all distributions have staff, some have "wheel" instead. #TODO 神馬意思?
            21 group="staff"
            22 grep -q '^staff:' /etc/group || group="wheel"
            23 # 改變設(shè)備的組和模式。腳本必須以root運行,但設(shè)備使用可能需要其它用戶寫。
            24 chgrp $group /dev/${device}[0-3]
            25 chmod $mode /dev/${device}[0-3]
            26 
              回復(fù)  更多評論 刪除評論  修改評論

            # file_operations結(jié)構(gòu) 2010-11-16 15:24 小默

            一些處理文件的回調(diào)函數(shù)

            1 // init file_operations
            2 struct file_operations scull_fops = {
            3 .owner = THIS_MODULE,
            4 .llseek = scull_llseek,
            5 .read = scull_read,
            6 .write = scull_write,
            7 .ioctl = scull_ioctl,
            8 .open = scull_open,
            9 .release = scull_release,
            10 };  回復(fù)  更多評論 刪除評論  修改評論

            # 注冊字符設(shè)備 2010-11-16 15:25 小默

            12 // 使用struct scull_dev結(jié)構(gòu)表示每個設(shè)備
            13 // TODO 沒有理解什么意思
            14 struct scull_dev {
            15 struct scull_qset *data; // pointer to first quantum set
            16 int quantum; // the current quantum size
            17 int qset; // the current array size
            18 unsigned long sizee; //amount of data stored here
            19 unsigned int access_key; // used by sculluid and scullpriv
            20 struct semaphore sem; // matual exclusion semaphore
            21 struct cdev cdev; // 字符設(shè)備結(jié)構(gòu)
            22 };
            23 
            24 // 初始化struct cdev,并添加到系統(tǒng)中
            25 static void scull_setup_cdev(struct scull_dev *dev, int index)
            26 {
            27 int err, devno = MKDEV(scull_major, scull_minor + index);
            28 
            29 // TODO 初始化已經(jīng)分配的結(jié)構(gòu). 不是很理解
            30 // cdev結(jié)構(gòu)嵌套在struct scull_dev中,必須調(diào)用cdev_init()來初始化cdev結(jié)構(gòu)
            31 cdev_init(&dev->cdev, &scull_fops);
            32 dev->cdev.owner = THIS_MODULE;
            33 dev->cdev.ops = &scull_fops;
            34 // 添加到系統(tǒng)中
            35 err = cdev_add(&dev->cdev, devno, 1);
            36 if(err)
            37 printk(KERN_NOTICE "Error %d adding scull%d", err, index);
            38 }
              回復(fù)  更多評論 刪除評論  修改評論

            # system-config-selinux 2010-11-27 02:54 小默

            system-config-selinux
            什么時候改了,汗 //TODO  回復(fù)  更多評論 刪除評論  修改評論

            # read & write 2010-11-30 22:12 小默

            // 表示每個設(shè)備
            struct scull_dev{
            struct scull_qset *data; // pointer to first quantum set
            int quantum; // the current quantum size - 當(dāng)前量子和量子集大小
            int qset; // the current array size - 每個內(nèi)存區(qū)域為一個量子,數(shù)組為一個量子集
            unsigned long size; // amount of data stored here
            unsigned int access_key; // used by sculluid and scullpriv
            struct semaphore sem; // mutual exclusion semaphore

            struct cdev cdev; // char device structure
            };

            // 量子集,即一個內(nèi)存區(qū)域的數(shù)組
            struct scull_qset{
            void **data;
            struct scull_qset *next;
            };

            ssize_t scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
            {
            struct scull_dev *dev = file->private_data;
            struct scull_qset *dptr; // 量子集中的第一個元素
            int quantum = dev->quantum, qset = dev->qset; // 當(dāng)前量子和量子集大小
            int itemsize = quantum * qset; // listitem中的字節(jié)數(shù)=量子大小*量子集大小
            int item, s_pos, q_pos, rset;
            ssize_t retval = 0;

            if(down_interruptible(&dev->sem)) // TODO
            return -ERESTARTSYS;
            if(*f_pos > dev->size)
            goto out;
            if(*f_pos + count > dev->size)
            count = dev->size - *f_pos;

            // 查找listitem, qset index, and 量子中的偏移量
            item = (long)*f_pos / itemsize;
            rest = (long)*f_pos % itemsize;
            s_pos = rest / quantum;
            q_pos = rest % quantum;

            // 遍歷list到右側(cè)
            dptr = scull_follow(dev, item); // 量子集中的第一個元素
            if(dptr == NULL || !dptr->data || !dptr->data[s_pos])
            goto out;

            // 只讀取到這個量子的尾部
            if(count > quantum - q_pos)
            count = quantum - q_pos;

            if(copy_to_user(buf, dptr->data[s_pos] + q_pos, count)){
            retval = -EFAULT;
            goto out;
            }
            *f_pos += count;
            retval = count;

            out:
            up(&dev->sem);
            return retval;
            }

            // 一次處理單個量子
            ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
            {
            struct scull_dev *dev = filp->private_data;
            struct scull_qset *dptr;
            int quantum = dev->quantum, qset = dev->qset;
            int itemsize = quantum * qset;
            int item, s_pos, q_pos, rest;
            ssize_t retval = -ENOMEM; // value used in "goto out" statements
            if(down_interruptible(&dev->sem))
            return -ERESTARTSYS;

            // 查找列表元素,qset index and 量子中的偏移量
            item = (long)*f_pos / itemsize;
            rest = (long)*f_pos % itemsize;
            s_pos = rest / quantum;
            q_pos = rest % quantum;

            // 遍歷list到右側(cè)
            dptr = scull_follow(dev, item);
            if(dptr == NULL):
            goto out;
            if(!dptr->data){
            dptr->data = kmalloc(qset * sizeof(char), GPL_KERNEL);
            if(!dptr->data)
            goto out;
            memset(dptr->data, 0, qset * sizeof(char *));
            }
            if(!dptr->data[s_pos]){
            dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
            if(!dptr->data[s_pos])
            goto out;
            }

            // 只寫到這個量子的結(jié)束
            if(count > quantum-q_pos)
            count = quantum - q_pos;
            // 從用戶空間拷貝一整段數(shù)據(jù)to from count
            if(copy_from_user(dptr->data[s_pos]+q_pos, buf, count)){
            retval = -EFAULT;
            goto out;
            }
            *f_pos += count;
            retval = count;

            // 更新size
            if(dev->size < *f_pos)
            dev->size = *f_pos;

            out:
            up(&dev->sem);
            return retval;
            }

            //-------------------------------


            // read和write的"矢量"版本
            // readv輪流讀取指示的數(shù)量到每個緩存;writev收集每個緩存的內(nèi)容到一起并且作為單個寫操作送出它們。
            // count參數(shù)告訴有多少iovec結(jié)構(gòu),這些結(jié)構(gòu)由應(yīng)用程序創(chuàng)建,但是內(nèi)核在調(diào)用驅(qū)動之前拷貝它們到內(nèi)核空間。
            ssize_t (*readv)(struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos);
            ssize_t (*writev)(struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos);

            // iovec描述了一塊要傳送的數(shù)據(jù)
            struct iovec{
            void __user *iov_base; // 開始于iov_base(在用戶空間)
            __kernel_size_t iov_len; // 并有iov_len長
            };
              回復(fù)  更多評論 刪除評論  修改評論

            # 重定向控制臺消息 2010-11-30 22:44 小默

            // 重定向控制臺消息
            // 使用一個參數(shù)指定接收消息的控制臺的編號
            int main(int argc, char **argv)
            {
            char bytes[2] = {11, 0}; // 11 是 TIOCLINUX 的功能號

            if(argc == 2) bytes[1] = atoi(argv[1]); // the chosen console
            else{
            fprintf(stderr, "%s: need a single arg\n", argv[0]);
            exit(1);
            }
            // TIOCLINUX傳遞一個指向字節(jié)數(shù)組的指針作為參數(shù),數(shù)組的第一個字節(jié)是一個數(shù)(需要指定的子命令)。
            // 當(dāng)子命令是11時,下一個字節(jié)指定虛擬控制臺。
            if(ioctl(STDIN_FILENO, TIOCLINUX, bytes)<0){ // use stdin
            fprintf(stderr, "%s: ioctl(stdin, TIOCLINUX): %s\n", argv[0], stderror(errno));
            exit(1);
            }
            exit(0);
            }
              回復(fù)  更多評論 刪除評論  修改評論

            # Implementing files in /proc 2010-12-04 00:42 小默

            // 在proc里實現(xiàn)文件,在文件被讀時產(chǎn)生數(shù)據(jù)。
            // 當(dāng)一個進程讀你的/proc文件,內(nèi)核分配了一頁內(nèi)存,驅(qū)動可以寫入數(shù)據(jù)返回給用戶空間。
            // buf 寫數(shù)據(jù)的緩沖區(qū);start有關(guān)數(shù)據(jù)寫在頁中哪里;eof必須被驅(qū)動設(shè)置,表示寫數(shù)據(jù)結(jié)束;data用來傳遞私有數(shù)據(jù)。
            // 假定不會有必要產(chǎn)生超過一頁的數(shù)據(jù),并且因此忽略了start和offset值。
            int scull_read_procmem(char *buf, char **start, off_t offset, int count, int *eof, void *data)
            {
            int i, j, len = 0;
            int limit = count - 80; // Don't print more than this

            for(i = 0; i < scull_nr_devs && len <= limit; i++){ // TODO scull_nr_devs ?
            struct scull_dev *d = &scull_devices[i];
            struct scull_qset *qs = d->data;
            if(down_interruptible(&d->sem))
            return -ERESTARTSYS;
            // 設(shè)備號,量子集大小,量子大小,存儲的數(shù)據(jù)量
            len += sprintf(buf+len, "\nDevice %i: qset %i, q %i, sz %li\n", i, d->qset, d->quantum, d->size); 
            for(; qs && len <= limit; qs = qs->next){ //scan the list 遍歷量子鏈表
            // 元素地址、鏈表地址
            len += sprintf(buf + len, " item at %p, qset at %p\n", qs, qs->data); // %p 顯示一個指針
            if(qs->data && !qs->next) // dump only the last item
            for(j = 0; j < d->qset, j++){
            if(qs->data[j])
            len += sprintf(buf+len, "%4i: %8p\n", j, qs->data[j]);
            }

            }
            up(&scull_devices[i]);
            }
            *eof = 1;
            return len; // 返回實際在頁中寫了多少數(shù)據(jù)
            }


            /// 移除entry的一些問題
            // 移除可能發(fā)生在文件正在被使用時。/proc入口沒有owner,沒有引用計數(shù)。
            // 內(nèi)核不檢查注冊的名字是否已經(jīng)存在,可能會有多個entry使用相同名稱。而且在訪問和remove_proc_entry時,它們沒有區(qū)別。。。悲劇。。。  回復(fù)  更多評論 刪除評論  修改評論

            # The seq_file interface 2010-12-04 10:56 小默

            // 創(chuàng)建一個虛擬文件,遍歷一串?dāng)?shù)據(jù),這些數(shù)據(jù)必須返回用戶空間。
            // start, next, stop, show

            // sfile 總被忽略;pos指從哪兒開始讀,具體意義完全依賴于實現(xiàn)。
            // seq_file典型的實現(xiàn)是遍歷一感興趣的數(shù)據(jù)序列,pos就用來指示序列中的下一個元素。
            // 在scull中,pos簡單地作為scull_array數(shù)組的索引。
            // 原型
            void *start(struct seq_file *sfile, loff_t *pos);
            // 在scull中的實現(xiàn)
            static void *scull_seq_start(struct seq_file *s, loff_t *pos)
            {
            if(*pos >= scull_nr_devs)
            return NULL; // no more to read
            return scull_devices + *pos; // 返回供迭代器使用的私有數(shù)據(jù)
            }

            // next把迭代器后挪一位,返回NULL表示沒有更多數(shù)據(jù)了
            // v:上一次start/next調(diào)用返回的迭代器 TODO ???返回的不是私有數(shù)據(jù)么?
            // pos: 文件中的當(dāng)前位置。
            void *next(struct seq_file *sfile, void *v, loff_t *pos);
            // scull的實現(xiàn)
            static void *scull_seq_next(struct seq_file *s, void *v, loff_t *pos)
            {
            (*pos)++;
            if(*pos >= scull_nr_devs)
            return NULL;
            return scull_devices + *pos;
            }

            // 內(nèi)核完成迭代器,調(diào)用stop清理
            void stop(struct seq_file *sfile, void *v);
            // scull沒有要清理的東西,stop方法是空的

            // start到stop期間不會有sleep或者非原子操作,可以放心的在start中獲得信號量或自旋鎖。整個調(diào)用序列都是原子的。天書啊 TODO ???

            // 在start和stop期間,內(nèi)核調(diào)用show輸出迭代器v生成的數(shù)據(jù)到用戶空間
            int show(struct seq_file *sfile, void *v);
            // 輸出,等效于用戶空間的printf。返回非0值表示緩沖滿,輸出的數(shù)據(jù)會被丟棄。不過大多數(shù)實現(xiàn)都忽略返回值。
            int seq_sprintf(struct seq_file *sfile, const char *fmt, ...);
            // 等效于用戶空間的putc和puts
            int seq_putc(struct seq_file *sfile, char c);
            int seq_puts(struct seq_file *sfile, const char *s);
            // 如果s中有esc中的數(shù)據(jù),這些數(shù)據(jù)用8進制輸出。常見的esc是"\t\n\\",用于保持空格,避免搞亂輸出。
            int seq_escape(struct seq_file *m, const char *s, const char *esc);
            // scull中show實現(xiàn)
            static int scull_seq_show(struct seq_file *s, void *v)
            {
            struct scull_dev *dev = (struct scull_dev *)v;
            struct scull_qset *d;
            int i;

            if(down_interrutible(&dev->sem))
            return -ERESTARTSYS;
            seq_printf(s, "\nDevice %i: qset %i, q %i, sz %li\n",
            (int)(dev - scull_devices), dev->qset,
            dev->quantum, dev->size);
            for(d = dev->data; d; d = d->next){ // 遍歷鏈表
            seq_printf(s, " item at %p, qset at %p\n", d, d->data);
            if(d->data && !d->next) // dump only the last item
            for(i = 0; i < dev->qset; i++){
            if(d->data[i])
            seq_printf(s, " %4i: %8p\n", i, d->data[i]);
            }
            }
            up(&dev->sem);
            return 0;
            }

            // 迭代器:指向scull_dev的一個指針,囧。。。
            // 迭代器操作集
            static struct seq_operations scull_seq_ops = {
            .start = scull_seq_start,
            .next = scull_seq_next,
            .stop = scull_seq_stop,
            .show = scull_seq_show
            };

            /// 用file_operations結(jié)構(gòu)結(jié)構(gòu),實現(xiàn)內(nèi)核read/seek文件的所有操作。
            // 創(chuàng)建一個open方法,連接文件和seq_file操作 TODO 沒看懂
            static int scull_proc_open(struct inode *inode, struct file *file)
            {
            // seq_open 連接文件和上面定義的scull_seq_ops
            return seq_open(file, &scull_seq_ops);
            }
            // file_operations結(jié)構(gòu)
            static struct fle_operations scull_proc_ops = {
            .owner = THIS_MODULE,
            .open = scull_proc_open,
            .read = seq_read,
            .llseek = seq_lseek,
            .release = seq_release
            };

            // 在/proc中創(chuàng)建設(shè)備
            entry = create_proc_entry("scullseq", 0, NULL);
            if(entry)
            entry->proc_fops = &scull_proc_ops;
            // create_proc_entry原型
            struct proc_dir_entry *create_proc_entry(const char *name,
            mode_t, mode,
            struct proc_dir_entry *parent);  回復(fù)  更多評論 刪除評論  修改評論

            # strace命令 - debug 2010-12-04 14:12 小默

            略 O(∩_∩)O~~  回復(fù)  更多評論 刪除評論  修改評論

            # ldd3_4.5_Debugging System Faults 2010-12-05 14:46 小默

            講了兩部分

            一、system opps
            空指針引用,或者局部變量賦值覆蓋了原eip,導(dǎo)致頁錯誤

            二、系統(tǒng)掛起 // TODO 看得云里霧里的
            死循環(huán)等引起
            假掛起:鼠標(biāo)鍵盤等外設(shè)沒有響應(yīng)了,系統(tǒng)實際正常。可以看時間。。。
            插入schedule調(diào)用防止死循環(huán),作用是允許其他進程從當(dāng)前進程竊取時間。講了些弊端,沒看懂
            sysrq:沒看懂
              回復(fù)  更多評論 刪除評論  修改評論

            # re: 蓋樓 2010-12-26 06:13 小默

            所謂原子操作,就是該操作絕不會在執(zhí)行完畢前被任何其他任務(wù)或事件打斷,也就說,它的最小的執(zhí)行單位,不可能有比它更小的執(zhí)行單位。
            原子操作主要用于實現(xiàn)資源計數(shù),很多引用計數(shù)(refcnt)就是通過原子操作實現(xiàn)的。

            原子類型定義:
            typedef struct
            {
            volatile int counter;
            }
            atomic_t; 

            volatile修飾字段告訴gcc不要對該類型的數(shù)據(jù)做優(yōu)化處理,對它的訪問都是對內(nèi)存的訪問,而不是對寄存器的訪問。  回復(fù)  更多評論刪除評論  修改評論

            # spinlock_t 2010-12-26 16:24 小默

            17 typedef struct {
            18 volatile unsigned int lock;
            19 #ifdef CONFIG_DEBUG_SPINLOCK
            20 unsigned magic;
            21 #endif
            22 } spinlock_t;  回復(fù)  更多評論 <a id="AjaxHolder_Comments_CommentList_ctl24_DeleteLink"

            ch03 字符驅(qū)動

            3.4 字符設(shè)備注冊

            struct cdev  <linux/cdev.h>

            1. 分配并初始化一個cdev結(jié)構(gòu)。

            如果期望獲得一個獨立的cdev結(jié)構(gòu):

            struct cdev *my_cdev = cdev_alloc();
            my_cdev
            ->ops = &my_fops;


            如果想把cdev嵌入到自定義的設(shè)備結(jié)構(gòu)中:

            void cdev_init(struct cdev *cdev, struct file_operations *fops);


            上面兩種方法,都需要初始化owner成員為THIS_MODULE

            2.把新建立的cdev結(jié)構(gòu)告訴內(nèi)核

            // 把已經(jīng)分配、初始化的cdev結(jié)構(gòu)通知內(nèi)核。立即生效。出錯時返回一個負的錯誤碼。
            int cdev_add(struct cdev *dev, 
                           dev_t num,           
            // 設(shè)備響應(yīng)的第一個設(shè)備號
                           unsigned int count);  // 關(guān)聯(lián)到設(shè)備上的設(shè)備號數(shù)量


            3.移除cdev結(jié)構(gòu)

            // 從系統(tǒng)中移除一個字符設(shè)備
            void cdev_del(struct cdev *dev);


            scull中字符設(shè)備注冊

             // 注冊字符設(shè)備
            static void scull_setup_cdev(struct scull_dev *dev, int index)
            {
                
            int err, devno = MKDEV(scull_major, scull_minor + index); // MKDEV 把主次設(shè)備號合成為一個dev_t結(jié)構(gòu)

                
            // 分配并初始化cdev結(jié)構(gòu)。struct cdev內(nèi)嵌在struct scull_dev中
                cdev_init(&dev->cdev, &scull_fops);
                dev
            ->cdev.owner = THIS_MODULE;
                dev
            ->cdev.ops = &scull_fops;
                
            // 通知內(nèi)核,立即生效
                err = cdev_add (&dev->cdev,     // struct cdev *p: the cdev structure for the device
                                devno,          // dev_t dev: 第一個設(shè)備號
                                1);             // unsigned count: 該設(shè)備連續(xù)次設(shè)備號的數(shù)目
                /* Fail gracefully if need be */
                
            if (err)
                    printk(KERN_NOTICE 
            "Error %d adding scull%d", err, index);
            }




            // 注冊字符設(shè)備過去的方法
            int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
            int unregister_chrdev(unsigned int major, const char *name);
             

            3.5 open 和 release

            3.5.1 open 方法

            open方法在驅(qū)動中用來做一些初始化準(zhǔn)備工作。

            在多數(shù)驅(qū)動中,open進行的工作:
            - 檢查設(shè)備特定的錯誤(device-not-ready等)
            - 如果設(shè)備第一次打開,初始化設(shè)備
            - 更新 f_op 指針,如果需要的話
            - 給 filp->private_data 賦值

            open原型:

            int (*open)(struct inode *inode, struct file *filp);

            如何確定打開的是哪個設(shè)備:
            - inode中i_cdev成員,是之前建立的cdev結(jié)構(gòu)。通常我們需要的不是cdev,而是包含cdev的scull_dev結(jié)構(gòu),這時使用    
            container_of(pointer, container_type, container_field);
            - 查看存儲在inode中的次設(shè)備號。如果使用register_chrdev注冊設(shè)備,必須采用這種方法。

            scull設(shè)備是全局和永久的,沒有維護打開計數(shù),不需要"如果設(shè)備第一次打開,初始化設(shè)備"的操作。  // TODO
            scull_open:
            /// 打開設(shè)備
            int scull_open(struct inode *inode, struct file *filp)
            {
                
            struct scull_dev *dev; /* device information */

                
            // 文件私有數(shù)據(jù),設(shè)置成對應(yīng)的scull_dev
                dev = container_of(inode->i_cdev, struct scull_dev, cdev);
                filp
            ->private_data = dev; /* for other methods */

                
            /* now trim to 0 the length of the device if open was write-only */
                
            // 文件以只讀模式打開時,截斷為0
                if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
                    
            if (down_interruptible(&dev->sem)) // 申請新號量,可中斷
                        return -ERESTARTSYS;
                    
            // 釋放整個數(shù)據(jù)區(qū)
                    scull_trim(dev); /* ignore errors */ 
                    up(
            &dev->sem);  // 釋放信號量
                }
                
            return 0;          /* success */
            }

            3.5.2 release方法

            release進行的工作:
            - 釋放open在file->private_data中申請的一切
            - 在調(diào)用最后一個close時關(guān)閉設(shè)備

            scull的基本形式?jīng)]有需要關(guān)閉的設(shè)備。
            scull_release:
            int scull_release(struct inode *inode, struct file *filp)
            {
                
            return 0;
            }


            close 和 release
            并不是每一個close系統(tǒng)調(diào)用,都會引起對release的調(diào)用。
            內(nèi)核對一個打開的file結(jié)構(gòu),維護一個引用計數(shù)。dup和fork都只遞增現(xiàn)有file結(jié)構(gòu)的引用計數(shù)。只有當(dāng)引用計數(shù)為0時,對close的調(diào)用才引起對release的調(diào)用。
            3.6 scull的內(nèi)存使用 

            how & why 執(zhí)行內(nèi)存分配
            scull 使用的內(nèi)存區(qū)(被稱為device)長度可變,隨寫的內(nèi)容增多而增長;修剪通過用短文件重寫device實現(xiàn)。

            1. 內(nèi)核中管理內(nèi)存使用的兩個核心函數(shù):
            void *kmalloc(size_t size, int flags);
            void kfree(void *ptr);
            kmalloc試圖申請size字節(jié)內(nèi)存,返回指向內(nèi)存的指針,或者申請失敗時返回NULL。flags指定如何使用內(nèi)存,在這里先用GFP_KERNEL。
            kfree用來釋放kmalloc申請的內(nèi)存。不能用kfree釋放其它不是kmalloc申請的內(nèi)存。

            kmalloc不是申請大塊內(nèi)存的有效方法,但為了代碼簡單易懂,在這里暫時使用kmalloc。另外,我們沒有限制“device”區(qū)域大小:理論上不該給數(shù)據(jù)施加武斷的限制;實踐上scull可以暫時吃光所有系統(tǒng)內(nèi)存來模擬低內(nèi)存環(huán)境下的測試。可以使用 cp /dev/zero /dev/scull0 吃光所有RAM內(nèi)存,也可以用 dd 工具指定分配給scull的內(nèi)存。

            2. scull device結(jié)構(gòu):
            在scull中,每個device是一系列指向scull_device結(jié)構(gòu)的指針組成的鏈表。每個scull_device結(jié)構(gòu)默認指向最多4M的內(nèi)存區(qū):有多個量子集,每個量子集有多個量子。每個scull_device的布局如下:


            struct scull_qset {
                void **data;
                struct scull_qset *next;
            };
            3. 量子和量子集大小設(shè)置:
            在scull中單寫一個字節(jié)需要8000kb或1200kb內(nèi)存:量子用掉4000;量子集用掉4000kb或8000kb(根據(jù)在目標(biāo)平臺上一個指針代碼32位或者64位)。但是,當(dāng)寫入數(shù)據(jù)量比較大時,鏈表的開銷還可以:每4M字節(jié)一個鏈表。

            為量子和量子集選擇合適的值是策略問題,而不是機制。scull驅(qū)動不應(yīng)該強制使用特定值,而是可以由用戶設(shè)置:編譯時改變scull.h中宏SCULL_QUANTUM和SCULL_QSET;模塊加載時設(shè)置scull_quantum和scull_qset;運行時通過ioctl改變。

            4. scull中的內(nèi)存管理的代碼段

            // 釋放整個數(shù)據(jù)區(qū)。簡單遍歷列表并且釋放它發(fā)現(xiàn)的任何量子和量子集。
            // 在scull_open 在文件為寫而打開時調(diào)用。
            // 調(diào)用這個函數(shù)時必須持有信號量。
            /*
             * Empty out the scull device; must be called with the device
             * semaphore held.
             */
            int scull_trim(struct scull_dev *dev)
            {
                struct scull_qset *next, *dptr;
                int qset = dev->qset;   /* "dev" is not-null */ // 量子集大小
                int i;

                // 遍歷多個量子集。dev->data 指向第一個量子集。
                for (dptr = dev->data; dptr; dptr = next) { /* all the list items */
                    if (dptr->data) { // 量子集中有數(shù)據(jù)
                        for (i = 0; i < qset; i++) // 遍歷釋放當(dāng)前量子集中的每個量子。量子集大小為qset。 
                            kfree(dptr->data[i]);
                        kfree(dptr->data); // 釋放量子指針數(shù)組
                        dptr->data = NULL; 
                    }
                    // next獲取下一個量子集,釋放當(dāng)前量子集。
                    next = dptr->next;
                    kfree(dptr);
                }
                // 清理struct scull_dev dev中變量的值
                dev->size = 0;
                dev->quantum = scull_quantum;
                dev->qset = scull_qset;
                dev->data = NULL;
                return 0;
            }

            3.7 read 和 wirte

            read和write進行在內(nèi)核和用戶空間之間復(fù)制數(shù)據(jù)的工作。
            原型:
            ssize_t read(struct file *filp, char _ _user *buff,
                size_t count, loff_t *offp);
            ssize_t write(struct file *filp, const char _ _user *buff,
                size_t count, loff_t *offp);
            filp是指向文件的指針。count是要讀寫字節(jié)數(shù)。buff指向用戶空間地址。offp是指向long offset type對象的指針,指明要訪問的文件位置。
            返回實際傳輸?shù)淖止?jié)數(shù),出錯返回負值。

            訪問用戶空間地址char __user *buff:

            驅(qū)動中不應(yīng)該直接對用戶空間地址指針 *buff 解引用。原因:
            - 用戶空間地址指針在內(nèi)核中可能根本無效,這取決于主機體系結(jié)構(gòu)和內(nèi)核配置。
            - 即便這個指針在內(nèi)核中代表同一個東西,但用戶空間是分頁的,地址可能不在RAM中。直接解引用 用戶空間地址指針 可能引發(fā)頁錯誤。
            - 用戶程序提供的指針可能是錯誤的或者惡意的。盲目解引用用戶提供的指針,將允許用戶訪問系統(tǒng)內(nèi)存中任何地方。

            應(yīng)該通過內(nèi)核提供的函數(shù)訪問用戶空間。
            scull中復(fù)制一整段數(shù)據(jù),使用下面兩個函數(shù):
            unsigned long copy_to_user(void _ _user *to,
                                       const void *from, 
                                       unsigned long count);
            unsigned long copy_from_user(void *to, 
                                         const void _ _user *from, 
                                         unsigned long count);
            使用上面兩個函數(shù)需要注意的一點:當(dāng)用戶空間地址不在內(nèi)存中時,進程會投入睡眠等待內(nèi)存調(diào)度,所以任何存取用戶空間的函數(shù)必須是可重入的。
            數(shù)據(jù)傳送完成后需要修改*offp為當(dāng)前的文件位置。
            read參數(shù):

            讀device時需要考慮的特殊情況:當(dāng)前讀位置大于設(shè)備大小

            這種情況發(fā)生在:進程a在讀設(shè)備,同時進程b寫設(shè)備將長度截斷為0。此時scull的read返回0表示已到文件尾(沒有更多的數(shù)據(jù)可以讀)

            ch04 調(diào)試技術(shù)

            4.1 內(nèi)核中的調(diào)試支持

            編譯內(nèi)核時,"kernel hacking"菜單中有一些調(diào)試選項。
            書中列了一些,暫時沒有看
            4.2 debugging by print

            掃下目錄,暫時沒有興趣細看o(╯□╰)o

            4.2.1 printk
            4.2.2 重定向控制臺消息
            4.2.3 消息是如何被記錄的
            4.2.4 關(guān)閉或打開消息
            4.2.5 速率限制
            4.2.6 打印設(shè)備號 
            4.3-4.6 心情不好,不想看鳥~

            ch05 并發(fā)和競爭

            早期內(nèi)核不支持多處理器,只有硬件中斷服務(wù)涉及到并發(fā)問題。
            5.1 scull 的缺陷
            write邏輯中的一段代碼,scull需要考慮申請的內(nèi)存是否獲得:
                if (!dptr->data[s_pos]) {
                    dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
                    if (!dptr->data[s_pos])
                        goto out;
                }
            假設(shè)在同一時刻,A和B兩個進程試圖獨立地寫同一個scull設(shè)備的相同偏移。兩個進程同時到達if判斷,此時sptr->data[s_pos]為NULL,兩個進程都申請了一塊內(nèi)存賦值給dptr->data[s_pos]。dptr->data[s_pos]最后只會保留一個值,假設(shè)B賦值在A之后,A申請的內(nèi)存就被丟掉無法再獲得,從來產(chǎn)生內(nèi)存泄露(memory leak)。
            上面是一個競爭狀態(tài)(race condition)的示例,產(chǎn)生競爭狀態(tài)的原因是沒有控制對 共享數(shù)據(jù) 的訪問。
            5.2 并發(fā)和它的管理 

            競爭條件是伴隨著資源的共享訪問而來的。內(nèi)核代碼應(yīng)該有最小的共享,其中最明顯的應(yīng)用就是盡可能少地使用全局變量。

            共享無處不在。硬件是共享的,軟件資源常常需要共享給多個線程。需要牢記全局變量遠非唯一的共享方式。當(dāng)把指針傳遞到內(nèi)核其他部分時,可能就創(chuàng)建了一個新的共享。

            資源共享的hard rule:如果硬軟件資源被多于一個執(zhí)行線程共享,并且不同線程對統(tǒng)一資源可能產(chǎn)生不同的視圖,則必須明確管理對資源的訪問。比如5.1末尾提到的例子,B看不到A已經(jīng)為dptr->data[s_pos]分配了內(nèi)存,于是B才重新分配了一塊內(nèi)存,并覆蓋掉了A的內(nèi)存指針。

            另外一個重要的規(guī)則:如果內(nèi)核代碼創(chuàng)建了一個可能被內(nèi)核其它部分共享的對象,這個對象必須一直存在直到?jīng)]有外部引用。這里有2層意思:只有做好所有準(zhǔn)備后才能告訴內(nèi)核設(shè)備存在了;必須記錄共享對象的引用數(shù)。
            5.3 信號量和互斥

            我們的目的是對scull數(shù)據(jù)結(jié)構(gòu)的操作是原子的(atomic):也就是說在有其它線程會影響到這個數(shù)據(jù)結(jié)構(gòu)前,操作必須一次完成。為此需要設(shè)置一個臨界區(qū)(critical sections):其中的代碼在任何時刻只能由一個線程執(zhí)行。

            內(nèi)核提供了多種原語來滿足不同臨界區(qū)的要求。在這個例子中,scull驅(qū)動可以睡眠等待:只有用戶的直接請求會產(chǎn)生對scull數(shù)據(jù)的訪問,不會有來自中斷處理或者異步上下文中的訪問,另外scull也沒有持有關(guān)鍵的系統(tǒng)資源。所以可以使用鎖機制:可能導(dǎo)致進程在等待訪問臨界區(qū)時睡眠。

            另外很重要的一點,我們在臨界區(qū)中將執(zhí)行另外一個可能會導(dǎo)致線程休眠的操作:kmalloc申請內(nèi)存。所以我們選用的鎖原語,必須能夠在擁有鎖的線程休眠時正常工作。在這里最合適的機制是信號量。

            一個信號量本質(zhì)上是一個整數(shù)值,它和一對PV函數(shù)混合使用。希望進入臨界區(qū)的線程在相關(guān)信號量上調(diào)用P函數(shù):如果信號量值>0,值減1,,線程繼續(xù);否則線程休眠等待其它人釋放信號量。用V解鎖信號量:增加信號量值,并在必要時喚醒等待線程。

            信號量的初始值表示總共有幾個資源可供同時使用。當(dāng)信號量用于互斥時(mutual exclusion,避免多個線程同時在一個臨界區(qū)中運行),信號量的值應(yīng)該初始化為1。這時,信號量也常常稱為互斥體。

            訪問臨界區(qū)的原語
            | 鎖:可能導(dǎo)致進程休眠。
            | | 信號量:能夠在擁有鎖的線程休眠時正常工作。
            | | | 互斥體:初始化為1的信號量,用戶互斥。

            5.3.1 Linux 信號量的實現(xiàn)

            <linux/semaphore.h>
            struct semaphore

            信號量有多種聲明和初始化的方法。

            void sema_init(struct semaphore *sem, int val);
            創(chuàng)建信號量sem,賦予初始值val。

            DECLARE_MUTEX(name);
            DECLARE_MUTEX_LOCKED(name);
            第一個宏初始化信號量name為1。
            第二個宏初始化信號量name為0。這時互斥體的初始狀態(tài)是鎖定的,在允許任何線程訪問之前,必須被顯示解鎖。

            void init_MUTEX(struct semaphore *sem);
            void init_MUTEX_LOCKED(struct semaphore *sem);
            用于互斥體必須在運行時初始化,例如在動態(tài)分配互斥體的情況下。

            void down(struct semaphore *sem);
            int down_interruptible(struct semaphore *sem);
            int down_trylock(struct semaphore *sem);
            down是linux下的P操作,減少信號量的值,并在必要時一直等待。
            down_interruptible在等待時可被用戶中斷。如果操作被中斷,該函數(shù)返回非0值,并且線程不會獲得信號量。在使用down_interrupible時,必須檢查返回值。非中斷操作會建立不可殺進程(ps輸出中的D state),一般不提倡使用。
            down_trylock如果不能獲得信號量,立即返回非零值,永遠不會休眠。

            void up(struct semaphore *sem);
            up是linux下的V操作。
            任何獲得信號量的線程都必須通過up釋放信號量。如果線程在擁有信號量的時候出錯,必須在將錯誤返回給調(diào)用者之前,釋放信號量。

            5.3.2 在scull中使用信號量

            使用鎖原語的關(guān)鍵:明確要保護的資源;確保對該資源的每次訪問都使用了合適的鎖原語。

            在scull驅(qū)動中,要限制訪問的資源都在scull_dev結(jié)構(gòu)中。我們?yōu)槊總€scull設(shè)備使用一個單獨的信號量:沒有理由讓一個進程在其他進程訪問別的設(shè)備時等待;允許在不同設(shè)備上的操作并行進行。

            信號量必須在scull設(shè)備對系統(tǒng)中其他部分可用前被初始化。我們在scull_init_module中,在調(diào)用scull_setup_cdev(向內(nèi)核注冊設(shè)備)前調(diào)用了init_MUTEX。
            必須仔細檢查代碼,確保在不擁有該信號量時不訪問scull_dev結(jié)構(gòu)。例如,在scull_write開始處先申請dev->sem。
            最后,scull_write必須釋放信號量。

            5.3.3 Reader/Writer 信號量

            semaphore對所有要訪問數(shù)據(jù)的線程互斥,但實際上多個線程同時讀數(shù)據(jù)不會出錯。

            rwsem允許一個線程寫數(shù)據(jù) 或者 多個線程讀數(shù)據(jù),寫優(yōu)先級大于讀。也就是說,讀寫不能同時進行,讀寫同時申請rwsem時,寫優(yōu)先獲得。結(jié)果是,如果有大量線程等待寫數(shù)據(jù),可能讀申請很久都得不到滿足,餓死。所以,最好在很少需要寫數(shù)據(jù),并且寫數(shù)據(jù)用時很短時使用rwsem。

            下面是使用rwsem的接口:

            <linux/rwsem.h>
            struct rw_semaphore

            void init_rwsem(struct rw_semaphore *sem);
            rwsem必須在運行時顯式的初始化。

            void down_read(struct rw_semaphore *sem);
            int down_read_trylock(struct rw_semaphore *sem);
            void up_read(struct rw_semaphore *sem);
            down_read可能會使進程進入不可中斷的休眠。
            down_read_trylock在獲得訪問權(quán)限時返回非0,其它情況下返回0。 --注意返回值和一般內(nèi)核函數(shù)不同:一般內(nèi)核函數(shù)出錯返回非0。

            void down_write(struct rw_semaphore *sem);
            int down_write_trylock(struct rw_semaphore *sem);
            void up_write(struct rw_semaphore *sem);
            void downgrade_write(struct rw_semaphore *sem);
            在結(jié)束修改后調(diào)用downgrage_write,允許其它讀用戶訪問。
            5.4 complete 機制

            問題提出:

            內(nèi)核編程中經(jīng)常遇到一種情況,啟動當(dāng)前線程外部進行一項任務(wù),并等待它完成。例如:創(chuàng)建一個新的內(nèi)核線程或用戶空間進程;對一個已有進程的某個請求;某個硬件動作。

            這時可以使用信號量來同步這兩個任務(wù):start外部任務(wù)后馬上down(sem);當(dāng)外部任務(wù)完成工作時up(sem)。
                struct semphore sem;

                init_MUTEX_LOCKED(&sem);
                start_external_task(&sem);
                down(&sem);

                當(dāng)外部任務(wù)完成工作時,up(&sem);

            但信號量并不是完成這項工作的最好方法:這里調(diào)用down的線程幾乎總是要等待,會影響性能;信號量聲明為自動變量,在某些情況下,信號量圍在外部線程調(diào)用up前消失。

            解決方法:

            complete機制,允許一個線程告訴另一個線程某項工作已完成。

            <linux/completion.h>
            struct completion

            DECLARE_COMPLETION(my_completion);
            創(chuàng)建completion。

            struct completion my_completion;
            /* .. */
            init_completion(&my_completion);
            動態(tài)的創(chuàng)建和初始化completion。

            void wait_for_completion(struct completion *c);
            等待completion。該函數(shù)執(zhí)行的是非中斷等待,如果沒有人完成該任務(wù),則將產(chǎn)生一個不可殺進程。

            void complete(struct completion *c);
            void complete_all(struct completion *c);
            complete喚醒一個線程,complete_all喚醒等待的所有線程。
            用complete完成的completion可以重復(fù)使用,用complete_all完成的completion在重新使用前要重新初始化:INIT_COMPLETION(struct completion c)。

            completion機制的典型應(yīng)用:內(nèi)核模塊exit時等待線程終止
            thread
            {
                ...
                while(1){
                if(condition) break;
                }
                收尾工作
                complete_and_exit(struct completion *c, long retval);
            }

            module_exit
            {
                滿足condition
                wait_for_complete
            }

            TODO:wait_for_complete和down在等在時有什么區(qū)別?為什么后者效率就高了?
            5.5 自旋鎖

            自旋鎖可在不能休眠的代碼中使用,如中斷處理例程。 // TODO:休眠和自旋 有什么區(qū)別?優(yōu)劣之處?

            一個自旋鎖是一個互斥設(shè)備,只能有兩個值:locked和unlocked。它通常是一個int值的單個bit。申請鎖的代碼test這個bit:如果鎖可用,則set這個bit,執(zhí)行臨界區(qū)代碼;否則代碼進入重復(fù)測試這個bit的忙循環(huán),直到鎖可用。

            自旋鎖的實現(xiàn):"test and set"的過程必須以原子方式完成;當(dāng)存在自旋鎖時,申請自旋鎖的處理器進入忙循環(huán),做不了任何有用的工作。

            自旋鎖最初是為了在多處理器上使用設(shè)計的。工作在可搶占內(nèi)核的單處理器行為類似于SMP。如果非搶占式的單處理器系統(tǒng)進入自旋,會永遠自旋下去;因此,非搶占式的單處理器上的自旋鎖被優(yōu)化為什么都不過(但改變IRQ掩碼狀態(tài)的例程是個例外)。

            5.5.1 自旋鎖API介紹

            <linux/spinlock.h> spinlock_t

            初始化,在編譯時或運行時:
            spinlock_t mylock = SPIN_LOCK_UNLOCKED;
            void spin_lock_init(spinlock_t *lock);

            獲得鎖:
            void spin_lock(spinlock_t *lock);

            釋放已經(jīng)獲得的鎖:
            void spin_unlock(spinlock_t *lock);

            5.5.2 自旋鎖和原子上下文

            使用自旋鎖的核心規(guī)則:任何擁有自旋鎖的代碼都必須是原子的。它不能休眠,不能因為任何原因放棄處理器(除了某些中斷服務(wù))。

            許多內(nèi)核函數(shù)可以休眠:在用戶空間和內(nèi)核空間復(fù)制數(shù)據(jù),用戶空間頁可能要從磁盤上換入,需要休眠;分配內(nèi)存的操作(kmalloc)在等待可用內(nèi)存時會休眠。
            內(nèi)核搶占的情況由自旋鎖代碼本身處理:代碼擁有自旋鎖時,在相關(guān)處理器上的搶占會被禁止。

            在擁有自旋鎖時,需要禁止中斷。考慮一個例子:驅(qū)動程序已經(jīng)獲得了一個控制對設(shè)備訪問的自旋鎖;設(shè)備產(chǎn)生了一個中斷,中斷處理例程被調(diào)用,中斷處理例程在訪問這個設(shè)備之前,也需要這個鎖;如果中斷處理例程在最初擁有鎖的代碼所在的處理器上自旋,非中斷代碼將沒有任何機會來釋放這個鎖,處理器將永遠自旋下去。 //TODO Q:在一個CPU上被中斷的代碼,必須在同一CPU上恢復(fù)執(zhí)行?

            擁有鎖的時間要盡可能短:避免其它CPU自旋等待相同鎖的時間過長;本CPU無法調(diào)度高優(yōu)先級進程(搶占被禁止)。

            5.5.3 自旋鎖函數(shù)

            獲得自旋鎖的函數(shù)有4個:
            void spin_lock(spinlock_t *lock);
            void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
            void spin_lock_irq(spinlock_t *lock);
            void spin_lock_bh(spinlock_t *lock);
            spin_lock_irpsave會在獲取自旋鎖之前,禁止本地CPU上的中斷,并把先前中斷狀態(tài)保存在flags中。spin_lock_irp不跟蹤中斷標(biāo)志,用于明確知道沒有其它代碼禁止本地處理器的中斷時。spin_lock_bh只禁止軟件中斷,會讓硬件中斷繼續(xù)打開。
            如果某個自旋鎖可以被運行在軟件中斷或硬件中斷上下文的代碼中獲得,則必須使用某個禁止中斷的spin_lock形式。

            對應(yīng)有4個釋放自旋鎖的函數(shù):
            void spin_unlock(spinlock_t *lock);
            void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
            void spin_unlock_irq(spinlock_t *lock);
            void spin_unlock_bh(spinlock_t *lock);
            spin_lock_irqsave和spin_unlock_irqsave必須在同一函數(shù)中調(diào)用。

            非阻塞的自旋鎖操作:
            int spin_trylock(spinlock_t *lock);
            int spin_trylock_bh(spinlock_t *lock);
            spin_trylock和spin_trylock_bh在獲得自旋鎖時返回非零值,否則返回0.

            5.5.4 讀取者/寫入者 自旋鎖

            與rwsem類似,允許多個reader同時進入臨界區(qū),writer必須互斥訪問。
            可能造成reader饑餓,原因同rwsem。

            下面是相關(guān)API。

            靜/動態(tài)初始化:

            rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* Static way */

            rwlock_t my_rwlock;
            rwlock_init(&my_rwlock);  /* Dynamic way */

            reader API:

            void read_lock(rwlock_t *lock);
            void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
            void read_lock_irq(rwlock_t *lock);
            void read_lock_bh(rwlock_t *lock);

            void read_unlock(rwlock_t *lock);
            void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
            void read_unlock_irq(rwlock_t *lock);
            void read_unlock_bh(rwlock_t *lock);

            writer API:

            void write_lock(rwlock_t *lock);
            void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
            void write_lock_irq(rwlock_t *lock);
            void write_lock_bh(rwlock_t *lock);
            int write_trylock(rwlock_t *lock);

            void write_unlock(rwlock_t *lock);
            void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
            void write_unlock_irq(rwlock_t *lock);
            void write_unlock_bh(rwlock_t *lock);
            5.6 鎖陷阱

            5.6.1 不明確的規(guī)則

            在創(chuàng)建可被并行訪問對象的同時,定義控制訪問的鎖。

            如果某個已經(jīng)獲得鎖的函數(shù),試圖調(diào)用其他同樣試圖獲得同一鎖的函數(shù),代碼就會死鎖。

            有些函數(shù)需要假定調(diào)用者已經(jīng)獲得了某些鎖:如果函數(shù)供外部調(diào)用,必須顯式的處理鎖定;如果是內(nèi)部函數(shù),必須顯式說明這種假定,防止以后忘記。

            5.6.2 鎖的順序規(guī)則

            在必須獲得多個鎖時,應(yīng)該始終以相同的順序獲得:假設(shè)線程1和線程2都要獲得LOCK1、LOCK2,如果線程1拿到了LOCK1,同時線程2拿到了LOCK2,就出現(xiàn)死鎖了。

            兩條TIPS。如果需要獲得一個局部鎖(如設(shè)備鎖)和一個內(nèi)核更中心位置的鎖,先獲得局部鎖。如果需要同時擁有信號量和自旋鎖,必須先獲得信號量:在擁有自旋鎖時調(diào)用down(可能導(dǎo)致休眠)是個嚴重錯誤。

            5.6.3 細粒度鎖和粗粒度鎖的對比

            粗粒度鎖影響性能,細粒度鎖增加復(fù)雜性。

            應(yīng)該在最初使用粗粒度鎖,抑制自己過早考慮優(yōu)化的欲望,因為真正的性能瓶頸常出現(xiàn)在非預(yù)期的地方。
            ch06 高級字符驅(qū)動程序操作

            6.1 ioctl

            支持通過設(shè)備驅(qū)動程序執(zhí)行各種類型的硬件控制。

            用戶空間原型:
            int ioctl(int fd, unsigned long cmd, ...);

            內(nèi)核空間原型:
            int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
            注意不管用戶控件傳遞的參數(shù)的值還是指針,在內(nèi)核中都用unsigned long表示。

            大多數(shù)ioctl實現(xiàn)都包括一個switch語句,根據(jù)cmd參數(shù)選擇對應(yīng)操作。

            6.1.1 選擇ioctl命令

            兩個關(guān)于選擇ioctl編號的文檔。include/asm/ioctl.h定義了要使用的位字段:類型(幻數(shù))、序數(shù)、傳送方向、參數(shù)大小等。Documentation/ioctl-numbet.txt中羅列了內(nèi)核所使用的幻數(shù),選擇自己的幻數(shù)時注意避免和內(nèi)核沖突。

            4個位字段:type是8位寬的幻數(shù);number是8位寬的序數(shù);direction數(shù)據(jù)傳輸方向:該字段是個位掩碼,可用AND從中分解出_IOC_READ和_IOC_WRITE;size是所涉及的用戶數(shù)據(jù)大小。

            構(gòu)造cmd的宏(在<linux/ioctl.h>中包含的<asm/ioctl.h>中定義):
            _IO(type, nr)構(gòu)造無參數(shù)的ioctl命令;_IOR(type, nr, datatype)讀數(shù)據(jù);_IOW(type, nr, datatype)寫數(shù)據(jù);_IORW(type, nr, datatype)雙向傳輸。
            type和number通過參數(shù)type和nr傳入,size通過對datatype取sizeof獲得。

            對cmd解碼的宏:
            _IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr), and _IOC_SIZE(nr).

            6.1.2 返回值

            cmd不匹配任何合法操作時,返回-EINVAL。

            6.1.3 預(yù)定義命令

            內(nèi)核預(yù)定義了一些ioctl命令。其中一類是可用于任何文件的命令,幻數(shù)都是“T”。

            6.1.4 使用ioctl參數(shù)

            如果ioctl的附加參數(shù)是指向用戶空間的指針,必須確保指向的用戶空間是合法的。

            acess_ok函數(shù)驗證地址:檢查所引用的內(nèi)存是否位于當(dāng)前進程有訪問權(quán)限的區(qū)域;特別地,要確保地址沒有指向內(nèi)核空間的內(nèi)存區(qū)。

            6.1.5 權(quán)能與受限操作

            <linux/capability.h>

            基于權(quán)能(capability)的系統(tǒng)把特權(quán)操作分成獨立的組。

            <sys/sched.h>
            int capable(int capability);
            檢查進程是否擁有權(quán)能capability。
            6.3 poll 和 select

            驅(qū)動程序決定,是否可以對一個或多個打開的文件,做非阻塞的讀取或?qū)懭搿?/div>

            驅(qū)動程序的poll方法:
            unsigned int (*poll)(struct file *filp, poll_table *wait);
            當(dāng)用戶程序在與驅(qū)動相關(guān)聯(lián)的文件描述符上執(zhí)行poll/select時被調(diào)用。
            分兩步處理:調(diào)用poll_wait向poll_table添加一個或多個 可指示poll狀態(tài)變化的等待隊列;返回一個描述操作是否可以利益執(zhí)行的位掩碼。

            void poll_wait(struct file *, wait_queue_head_t *, poll_table);
            ch12 pci驅(qū)動

            PCI是一套完整的規(guī)則,定義計算機的各個部分如何交互。PCI驅(qū)動負責(zé)發(fā)現(xiàn)硬件并訪問。

            PCI體系結(jié)構(gòu)的目的:提高計算機和外設(shè)間數(shù)據(jù)傳輸性能;盡可能地和平臺無關(guān);簡化外設(shè)的添加和移除。

            PCI設(shè)備在啟動時配置。設(shè)備驅(qū)動必須可以訪問配置信息,并完成設(shè)備初始化。不需要PCIqudo
            12.1.1 PCI尋址

            struct dev_pci 代表pci設(shè)備。

            每個PCI外設(shè)由 <總線號,設(shè)備號,功能號> 確定。Linux另外引入了PCI domains的概念,每個domain可以有256根總線,每根總線可以有32個設(shè)備,每個設(shè)備下可以有8個功能號(例如一個audio設(shè)備有一個CD-ROM功能號):所以每個功能可以由16位的地址表示。
            查看16位硬件地址:lspci;/proc/pci;/proc/bus/pci。地址一般按16進制表示:<8位總線號:8位設(shè)備號和功能號>;<8位總線號:5位設(shè)備號,3位功能號>;<domain:bus:device:funciton>。

            較新的工作站至少有兩根總線。每兩根總線用特殊外設(shè)bridges連接。PCI系統(tǒng)的總體布局是一棵樹:總線通過bridges連接到上層總線上,直到樹根(0號總線)。

            外設(shè)板答復(fù)3類地址空間的查詢:內(nèi)存單元,I/O端口,配置寄存器空間。同一PCI總線上的所有設(shè)備共享前兩個地址(也就是說,訪問一個內(nèi)存單元時,該總線上的所有設(shè)備都能看到總線周期)。配置空間利用了地理尋址,同一時刻只對應(yīng)一個slot,不會產(chǎn)生沖突。

            下面的一堆沒有看懂。

            12.1.2 啟動時間 

            當(dāng)PCI設(shè)備上電時,它只響應(yīng)配置信息的存取:設(shè)備沒有內(nèi)存空間,也沒有I/O端口被映射到內(nèi)存空間;中斷報告也被關(guān)閉。

            系統(tǒng)啟動時,固件(bios或內(nèi)核)讀取每個PCI外設(shè)的配置信息,為其分配地址去。在驅(qū)動存取設(shè)備時,它的內(nèi)存區(qū)和I/O區(qū)已經(jīng)被映射到處理器的地址空間。

            通過讀/proc/bus/pcidrivers 和 /proc/bus/pci/*/* 可查看PCI設(shè)備列表和配置寄存器。

            12.1.3 配置寄存器和初始化

            所有PCI設(shè)備都有至少256字節(jié)的配置地址空間:前64個字節(jié)是標(biāo)準(zhǔn)的,剩下的依賴于設(shè)備。

            PCI寄存器是小端模式。如果需要在主機序和PCI序之間轉(zhuǎn)換,參考<asm/byteorder.h>

            寄存器類型:
            vendorID    16位,硬件制造商
            deviceID    16位,由硬件制造商選擇。vendorID和deviceID合起來被稱作“簽名”,驅(qū)動依靠簽名來標(biāo)識設(shè)備。
            classID 16位,類。一個驅(qū)動可以同時支持幾個屬于同一類,但有不同簽名的設(shè)備。
            subsystem venderID/subsystem deviceID 進一步標(biāo)識設(shè)備。

            struct pci_device_id 定義一個驅(qū)動支持的PCI設(shè)備的列表:
            __u32 vendor;    // 驅(qū)動支持的設(shè)備的PCI供應(yīng)商和設(shè)備ID,PCI_ANY_ID表示支持任何。
            __u32 device;
            __u32 subvendor;
            __u32 subdevice;
            __u32 class;    // 驅(qū)動支持的PCI設(shè)備種類,PCI_ANY_ID表示支持任何類
            __u32 class_mask;
            kernel_ulong_t driver_data;    // 用來持有信息,也可以來標(biāo)識設(shè)備。

            初始化一個struct pci_device_id:
            PCI_DEVICE(vendor, device);
            PCI_DEVICE(device_class, device_class_mask);

            12.1.4 MODULEDEVICETABLE宏

            MODULEDEVICETABLE將pci_device_id結(jié)構(gòu)輸出到用戶空間,供熱插拔和模塊加載系統(tǒng)知道驅(qū)動模塊匹配的硬件。

            該宏創(chuàng)建 指向struct pci_device_id的 局部變量_mod_pci_device_table;depmod遍歷所有模塊,將查找到的_mod_pci_device_table添加到文件 /lib/modules/KERNEL_VERSION/modules.pcimap中;當(dāng)有新的PCI設(shè)備連到計算機上,熱插拔系統(tǒng)使用文件modules.pcimap找到對應(yīng)的驅(qū)動并加載。

            12.1.5 注冊一個PCI驅(qū)動

            PCI驅(qū)動結(jié)果,包含許多回調(diào)函數(shù)和變量。
            struct pci_driver 
            const char *name;    // 驅(qū)動名稱,在內(nèi)核所有PCI驅(qū)動里必須是唯一的。可以在/sys/bud/pci/drivers中查看。
            const struct pci_device_id *id_tables;    // 指向struct pci_device_id表的指針。
            int (*probe)(struct pci_dev *dev, const struct pci_device_id *id);    // PCI核心發(fā)現(xiàn)與該驅(qū)動匹配的struct pci_dev時,調(diào)用probe。如果驅(qū)動支持該pci_dev,正確初始化設(shè)備并返回0;否則返回一個錯誤值。
            int (*remove)(struct pci_dev);    // 當(dāng)struct pci_dev從系統(tǒng)中移除時,由PCI核心調(diào)用;PCI驅(qū)動被移除時,也會調(diào)用remove函數(shù)來移除驅(qū)動支持的設(shè)備。
            int (*suspend)(struct pci_dev *dev, u32 state);    // 當(dāng)struct pci_dev被掛起時,由PCI核心調(diào)用。state指明掛起狀態(tài)。可選的實現(xiàn)。
            int (*resume)(struct pci_dev *dev);    // 當(dāng)pci_dev恢復(fù)時,由PCI核心調(diào)用。

            總之,創(chuàng)建一個pci驅(qū)動時,至少只需初始化4個成員:
            static struct pci_driver pci_driver = {
                .name = "pci_skel",
                .id_table = ids,
                .probe = probe,
                .remove = remove,
            };

            注冊一個PCI驅(qū)動:
            int pci_register_driver(struct pci_driver *drv);
            .注冊struct pci_driver到PCI核心,成功返回0,失敗返回一個錯誤值。它不返回綁定到驅(qū)動上的設(shè)備號;實際上,當(dāng)沒有設(shè)備被綁定到驅(qū)動上時,也算注冊成功:驅(qū)動把自己注冊到PCI核心,當(dāng)PCI核心發(fā)現(xiàn)有新設(shè)備時,PCI核心調(diào)用驅(qū)動提供的回調(diào)函數(shù)初始化設(shè)備。

            卸載一個PCI驅(qū)動:
            void pci_unregister_driver(struct pci_driver *drv);
            該函數(shù)移除綁定到這個驅(qū)動上的所有設(shè)備,然后調(diào)用驅(qū)動的remove函數(shù)。 TODO Q:調(diào)用remove 和 移除綁定到驅(qū)動上的設(shè)備 不一樣么?

            12.1.6 老式PCI探測

            老版本內(nèi)核中,PCI驅(qū)動手動瀏覽系統(tǒng)中PCI設(shè)備。在2.6中已經(jīng)去掉了驅(qū)動瀏覽PCI設(shè)備列表的功能(處于安全考慮),而是由PCI核心發(fā)現(xiàn)設(shè)備時調(diào)用已經(jīng)注冊的驅(qū)動。

            查找一個特定PCI設(shè)備:
            struct pci_dev *pci_get_device(unsigned int vendor, unsigned int device, struct pci_dev *from);
            struct pci_dev *pci_get_subsys(unsigned int vendor, unsigned int device, unsigned int ss_vendor, unsigned int ss_device, struct pci_dev *from);
            如果找到匹配設(shè)備,遞增struct pci_dev中的found引用計數(shù);當(dāng)驅(qū)動用完該pci_dev時,需要調(diào)用pci_dev_put遞減found引用計數(shù)。
            from表示從哪個設(shè)備開始探索,如果from設(shè)為NULL,表示返回第一個匹配的設(shè)備。
            該函數(shù)不能從中斷上下文中調(diào)用。

            在指定的pci bus上查找設(shè)備:
            struct pci_dev *pci_get_slot(struct pci_bus *bus, unsigned int devfn);
            devfn指明pci設(shè)備的功能號。

            12.1.7 使能pci設(shè)備

            int pci_enable_device(struct pci_dev *dev);
            在PCI驅(qū)動的probe()函數(shù)中,必須調(diào)用pci_enable_device(),之后驅(qū)動才可以存取PCI設(shè)備的資源。

            12.1.8 存取配置空間

            驅(qū)動已經(jīng)探測到設(shè)備后,需要讀寫3個地址空間:內(nèi)存、I/O端口和配置空間。其中,需要先讀取配置空間,獲取設(shè)備被映射到的內(nèi)存和I/O空間。

            配置空間可以通過8/16/32為數(shù)據(jù)傳輸來讀寫:
            int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);
            int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);
            int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);
            int pci_write_config_byte(struct pci_dev *dev, int where, u8 val);
            int pci_write_config_word(struct pci_dev *dev, int where, u16 val);
            int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);
            where指從配置空間開始的字節(jié)偏移,讀取到的值從val返回。
            word和dword函數(shù)負責(zé)從小端到處理器本地字節(jié)序的轉(zhuǎn)換。

            12.1.9 存取I/O和內(nèi)存空間

            一個PCI設(shè)備至多有6個I/O地址區(qū):每個地址區(qū)要么是內(nèi)存區(qū),要么是I/O區(qū)。I/O寄存器不應(yīng)該被緩存,所以對于在內(nèi)存中實現(xiàn)I/O寄存器的設(shè)備,必須在配置寄存器中設(shè)置一個"內(nèi)存可預(yù)取"位,表明內(nèi)存區(qū)是否可預(yù)取。

            一個PCI設(shè)備使用6個32位的配置寄存器,指明它的地址區(qū)大小和位置。這6個配置寄存器的符號名是PCI_ADDRESS_0到PCI_BASE_ADDRESS_5。

            獲取設(shè)備的地址區(qū):
            unsigned long pci_resource_start(struct pci_dev *dev, int bar);
            unsigned long pci_resource_end(struct pci_dev *dev, int bar);
            unsigned long pci_resource_flags(struct pci_dev *dev, int bar);
            返回第bar個地址區(qū)的第一個地址,最后一個地址,flags資源標(biāo)識。
            資源標(biāo)識定義在<linux/ioport.h>中。

            12.1.10 PCI中斷

            linux啟動時,固件給設(shè)備分配一個唯一的中斷號。
            PCI設(shè)備的中斷號存儲在配置寄存器60(PCI_INTERRUPT_LINE),1字節(jié)寬。
            如果不支持中斷,寄存器61(PCI_INTERRUPT_PIN)是0.
            ch17 網(wǎng)絡(luò)驅(qū)動程序

            網(wǎng)絡(luò)驅(qū)動程序異步接收來自外部世界的數(shù)據(jù)包,發(fā)送給內(nèi)核。

            octet指一組8個的數(shù)據(jù)位,是能為網(wǎng)絡(luò)設(shè)備和協(xié)議所理解的最小單位。


            17.1 snull設(shè)計

            snull模擬了和遠程主機的回話,不依賴于硬件。只支持ip流。

            17.1.1 分配ip號

            snull創(chuàng)建了兩個接口。通過一個接口傳輸?shù)臄?shù)據(jù),都會出現(xiàn)在另外一個接口上。

            如果給兩個接口使用相同的ip號,內(nèi)核會使用回環(huán)通道,而不會通過snull,所以在傳輸中需要修改源及目的地址。 // TODO


            17.2 連接到內(nèi)核

            推薦閱讀 lookup.c plip.c e100.c

            17.2.1 設(shè)備注冊

            網(wǎng)絡(luò)接口沒有主次設(shè)備號一類的東西,而是對每個新檢測到的接口,向全局的網(wǎng)絡(luò)連接設(shè)備鏈表中插入一個數(shù)據(jù)結(jié)構(gòu) net_device。
            <linux/netdevice.h>
            struct net_device *snull_devs[2];
            struct net_device包含了一個kobject和引用計數(shù)。

            由于和其它結(jié)構(gòu)相關(guān),必須動態(tài)分配:
            struct net_device *alloc_netdev(int sizeof_priv, const char *name, void(*setup)(struct net_device *));
            sizeof_priv是驅(qū)動程序私有數(shù)據(jù)大小;name接口名稱,在用戶空間可見;setup初始化函數(shù),用來設(shè)置net_device結(jié)構(gòu)剩余部分。
            網(wǎng)絡(luò)子系統(tǒng)對alloc_netdev函數(shù),為不同種類的接口封裝了許多函數(shù),最常用的是alloc_etherdev<linux/etherdevice.h>。

            調(diào)用register_netdev注冊設(shè)備,立即生效。

            17.2.2 初始化每個設(shè)備

            在調(diào)用register_netdev之前,必須完成初始化。
            先調(diào)用ether_setup(struct net_device)為結(jié)構(gòu)設(shè)置默認值,然后再給我們關(guān)心的成員賦值。在scull中,flags設(shè)置為IFF_NOARP,表示接口不使用ARP協(xié)議;hard_header_cache設(shè)置為NULL,禁止對ARP的緩存。

            struct net_device中priv成員保存設(shè)備私有數(shù)據(jù),和net_device結(jié)構(gòu)同時分配內(nèi)存(字符驅(qū)動中fops->private是單獨分配的)。不推薦直接訪問priv指針,一般通過netdev_priv函數(shù)訪問:
            struct scull_priv *priv = netdev_priv(dev);

            17.2.3 模塊的卸載

            for (i = 0; i < 2;  i++) {
                if (snull_devs[i]) {
                    unregister_netdev(snull_devs[i]);
                    snull_teardown_pool(snull_devs[i]);
                    free_netdev(snull_devs[i]);
                }
            }

            unregister_netdev從系統(tǒng)中刪除接口;free_netdev將net_device結(jié)構(gòu)返回給系統(tǒng)(引用計數(shù)減1);snull_teardown_pool是內(nèi)部清楚函數(shù),必須在free_netdev之前調(diào)用:調(diào)用free_netdev之后不能再引用設(shè)備或私有數(shù)據(jù)區(qū)。
            17.3 net_device結(jié)構(gòu)細節(jié)

            //一堆成員列表,米有看。。。
            17.4 打開和關(guān)閉

            在接口能夠傳輸數(shù)據(jù)包之前,內(nèi)核必須打開接口并賦予其地址。
            用ifconfig給接口賦予地址時,執(zhí)行兩個任務(wù):通過ioctl(SIOCSIFADDR)賦予地址,該任務(wù)由內(nèi)核進行;通過ioctl(SIOCSIFFLAGS)設(shè)置dev->flags中的IFF_UP標(biāo)志以打開接口,該命令會調(diào)用設(shè)備的open方法。

            在接口被關(guān)閉時,ifconfig使用ioctl(SIOCSIFFLAGS)清除IFF_UP標(biāo)志,然后調(diào)用stop函數(shù)。

            open的工作:將MAC地址從硬件設(shè)備復(fù)制到dev->dev_addr;啟動接口的傳輸隊列(允許接口傳輸數(shù)據(jù)包)。

            stop的工作:關(guān)閉接口的傳輸隊列、

            void netif_start_queue(struct net_device *dev);
            void netif_stop_queue(struct net_device *dev);
            cg_cdev

            insmod后
            # cat /dev/devices
            可以看到字符設(shè)備中列的有cg_cdev。
            --
                /dev/...目錄和/proc/devices/...下的設(shè)備的關(guān)系

                /proc/devices/中的設(shè)備是通過insmod加載到內(nèi)核的,它的主設(shè)備號(major)作為mknod的參數(shù)。 
            /dev/*.* 是通過mknod加上去的,格式:mknod device c/b major minor 注意要在/dev目錄下建立設(shè)備節(jié)點,如:cd /dev 然后,mknod nrf24l01 c 238 0,用戶程序可通過設(shè)備名/dev/nrf24l01來調(diào)用驅(qū)動程序從而訪問硬件。 

            --

            使用awk工具可以從/proc/devices 文件中獲取設(shè)備號,建立一個.sh的腳本文件加入腳本:

            module=xxx
            major=`awk "\\$2==\"$module\" {print \\$1}" /proc/devices`
            insmod xxx.ko
            mknod /dev/xxx c $major 0

            xxx為設(shè)備名

            posted on 2011-01-05 23:24 小默 閱讀(2968) 評論(0)  編輯 收藏 引用 所屬分類: Linux

            導(dǎo)航

            統(tǒng)計

            • 隨筆 - 289
            • 文章 - 0
            • 評論 - 84
            • 引用 - 0

            留言簿(13)

            隨筆分類(287)

            隨筆檔案(289)

            漏洞

            搜索

            •  

            積分與排名

            • 積分 - 295551
            • 排名 - 89

            最新評論

            閱讀排行榜

            久久精品中文字幕一区| 亚洲狠狠综合久久| 亚洲综合日韩久久成人AV| 色偷偷久久一区二区三区| 色诱久久久久综合网ywww| 中文字幕成人精品久久不卡| 久久亚洲2019中文字幕| 亚洲愉拍99热成人精品热久久| 久久国产精品无码HDAV| 一级做a爰片久久毛片毛片| 久久久久久午夜成人影院| 久久综合伊人77777麻豆| 国产精品久久久久影院嫩草| 亚洲精品tv久久久久| 狠狠色丁香久久综合婷婷| 亚洲精品无码久久不卡| 99久久精品免费看国产| 无码专区久久综合久中文字幕| 91精品国产高清久久久久久国产嫩草| 久久综合鬼色88久久精品综合自在自线噜噜 | 欧美久久久久久精选9999| 久久不见久久见免费视频7| 香蕉aa三级久久毛片| 欧美激情精品久久久久| 久久精品无码一区二区WWW | 精品久久久久久无码国产| 99精品国产在热久久无毒不卡 | 久久香蕉国产线看观看精品yw| 人妻丰满?V无码久久不卡| 国产成人久久777777| 精品一区二区久久| 97久久天天综合色天天综合色hd| 无码专区久久综合久中文字幕| 一本色道久久综合亚洲精品| 狠狠色综合网站久久久久久久高清| 思思久久99热免费精品6| 色婷婷噜噜久久国产精品12p| 久久夜色撩人精品国产| 国产精品久久久久久久app | 久久久久女人精品毛片| 精品国产乱码久久久久久郑州公司|