论坛

测试看门狗

开始于 凝视 2020年9月27日
Il 28/09/2020 12:56, 大卫·布朗 ha scritto:
> On 28/09/2020 12:12, 凝视 wrote: >> Il 28/09/2020 11:26, 大卫·布朗 ha scritto: >>> On 28/09/2020 08:38, 凝视 wrote: >>>> Il 27/09/2020 18:38, 大卫·布朗 ha scritto: >>>>> On 27/09/2020 17:56, 凝视 wrote: >>>>>> I usually enable watchdog during boot init code. I usually use the >>>>>> internal watchdog that is available in the MCU I'm using. I know an >>>>>> external watchdog can be more secure, but internal watchdog is usually >>>>>> better than nothing. >>>>>> >>>>>> Some MCUs can be programmed to enable watchdog immediately after reset >>>>>> 和 stays enabled forever. Other times I enable watchdog timer >>>>>> immediately after some initialization code. >>>>>> >>>>>> During my previous thread "Power On Self Test", Richard Damon said: >>>>>> >>>>>>> "Yes, testing watchdogs is tricky." >>>>>> >>>>>> Tricky? Why? >>>>>> >>>>> >>>>> Certainly it is not hard to test a watchdog trigger by simply not >>>>> kicking it for a while. >>>>> >>>>> But think about what the watchdog is for - what are you actually trying >>>>> to do with it?&#2013266080; Are you trying to find where you have an infinite loop >>>>> in your program, that you have failed to find with other debugging? >>>>> The >>>>> watchdog /might/ help as a last resort, but you typically have no idea >>>>> what has gone wrong. >>>> >>>> Yes, you're right, but I don't think watchdog is a solution for every >>>> issues that could happen in the field. It is a solution for big problems >>>> with the execution flow of the program, because normal flow feeds the >>>> watchdog, unexepected flow doesn't. >>>> >>>> >>>>> The "good" reason for a watchdog is to handle unexpected hardware >>>>> issues >>>>> that have lead to a broken system - things like out-of-spec electrical >>>>> interference, cosmic ray bit-flips, power glitches, etc.&#2013266080; 怎么样 do you >>>>> test that the watchdog does its job in such situations?&#2013266080; If these were >>>>> faults that were easily provoked, you'd already have hardware solutions >>>>> in place to deal with them. >>>> >>>> I didn't think watchdog was used for hardware issues, but software bugs. >>>> For example, when you use a wrongly initialized function pointer 和 >>>> jump to a wrong address in the code... or similar things. >>>> >>> >>> You are not supposed to use a watchdog for that - you are supposed to >>> use good software development techniques 和 comprehensive testing. >>> (Yes, I know the real world is not perfect.) >> >> Yes I know, but Good Software Development Techniques aren't perfect, so >> the watchdog is one of the last chance to put the system in a safe state >> (restarting in my case). >> >> I admit it can be useful for hardware issues too. >> >> >> <ot> >> >>> In embedded development, treat function pointers like dynamic memory - >>> something you /really/ want to avoid unless there is no other option, >>> because it is often simple to get wrong, >> >> Why? Poor code (responsability of the developer) or 和 intrinsic >> characteristics of the function pointers? > > As I said - they are easy to get wrong, 和 difficult to analyse. They > make a mockery of call chain analysis, stack size checking, 和 other > things that depend on a static code flow. They make it difficult or > impossible to follow code flows backwards, or simply to answer the > question "where can this code be called from?". > > Sometimes function pointers /are/ the best way to organise code. But > they should be used with care 和 consideration.
Sure, but I imagine this is true for every code.
> (I even recommend minimising the use of data pointers, for similar > reasons, though obviously data pointers have more essential uses.) > >> >> Even null-terminated strings can be risky if you process a string >> without the null char at the end. Your "Good Software Development >> Techniques" should help to avoid such errors. >> > > Making mistakes in programming is certainly easy. > >> I think function pointers are a good solution in many cases. For >> example, I don't like a long if/else: >> >> &#2013266080; if (me->type == LAMP1) lamp1_func(); >> &#2013266080; else if (me->type == LAMP2) lamp2_func(); >> &#2013266080; else if (me->type == LAMP3) lamp3_func(); >> >> I prefer to put a function pointer in the struct 和 call it directly: >> >> &#2013266080; me->func(); > > That, to me, is a very bad design choice. > > typedef enum { lampType1, lampType2, lampType3 } lampTypes; > > Whatever the "me" struct is, have its "type" field (after a name change) > of type "lampTypes". Then you have: > > switch (me->lamp_type) { > case lampType1 : lamp1_func(); break; > case lampType2 : lamp2_func(); break; > case lampType3 : lamp3_func(); break; > } > > > This way you have a clear structure in the call graphs - you know > exactly what can be called, from where, by what. You have flexibility - > the lampX_func functions don't have to have the same signature. You > have better optimisation, as the compiler can combine parts of these (if > they are visible or you are using LTO). You can't get bad or > uninitialised function pointers.
As a function pointer can assume a bad or uninitialized value, don't you think the lamp_type variable could assume a bad or uninitialized value? You can't miss out on anything,
> because your compiler or third-party static analysis tool will tell you > if an enumeration is missed in the switch.
But you need to write the possibly-long switch statement every time you have to call a function that depends on the type of me. The code will be cluttered with those switch(), instead of a simple function call: void lamp_on(struct mystruct *me) { me->on(); } void lamp_off(struct mystruct *me) { me->off(); } void lamp_dim(struct mystruct *me, uint8_t level) { me->dim(level); } void lamp_rgb(struct mystruct *me, uint32_t rgb) { me->rgb(rgb); } Suppose you need to create a new lamp type, you need to touch many parts of the code, instead of initialize function pointers in one point only.
>> This helps to mimic OOP in C too. > > That is not a good thing. If you want to use OOP, use C++. (C++ > compilers 和 analysis tools know far more about virtual functions than > about unrestricted function pointers.) > >> >>> it is far more difficult to >>> analyse than static alternatives, >> >> This is true. >> >>> 和 it is also less efficient. >> >> Do you think my example above is less efficient with function pointer? > > It is not unlikely, but it depends on the rest of the source code 和 > organisation. If the source of these lampX_func functions is known to > the compiler when it generates the switch statement, 和 they are > declared "static" (or if you are using LTO), then yes, the switch > solution will probably be more efficient. > > But efficiency arguments are secondary to making code easier to write > correctly, harder to write incorrectly (these are not the same thing), > easier to analyse, 和 easier to debug.
I'm not an expert 和 I don't pretend to be a programming guru. However I usually study embedded programming by reading online blogs, newsgroups (like this) 和 books, 和 I see mentioneds function pointers many time. For example, in Grenning's book "Test Driven Development for Embedded C" there's a chapter "SOLID[1] C Design Models" where function pointers are used to reduce (completely avoid) switch statement. [1] Here SOLID stands for - S Single Responsability Principle - O Open Closed Principle - L Liskov Substitution Principle - I Interface Segregation principle - D Dependecy Inversion Principle
On 28/09/2020 17:34, Rick C wrote:
> On Monday, September 28, 2020 at 5:29:29 AM UTC-4, 大卫·布朗 > wrote: >> On 28/09/2020 09:17, Rick C wrote:
>>> I recall a software team who thought it was a good idea to use a >>> timer driven interrupt routine to tickle the watchdog. >>> >> >> That is fine - /if/ the kicking (I kick my watchdogs, rather than >> tickle them!) is conditional on other checks. For example, each >> other task in the system sets a boolean flag when it runs its loop, >> 和 the watchdog check in the timer interrupt checks that all flags >> are on before kicking the watchdog 和 resetting all the flags. >> It's a standard way to get the effect of having multiple >> watchdogs. > > Yes, but they weren't doing that.
Then I agree with your criticism. It sounds a lot like "The software spec said implement a watchdog routine. We made one" without actually thinking about what would be /useful/.
> Also, you need to vet the watchdog > software very carefully. A "sometimes" watchdog is about the same as > none. >
Agreed.
On 29/09/2020 08:44, 凝视 wrote:
> Il 28/09/2020 12:56, 大卫·布朗 ha scritto: >> On 28/09/2020 12:12, 凝视 wrote: >>> Il 28/09/2020 11:26, 大卫·布朗 ha scritto: >>>> On 28/09/2020 08:38, 凝视 wrote: >>>>> Il 27/09/2020 18:38, 大卫·布朗 ha scritto: >>>>>> On 27/09/2020 17:56, 凝视 wrote: >>>>>>> I usually enable watchdog during boot init code. I usually use the >>>>>>> internal watchdog that is available in the MCU I'm using. I know an >>>>>>> external watchdog can be more secure, but internal watchdog is >>>>>>> usually >>>>>>> better than nothing. >>>>>>> >>>>>>> Some MCUs can be programmed to enable watchdog immediately after >>>>>>> reset >>>>>>> 和 stays enabled forever. Other times I enable watchdog timer >>>>>>> immediately after some initialization code. >>>>>>> >>>>>>> During my previous thread "Power On Self Test", Richard Damon said: >>>>>>> >>>>>>>> "Yes, testing watchdogs is tricky." >>>>>>> >>>>>>> Tricky? Why? >>>>>>> >>>>>> >>>>>> Certainly it is not hard to test a watchdog trigger by simply not >>>>>> kicking it for a while. >>>>>> >>>>>> But think about what the watchdog is for - what are you actually >>>>>> trying >>>>>> to do with it?&#2013266080; Are you trying to find where you have an infinite >>>>>> loop >>>>>> in your program, that you have failed to find with other debugging? >>>>>> The >>>>>> watchdog /might/ help as a last resort, but you typically have no >>>>>> idea >>>>>> what has gone wrong. >>>>> >>>>> Yes, you're right, but I don't think watchdog is a solution for every >>>>> issues that could happen in the field. It is a solution for big >>>>> problems >>>>> with the execution flow of the program, because normal flow feeds the >>>>> watchdog, unexepected flow doesn't. >>>>> >>>>> >>>>>> The "good" reason for a watchdog is to handle unexpected hardware >>>>>> issues >>>>>> that have lead to a broken system - things like out-of-spec >>>>>> electrical >>>>>> interference, cosmic ray bit-flips, power glitches, etc.&#2013266080; 怎么样 do you >>>>>> test that the watchdog does its job in such situations?&#2013266080; If these >>>>>> were >>>>>> faults that were easily provoked, you'd already have hardware >>>>>> solutions >>>>>> in place to deal with them. >>>>> >>>>> I didn't think watchdog was used for hardware issues, but software >>>>> bugs. >>>>> For example, when you use a wrongly initialized function pointer 和 >>>>> jump to a wrong address in the code... or similar things. >>>>> >>>> >>>> You are not supposed to use a watchdog for that - you are supposed to >>>> use good software development techniques 和 comprehensive testing. >>>> (Yes, I know the real world is not perfect.) >>> >>> Yes I know, but Good Software Development Techniques aren't perfect, so >>> the watchdog is one of the last chance to put the system in a safe state >>> (restarting in my case). >>> >>> I admit it can be useful for hardware issues too. >>> >>> >>> <ot> >>> >>>> In embedded development, treat function pointers like dynamic memory - >>>> something you /really/ want to avoid unless there is no other option, >>>> because it is often simple to get wrong, >>> >>> Why? Poor code (responsability of the developer) or 和 intrinsic >>> characteristics of the function pointers? >> >> As I said - they are easy to get wrong, 和 difficult to analyse.&#2013266080; They >> make a mockery of call chain analysis, stack size checking, 和 other >> things that depend on a static code flow.&#2013266080; They make it difficult or >> impossible to follow code flows backwards, or simply to answer the >> question "where can this code be called from?". >> >> Sometimes function pointers /are/ the best way to organise code.&#2013266080; But >> they should be used with care 和 consideration. > > Sure, but I imagine this is true for every code.
True. But some code techniques have higher "cost" than others, where cost includes risk of mistakes, risk of misunderstandings (other people may be maintaining the code), portability cost (a function pointer is efficient on an ARM but /very/ inefficient on an 8051), challenges in debugging, difficulty in analysis, 和 run-time efficiency cost. The cost/benefit trade-offs for small embedded systems are often very different from "big" system programming. Function pointers might be the right choice in a big system program, but wrong in a small embedded system.
> > >> (I even recommend minimising the use of data pointers, for similar >> reasons, though obviously data pointers have more essential uses.) >> >>> >>> Even null-terminated strings can be risky if you process a string >>> without the null char at the end. Your "Good Software Development >>> Techniques" should help to avoid such errors. >>> >> >> Making mistakes in programming is certainly easy. >> >>> I think function pointers are a good solution in many cases. For >>> example, I don't like a long if/else: >>> >>> &#2013266080;&#2013266080; if (me->type == LAMP1) lamp1_func(); >>> &#2013266080;&#2013266080; else if (me->type == LAMP2) lamp2_func(); >>> &#2013266080;&#2013266080; else if (me->type == LAMP3) lamp3_func(); >>> >>> I prefer to put a function pointer in the struct 和 call it directly: >>> >>> &#2013266080;&#2013266080; me->func(); >> >> That, to me, is a very bad design choice. >> >> typedef enum { lampType1, lampType2, lampType3 } lampTypes; >> >> Whatever the "me" struct is, have its "type" field (after a name change) >> of type "lampTypes".&#2013266080; Then you have: >> >> &#2013266080;&#2013266080;&#2013266080;&#2013266080;switch (me->lamp_type) { >> &#2013266080;&#2013266080;&#2013266080;&#2013266080;&#2013266080;&#2013266080;&#2013266080; case lampType1 : lamp1_func(); break; >> &#2013266080;&#2013266080;&#2013266080;&#2013266080;&#2013266080;&#2013266080;&#2013266080; case lampType2 : lamp2_func(); break; >> &#2013266080;&#2013266080;&#2013266080;&#2013266080;&#2013266080;&#2013266080;&#2013266080; case lampType3 : lamp3_func(); break; >> &#2013266080;&#2013266080;&#2013266080;&#2013266080;} >> >> >> This way you have a clear structure in the call graphs - you know >> exactly what can be called, from where, by what.&#2013266080; You have flexibility - >> the lampX_func functions don't have to have the same signature.&#2013266080; You >> have better optimisation, as the compiler can combine parts of these (if >> they are visible or you are using LTO).&#2013266080; You can't get bad or >> uninitialised function pointers.&#2013266080; > > As a function pointer can assume a bad or uninitialized value, don't you > think the lamp_type variable could assume a bad or uninitialized value? >
No. How could it have an uninitialised value? That would be a fault in the code that first sets up the lamp object, 和 when this is done in one place, it's hard to get wrong. 怎么样 could it have a bad value? You've made this an enumerated type - stick to your programming standard that says you only use elements of the type, combine it with compiler warnings or third-party linters 和 only assign valid lampTypes values. (If you are using C++, use a class enum here.) Of course you could have stack corruption, or runaway pointers, that result in the memory for lamp_type being overwritten with rubbish - but then you have serious problems anyway. (And avoiding function pointers means you can analyse your stack usage more realistically, 和 minimising data pointers makes runaway pointers less of a risk.) You can also add a clause default : panic("lamp_type is %i", (int) lamp_type); if you think it is an issue. Note that this will catch /any/ bad value, unlike any check on a function pointer.
> > You can't miss out on anything, >> because your compiler or third-party static analysis tool will tell you >> if an enumeration is missed in the switch. > > But you need to write the possibly-long switch statement every time you > have to call a function that depends on the type of me.
Yes.
> The code will be cluttered with those switch(), instead of a simple > function call: > > void lamp_on(struct mystruct *me) { me->on(); } > void lamp_off(struct mystruct *me) { me->off(); } > void lamp_dim(struct mystruct *me, uint8_t level) { me->dim(level); } > void lamp_rgb(struct mystruct *me, uint32_t rgb) { me->rgb(rgb); } >
You'd rather clutter it with lots of function pointers in your "mystruct" struct (wasting ram, code, run-time, source code, 和 opening new risks for errors) ? A good clue that your design choices are questionable (I won't say "wrong" - there are no absolutes here) is when you have far more flexibility than you need. Your arrangement means a lamp can have an "on" function that uses a GPIO 和 an "off" function that uses SPI. With my method, if your lamp_type is "lampGPIO" then that is used for off(), on(), 和 everything else.
> Suppose you need to create a new lamp type, you need to touch many parts > of the code, instead of initialize function pointers in one point only. >
Yes. It is a small price to pay.
> >>> This helps to mimic OOP in C too. >> >> That is not a good thing.&#2013266080; If you want to use OOP, use C++.&#2013266080; (C++ >> compilers 和 analysis tools know far more about virtual functions than >> about unrestricted function pointers.) >> >>> >>>> it is far more difficult to >>>> analyse than static alternatives, >>> >>> This is true. >>> >>>> 和 it is also less efficient. >>> >>> Do you think my example above is less efficient with function pointer? >> >> It is not unlikely, but it depends on the rest of the source code 和 >> organisation.&#2013266080; If the source of these lampX_func functions is known to >> the compiler when it generates the switch statement, 和 they are >> declared "static" (or if you are using LTO), then yes, the switch >> solution will probably be more efficient. >> >> But efficiency arguments are secondary to making code easier to write >> correctly, harder to write incorrectly (these are not the same thing), >> easier to analyse, 和 easier to debug. > > I'm not an expert 和 I don't pretend to be a programming guru. > 怎么样ever I usually study embedded programming by reading online blogs, > newsgroups (like this) 和 books, 和 I see mentioneds function pointers > many time.
Certainly function pointers /are/ used in this way. But I am telling you that /I/ do not think they are a good idea in such contexts - I don't think they are a good cost/benefit trade-off. I have also read a lot of online blogs, websites, etc., 和 while some are good, a lot of them are crap. Some dangerously so.
> > For example, in Grenning's book "Test Driven Development for Embedded C" > there's a chapter "SOLID[1] C Design Models" where function pointers are > used to reduce (completely avoid) switch statement. > > > [1] Here SOLID stands for > - S Single Responsability Principle > - O Open Closed Principle > - L Liskov Substitution Principle > - I Interface Segregation principle > - D Dependecy Inversion Principle >
Based /solely/ on those comments (and I know doing so is grossly unfair), that book would make an excellent stand for a pot plant. If you want to do something like this, use C++. Not C. Making half-arsed sort-of-OOP code in C leaves you with something that is the worst of both languages. (That doesn't mean you can't do TDD in C - but it is not the same as doing it in C++.) If you program in decent, clean C, your code flow is clear 和 can be followed in the code, by analysis programs, in call-graphs, by the debugger, by stack size checkers. But you have to write your switches and you have to use manual rules about enums 和 other restrictions. If you program in solid, modern C++, your code flow is harder to track, your debugging is tougher as there is a wider separation between source code 和 object code, but the language 和 the compiler enforce rules for you. You don't need to worry about bad or uninitialised data because the language won't let you create such objects.
On 9/28/20 6:12 PM, 凝视 wrote:
>> You are not supposed to use a watchdog for that - you are supposed to >> use good software development techniques 和 comprehensive testing. >> (Yes, I know the real world is not perfect.) > > Yes I know, but Good Software Development Techniques aren't perfect, so > the watchdog is one of the last chance to put the system in a safe state > (restarting in my case).
What make you so sure that the same guy who messed up the real software get's the watchdog right?