Objectives:Â Â
- Install and use Android Virtual Devices.
- Install NDK, cross compile the program and run it on AVD.
- Effectively use Linux system calls for process control and management.
- Familiarize task_struct
- Concurrent execution of processes.
Detailed description and guide about the problem is in Guide
-
Makefile make file for pstree Module.
-
sys_pstree.c Module for pstree system call.
-
structure
prinfo
A simplified structure to describe a process, include its name, state, user ID, process ID, and process ID of its parent, children, sibling. -
Function
transTaskToPrinfo()
Linux maintains a list of all processes in a doubly linked list. Each entry in this list is a
task_struct
structure. We need to transfertask_struct
to ourprinfo
structure. -
Function
bfs()
Write the process tree into buffer in DFS order.
-
Function
sys_pstree()
Use copies of two argument and call function
bfs
. -
Function
addsyscall_init()
replace old system call with new one.
-
Function
addsyscall_exit()
replace new system call with old one.
-
-
Android.mk Make file for ptree.c
-
textscript.txt
A result of Problem 2.
-
ptree.c To print pstree
- structure
prinfo
omitted. - Function
printPrinfo()
Print the process tree from the DFS order buffer. Use tabs to indent children with respect to their parents. main()
make a copy of arguments, call pstree system call, and then print buffer.
- structure
-
Android.mk make file for test.c
-
textscript.txt A result of Problem 3.
-
test.c
Generate a new process and output “StudentID Parent” with PID, then generates its children process output “StudentID Child” with PID. Use
execl()
to execute ptree in the child process.
-
Server
-
Android.mk
make file for server.c
-
server.c
Server receives message from client, encrypts it and sends it back. After receiving a message from a new client, server creates a new thread to deal with it. If there are more than two processes, the function
serve()
will block the later process and continue to receive, encrypt and send back the message from process unblocked.
-
-
Client
-
Android.mk
make file for client.c
-
client.c
Client just sends input massage and receives massage from server until user inputs ":q".
-
-
textscript.txt
A result of Problem 4.
-
Install JDK and modify environment variables
-
Install SDK and set up AVD as required
-
Install necessary lib
sudo apt-get install libc6:i386 libgcc1:i386 gcc-4.6-base:i386 libstdc++5:i386 libstdc++6:i386
Change
gcc-4.6-base:i386
togcc-4.8-base:i386
-
Set up NDK and modify environment variables
-
Build HelloWord project and module
- It's quite strange that
emulator –avdYourAvdName–kernel KernelLocation –show-kernel
doesn't work for me. I search on the Internet and finally find thatemulator64-arm –avdYourAvdName–kernel KernelLocation –show-kernel
works. - I need to change
CROSS_COMPILE
in makefile from relative path to absolute path.
- It's quite strange that
Write a pstree system call in Android.
We need to transfer the task_struct
in Linux to a simplified structure prinfo
, and then use DFS to store the process tree in an array called buffer
.
​ Here just lists some main modification in my program and other parts are omitted.
-
Function transTaskToPrinfo
The function transfer
task_struct
to our simplifiedprinfo
structure. Some elements are easy to find, butfirst_child_pid
andnext_sibling_pid
need special attention. The doubly linked list has empty head node which children point to and last child will point to it's parent( exactly speaking, parent's children part). In fact, it's a cycle.void transTaskToPrinfo(struct task_struct *task, struct prinfo *prin) { prin->parent_pid = task->parent->pid; prin->pid = task->pid; prin->state = task->state; get_task_comm(prin->comm, task); //process name prin->uid = task->cred->uid; //user id prin->first_child_pid = (list_empty(&(task->children)))? 0: list_entry((&task->children)->next,struct task_struct,sibling)->pid; //children is list head, so the next element is the actual first child if (list_empty(&(task->sibling))) prin->next_sibling_pid = 0; else{ pid_t sibling_pid = list_entry(&task->sibling, struct task_struct,sibling)->pid; //sibling is list entry prin->next_sibling_pid = (sibling_pid == prin->parent_pid)? 0:sibling_pid; } //if there isn't a sibling, sibling.next point to parent or it's empty }
-
Function DFS
Simply use DFS. Pay attention to
list_for_each()
whose definition is written in comment.void dfs(struct task_struct *task, struct prinfo *buf, int *nr) { struct task_struct *tmp; struct list_head *lst; transTaskToPrinfo(task,&buf[*nr]); *nr = *nr + 1; //define list_for_each(pos, head) //for (pos = (head)->next; pos != (head); pos = pos->next) list_for_each(lst, &task->children){ tmp = list_entry(lst, struct task_struct, sibling); dfs(tmp, buf, nr); } }
-
Function sys_pstree
The key is to create template variable for
buffer
andnr
and then copy back to them, as well as usingread_lock()
to avoid sleep.int sys_pstree(struct prinfo *buf, int *nr) { struct prinfo *buf_t; int *nr_t; buf_t = kmalloc_array(1000, sizeof(*buf), GFP_KERNEL); nr_t = kmalloc(sizeof(int), GFP_KERNEL); if (buf_t == NULL || nr_t == NULL) { printk("Allocation initialize failed!\n"); return -EFAULT; } *nr_t = 0; //dfs read_lock(&tasklist_lock); //avoid sleep dfs(&init_task, buf_t, nr_t); read_unlock(&tasklist_lock); // copy to user if (copy_to_user(buf, buf_t, 1000 * sizeof(*buf_t))) { printk("Copy_to_user failed!\n"); return -EFAULT; } if (copy_to_user(nr, nr_t, sizeof(int))) { printk("Copy_to_user failed!\n"); return -EFAULT; } kfree(buf_t); kfree(nr_t); return *nr; }
We need to print the DFS order process tree returned by pstress system call and use tabs to indent children with respect to their parents. We can use function printTree()
to do all of this and the main function just call pstree system call and printTree()
. The detailed description of printTree()
lies in the next part.
Since we use buffer
to store the DFS order process tree, the key is to calculate how many tabs are there before we print the information of process. For buf[i]
and buf[i-1]
, there may exist two relationship--buf[i-1]
is parent of buf[i]
or buf[i]
is a sibling of buf[i-1]
's ancestor. We can distinguish them by parent_pid
. If buf[i-1]
is parent of buf[i]
, we add a tab. If buf[i-1]
is a sibling of buf[i-1]
's ancestor, we subtract tabs until they are at the same level.
void printTree(struct prinfo *buf, int *nr)
{
int pid_pos[1000] = {0}; //the position of pid
int tab_num = 0; //the number of tab
printf("%s,%d,%ld,%d,%d,%d,%ld\n", buf[0].comm, buf[0].pid,
buf[0].state,buf[0].parent_pid, buf[0].first_child_pid,
buf[0].next_sibling_pid, buf[0].uid);
int i=1;
while(i < *nr)
{
//calculate number of tab
if(buf[i].parent_pid == buf[i-1].pid)
//buf[i-1] is parent of buf[i], forward
tab_num++;
else if (buf[i].parent_pid != buf[i-1].parent_pid)
//buf[i] is a sibling of buf[i-1]'s ancestor, backward
{
int tmp = buf[i-1].parent_pid;
tab_num--;
while(buf[i].parent_pid!=buf[pid_pos[tmp]].parent_pid)
{
tmp = buf[pid_pos[tmp]].parent_pid; //find parent
tab_num--;
}
}
//record the position of pid
pid_pos[buf[i].pid] = i;
//print tab
int j = 0;
while(j < tab_num)
{
printf("\t");
j++;
}
//print process information
printf("%s,%d,%ld,%d,%d,%d,%ld\n", buf[i].comm, buf[i].pid,
buf[i].state,buf[i].parent_pid, buf[i].first_child_pid,
buf[i].next_sibling_pid, buf[i].uid);
i++;
}
}
We need to use fork()
to create a child process. Given that fork()
returns the process ID of child in parent process and it returns 0 in child, we can use this to distinguish child and parent process, and then we can do different things in different processes.
- The program use
fork()
to create a child process, printpid
of both processes, and useexecl()
to execute programptreeARM
in child program. - I put all the files in
/data/misc
folder in AVD, so the path parameter in functionexecl()
is/data/misc/ptreeARM
.
int main(int argc, char *argv[])
{
pid_t child_pid = fork();
if (child_pid < 0)
printf("Error!");
else if (child_pid == 0)
{ //child process
printf("517030910102 Child, %d\n", getpid());
execl("/data/misc/ptreeARM", "ptreeARM", NULL); // execute ptree
}
else
printf("517030910102 Parent, %d\n", getpid()); // parent process
return 0;
}
- Server receives message from client, encrypts it and sends it back.
- After receiving a message from a new client, server creates a new thread to deal with it.
- Use
cliNum
to record the number of client and usepthread_mutex_lock()
andpthread_mutex_lock()
to guarantee synchronization. - If there are more than two processes, the function
serve()
will usepthread_cond_wait()
to block the later process and continue to receive, encrypt and send back the message from process unblocked. - After receiving ":q" from a unblocked client, server will end the thread and signal a waiting client.
- Client just sends massage user input and receives massage from server until user inputs ":q".
Here just lists main modification.
-
Server
-
add some global variables
int cliNum = 0; //the number of cilents int sockfd_list[10000]; // store the sockfd from cilents pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //mutex for counting pthread_mutex_t cond_mutex = PTHREAD_MUTEX_INITIALIZER; //mutex for remaining two cilent pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // condition for making sure only two clients can get served
-
Modification in main function
//Concurrent multi-thread service int k = 0; while(1) //receive different cilents { newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); if (newsockfd < 0) printf("ERROR on accept\n"); sockfd_list[k] = newsockfd; pthread_t thread; n = pthread_create( &thread, NULL, serve, (void*)&sockfd_list[k]); if(n){ printf("Error - pthread_create() return code: %d\n",n); exit(EXIT_FAILURE); } k++; }
-
Modification in
server()
functionvoid *serve(void *sockfd) { int newsockfd = (int)(*((int*)sockfd)); int n, i; char buffer[256]; bzero(buffer,256); //clear buffer n = read(newsockfd,buffer,255); if (n < 0) printf("ERROR reading from socket\n"); pthread_mutex_lock(&mutex); cliNum++; pthread_mutex_unlock(&mutex); pthread_mutex_lock(&cond_mutex); // make sure only two clients get served if (cliNum>=3) { if (cliNum == 3) printf("Server thread closing...\n"); n = write(newsockfd,"Please waiting",14); if (n < 0) printf("ERROR writing to socket"); pthread_cond_wait(&cond, &cond_mutex); bzero(buffer,256); n = read(newsockfd,buffer,255); } else printf("Receiving message: %s \n",buffer); pthread_mutex_unlock(&cond_mutex); while(1) //receive different message from same cilent { for(i = 0; i < strlen(buffer); i++) // encrytion if('a' <= buffer[i] && buffer[i] <= 'z') buffer[i] = 'a' + (buffer[i] - 'a' + 29) % 26; else if('A' <= buffer[i] && buffer[i] <= 'Z') buffer[i] = 'A' + (buffer[i] - 'A' + 29) % 26; n = write(newsockfd,buffer,strlen(buffer)); if (n < 0) printf("ERROR writing to socket"); bzero(buffer,256); n = read(newsockfd,buffer,255); if (n < 0) printf("ERROR reading from socket\n"); if (strcmp(buffer,":q")) // ":q" to quit printf("Receiving message: %s \n",buffer); else break; } pthread_mutex_lock(&mutex); cliNum--; pthread_mutex_unlock(&mutex); pthread_cond_signal(&cond); //signal a waiting thread when one finishs pthread_exit(0); }
-
-
Client
gets(buffer); while(strcmp(buffer,":q")) //":q" to quit { n = write(sockfd,buffer,strlen(buffer)); if (n < 0) printf("ERROR reading from socket\n"); bzero(buffer,256); n = read(sockfd,buffer,255); if (n < 0) printf("ERROR reading from socket\n"); printf("From server: %s\n",buffer); bzero(buffer,256); gets(buffer); } n = write(sockfd,buffer,strlen(buffer)); //send ":q" to server if (n < 0) printf("ERROR reading from socket\n"); printf("Cilent closing...");
No result can be shown in this part
Here just shows a screenshot with part of result. The whole result can be found in the textscript.txt of problem two.
Here just shows a screenshot with part of result. The whole result can be found in the textscript.txt of problem three.
Here these screenshots show all the steps of this execution. The final result can also be found in the textscript.txt of problem four.