Operating System Assembly language for different architectures
I am new to this subject so forgive my ignorance?
I just started learning assembly for x86 processors on my linux system. I wrote a simple bootloader which worked but was specific to x86. Also I understand the registers etc are very different for intel and amd or 32 bit and 64 bits. So how is an operating system or application written for various architectures at the same time. Yes applications are operating system specific but shouldn't they be cpu specific also. Like when they are compiled into machine code, they need to be made for one type of processor(since registers etc are different for different architectures)? So how do they do it?
Or is it possible that all x86 has common registers and instruction set like athlon, pentium, i3, i5 etc and it is only different between x86 and 64 bit architecture?
What you said at the end is correct - all x86 has a common set of instructions, although the details of what it does under the hood is obviously very different. For example, on Intel hardware you actually have hidden registers which the x86 assembly instructions can't actually access.
The parts of the operating system that are written in assembly would require different assembly instructions for different architectures (x86 vs PPC vs ARM etc...)
The parts of the operating system that are written in c may also have some differences, for example when moving to x64 you can no longer assume that a pointer is 4 bytes. To deal with these differences in the C level the code would typically use lots of #ifdefs to deal with the differences.
This is of course a very big topic and the above is just a very general overview.
When you start looking at low-level code (e.g. boot code, kernels, etc) the idea that high level language code is portable is nonsense. There are a lot of things where different CPUs need different code but no high level language has support. This includes switching CPU modes, setting up paging, storing/loading task state (during task switches), etc.
To work around this you end up using non-portable code (assembly). For kernel code this typically means using things like conditional code (e.g. #ifdef i386) and shifting the non-portable parts into special (CPU/architecture specific) directories; so that the majority of the code is portable (but a small part has to be rewritten for each different case).
The same applies to boot code; but here it gets a little messier because there may be several different "pre-boot environments" (e.g. BIOS and UEFI) for the exact same CPU. I think everyone deals with this in their own different way.
Note that 80x86 has "historically related" registers and instruction sets. This does not mean the same code can work on all 80x86 CPUs (or even the same CPU in different modes). There's several modes (mainly 16-bit, 32-bit and 64-bit) that require different code, and then there's several extensions (3DNow, MMX, SSE, AVX) that add additional registers and may or may not exist. There are also parts of the CPU (e.g. performance monitoring) which are "model specific".