In particular I've spent last week trying to figure out TCP/IP port management and it was quite frustrating. Lack of docs and plenty of hardcore operations unexplained, not explicitly marking all macros and inline functions, no clear distinguishing of scope for objects, and I could go on for a few dozen more lines of this.
Only a fanboy or an egomaniacal author can argue that. Don't get me wrong, I like it overall and I prefer it to most other OSs.
My dislikes are more pragmatic, no good comments, packing multiple complex operations in a single line, and not making clear what is what (OK this last bit is style but every single coding style for C out there defines this!)
[Again, I'm playing a bit Devil's Advocate as I'm a Linux user myself. There are many things I love from this team.]
The coding style is consistent - I don't think I've ever seen a single deviation. Linux kernel code is fit for print. This helps enormously with readability. It's rare to find such a large codebase written by so many people that uniform.
The directory structure and file names are straightforward. Want to know where the main scheduling code is? kernel/sched.c. What about the data structures for scheduling? include/linux/sched.h. How is fork implemented? kernel/fork.c. This is important because it means the source is discoverable.
Functions are just the right length. A single function conceptually does one thing, as it should. I've never read through a function and thought "This should be factored out into several different functions."
Consistent and intuitive names. Even if I've never read a function, or seen the definition of a variable, I have a good idea of what it does just by the name. This is should be true for all code, but considering the size and complexity of the code, I find it impressive I've never thought "That's a stupid name." It helps that special functions follow certain naming conventions. For example, if I encounter a function named do_foo(), I know that it is the function called for a foo system call.
And most importantly, any time I've wanted to know how something works in the Linux kernel, I've always been able to figure it out with focused study and tracing. Focused study, not a heroic effort. If it takes longer than I thought it would, it's always because I had to learn a few new concepts along the way, not because the code was obtuse.
Reading the Linux kernel code helps if you do it using LXR, http://lxr.linux.no/, which does a cross-reference of the code. Most variable names, functions and structures are links to their definitions and occurrences.
I see Linux developers talk about regression tests but couldn't find where is the central repository of this (is there one?) Regression tests IMHO should be distributed with the source code so everybody can run them. (I understand testing a kernel is not like testing any other user space program.) The BSDs do it, for example.
I remember reading about a study about a very different style of development from years ago, where automated testing was NEVER done, but instead formalized models of the program were created and mathematically verified.
If I remember correctly, the final result was a similar level of quality to an extensively tested system developed today, and the code tended to be of somewhat higher quality thanks to the extra thought put into it. (But I'm only going off vague memories here... I might be misremembering)
I'm actually quite disappointed to see my original comment with a negative score. I'm going to assume it was just badly phrased, as I don't seriously think that anyone believes that more testing of software results in a decrease in quality.
I've seen projects with more tests than code, and I think that shows a problem with development methodologies. Simply reasoning about the flow through the code (and possibly using a powerful type system [think Ocaml] to help catch errors at compile time) would remove the need for many of these tests (not all tests should go, of course) and would result in tighter code.
I do think having a suite of repeatable test cases you can run against developing software is a useful thing to have, though. Not only can you test for correctness, but you can also run benchmarks against each modification to see if your performance or memory usage is going up or down.
It probably depends on what you're trying to do...