當前位置:首頁 > 公眾號精選 > 嵌入式云IOT技術(shù)圈
[導(dǎo)讀]什么是回調(diào)函數(shù)?

微信公眾號:楊源鑫
如果你覺得本文對你有幫助,歡迎留言探討!

一、C語言回調(diào)函數(shù)

什么是回調(diào)函數(shù)?
百度的權(quán)威解釋如下:

    回調(diào)函數(shù)就是一個通過函數(shù)指針調(diào)用的函數(shù)。如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個函數(shù),當這個指針被用來調(diào)用其所指向的函數(shù)時,我們就說這是回調(diào)函數(shù)?;卣{(diào)函數(shù)不是由該函數(shù)的實現(xiàn)方直接調(diào)用,而是在特定的事件或條件發(fā)生時由另外的一方調(diào)用的,用于對該事件或條件進行響應(yīng)。

 1#include 
2void print();
3int main(void)
4
{
5    void (*fuc)(); 
6    fuc = print ; 
7    fuc();  
8
9void print()
10
{
11    printf("hello world!\n");
12}

運行結(jié)果:

    從這個例子可以看到,我們首先定義了一個函數(shù)指針fuc ,這個函數(shù)指針的返回值為void型,然后我們給函數(shù)指針賦值,賦值為print,也就是print函數(shù)的首地址,此時fuc獲得了print的地址,fuc的地址等于print的地址,所以最終調(diào)用fuc();也就相當于調(diào)用了print();那么我寫的這個例子明顯和百度解釋的不符合啊?定義是如果你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個函數(shù),當這個指針被用來調(diào)用其所指向的函數(shù)時,我們就說這是回調(diào)函數(shù),確實,有所不同,但道理是一樣的,我們接下來再來看一個例子。

 1#include 
2
3int add_ret() ;
4
5int add(int a , int b , int (*add_value)())
6
{
7    return (*add_value)(a,b);
8}
9
10int main(void)
11
{
12    int sum = add(3,4,add_ret);
13    printf("sum:%d\n",sum);
14    return 0 ;
15
16
17int add_ret(int a , int b)
18
{
19    return a+b ;
20}

運行結(jié)果:

從這個例子里,我們看到:
    這樣子不就符合我們的定義了嘛?我們把函數(shù)的指針(地址),這里也就是add_ret,作為參數(shù)int add(int a , int b , int (add_value)()) , 這里的參數(shù)就是int(add_value)() , 這個名字可以隨便取,但是要符合C語言的命名規(guī)范。當這個指針被用來調(diào)用其所指向的函數(shù)時,我們就說這是回調(diào)函數(shù)。       我們看到add函數(shù)內(nèi)部,return (add_value)(a,b) ; 這個(add_value)(a,b)相當于對指針進行了簡引用,我們在main函數(shù)中,傳入具體要實現(xiàn)功能的函數(shù),add_ret,這個函數(shù)很簡單,就是實現(xiàn)兩數(shù)相加并返回,這里剛剛好,簡引用,相當于取出指針返回地址里的值,這個值就是return a+b,也就是我們傳入a和b兩數(shù)相加的結(jié)果。

    那么,回調(diào)函數(shù)究竟有什么作用呢?

   說到這里,就有了用戶和開發(fā)者之間的概念,假設(shè),用戶是實現(xiàn)add_ret這個函數(shù),而開發(fā)者是實現(xiàn)add這個函數(shù),現(xiàn)在的需求是,用戶將add_ret這個函數(shù)以參數(shù)的形式傳入開發(fā)者實現(xiàn)的add函數(shù),add函數(shù)就會返回一個數(shù)字給用戶,開發(fā)者沒必要告訴用戶他實現(xiàn)了什么東西,用戶也并不知道開發(fā)者是怎么實現(xiàn)的,用戶只需要傳入自己寫的函數(shù),便可以得到開發(fā)者實現(xiàn)的函數(shù)的返回值,開發(fā)者可以將內(nèi)容封裝起來,將頭文件以及庫文件提供給用戶。

    接下來,我們用Linux來演示下這個結(jié)果,我們在目錄下創(chuàng)建三個文件main.c,vendor.c,vendor.h

main.c是用戶開發(fā)的。

vendor.c和vendor.h是開發(fā)者實現(xiàn)的。

在main.c中,代碼如下:

 1#include 
2#include "vendor.h"
3
4int add_ret(int a , int b)
5
{
6        return a + b ;
7}
8
9int main(void)
10
{
11    int sum = add(3,4,add_ret);
12    printf("sum:%d\n",sum);
13    return 0 ;
14}

vendor.c,代碼如下:

1#include "vendor.h"
2int add(int a , int b , int (*add_value)())
3
{
4        return (*add_value)(a,b);
5}

vendor.h,代碼如下:

1#ifndef __VENDOR_H
2#define __VENDOR_H
3
4int add(int a , int b , int (*add_value)());
5
6#endif 

接下來,我們制作一個動態(tài)鏈接庫,最終開發(fā)者把vendor.c的內(nèi)容封起來,把vendor.h提供給用戶使用。

 1#include 
2#include "vendor.h"
3
4int add_ret(int a , int b)
5
{
6        return a + b ;
7}
8
9int main(void)
10
{
11    int sum = add(3,4,add_ret);
12    printf("sum:%d\n",sum);
13    return 0 ;
14}

    在linux下制作動態(tài)鏈接庫,將vendor.c和vendor.h打包成一個動態(tài)鏈接庫

先明白以下幾個命令是什么意思:

生成動態(tài)庫:

gcc -shared -fPIC dvendor.c -o libvendor.so

參數(shù)含義:

-shared : 生成動態(tài)庫;

-fPIC  : 生成與位置無關(guān)代碼;

-o               :指定生成的目標文件;

使用動態(tài)庫:

gcc main.c -L . –lvendor -o main

-L : 指定庫的路徑(編譯時); 不指定就使用默認路徑(/usr/lib/lib)

-lvendor : 指定需要動態(tài)鏈接的庫是誰;

代碼運行時需要加載動態(tài)庫:

./main 加載動態(tài)庫 (默認加載路徑:/usr/lib /lib ./ …)

./main

    我們將編譯動態(tài)庫生成的libvendor.so拷貝到/usr/lib后,現(xiàn)在就不需要vendor.c了,此時我們將vendor.c移除,也可以正常的編譯并且執(zhí)行main函數(shù)的結(jié)果,這就是回調(diào)函數(shù)的作用之一。

操作流程如下:

二、回調(diào)函數(shù)在Linux內(nèi)核中的應(yīng)用

    回調(diào)函數(shù)在Linux內(nèi)核里得到了廣泛的應(yīng)用,接下來,我將引用Linux內(nèi)核中文件操作結(jié)構(gòu)體來詳細的說明。
    我們首先來看到這個結(jié)構(gòu)體,這段代碼位于linux內(nèi)核的include/linux/fs.h中,由于代碼眾多,我只截取幾個最基本的例子:
File_operations文件操作結(jié)構(gòu)體:

 1struct file_operations {
2    struct module *owner;
3    loff_t (*llseek) (struct file *, loff_t, int);
4    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
5    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
6    ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
7    ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
8    int (*readdir) (struct file *, void *, filldir_t);
9    unsigned int (*poll) (struct file *, struct poll_table_struct *);
10    long (*unlocked_ioctl) (struct file *, unsigned intunsigned long);
11    long (*compat_ioctl) (struct file *, unsigned intunsigned long);
12    int (*mmap) (struct file *, struct vm_area_struct *);
13    int (*open) (struct inode *, struct file *);
14    int (*flush) (struct file *, fl_owner_t id);
15    int (*release) (struct inode *, struct file *);
16    int (*fsync) (struct file *, loff_t, loff_t, int datasync);
17    int (*aio_fsync) (struct kiocb *, int datasync);
18    int (*fasync) (intstruct file *, int);
19    int (*lock) (struct file *, intstruct file_lock *);
20    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
21    unsigned long (*get_unmapped_area)(struct file *, unsigned longunsigned longunsigned longunsigned long);
22    int (*check_flags)(int);
23    int (*flock) (struct file *, intstruct file_lock *);
24    ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
25    ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
26    int (*setlease)(struct file *, longstruct file_lock **);
27    long (*fallocate)(struct file *file, int mode, loff_t offset,
28              loff_t len);
29};

    這段代碼中,利用結(jié)構(gòu)體的封裝思想,將函數(shù)指針封裝在一個file_operations結(jié)構(gòu)體里,然后,在具體實現(xiàn)驅(qū)動的時候,實現(xiàn)具體的函數(shù),再賦值給結(jié)構(gòu)體里的函數(shù)指針做好初始化操作,我們來看看友善之臂的led驅(qū)動就明白了。
以下這段代碼截取友善之臂提供的linux內(nèi)核中的tiny4412_led.c

 1static struct file_operations tiny4412_led_dev_fops = {
2    .owner          = THIS_MODULE,
3    .unlocked_ioctl = tiny4412_leds_ioctl,
4};
5
6static struct miscdevice tiny4412_led_dev = {
7    .minor          = MISC_DYNAMIC_MINOR,
8    .name           = DEVICE_NAME,
9    .fops           = &tiny4412_led_dev_fops,
10};

    首先,先是定義了一個結(jié)構(gòu)體變量,并對結(jié)構(gòu)體變量進行初始化,在這個驅(qū)動中,只實現(xiàn)了ioctl函數(shù),對照著上面的結(jié)構(gòu)體,ulocked_ioctl就是結(jié)構(gòu)體中的這個函數(shù)指針。
long (*unlocked_ioctl) (struct file *,unsigned int, unsigned long);
    再來看看友善實現(xiàn)的adc驅(qū)動里,也是這么來做,這里看到 : 也是C語言結(jié)構(gòu)體的一種初始化方式,也是合理的。

 1static struct file_operations adc_dev_fops = {
2    owner:  THIS_MODULE,
3    open:   exynos_adc_open,
4    read:   exynos_adc_read,    
5    unlocked_ioctl: exynos_adc_ioctl,
6    release:    exynos_adc_release,
7};
8static struct miscdevice misc = {
9    .minor  = MISC_DYNAMIC_MINOR,
10    .name   = "adc",
11    .fops   = &adc_dev_fops,
12};

    在內(nèi)核中,有很多這樣的函數(shù)指針,所以,當我們了解了這樣的套路以后,再去學(xué)習(xí)linux內(nèi)核,我們的思想就會清晰很多了。
再來看看回調(diào)函數(shù)在linux內(nèi)核里的基本應(yīng)用。
從上節(jié)我們了解到,回調(diào)函數(shù)的本質(zhì)其實也就是函數(shù)指針,只不過定義有所區(qū)別。它的定義就是:你把函數(shù)的指針(地址)作為參數(shù)傳遞給另一個函數(shù),當這個指針被用來調(diào)用其所指向的函數(shù)時,我們就說這是回調(diào)函數(shù)。
    接下來我們來看一個例子:
    這段代碼摘自友善之臂的button驅(qū)動:

 1static int tiny4412_buttons_open(struct inode *inode, struct file *file)
2{
3    int irq;
4    int i;
5    int err = 0;
6
7    for (i = 0; i < ARRAY_SIZE(buttons); i++) {
8        if (!buttons[i].gpio)
9            continue;
10
11        setup_timer(&buttons[i].timer, tiny4412_buttons_timer,
12                (unsigned long)&buttons[i]);
13
14        irq = gpio_to_irq(buttons[i].gpio);
15        err = request_irq(irq, button_interrupt, IRQ_TYPE_EDGE_BOTH, 
16                buttons[i].name, (void *)
&buttons[i]);
17        if (err)
18            break;
19    }
20
21    if (err) {
22        i--;
23        for (; i >= 0; i--) {
24            if (!buttons[i].gpio)
25                continue;
26
27            irq = gpio_to_irq(buttons[i].gpio);
28            disable_irq(irq);
29            free_irq(irq, (void *)&buttons[i]);
30
31            del_timer_sync(&buttons[i].timer);
32        }
33
34        return -EBUSY;
35    }
36
37    ev_press = 1;
38    return 0;
39}

我們在tiny4412_buttons_open函數(shù)里看到

1err = request_irq(irq, button_interrupt, IRQ_TYPE_EDGE_BOTH,
2                                 buttons[i].name,(void *)
&buttons[i]);

我們來看看request_irq這個函數(shù):

1static inline int __must_check
2request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
3        const char *name, void *dev)

4
{
5    return request_threaded_irq(irq, handler, NULL, flags, name, dev);
6}

    到這里我們就明白了,第二個參數(shù)是一個用typedef重新定義的一個新類型的函數(shù)指針。
    那么也就是說一旦執(zhí)行了tiny4412的open函數(shù),就會通過request_irq去通過回調(diào)函數(shù)去執(zhí)行按鍵中斷,并返回一個中斷句柄。這個回調(diào)函數(shù),其實就是一個中斷服務(wù)函數(shù)。

1static irqreturn_t button_interrupt(int irq, void *dev_id)
2
{
3    struct button_desc *bdata = (struct button_desc *)dev_id;
4
5    mod_timer(&bdata->timer, jiffies + msecs_to_jiffies(40));
6
7    return IRQ_HANDLED;
8}

    回調(diào)函數(shù)在內(nèi)核中就是這么來使用的,當然,還有其它的,比如我們在tiny4412的open函數(shù)里面還看到:

1   setup_timer(&buttons[i].timer,tiny4412_buttons_timer,
2              (unsignedlong)&buttons[i]);

    這個函數(shù)的作用是注冊一個定時器,通過回調(diào)函數(shù)tiny4412_buttons_timer來進行觸發(fā)。
如果你不看它的定義,你可能以為它是一個普通函數(shù),其實它是一個宏封裝的。

1#define setup_timer(timer, fn, data)                    \
2    do {                                \
3        static struct lock_class_key __key;         \
4        setup_timer_key((timer), #timer, &__key, (fn), (data));\
5    } while (0)

這個宏函數(shù)通過調(diào)用setup_timer_key這個函數(shù)來實現(xiàn)定時器的注冊:

 1static inline void setup_timer_key(struct timer_list * timer,
2                const char *name,
3                struct lock_class_key *key,
4                void (*function)(unsigned long),
5                unsigned long data)
6{
7    timer->function = function;
8    timer->data = data;
9    init_timer_key(timer, name, key);
10}

    通過這個例子,我們更加了解到回調(diào)函數(shù)在Linux內(nèi)核中的應(yīng)用,為學(xué)習(xí)Linux內(nèi)核,分析linux內(nèi)核源代碼打下了基礎(chǔ)。

三、回調(diào)函數(shù)在Posix應(yīng)用API中的使用  
    其實,在Posix應(yīng)用編程里,我們也能用到回調(diào)函數(shù),我們來看看多線程編程中,經(jīng)常使用的pthread_create函數(shù):
我們先來看看它的原型:

1int  pthread_create((pthread_t *thread,  pthread_attr_t  *attr,  void  *(*start_routine)(void *),  void  *arg)

參數(shù)說明:

(1)    thread:表示線程的標識符

(2)    attr:表示線程的屬性設(shè)置

(3)    start_routine:表示線程函數(shù)的起始地址

(4)    arg:表示傳遞給線程函數(shù)的參數(shù)

函數(shù)的返回值為:

(1)    success:返回0

(2)    fair:返回-1

    看到這個函數(shù)的第三個參數(shù),這不就是一個函數(shù)指針,同時也是一個回調(diào)函數(shù)嘛!這就是函數(shù)指針和回調(diào)函數(shù)在UNIX環(huán)境多線程編程中的應(yīng)用。

我們在windows的dev C++上寫一個測試程序來看看:

 1#include 
2#include 
3
4
5void *function(void *args)
6
{
7    while(1)
8    {
9        printf("hello world1!\n");
10        sleep(1);
11    }
12}
13int main(void)
14
{
15    pthread_t tid ;
16    tid = pthread_create(&tid , NULL , function , NULL);
17    while(1)
18    {
19        printf("hello world!\n");
20        sleep(1);
21    }
22    return 0 ;
23}

運行結(jié)果:


    我們會看到在main函數(shù)里的打印語句和在線程回調(diào)函數(shù)void *function(void *args)里打印語句在同時打印。
關(guān)于這個函數(shù)的如何使用,網(wǎng)上文章有很多講得非常的詳細,這里僅僅只是寫函數(shù)指針和回調(diào)函數(shù)的應(yīng)用,詳細可以參考這篇文章,了解進程和線程。
http://blog.csdn.net/tommy_wxie/article/details/8545253

    當然,應(yīng)用里還有其它的API通用運用到了回調(diào)函數(shù),期待大家在實踐中去發(fā)掘。

另外,推薦一下韋東山老師的嵌入式課程:

移動互聯(lián)網(wǎng),人工智能,物聯(lián)網(wǎng)等技術(shù)正在飛速發(fā)展,這都離不開嵌入式技術(shù)的支持,如果您想創(chuàng)業(yè)自己開發(fā)產(chǎn)品,韋東山老師的視頻一定可以幫助您完成創(chuàng)客夢想。

[分享]韋東山嵌入式linux 第1期 ARM裸機實戰(zhàn)
https://j.youzan.com/zRcvW9
[分享]韋東山嵌入式linux第2期  驅(qū)動大全 
https://j.youzan.com/o3nvW9
[分享]韋東山嵌入式linux 第3期 項目實戰(zhàn) 
https://j.youzan.com/z0cvW9
[分享]韋東山第4期 Android系統(tǒng)視頻
https://j.youzan.com/ifMlW9
[分享]韋東山嵌入式Linux arm 不帶s3c2440開發(fā)板java入門視頻
https://j.youzan.com/1VnvW9
[分享]韋東山嵌入式linux視頻不帶ARM9開發(fā)板c++入門 手把手指導(dǎo)
https://j.youzan.com/NdnvW9
[分享]韋東山嵌入式Linux SPI arm視頻不帶s3c2440開發(fā)板 手把手指導(dǎo)
https://j.youzan.com/xHcvW9
[分享]錄制完畢韋東山嵌入式Linux視頻JZ2440s3c2440開發(fā)板設(shè)備樹詳解
https://j.youzan.com/lmnvW9




免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉