---
# System prepended metadata

title: 'linux2021: ilkclord'

---

# linux2021: ilkclord

> [quiz-link](https://hackmd.io/@sysprog/linux2021-summer-quiz)

## $\delta$ - 1
This program is a mpmc model , and the number of consumer and producer are set to be 4 .
The pop and push action are defined as 
* `pop` : pop out the elements from the top of the linked list
* `push`  : push an elements into the tail of the linked list

The program starts by 
1 . Initialize the queue
2 . Creating the consumer and producer thread
3 . Start poping and pushing
4 . Send the kill signal to end the program
### Thread Safety
The program assures the safety of data by using a queue with two `mutex_lock` 
```clike
typedef struct { /* Two lock queue */
    node_t *first, *last;
    mtx_t *first_mutex, *last_mutex;
} con_queue_t;
```
`first_mutex` : protect the `first`
`last_mutex` : protect the `last`

* **poping**
    `con_pop` take use of `first` to do the pop action .It has to assure the `first` is only used by sigle thread at the same time , so it lock the block which needs to use `first`
```clike
    mtx_lock(queue->first_mutex);

    node_t *node = queue->first;             /* Node to be removed */
    node_t *new_header = queue->first->next; /* become the first in the queue */

    /* Queue is empty */
    if (!new_header) {
        mtx_unlock(queue->first_mutex);
        return NULL;
    }

    /* Queue not empty: retrieve data and rewire */
    void *return_value = node->value;
    queue->first = new_header;

    mtx_unlock(queue->first_mutex);
```

* **pushing**
    `con_push` takes use of `last` to do the push , and it assures `last` in a similar way . To keep the safety , a `mutex_lock` is applied to lock the block that needs to use `last`.
```clike
    mtx_lock(queue->last_mutex);
    if(queue->last) queue->last->next = node ;
    queue->last = node;
    mtx_unlock(queue->last_mutex);
```
### Thread Funtion

#### push_thread

This thread push elements into the queue . The thread terminate when it has push the limited number of elments in .
```clike=
int push_thread(void *queue_ptr)
{
    con_queue_t *queue = (con_queue_t *) queue_ptr;

    /* Push ints into queue */
    for (int i = 0; i < NUM; ++i) {
        int *pushed_value = malloc(sizeof(int));
        *pushed_value = i;
        if (con_push(queue, pushed_value) != Q_OK)
            printf("Error pushing element %i\n", i);
    }

    thrd_exit(0);
}
```
#### pop_thread
This thread keeps poping  out the elements from the queue , and it also free the elements it popped out .The thread will terminate when it pops out the element with value -1 , which is a killed signal .
```clike=
int pop_thread(void *queue_ptr)
{
    con_queue_t *queue = (con_queue_t *) queue_ptr;

    /* Read values from queue. Break loop on -1 */
    while (1) {
        int *popped_value = con_pop(queue);
        if (popped_value) {
            if (*popped_value == -1) {
                free(popped_value);
                break;
            }

            free(popped_value);
        }
    }

    thrd_exit(0);
}
```
### Flow Chart

* program
```graphviz

Digraph G{
    Initilaize->"create threads" ->pushing ->terminate ->"Send killing signal" ->End
    "create threads" ->popping->End


}
```
* queue
```graphviz
Digraph G{
    push->last
    first->"node1"->"node2"->"node3"->"..."-> last
    first->pop
    rankdir = LR

}
```
### Problem
After the testing , we find that thhere is possibility that the program will be stock in a loop , which is the while_loop in the `pop_thread` .

Browse the code , the problem may be in `cond_pop`

`cond_pop` : 
```clike=
void *con_pop(con_queue_t *queue)
{
    mtx_lock(queue->first_mutex);

    node_t *node = queue->first;             /* Node to be removed */
    node_t *new_header = queue->first->next; /* become the first in the queue */

    /* Queue is empty */
    if (!new_header) {
        mtx_unlock(queue->first_mutex);
        return NULL;
    }

    /* Queue not empty: retrieve data and rewire */
    void *return_value = node->value;
    queue->first = new_header;

    mtx_unlock(queue->first_mutex);

    /* Free removed node and return */
    free(node);
    return return_value;
}
```

In line 9 , the if gate block the empty queue , but it checks the `!new_header` instead of `!node` , this makes the queue will alwalys has at least 1 elements inside . For the last one data in the queue , it will never be popped out , so we can changed the the check condition to `!node` .

### Improvement
In line 9 , the if gate block the empty queue , but it checks the `!new_header` instead of `!node` , this makes the queue will alwalys has at least 1 elements inside .For the last one data in the queue , it will never be popped out .

I guess the reason for this design is to keep the connection between `first` and `last` .
#### Solution 1
Add one more condition to check if killing signal has received .
```clike
if (!new_header && *(int *)(node->value) != -1 ) {
        mtx_unlock(queue->first_mutex);
        return NULL;
}
```

#### Solution 2
We can changed the check condition to `!node` ,too .But there exists some bugs . I analyze the behavoirs of `first` and `last` .

Initialize of `last` and `first`
```clike
    node_t *dummy = _con_node_init(NULL);
    if (!dummy) {
        con_free(queue);
        return NULL;
    }

    queue->first = queue->last = dummy;
```
`first` and `last`points to a same node `dummy ` .In expected situation , `last` will grows the queue bigger and indicates the tail ,`first` pops out the elements and decrease the size of queue .

Consider the special case , the elements were all popped out ,so `first` and `last` points to `NULL` . This is a serious problem , since `first` and `last` no longer has connection between each other , the pop function can't pop the elements out . 

Expected : 
```graphviz
Digraph G{
first->list->last
rankdir = LR
}
```

Broken : 
```graphviz
Digraph G{
first
list->last
rankdir = LR
}
```
I add the modificatoin below to reconnect `first` and `last` and assure the correctness .

* `con_push`
    I use if_else to connect `first` and `last` when pushing into empty queue ,also assure the thread safety .
```clike=
   if(queue->last != NULL) 
      queue->last->next = node ;//AAA
    else{
      mtx_lock(queue->first_mutex) ;
      queue->first = node ;
      mtx_unlock(queue->first_mutex) ;
    }

```
* `con_pop`
    I switch the order below , to check the if `first` is `NULL`  , and return `NULL` if statement is true .
```clike=
    node_t *node = queue->first;             /* Node to be removed */
    if (!node) {
        mtx_unlock(queue->first_mutex);
        return NULL;
    }
    node_t *new_header = queue->first->next; /* become the first in the queue */

```
## $\delta$ - 2
To modify the program to a lock free program , I replace the `mutex_lock` by C11 atomic operation .
### C11 Atomic

The only two change is in `con_pop` and `con_push`

* `con_pop`
```clike
void *con_pop(con_queue_t *queue)
{

    node_t *node = atomic_load(&(queue->first)); 
    /*Empty queue*/
    if(!node->next && *(int *)(node->value)!= -1)
        return NULL ;

    
    /*test if the node we fetched isn't use by others
     * if isn't , modified it to next one
     * */
    if(!atomic_compare_exchange_strong(&(queue->first) , &node , node->next))
      return NULL ;

    
    void *return_value = node->value ;// BBB;
    /* Free removed node and return */
    free(node);
    return return_value;
}

```
We applied atomic operation by first fetching the value of `first` . Then we check if `first` has been modified before we record the `return_value` . If it has been modified , it is unsafe operation , we return `NULL`

* `con_push`
```clike
int con_push(con_queue_t *restrict queue, void *restrict new_element)
{
    /* Prepare new node */
    node_t *node = _con_node_init(new_element);
    if (!node)
        return Q_ERROR;
    
    while(1){
        node_t *tmp = atomic_load(&(queue->last)) ;
      
        if(atomic_compare_exchange_strong(&(queue->last) , &tmp , node)){
            atomic_store(&(tmp->next) , node) ;
            break ;
        }
    }
    return Q_OK;
}
```
We applied it in a similar way  , but we let it try untill it successed . Because in the original `con_push` we return `Q_ERROR` only if the `malloc` failed , so we repeat this operation untill sucess .

