I have 20+ years of embedded systems, mostly 8 and 16 micros. The short answer to your question is the same as any other software development - don't optimise till you know you need to, and then don't optimise till you know what you need to optimise. Write you code so it's reliable, readable and maintainable first. Premature optimisation is as much, if not more of, a problem in embedded systems
When you program "without wasting any resources.", do you consider your time a resource? If not, who is paying you for your time, and if no one, do you have anything better to do with it. Once choice any embedded system designer has to make is the cost of hardware vs cost of engineering time. If you will be shipping 100 units, use a bigger micro, at 100,000 units, a $1.00 saving per unit is the same as 1 man year of software development (ignoring time to market, opportunity cost etc), at 1 Million units, you start getting ROI for being obsessive about resource usage, but be careful because many an embedded project never made the 1 million mark because they designed to sell 1 million (High initial investment with low production cost), and went bust before they got there.
That said, things you need to consider and be aware of with (small) embedded systems, because these will stop it working, in unexpected ways, not just make it go slow.
a) Stack - you usually have only a small stack size and often limited stack frame sizes. You must be aware of what your stack utilisation is at all times. Be warned, stack problems cause some of the most insidious defects.
b) Heap - again, small heap sizes so be careful about unwarranted memory allocation. Fragmentation becomes an issue. With these two, you need to know what you do when you run out - it does not happen on a large system due OS provided paging. i.e. When malloc returns NULL, do you check for it and what do you do. Every mallow needs a check and handler, code bloat?. As a guide - don't use it if there’s an alternative. Most small systems do not use dynmaic memory for these reasons.
c) Hardware interrupts - You need to know how to handle these in a safe and timely manner. You also need to know how to make safe re-entrant code. For instance, C standard libs are generally not re-entrant, so should not be used inside interrupt handlers.
d) Assembly - almost always premature optimisation. At most a small amount (inlined) is needed to achive something that C just cannot do. As an exercise, write a small method in hand crafted assembly (from scratch). Do the same in C. Measure the performance. I bet the C will be faster, I know it will be more readable, maintainable and extendable. Now for part 2 of the exercise - write a useful program in assembly and C.
As another exercise, have a look how much of the Linux kernal is assembler, nthen read ,the paragraph below about the linux kernel.
It is worth knowing how to do it, it might even be worth being proficient in the languages for one or two common micros.
e) "register unsigned int variable_name", "register" is, and always has been, a hint to the compiler, not an instruction, back in the early 70's (40 years ago), it made sense. In 2012, it's a waste of keystrokes as the compilers are so smart, and micros instructions sets so complex.
Back to your linux comment - the problem you have here is that we are not talking a mere 1 million units, we are talking 100's of millions, with a life time of forever. The engineering time and cost to get it as optimal as humanly possible is worth while. Although a good example of very best engineering practise, it would be commercial suicide for most embedded systems developers to be as pedantic as the linux kernal requires.