Thursday 25 November 2021

Draft : Interview Material

 When to use semaphore/mutex:-

----------------------------


Here is how I remember when to use what -


Semaphore: Use a semaphore when you (thread) want to sleep till some other thread tells you to wake up. Semaphore 'down' happens in one thread (producer) and semaphore 'up' (for same semaphore) happens in another thread (consumer) e.g.: In producer-consumer problem, producer wants to sleep till at least one buffer slot is empty - only the consumer thread can tell when a buffer slot is empty.


Mutex: Use a mutex when you (thread) want to execute code that should not be executed by any other thread at the same time. Mutex 'down' happens in one thread and mutex 'up' must happen in the same thread later on. e.g.: If you are deleting a node from a global linked list, you do not want another thread to muck around with pointers while you are deleting the node. When you acquire a mutex and are busy deleting a node, if another thread tries to acquire the same mutex, it will be put to sleep till you release the mutex.


-------------------------------------------------------------------------------------------------------------------------------------


What are Conditional Variables in OS?


A conditional variable in operating system programming is a special kind of variable that is used to determine if a certain condition has been met or not. It is used to communicate between threads when certain conditions become true.


A conditional variable is like a queue. A thread stops its execution and enters the queue if the specified condition is not met. Once another thread makes that condition true, it sends a signal to the leading thread in the queue to continue its execution.


If there is no thread in the queue, the signal is wasted.


Supported actions

There are two types of actions that can be performed with condition variables:


wait

signal

We use the wait instruction in a thread if we want to halt the execution of that thread till a certain condition is met.


We use the signal instruction if we want to continue executing the leading thread in the waiting queue.


---------------------------------------------------------------------------------------------------------------------------------------------------------------------



Difference between a conditional variable and a semaphore:--


Often semaphore is confused with the conditional variable. Some of the differences between a semaphore and a conditional variable are listed in the table below:


Conditional Variable:--

The thread waits till a certain condition is met.

The signal() is wasted if there is no thread waiting.

We can run all waiting threads using broadcast unlocking.

The wait() function almost always block its caller.

It is mostly used with mutex. It signals the change in state from one thread to another.


Semaphore:---

Only those threads wait which make the value of the semaphore less than or equal to 0.

The sem_post() is not wasted even if no thread is waiting. The value of the semaphore is increased.

We can not run all waiting threads using broadcast unlocking.

The wait() function does not always block its caller.

It is mostly used to solve critical section problems.


--------------------------------------------------------------------------------------------------------------------------------------------------------------------------




producer consumer problem:-

--------------------------



typedef struct 

{

char buf[SIZE];

int occupied;

int nextin;

int nextout;

pthread_mutex_t mutex;

pthread_cond_t more;

pthread_cond_t less;

}buffer_t;


void producer ( buffer_t *b, int item)

{

pthread_mutex_lock(&b->mutex);

while ( b->occupied >= SIZE)

pthread_cond_wait ( &b->less, &b->mutex);

b->buf[b->nextin++] = item;

b->nextIn = b->nextIn % SIZE;

b->occupied++;

pthread_cond_signal( &b->more); // this will unblock eht consumer to read 

pthread_mutex_unlock(&b->mutex);

}



char consumer( buffer_t *b)

{

char item;

pthread_mutex_lock(&b->mutex);

while ( b->occupied <= 0)

pthread_cond_wait(&b->more, &b->mutex);

item = buf[b->nextout++];

b->nextOut = b->nextOut%SIZE;

b->occupied--;

pthread_cond_signal(&b->less);

pthread_mutex_unlock(&b->mutes);

}



-------------------------------------------------------------------------------------------------


//arr is given find max consucitive sum of elements.


int maxConsecutiveSum ( int arr[])

{

int currentMax = arr[0], globalMax = arr[0];

for ( int i=0; i<n; i++)

{

if ( currentMax < 0 )

currentMax = arr[i];

else

currentMax = currentMax + arr[i];

if ( globalMax < currentMax)

globalMax = currentMax;

}

return globalMax;

}





-------------------------------------------------------------------------------------------------


Trees questions:-

-----------------



tree creations:-


struct node

{

int data;

struct node *left;

stcurt node *right;

};


node *createNode(int item)

{

node* newNode = new node();

newNode->data = item;

newNode->left = NULL;

newNode->right = NULL;

}




//sum of left leaves ....


int sumOfLeftLeaves ( node *root)

{

if ( root )

{

if ( root->left && !root->left->left && !root->left->right )

return root->left->data + sumOfLeftLeaves ( root->right);

return sumOfLeftLeaves ( root->left) + sumOfLeftLeaves ( root->right);

}

return 0;

}




//insert node in bst 


struct node *insert(struct node *root, int item)

{

if (root == NULL)

return (createNode(item));

if ( item > node->data )

return insert(root->left, item);

else

return insert(root->right, item);

return root;

}




//connect nodes at same level and last nodes of each level with first node of next level...

//basically it will create a linkedlist like structure were first node will be root and last node will last node of last level...


void linkllNOdesLevelWise( node *root)

{

node *lastNode, *currentNode;

lastNode = currentNode = root;

while ( currentNode != NULL )

{

if ( currentNode->left != NULL )

{

lastNode->next = currentNode->left;

last = currentNode->left;

}

if ( currentNode->right != NULL)

{

last->next = currentNode->right;

last = currentNode->right;

}

lastNode->next = NULL;

currentNode = currentNode->next;

}

}



//


// Least common ancestor of two nodes in tree.


struct node *lca ( struct node *root, int n1, int n2 )

{

struct node *temp = root;

while ( temp != NULL )

{

if ( temp->data > n1 && temp->data > n2 ) // means lca will lie in left subtree of this node.

{

temp = temp -> left; 

}

if else ( temp->data < n1 && temp->data < n2 )

{

temp = temp -> right;

}

else

break;

}

return temp;

}



time complexity - o(h)



//simple but incorrect


bool isBST(struct node *root)

{

if ( root == NULL )

retur true;

if ( (root->left && root->data < root->left->data) || (root->right && root->data > root->right->data ) )

{

return false;

}

if (  !isBST(root->left) || !isBST(root->right))

return false;

return true;

}


here there is a possibility that for each node it correct but if you see for whole tree then for some upper nodes its not correct.

so we need to check root->data with max(root->left) and root->data with min(root->right);


bool isBST(struct node *root)

{

if ( root == NULL )

retur true;

if ( (root->left && root->data <= maxValue(root->left) || (root->right && root->data >= minValue(root->right) ) )

{

return false;

}

if (  !isBST(root->left) || !isBST(root->right))

return false;

return true;

}


// Max depth or height of the tree


int maxHeight(strct node *root)

{

if ( root == NULL)

reutrn -1;

else

{

int ldepth = maxHeight( root->left);

int rdepth = maxHeight( root->right);

if ( ldepth > rdepth )

return ldepth + 1;

else

return rdepth + 1;

}

}


//Get max depth or heith using level wise traversal using queue.


1,NULL


int height ( strcut node *root)

{

int depth = 0;

queue <node*> q;

q.push(root);

q.push(NULL);

while ( !q.empty() )

{

node *temp = q.front();

q.pop();

if ( temp == NULL)

depth++;

if ( temp != NULL)

{

if ( temp -> left )

{

q.push(temp->left);

}

if ( temp->right)

{

q.push(temp->right);

}

}

else if ( !q.empty())

{

q.push(NULL);

}

}

return depth;


}


/////////////////////////////////////////


int findMin( struct node *root)

{

if ( root == NULL)

return INT_MIN;

int res = root->data;

int lres = findMin(root->left);

int rres = findMin(root->right);

if (lres > res)

        res = lres;

    if (rres > res)

        res = rres;

    return res;

}


///////////////////////////////////////////


Convert binary tree to BST

--------------------------

steps :- 

inorder BT and store in arr

sort the arr

inorder traversal of tree take  arr element and copy elements to tree 

modified tree will be BST....



void BT_to_BST( struct node *root)

{

if ( root == NULL)

return;

int n = count( root); // count the number of nodes .

int *arr = new int[n];

int i =0;

storeInOrder ( root, arr, &i);

qsort ( arr, n );

//copy arr elements to BT

i = 0;

arrToBSt( arr, root, &1);

delete [] arr;

}



int count ( struct node * root)

{

if ( root == NULL)

return 0;

return count ( root->left ) + count ( root->right) + 1;

}


void storeInOrder ( strcut node * root, int inorder [], int *index )

{

if ( node == NULL)

return;

storeInorder ( root->left, inorder, index );

inorder [*index] = root->data;

(*index)++;

storeInOrder ( root->right, inorder, index);

}


void arrToBst( struct node *root, int *arr, int *index)

{

if ( root == NULL)

return;

arrToBST( root->left, arr, index);

root->data = arr[*index];

(*index)++;

arrToBST( root->right, arr, index);

}


int main()

{

node *root = NULL;

root = createNode(10);

root->left = createNode(8);

root->right = createNode(12);

root->left->left = createNode(6);

root->left->right = createNode (7);

return;

}


//////////////////////////////////////////////



Smart pointers in c++

----------------------


Smart pointer :-


class SmartPtr

{

private:

T* m_data;

public:

SmartPtr (T* pValue )

{

m_data = pValue;

}

~SmartPtr()

{

delete m_data;

}

T & operator*()

{

return *m_data;

}

T & operator->()

{

return m_data;

}

};


int main ()

{

SmartPtr<int> *ptr ( new int());

*ptr = 10;

ptr->display();

return 0;

}


--------------------------------------------------------------

Unique pointer:-

---------------

two pointers can not be point to same object at the same time.

Here one pointer can pass the control to other pointer.


ex:- P1 = object1

p2 = object1 // here p1 passed its control to p2.

 

class rectangle 

{

int len;

int width;

public:

rectangle( int l, int b )

{

len = l;

width = b;

}

int area ( int l, int b)

{

return l * b;

}

};

 

int main ()

{

uniquePtr < rectangle > *ret1 = new rectangle ( 10, 5);

cout << ret1->area(); // it will print 50

uniquePtr < rectangle > *ret2;

ret2 = move ( ret1); // ret1 passed the control to ret2.

cout << ret2->area() // print 50

cout << ret1->area() // it will give an error as its not pointing to any object  now.

return 0;

}


 

Shared Pointer:-

----------------

Here basically more than one pointer shares particular object.

It maintains the reference count.


class rectangle 

{

int len;

int width;

public:

rectangle( int l, int b )

{

len = l;

width = b;

}

int area ( int l, int b)

{

return l * b;

}

};

 

int main ()

{

shared_ptr<rectangle> *p1 = new Rectangle(10, 5);

cout << p1->area() // print 50

shared_ptr<rectangle> *p2;

p2 = p1;

cout << p2->area(); // print 50

cout << p1->area(); // print 50, it will not give an error

cout << p1.useCount() // print 2 as reference count is 2

// Relinquishes ownership of p1 on the object

// and pointer becomes NULL

p1.reset();

cout << p1.get() << endl; // it will print null as it not pointing to any address now

cout << p2.use_count() << endl; // print 1

cout << p2.get() << endl; // it will give an address of object as its still pointing the same.

}



Weak pointer:-

--------------

This is used in place of shared_ptr in one problem - Cyclic dependency problem.

Here suppose we have two classs A and B and have one - one shared pointer in each class which can access to object of each others classes.

So here if we use shared pointer then it will always maintain a reference of each other and will not allow to delete the either of the objects.


So here weak pointer work if we declare either of one pointer as weak.




class Singleton

{

private:

static Singleton *Instance;

Singleton();

public:

Singleton *getInstance();

};



Singleton* Singleton::Instance = 0;


Singleton* Singleton::getInstance()

{

if ( Instance == NULL )

{

instance = new Singleton();

}

return instance;

}



singletone:-

-------------


#include <iostream>


class Singleton

{

    private:

        /* Here will be the instance stored. */

        static Singleton* instance;


        /* Private constructor to prevent instancing. */

        Singleton();


    public:

        /* Static access method. */

        static Singleton* getInstance();

};


/* Null, because instance will be initialized on demand. */

Singleton* Singleton::instance = 0;


Singleton* Singleton::getInstance()

{

    if (instance == 0)

    {

        instance = new Singleton();

    }


    return instance;

}


Singleton::Singleton()

{}


int main()

{

    //new Singleton(); // Won't work

    Singleton* s = Singleton::getInstance(); // Ok

    Singleton* r = Singleton::getInstance();


    /* The addresses will be the same. */

    std::cout << s << std::endl;

    std::cout << r << std::endl;


--------------------------------------------------------------------------------------------------

Need to read about PKI --


-PKI - its a software, encryption technologies, security methods used by organization to secure their data or to secure their business communications.

-How PKI helpful:

PKI provides below things.

Confidentiality - by data encryption

Integrity - by digital signature - 

1.sender calculate hash of data using any hash algorithm ( sha256). - this the part1

2.Sender encrypts the hash with its own private key and create a signature -- this is called a digital signature.

3.sender send the encrypted data by senders public key + hash + algo used in hash in plain text + its own digital signature.

4.reciever will decrypt the data with its own private key, calculate hash on date using same hash algorithm and calculate a hash.

5.Now reciever will decrpty the digital signatures by senders public key and get the hash. Now he will compare both the hash to check integrity.



Authenticity :- Its provided by digital signatures, hash algorithm - if reciever can decrypt the data it means that data is encrypted by its corresponding public key. 

Non-repudiation:- Digital signatures or audit logs - sender cant deny that its not sent data. 

Availability - multiple CAs are available to sign or allocate certificate to user/computer/program


- all component of PKI  - Digital certificate, CA, digital signatures, hashing, message digest etc.


- How key length plays the role in security:-

Keys with more length will provide more security to data but it slows the encryption performance.

ex: RSA-2048 it will provide more security in encryption compared with 3DES ( 168 bits ) or AES-256 bits.


So what to do to achieve better security + better performance.

Do encryption of data with symmetric key algo ( like AES,3DES) + encrypt that symmetric key only with assymetric key ( RSA-2048).


- Use of ciphers which agreed in ssl handshake.

- Use of digital signature ..

Digital signature means - identity of user can be used in integrity/ authenticity


Hashing algorithms:-

MD5 -- 128 bbits to generate the hash 

SHA0 -- 168 bits 

SHA1 -- 168 bits --  0 and 1 both got deprecated in 2017.


SHA2 -- now a days we are using SHA2 ( SHA-224 or SHA256 )

SHA3


- server and client verification through server/client certificates.


- Why KS required?

- about security tools - blackduck, qualys.

- How to prevent many well known attacks like ddos, sql injuction. 

- How and which tools are helpful to use for prevention of such kind of attacks.

- difference between encoding and encryption 


---------------------------------------------------------------------------------------------------



How ssl works:-

Client side:-

CA - certificate authorities will provide the public keys to browser venders , these vendor keep on updating these public keys to correspondign browser, in this way all browsers have public keys of all the CAs.


Server side:- When any one wants to make secure communication for his site then he first create a CSR ( certificate signinig request ) and get it signed by CA.

in signing process basically CA encrypts checksome of server ( CSR ) with its private key and corresponding public key shares with browser vender. So at this poing all the browser must have corresponding public key via which CA signs any server's CSR.


Now comes to communication part:- ( high level of SSL handshake )

Here once client wants to communicate wtih server, it send connect request.

server response with its certificate.

client verfies its certificate by looking up corresponding CA's public key. its decrypts the certs and find server cert checksome also create hash and calculate checksome. Now it compares both the checksome.


Structure of signatures:-

- Version

- serial number

- signature algo

- Issuer

-- issued to

-- issued by

- Vality

-- start date

-- end date

-Subject public key

- X509 extension

- Signature 

-- Signatures algo 

-- Signatures


PKCS -- things need to covers....

x509 formats and other formats ....

some ssl apis() need to cover ...

RSA vs ECC --





=====================================================================================================================================================


Bitwise operator :

-------------------

The following are many other interesting problems using XOR operator:

---------------------------------------------------------------------


Return true in case two numbers have opposite sign.


n1 = -13

n2= 10 


Return true


for n1  = 8, n2 = 10 return false


--------------------


singbit of positive number is - 0

signbit of negative number is - 1


if we do xor ( eg: 1 xor 0 = 1, 1 xor 1 = 0, 0 xor 0 = 0 ) 

so do xor of these tow numbers - if result is < 0 ( negative ) means xor of signbits are 1 ( -ve num).



bool opposite( int num1, int num2)

{

return (( x ^ y) < 0);

}



int main ()

{

int num1 = -10;

int num2 = 5;

if ( oppositeSign( num1, num2) < 1 )

{

signs are opposite

}

else

signs are same

}


---------------------------------------------------------------------------------


Wednesday 20 October 2021

Distibuted system design

 User thinks that he is contacting with one computer only. But there are bunch of other machines/hardware/services are there in background. These machines/servies which runs in background are called distributed system.

Fallacies of Distributed system:-

Network is reliable

Latency is zero

Bandwidth is infinite

Topology doesnt change

Network is secure

Only one administration

Transport cost is zero


Distributed system Characterstics:-

- No shared clock 

- No shared Memory - each component having their own storage

- Shared resources - Anything should be shareable among the nodes eg; hardware/software/data.

- Concurrency and Consistency 

Distributed system communication:-

- Different parts of distributed systems needs to be able to talk.

- Requires agreed upon format and protocol.

- Lots of things can go wrong. Need to handle them.

--Client cant find server.

--Server crashes in between

--Server processed the request but somehow its response is not reached to client (network failed)

--Client crashes

Benefits of Distributed system:-

- More reliable, fault tolerant - high availability

- Scalability - multimachines or nodes we can add dependes of traffic

- Lower latency, increased performance - As lots of servers running in background

-Cost effective

System design performance matrics:-

Scalability:-

--Ability of a system to grow and manage increased traffic. Horizontal or vertical scalling needs to be applied.

--Increased volume of data or request

Reliability:-

--Probability a system will fail during a period of time

Failure can belongs to software or hardware. Need to monitor both by healthcheck related scripts on productions and take neccessory actions to reduce the chances of failure.

And other automated testing can be performed on production to find the bugs if any.

Any product is reliable when it keeps on working even after failure of hardware/software.

Availability:-

--Amount of time a system is operational during a period of time. High availability we need to use here. If one server failes, redirect requests to other server. In this way server is always available for the clients.

--Poorly designed system requires more downtime for the updates. I remember a decades ago where we need to update a small thing we used to down whole site and update it early morning when traffic flow was minimum.

Availability calculations:-

Availability % = ( available time / total time ) x 100

                        = 23/24 x 100 = 95.83% = 360 hrs in an year = 15 days of downtime required annually 

                        if availability is 95.83%.

Reliable Vs Available:-

- Reliable system is always an available system.

- Availablity can be achieved by high availabiliy , means by adding more servers, creating clusters and replications of those servers.

- Reliability says to detect early failure of software or hardware. So that traffic can be redirected working software/hardware timely.

- Reliable software are more profitable - because it doesnt required more servers/clusters/replications.

- Depends on your software you can think to give more priority to availability or reliablity. Eg: suppose software is social media, if you are posting a video and it got failed - no need to worry you can try it later...means its less reliable and you can live with it. But at same time if you take an example of plane and its in air. here reliability is more important that availability. you can't take risk to rely on availability.