Here comes a hopefully good explanation behind the idea of the routing system
as well as the drupal specific additions to it.
General overview
The Symfony components have two important concepts here. The http kernel is a system which gets the request, somehow ask other systems to produce to define the
piece of code which produces the requested output (a response object) and send the response back to the client. This piece of code is called controller,
so this can be either a pure php4 like function, a method on an object or even a anonymous function.
The system that knows which controller is responsible for the current request is the routing system.

Basic routing file
As module developer you define the list of routes and the corresponding controllers.
Here is an example for a json response:
taxonomy.autocomplete_vid:
path: '/taxonomy/autocomplete_vid/{taxonomy_vocabulary}'
defaults:
_controller: '\Drupal\taxonomy\Controller\TermAutocompleteController::autocompletePerVid'
requirements:
taxonomy_vocabulary: \d+
Most symfony documentation mention pattern, but drupal decided to just allow the non-deprecated "path" key in its routing file.
The key concept is the controller which gets some parameters from the system
and converts them to the response. In this example you have the parameter
'taxonomy_vocabulary'. So everything without an underscore is considered to
be a parameter to the controller. If you want to specify a default value,
you put it into the defaults array. In the same yml array you
specify the class and method connected with '::' to tell the system where to look
up stuff. Every other property has nothing to do with the controller parameters
and so are considered to be internal and so have an underscore as prefix.
Symfony itself also allows you to define regular expressions to validate
that the incoming parameter is valid (using 'requirements'). Here it would match to only numbers.
Controller resolver
Once symfony found out which controller is active on the current request,
it asks the so called controller resolver to create an instance of the controller,
that can be executed via call_user_func_array. The controller resolver has one
method to get the controller callable (object + method, anonymous function)
and one method to get the parameters passed to the controller, see Controller resolver
Drupal extensions
This is basically what symfony gives you.
Drupal though is slightly more complicated:
- You can check access to the route. For example calling user_access() was very common in Drupal 7 and below.
- You don't want to convert the taxonomy_vocabulary to its actual entity object
- You don't want to generate the full page response, but just the "main content".
Access check
Drupal has introduced a system on top of the symfony parts that checks whether
the user has access to the current route and alternative throw a 403 (access denied) exception. Access manager
In the routing file you specify this in the requirements part. The most common
bits are listed in the example:
path: '/user/{user}'
options:
_access_mode: 'ANY'
requirements:
_permission: 'access user profiles'
_entity_access: 'user.view'
_role: 'administrator'
_permission defines a call to user_access(), _role ensures that the user has a certain role (you can specify multiple ones via , for OR and + for AND logic).
_entity_access asks the entity system whether you have access to view the user entity. Per default drupal ensures that you add access checkers allow you to proceed, but you can switch it in the options via the _access_mode.
Upcasting
As mentioned in the listing you don't want to take care about loading an entity,
see /user/{user} as example. For entities you basically just use the name of the entity type and it will execute a entity_load with the ID passed in the URL. Param converter manager
Page response
As written before the controller is responsible to generate the response object.
This would be horrible in Drupal as a page consists of so much more like all the blocks appearing in its regions, the html and page templates etc. Therefore drupal specified a different key to specify a controller which returns the content of a page:
user.page:
path: '/user'
defaults:
_content: '\Drupal\user\Controller\UserController::userPage'
requirements:
_access: 'TRUE'
The string defined is the controller used to generate the render array for the main content region of your page.
Another addition is also the way how to deal with forms, as returning a page with a form is slightly
more complex than just a render array, so you can define _form with the FormInterface responsible for the current form.
user.pass:
path: '/user/password'
defaults:
_form: '\Drupal\user\Form\UserPasswordForm'
requirements:
_access: 'TRUE'
Note: This covers the most important points from my perspective, though there are for sure many more points to talk about.
TL;DR
- Underscores are specified for everything which are not parameters to the controller. This is coming as a sort of "standard" from symfony.
- These parameters are upcasted via the param converter and passed to the controller using the controller resolver
- Drupal has some additions to make it easier for people to interact with the symfony routing system.