C: Structure Initialization (Advanced)


There is one more way by which we can initialize structures. These way utilizes the benefits of both the ways of structure initialization described here. That is we are able to initialize the elements of the structure using the set notation and also we need not remember the order of the elements of the structure.

Suppose consider the structure ‘student‘ we already used

struct student {

     int roll;

     int class;

     char name[50];

};

Now let’s declare a variable st3 and initialize using the third method

student st3={

           .name = "Mark", //Notice the equal to and the comma

           .class  = 10,

           .roll  = 1038

};

As you can see the way by which the variable st3 has been initialized. It has used the set notation and also see the order is not the same as in the case of the structure ‘student


The following program demonstrates this. Note the different ways by which the array of structures has been initialized

/*

*Different Ways of structure initialization

*/#include < stdio.h>

#include < string.h>

#define NAME_LEN 25

typedef unsigned short age_t;

typedef unsigned int roll_t;

typedef struct student{

    char name[NAME_LEN];

    roll_t rno;

    age_t age;

}student;

int main()

{

/* Method 3a: Just like the Method 2a, but here you do not

* need to know order of the elements in the declaration

*/

student st3={

.name = "Mark",//Notice the equal to and comma

.age  = 23,
.rno  = 1038
};

printf("%s %hi %u\n\n",st3.name,st3.age,st3.rno);

/*

* Method 3b: For Initializing an array of structures

*/

student st4[]={
{

.name = "Neil",

.age  = 23,

.rno  = 1039      },

{

.name = "Peter",

.age  = 23,

.rno  = 1040

}
};

printf("%s %hi %u\n%s %hi %u\n\n",st4[0].name,st4[0].age,

st4[0].rno, st4[1].name,st4[1].age,st4[1].rno);

/*

* Method 3c : Change the order of initialization of the

* elements of the array. Normally as seen in Method 2b,

* the 0th array element is initialized  then 1st, then

* 2nd and so on. So by using a variation  of Method 3b,

* we  can initialize the array elements in any order

*/

student st5[5]={

  [3]={

              .name = "Titus",

              .age  = 22,

              .rno  = 1041      },

[2]={

              .name = "Stephen",

              .age  = 23,

              .rno  = 1042

      }

      /*As you can see only 2nd and 3rd array elements has

       * been initialized and that too not in order. Such

       * an initialization is useful, if we wish to allocate

       * some fixed size array but only initialize some element

       */
};

printf("%s %hi %u\n%s %hi %u\n\n",st5[2].name,st5[2].age,

st5[2].rno, st5[3].name,st5[3].age,st5[3].rno);

}
The Methods 3a, 3b and 3c are the ways of structure initialization that I have seen in the kernel code. If you find any other way to do the structure initialization, kindly comment.

Linux Kernel Support for Threads (Light Weight Processes)

Yes, Linux supports threads. Thread is also a context of execution like the processes. We can do programming with threads. The further discussion on this topic is with respect to the Linux 2.6 kernel. As of now, there is support for muliple threads in the Linux kernel and in the user space, we make utilize of the Threads library like POSIX threads. The way POSIX threads is implemented in Linux is different from other Operating Systems (Because they are no specific system calls for dealing with threads)

So the question arises if Linux does not have any system calls corresponding to threads creation, how are the POSIX threads created. And if POSIX thread implementation is done in the user space alone, there are many issues that has to be dealt with. Let’s think that POSIX thread is implemented completely in the user space, so the scheduling of threads is to be done by the POSIX threads library itself.

Threads must be light weight in the sense that all the threads in the same process share the same address space. Implementing POSIX threads in the user space can solve this issue. Because threads are nothing but an abstraction to the programmer. He/she enjoys some of the benefits of threads. But if one of the threads is blocked on a particular system call like read, the whole process will be blocked because the very idea of the threads is transparent to the kernel. So all the benefits of threads can not be utilized.

So how is this issue solved in Linux? It is done by implementing the so called ‘Light weight processes’. A fork() system call creates a new process. If the child process does not have any execve() like system calls, both the child and the parent process share the same address space for the text(program code). And the data address space is marked as ‘Copy on write’ that is in the beginning both the child and the parent process share the same data adrress space, but any attempt of changing the data by the child will result in creating a new data address space for the child.

Since now we got an idea about how the fork() system call works, we can now think about implementing threads. As we know that threads in the same process share the same address space for text and data, so we need not set any ‘Copy on write’. The issues of data synchroniztion which comes up when two threads access the same data varible has to be worked upon by the programmer.

There is a system call called clone called clone() or clone2() which helps in creating a child process but unlike fork(), we have more control on deciding the behaviour of the child process whether we want to have the new child share the same filesystem information, file descriptor table, signal handler table or the memory space of the parent. In fact the threads library makes use of the clone() system call to create new threads.

So by this time, we got an idea that threads in Linux are nothing but processes or better to be called ‘Light weight processes’(because of the sharing of data).

There is an advantage of this way of implementation. To the kernel everything is seen as processes and the scheduler has nothing to think about separate scheduling techniques for threads and processes. This makes the implementation as simple as possible and also solves the above blocking problem of the threads implementation in user space.

Let’s check a small snippet of code

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
  void * function1(void *arg)
{
   pthread_t tid=pthread_self();
   printf("In thread %u and process %u\n",tid,getpid());
}

void * function2(void *arg)
{
   pthread_t tid=pthread_self();
   printf("In thread %u and process %u\n",tid,getpid());
}

int main()
{
   void *status;
   pthread_t tid1,tid2;
   pthread_attr_t attr;

    if(pthread_create(&tid1,NULL,function1,NULL)){
        perror("Failure");
        exit(1);
   }

   if(pthread_create(&tid2,NULL,function2,NULL)){
       perror("Failure");
       exit(2);
   }

   pthread_join(tid1,NULL);
   pthread_join(tid2,NULL);
   printf("In main thread %u and process %u\n",pthread_self(),getpid());
}

To compile this program

$gcc thread.c -lpthread

On Executing, the output is

In thread 3086625680 and process 5480
In thread 3076135824 and process 5480
In main thread 3086628544 and process 5480

How can this be possible? By our above discussion, we found that threads are nothing but light weight processes. But getpid() (which returns the Process ID) gives same PID for all the processes. This is because the POSIX standard says that the threads must return the same PID (based on the assumption that they all are running in the same process). To deal with this issue, Linux introduced a tgid(Thread group Leader ID) field. The tgid is same as the PID of the first light weight process in the threads group(Group of threads created by a process including itself). System calls like getpid() has been so designed that they return the tgid of the process instead of the PID, thus threads in the same thread group get the same value from getpid().