• <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讀書(shū)筆記

            Google 筆記本
            ldd3讀書(shū)筆記

            最近編輯過(guò)的 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ū)動(dòng)分析

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

            用lshw查看網(wǎng)卡對(duì)應(yīng)的驅(qū)動(dòng)。我的是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看到對(duì)應(yīng)模塊:
            $lsmod

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

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

            --
            2  嘗試編譯驅(qū)動(dòng)
            把pcnet32.c拷貝出來(lái)。
            掃了眼net下面的Makefile,300行,嚇?biāo)牢银B(niǎo)。
            然后寫(xiě)了個(gè)4行的Makefile,居然編譯插入運(yùn)行一些正常,囧翻了。。。

            --
            3  分析代碼

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

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


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

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

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

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

            當(dāng)CPU運(yùn)行于硬/軟中斷環(huán)境下時(shí),不可以被阻塞。這時(shí)有2種方法實(shí)現(xiàn)和用戶(hù)態(tài)的通信。
            第1種方法:用一個(gè)和進(jìn)程上下文相關(guān)的內(nèi)核線程,完成從用戶(hù)態(tài)接收數(shù)據(jù)的任務(wù);再通過(guò)一個(gè)臨界區(qū)將數(shù)據(jù)傳給中斷過(guò)程。缺點(diǎn):中斷過(guò)程不能實(shí)時(shí)接收來(lái)自用戶(hù)態(tài)進(jìn)程的數(shù)據(jù)。
            第2中方法:使用netlink套接字。用一個(gè)軟中斷調(diào)用用戶(hù)實(shí)現(xiàn)指定的接收函數(shù);再通過(guò)臨界區(qū)將數(shù)據(jù)傳給中斷過(guò)程。因?yàn)槭褂密浿袛鄟?lái)接收數(shù)據(jù),所以可以保證數(shù)據(jù)接收的實(shí)時(shí)性。
            Q:軟中斷不可以被阻塞,怎么從用戶(hù)空間接收數(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  /*用戶(hù)空間進(jìn)程ID*/
            #define IMP2_K_MSG 1  /*內(nèi)核發(fā)出的消息*/
            #define IMP2_CLOSE 2  /*用戶(hù)空間進(jìn)程關(guān)閉*/

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

            /*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;

            /*信號(hào)SIGINT的處理函數(shù)-告訴內(nèi)核用戶(hù)端關(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è)置用戶(hù)端地址,綁定套接字和地址*/
            skfd = socket(PF_NETLINK, SOCK_RAW, NL_IMP2);
            /*netlink套接字,用戶(hù)層端地址*/
            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信號(hào)的處理函數(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;

            /*把用戶(hù)端進(jìn)程的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ā)來(lái)的數(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點(diǎn)截獲ICMP數(shù)據(jù)報(bào),記錄源地址和目錄地址
             * 使用netlink套接字將信息傳遞給用戶(hù)進(jìn)程,再由用戶(hù)進(jìn)程打印到終端上
             */
             
            #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ù),負(fù)責(zé)接收用戶(hù)層端的數(shù)據(jù)。運(yùn)行在軟中斷環(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)){
            /*用戶(hù)層端進(jìn)程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);
            }
            /*用戶(hù)端進(jìn)程關(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的用戶(hù)端發(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é)對(duì)齊后的數(shù)據(jù)報(bào)長(zhǎng)度*/

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

            /*填寫(xiě)數(shù)據(jù)報(bào)相關(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ì)算經(jīng)過(guò)字節(jié)對(duì)其后nlmsg的長(zhǎng)度*/
            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ā)送失敗,則撤銷(xiāo)套接字*/
            nlmsg_failure:
            if(skb)
            kfree_skb(skb);

            return -1;
            }

            /* netfilter NF_IP_PRE_ROUTING點(diǎn)的掛接函數(shù)
             * 截獲ip數(shù)據(jù)報(bào)
             */
            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的用戶(hù)層端*/
            }
            else
            read_unlock_bh(&user_proc.lock);
            }

            return NF_ACCEPT;
            }

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

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

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

            module_init(init);
            module_exit(fini);

            at the beginning

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

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

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

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

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

            # makefile寫(xiě)法 2010-11-15 17:14 小默

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

            如果模塊來(lái)自?xún)蓚€(gè)源文件file1.c和file2.c:
            obj-m := module.o
            module-objs := file1.o file2.o

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

            -----

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

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

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

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

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

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

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

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

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

            說(shuō)10次hello,Mom
            # insmod hellop howmany=10 whom="Mom"

            --

            static char *whom = "world"; //必須給默認(rèn)值
            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修改,不會(huì)通知模塊。不要使參數(shù)可寫(xiě),除非準(zhǔn)備好檢測(cè)參數(shù)改變。

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

            # 設(shè)備主次編號(hào) 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è)備

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

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

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

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

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

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

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

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

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

            # 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刪除無(wú)效的節(jié)點(diǎn),不能刪除device0,device1,device2...阿。。TODO
            11 rm -f /dev/${device}[0-3]
            12 
            13 major=$(awk "\\$2==\"$module\" {print \\$1}" /proc/devices) #TODO 沒(méi)有搞明白
            14 mknod /dev/${device}0 c $major 0 #創(chuàng)建4個(gè)虛擬設(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 # 改變?cè)O(shè)備的組和模式。腳本必須以root運(yùn)行,但設(shè)備使用可能需要其它用戶(hù)寫(xiě)。
            24 chgrp $group /dev/${device}[0-3]
            25 chmod $mode /dev/${device}[0-3]
            26 
              回復(fù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

            # 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ù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

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

            12 // 使用struct scull_dev結(jié)構(gòu)表示每個(gè)設(shè)備
            13 // TODO 沒(méi)有理解什么意思
            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()來(lái)初始化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ù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

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

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

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

            // 表示每個(gè)設(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 - 每個(gè)內(nèi)存區(qū)域?yàn)橐粋€(gè)量子,數(shù)組為一個(gè)量子集
            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
            };

            // 量子集,即一個(gè)內(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; // 量子集中的第一個(gè)元素
            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); // 量子集中的第一個(gè)元素
            if(dptr == NULL || !dptr->data || !dptr->data[s_pos])
            goto out;

            // 只讀取到這個(gè)量子的尾部
            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;
            }

            // 一次處理單個(gè)量子
            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;
            }

            // 只寫(xiě)到這個(gè)量子的結(jié)束
            if(count > quantum-q_pos)
            count = quantum - q_pos;
            // 從用戶(hù)空間拷貝一整段數(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ù)量到每個(gè)緩存;writev收集每個(gè)緩存的內(nèi)容到一起并且作為單個(gè)寫(xiě)操作送出它們。
            // count參數(shù)告訴有多少iovec結(jié)構(gòu),這些結(jié)構(gòu)由應(yīng)用程序創(chuàng)建,但是內(nèi)核在調(diào)用驅(qū)動(dòng)之前拷貝它們到內(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; // 開(kāi)始于iov_base(在用戶(hù)空間)
            __kernel_size_t iov_len; // 并有iov_len長(zhǎng)
            };
              回復(fù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

            # 重定向控制臺(tái)消息 2010-11-30 22:44 小默

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

            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傳遞一個(gè)指向字節(jié)數(shù)組的指針作為參數(shù),數(shù)組的第一個(gè)字節(jié)是一個(gè)數(shù)(需要指定的子命令)。
            // 當(dāng)子命令是11時(shí),下一個(gè)字節(jié)指定虛擬控制臺(tái)。
            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ù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

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

            // 在proc里實(shí)現(xiàn)文件,在文件被讀時(shí)產(chǎn)生數(shù)據(jù)。
            // 當(dāng)一個(gè)進(jìn)程讀你的/proc文件,內(nèi)核分配了一頁(yè)內(nèi)存,驅(qū)動(dòng)可以寫(xiě)入數(shù)據(jù)返回給用戶(hù)空間。
            // buf 寫(xiě)數(shù)據(jù)的緩沖區(qū);start有關(guān)數(shù)據(jù)寫(xiě)在頁(yè)中哪里;eof必須被驅(qū)動(dòng)設(shè)置,表示寫(xiě)數(shù)據(jù)結(jié)束;data用來(lái)傳遞私有數(shù)據(jù)。
            // 假定不會(huì)有必要產(chǎn)生超過(guò)一頁(yè)的數(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è)備號(hào),量子集大小,量子大小,存儲(chǔ)的數(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 顯示一個(gè)指針
            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í)際在頁(yè)中寫(xiě)了多少數(shù)據(jù)
            }


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

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

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

            // sfile 總被忽略;pos指從哪兒開(kāi)始讀,具體意義完全依賴(lài)于實(shí)現(xiàn)。
            // seq_file典型的實(shí)現(xiàn)是遍歷一感興趣的數(shù)據(jù)序列,pos就用來(lái)指示序列中的下一個(gè)元素。
            // 在scull中,pos簡(jiǎn)單地作為scull_array數(shù)組的索引。
            // 原型
            void *start(struct seq_file *sfile, loff_t *pos);
            // 在scull中的實(shí)現(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表示沒(méi)有更多數(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的實(shí)現(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沒(méi)有要清理的東西,stop方法是空的

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

            // 在start和stop期間,內(nèi)核調(diào)用show輸出迭代器v生成的數(shù)據(jù)到用戶(hù)空間
            int show(struct seq_file *sfile, void *v);
            // 輸出,等效于用戶(hù)空間的printf。返回非0值表示緩沖滿(mǎn),輸出的數(shù)據(jù)會(huì)被丟棄。不過(guò)大多數(shù)實(shí)現(xiàn)都忽略返回值。
            int seq_sprintf(struct seq_file *sfile, const char *fmt, ...);
            // 等效于用戶(hù)空間的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進(jìn)制輸出。常見(jiàn)的esc是"\t\n\\",用于保持空格,避免搞亂輸出。
            int seq_escape(struct seq_file *m, const char *s, const char *esc);
            // scull中show實(shí)現(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的一個(gè)指針,囧。。。
            // 迭代器操作集
            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),實(shí)現(xiàn)內(nèi)核read/seek文件的所有操作。
            // 創(chuàng)建一個(gè)open方法,連接文件和seq_file操作 TODO 沒(méi)看懂
            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ù)  更多評(píng)論 刪除評(píng)論  修改評(píng)論

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

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

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

            講了兩部分

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

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

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

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

            原子類(lèi)型定義:
            typedef struct
            {
            volatile int counter;
            }
            atomic_t; 

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

            # 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ù)  更多評(píng)論 <a id="AjaxHolder_Comments_CommentList_ctl24_DeleteLink"

            ch03 字符驅(qū)動(dòng)

            3.4 字符設(shè)備注冊(cè)

            struct cdev  <linux/cdev.h>

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

            如果期望獲得一個(gè)獨(dú)立的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成員為T(mén)HIS_MODULE

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

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


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

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


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

             // 注冊(cè)字符設(shè)備
            static void scull_setup_cdev(struct scull_dev *dev, int index)
            {
                
            int err, devno = MKDEV(scull_major, scull_minor + index); // MKDEV 把主次設(shè)備號(hào)合成為一個(gè)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: 第一個(gè)設(shè)備號(hào)
                                1);             // unsigned count: 該設(shè)備連續(xù)次設(shè)備號(hào)的數(shù)目
                /* Fail gracefully if need be */
                
            if (err)
                    printk(KERN_NOTICE 
            "Error %d adding scull%d", err, index);
            }




            // 注冊(cè)字符設(shè)備過(guò)去的方法
            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ū)動(dòng)中用來(lái)做一些初始化準(zhǔn)備工作。

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

            open原型:

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

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

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

                
            // 文件私有數(shù)據(jù),設(shè)置成對(duì)應(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 */
                
            // 文件以只讀模式打開(kāi)時(shí),截?cái)酁?
                if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
                    
            if (down_interruptible(&dev->sem)) // 申請(qǐng)新號(hào)量,可中斷
                        return -ERESTARTSYS;
                    
            // 釋放整個(gè)數(shù)據(jù)區(qū)
                    scull_trim(dev); /* ignore errors */ 
                    up(
            &dev->sem);  // 釋放信號(hào)量
                }
                
            return 0;          /* success */
            }

            3.5.2 release方法

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

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


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

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

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

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

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


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

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

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

            // 釋放整個(gè)數(shù)據(jù)區(qū)。簡(jiǎn)單遍歷列表并且釋放它發(fā)現(xiàn)的任何量子和量子集。
            // 在scull_open 在文件為寫(xiě)而打開(kāi)時(shí)調(diào)用。
            // 調(diào)用這個(gè)函數(shù)時(shí)必須持有信號(hào)量。
            /*
             * 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;

                // 遍歷多個(gè)量子集。dev->data 指向第一個(gè)量子集。
                for (dptr = dev->data; dptr; dptr = next) { /* all the list items */
                    if (dptr->data) { // 量子集中有數(shù)據(jù)
                        for (i = 0; i < qset; i++) // 遍歷釋放當(dāng)前量子集中的每個(gè)量子。量子集大小為qset。 
                            kfree(dptr->data[i]);
                        kfree(dptr->data); // 釋放量子指針數(shù)組
                        dptr->data = NULL; 
                    }
                    // next獲取下一個(gè)量子集,釋放當(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進(jìn)行在內(nèi)核和用戶(hù)空間之間復(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是要讀寫(xiě)字節(jié)數(shù)。buff指向用戶(hù)空間地址。offp是指向long offset type對(duì)象的指針,指明要訪問(wèn)的文件位置。
            返回實(shí)際傳輸?shù)淖止?jié)數(shù),出錯(cuò)返回負(fù)值。

            訪問(wèn)用戶(hù)空間地址char __user *buff:

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

            應(yīng)該通過(guò)內(nèi)核提供的函數(shù)訪問(wèn)用戶(hù)空間。
            scull中復(fù)制一整段數(shù)據(jù),使用下面兩個(gè)函數(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);
            使用上面兩個(gè)函數(shù)需要注意的一點(diǎn):當(dāng)用戶(hù)空間地址不在內(nèi)存中時(shí),進(jìn)程會(huì)投入睡眠等待內(nèi)存調(diào)度,所以任何存取用戶(hù)空間的函數(shù)必須是可重入的。
            數(shù)據(jù)傳送完成后需要修改*offp為當(dāng)前的文件位置。
            read參數(shù):

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

            這種情況發(fā)生在:進(jìn)程a在讀設(shè)備,同時(shí)進(jìn)程b寫(xiě)設(shè)備將長(zhǎng)度截?cái)酁?。此時(shí)scull的read返回0表示已到文件尾(沒(méi)有更多的數(shù)據(jù)可以讀)

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

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

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

            掃下目錄,暫時(shí)沒(méi)有興趣細(xì)看o(╯□╰)o

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

            ch05 并發(fā)和競(jìng)爭(zhēng)

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

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

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

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

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

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

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

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

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

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

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

            5.3.1 Linux 信號(hào)量的實(shí)現(xiàn)

            <linux/semaphore.h>
            struct semaphore

            信號(hào)量有多種聲明和初始化的方法。

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

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

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

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

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

            5.3.2 在scull中使用信號(hào)量

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

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

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

            5.3.3 Reader/Writer 信號(hào)量

            semaphore對(duì)所有要訪問(wèn)數(shù)據(jù)的線程互斥,但實(shí)際上多個(gè)線程同時(shí)讀數(shù)據(jù)不會(huì)出錯(cuò)。

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

            下面是使用rwsem的接口:

            <linux/rwsem.h>
            struct rw_semaphore

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

            void down_read(struct rw_semaphore *sem);
            int down_read_trylock(struct rw_semaphore *sem);
            void up_read(struct rw_semaphore *sem);
            down_read可能會(huì)使進(jìn)程進(jìn)入不可中斷的休眠。
            down_read_trylock在獲得訪問(wèn)權(quán)限時(shí)返回非0,其它情況下返回0。 --注意返回值和一般內(nèi)核函數(shù)不同:一般內(nèi)核函數(shù)出錯(cuò)返回非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,允許其它讀用戶(hù)訪問(wèn)。
            5.4 complete 機(jī)制

            問(wèn)題提出:

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

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

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

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

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

            解決方法:

            complete機(jī)制,允許一個(gè)線程告訴另一個(gè)線程某項(xiàng)工作已完成。

            <linux/completion.h>
            struct completion

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

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

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

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

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

            module_exit
            {
                滿(mǎn)足condition
                wait_for_complete
            }

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

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

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

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

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

            5.5.1 自旋鎖API介紹

            <linux/spinlock.h> spinlock_t

            初始化,在編譯時(shí)或運(yùn)行時(shí):
            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ī)則:任何擁有自旋鎖的代碼都必須是原子的。它不能休眠,不能因?yàn)槿魏卧蚍艞壧幚砥鳎ǔ四承┲袛喾?wù))。

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

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

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

            5.5.3 自旋鎖函數(shù)

            獲得自旋鎖的函數(shù)有4個(gè):
            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會(huì)在獲取自旋鎖之前,禁止本地CPU上的中斷,并把先前中斷狀態(tài)保存在flags中。spin_lock_irp不跟蹤中斷標(biāo)志,用于明確知道沒(méi)有其它代碼禁止本地處理器的中斷時(shí)。spin_lock_bh只禁止軟件中斷,會(huì)讓硬件中斷繼續(xù)打開(kāi)。
            如果某個(gè)自旋鎖可以被運(yùn)行在軟件中斷或硬件中斷上下文的代碼中獲得,則必須使用某個(gè)禁止中斷的spin_lock形式。

            對(duì)應(yīng)有4個(gè)釋放自旋鎖的函數(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在獲得自旋鎖時(shí)返回非零值,否則返回0.

            5.5.4 讀取者/寫(xiě)入者 自旋鎖

            與rwsem類(lèi)似,允許多個(gè)reader同時(shí)進(jìn)入臨界區(qū),writer必須互斥訪問(wèn)。
            可能造成reader饑餓,原因同rwsem。

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

            靜/動(dòng)態(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)建可被并行訪問(wèn)對(duì)象的同時(shí),定義控制訪問(wèn)的鎖。

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

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

            5.6.2 鎖的順序規(guī)則

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

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

            5.6.3 細(xì)粒度鎖和粗粒度鎖的對(duì)比

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

            應(yīng)該在最初使用粗粒度鎖,抑制自己過(guò)早考慮優(yōu)化的欲望,因?yàn)檎嬲男阅芷款i常出現(xiàn)在非預(yù)期的地方。
            ch06 高級(jí)字符驅(qū)動(dòng)程序操作

            6.1 ioctl

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

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

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

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

            6.1.1 選擇ioctl命令

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

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

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

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

            6.1.2 返回值

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

            6.1.3 預(yù)定義命令

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

            6.1.4 使用ioctl參數(shù)

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

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

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

            <linux/capability.h>

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

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

            驅(qū)動(dòng)程序決定,是否可以對(duì)一個(gè)或多個(gè)打開(kāi)的文件,做非阻塞的讀取或?qū)懭搿?/div>

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

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

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

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

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

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

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

            較新的工作站至少有兩根總線。每?jī)筛偩€用特殊外設(shè)bridges連接。PCI系統(tǒng)的總體布局是一棵樹(shù):總線通過(guò)bridges連接到上層總線上,直到樹(shù)根(0號(hào)總線)。

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

            下面的一堆沒(méi)有看懂。

            12.1.2 啟動(dòng)時(shí)間 

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

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

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

            12.1.3 配置寄存器和初始化

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

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

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

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

            初始化一個(gè)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)輸出到用戶(hù)空間,供熱插拔和模塊加載系統(tǒng)知道驅(qū)動(dòng)模塊匹配的硬件。

            該宏創(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è)備連到計(jì)算機(jī)上,熱插拔系統(tǒng)使用文件modules.pcimap找到對(duì)應(yīng)的驅(qū)動(dòng)并加載。

            12.1.5 注冊(cè)一個(gè)PCI驅(qū)動(dòng)

            PCI驅(qū)動(dòng)結(jié)果,包含許多回調(diào)函數(shù)和變量。
            struct pci_driver 
            const char *name;    // 驅(qū)動(dòng)名稱(chēng),在內(nèi)核所有PCI驅(qū)動(dòng)里必須是唯一的。可以在/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ū)動(dòng)匹配的struct pci_dev時(shí),調(diào)用probe。如果驅(qū)動(dòng)支持該pci_dev,正確初始化設(shè)備并返回0;否則返回一個(gè)錯(cuò)誤值。
            int (*remove)(struct pci_dev);    // 當(dāng)struct pci_dev從系統(tǒng)中移除時(shí),由PCI核心調(diào)用;PCI驅(qū)動(dòng)被移除時(shí),也會(huì)調(diào)用remove函數(shù)來(lái)移除驅(qū)動(dòng)支持的設(shè)備。
            int (*suspend)(struct pci_dev *dev, u32 state);    // 當(dāng)struct pci_dev被掛起時(shí),由PCI核心調(diào)用。state指明掛起狀態(tài)。可選的實(shí)現(xiàn)。
            int (*resume)(struct pci_dev *dev);    // 當(dāng)pci_dev恢復(fù)時(shí),由PCI核心調(diào)用。

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

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

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

            12.1.6 老式PCI探測(cè)

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

            查找一個(gè)特定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引用計(jì)數(shù);當(dāng)驅(qū)動(dòng)用完該pci_dev時(shí),需要調(diào)用pci_dev_put遞減found引用計(jì)數(shù)。
            from表示從哪個(gè)設(shè)備開(kāi)始探索,如果from設(shè)為NULL,表示返回第一個(gè)匹配的設(shè)備。
            該函數(shù)不能從中斷上下文中調(diào)用。

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

            12.1.7 使能pci設(shè)備

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

            12.1.8 存取配置空間

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

            配置空間可以通過(guò)8/16/32為數(shù)據(jù)傳輸來(lái)讀寫(xiě):
            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指從配置空間開(kāi)始的字節(jié)偏移,讀取到的值從val返回。
            word和dword函數(shù)負(fù)責(zé)從小端到處理器本地字節(jié)序的轉(zhuǎn)換。

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

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

            一個(gè)PCI設(shè)備使用6個(gè)32位的配置寄存器,指明它的地址區(qū)大小和位置。這6個(gè)配置寄存器的符號(hào)名是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個(gè)地址區(qū)的第一個(gè)地址,最后一個(gè)地址,flags資源標(biāo)識(shí)。
            資源標(biāo)識(shí)定義在<linux/ioport.h>中。

            12.1.10 PCI中斷

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

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

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


            17.1 snull設(shè)計(jì)

            snull模擬了和遠(yuǎn)程主機(jī)的回話,不依賴(lài)于硬件。只支持ip流。

            17.1.1 分配ip號(hào)

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

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


            17.2 連接到內(nèi)核

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

            17.2.1 設(shè)備注冊(cè)

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

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

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

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

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

            struct net_device中priv成員保存設(shè)備私有數(shù)據(jù),和net_device結(jié)構(gòu)同時(shí)分配內(nèi)存(字符驅(qū)動(dòng)中fops->private是單獨(dú)分配的)。不推薦直接訪問(wèn)priv指針,一般通過(guò)netdev_priv函數(shù)訪問(wèn):
            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)(引用計(jì)數(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)細(xì)節(jié)

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

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

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

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

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

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

            --

            使用awk工具可以從/proc/devices 文件中獲取設(shè)備號(hào),建立一個(gè).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 小默 閱讀(2953) 評(píng)論(0)  編輯 收藏 引用 所屬分類(lèi): Linux

            導(dǎo)航

            統(tǒng)計(jì)

            • 隨筆 - 289
            • 文章 - 0
            • 評(píng)論 - 84
            • 引用 - 0

            留言簿(13)

            隨筆分類(lèi)(287)

            隨筆檔案(289)

            漏洞

            搜索

            •  

            積分與排名

            • 積分 - 293110
            • 排名 - 90

            最新評(píng)論

            閱讀排行榜

            国内精品久久久久国产盗摄| 精品久久久久久无码人妻热| 久久被窝电影亚洲爽爽爽| 无码AV波多野结衣久久| 99精品久久精品一区二区| 久久久久久免费视频| 思思久久好好热精品国产| 一本久久综合亚洲鲁鲁五月天亚洲欧美一区二区 | 人妻无码中文久久久久专区| 国产成人精品白浆久久69| 97r久久精品国产99国产精| 久久久久99精品成人片三人毛片| 久久无码高潮喷水| 精品无码久久久久久久动漫| 狠狠色丁香久久婷婷综合蜜芽五月| 无码专区久久综合久中文字幕| 国产精品青草久久久久婷婷| 久久这里只精品99re66| 久久综合综合久久97色| 人妻精品久久无码专区精东影业| 久久免费精品视频| 亚洲午夜久久久影院| 国产毛片久久久久久国产毛片| 青青热久久综合网伊人| 777午夜精品久久av蜜臀| 久久99久久无码毛片一区二区 | 亚洲午夜久久久久久噜噜噜| 国产成人精品久久综合 | 精品国产乱码久久久久久1区2区 | 亚洲国产成人久久综合一区77| 精品久久久无码人妻中文字幕豆芽 | 久久精品aⅴ无码中文字字幕不卡| 久久亚洲视频| 亚洲欧洲久久久精品| 精品久久人人爽天天玩人人妻| 国产成人精品白浆久久69| 99久久精品免费看国产一区二区三区| 亚洲&#228;v永久无码精品天堂久久| 青青草原1769久久免费播放| www.久久热| 免费精品99久久国产综合精品|