日志标签 ‘进程控制’

IPC under Linux-Shared Memory

2010年8月27日

还记得在上篇文章中我们说到的N个子进程独自访问自己的临界区吗,那么今天要说的共享内存就可以实现多个进程都去访问一块内存et2 如果你对我们前面所说的几种IPC方式都了解,那么关于共享内存的基本知识你就有似曾相识的感觉。我们快速入门吧 e91

1.如何创建一块共享内存?

其实列出函数原型,你应该会知道每个参数的含义。

int shmget(key_t key,size_t size,int shmflg);

这里要说明的是size的大小,如果创建一块新的共享内存区,那么size必须大于0;如果size为0,那此时shmget函数的功能就是打开一个已存在的共享内存区(当然,key代表的内存区是否存在那是二话)。size为负,那么调用出错,返回-1。另外shmflg的取值以及用法都与信号量相同,可参考前面的文章。

当shmget调用成功后,返回整形id。

2.共享内存创建好了,进程如何使用?

我们创建好了内存块,但是创建进程此时和这块内存还没联系。所以,我们应该使用shmat函数将其附加到进程的地址空间(具体位于进程的堆栈段)。

void* shmat(int shmid,const void* shmaddr,int shmflg);

一般我们将shmaddr置为NULL,因为此时内存会自动分配一段何时的内存块。并且最终shmat函数将返回这个内存块的首地址。也许你对此处的void类型比较不解,其实void* shmaddr这样的定义方式其实是为了灵活使用:首先shmaddr是一个指针类型(内存地址),但其具体是指向何种类型的指针,在此并不具体说明。因为当你要使用这个指针时,可以用任何类型强制转换。

与此函数对应的是shmdt函数,它会让调用进程与shmaddr指向的共享内存脱离。

3.对共享内存的控制?

int shmctl(int shmid,int cmd,struct shmid_ds *buf);

cmd可取值:IPC_RMID,IPC_SET,IPC_STAT分别对应:删除共享内存区,设置共享内存区的shmid_ds结构,获取shmid_ds结构。这里不再赘述具体使用方法,可参考man手册。

介绍完这些,我们的重点就是要学会应用共享内存。《linuxC编程实战》一书中的例10-17非常经典,可以模拟进程之间的同步。该例还特别的定义了一个头文件shm.h,此头文件中加入了许多对信号量以及共享内存的操作函数。现在我们具体来分析,哪些语句实现进程的互斥,哪些语句实现的是进程的同步。

首先,不管是读进程,还是写进程,PV操作之间的代码都属于临界区。临界区的代码都是对共享内存去进行操作,所以应该加上PV操作,避免读写进程交互对共享内存进行访问。所以读写程序中的PV操作实现的是进程的互斥。

我们还可以发现,读写进程代码中都有waitsem(semid,0)语句。以写进程为例,每次写内存时,都要查看信号量(semid信号量集中的0号信号量)是否为1。如果为1,说明此时没有读进程访问共享内存;否则,说明有读进程在访问,此时写进程本应该阻塞,但是这里我们用sleep(1)来模拟进程的阻塞,而且不断的去测试信号量是否为1。

如果写进程成功进入临界区,那么它将写数据至共享内存。可能你对于strcpy(shmaddr,wbuf);这样的语句感到困惑:怎么能将wbuf赋值给共享内存?!其实仔细想想,这没有什么不合理。shmaddr是一个指针,wbuf是一个字符串数组的首字符的首地址,那么strcpy函数所做的就是很平常的事了。同时,我们也可以这么想:一般我们都是提前用malloc来分配内存空间,再让某个指针指向这片内存空间,而现在,上面的shmget以及shat以及完成了类似malloc的工作。

其实我们有这样的想法很自然,毕竟刚开始学习,通过实践可以从感性上认识这些。

那么很显然,waitsem(semid,0)语句实现的便是读写进程的同步问题——协调读写进程对共享内存的访问。

//写进程部分代码
	while(1)
	{
                waitsem(semid,0);
		p(semid,0);
		printf("writer:");
		fgets(wbuf,1024,stdin);
		wbuf[strlen(wbuf)-1]='\0';
		strcpy(shmaddr,wbuf);
		my_sleep(5);
		v(semid,0);
		my_sleep(3);
	}
//读进程部分代码
	while(1)
	{
                waitsem(semid,0);
		p(semid,0);
		printf("reader:");
		printf("%s\n",shmaddr);
		my_sleep(5);
		v(semid,0);
		my_sleep(3);
	}

理解了这个程序要干什么,那么我们再来想想它为什么要这么做:读写进程中为何要加入sleep函数?那我们来做几个测试吧。在测试之前,建议大家适当的修改waitsem函数和my_sleep函数,它可以让我们更好的感知当前进程的“死活”。

int waitsem(int semid,int index)
{
	while(semctl(semid,index,GETVAL,0)==0)
	{
		sleep(1);
		printf("I am waiting ..\n");
	}
	return 1;
}
void my_sleep(int i)
{
	while(i--)
	{
		printf("writer is sleeping..\n");//写进程中的my_sleep做相应改动
		sleep(1);
	}
}

这样增加了一些提示语,再分别在两个终端运行读写进程,可以发现第一个my_sleep函数的作用就是故意延长读(写)进程在临界区的访问时间,以使得写(读)进程阻塞。而第二个my_sleep函数则是故意阻塞写(读)进程,让读(写)进程有时间去读共享内存。好了,既然现在掌握了信号量和共享内存,那么我们就可以试着去完成一些简单的同步互斥的题目了,继续加油吧 e49 e49 e49

my_shell.c代码分析

2010年7月16日

大三的时候,那时候因为课程需要,我们调试过my_shell.c程序。当时只是对整个程序的结构了解,在涉及linux系统调用函数的时候就不是很清楚”为什么会这样做?”。my_shell.c程序核心函数便是do_cmd函数,此函数是整个程序的核心,负责对用户输入的命令进行执行。

本文便是分析do_cmd函数,以达到对my_shell.c程序有深入的理解。此函数会涉及文件操作中的相关系统调用函数,如果你和我一样调试并分析过前面所说的my_ls.c程序,那么接下来所述的内容对你来说并不困难。

我们按照如下方式定义do_cmd函数:

void do_cmd(int argcount, char arglist[100][256]);

其中,argcount是统计用户输入命令中选项的个数,arglist数组存储每个选项。比如输入:ls -l /那么此时argcount=3,而arglist[0],arglist[1],arglist[2]中分别存储的是:ls,-l,/。

首先此函数会检查用户输入的命令行参数中是否存在后台运行符。利用for循环,对arglist数组中的每个参数与”&”进行比较。由于arglist中的元素是字符串,那么应该选用strcmp函数,而不是利用==来判断。此外即便找到了后台运行符,还应该检查是否”&”位于参数末尾。如果i==argcount-1为假,那么便出错。实现代码如下:

//check if the command include character of running in the background
	for(i=0;i<\argcount;i++)
	{
		if(strncmp(arg[i],"&",1)==0)
		{
			if(i==argcount-1)
			{
				background=1;
				arg[argcount-1]=NULL;
				break;
			}
			else
			{
				printf("wrong command\n");
				return;
			}
		}
	}//for

接下来,检测命令行参数是否含有>,<,|符号,检测方法与上出代码原理类似。由于本程序要求用户输入的命令行参数只能包含上述符号其中之一,所以当flag大于一时,错误。参数中符号合法,接下来提取文件名,存于字符串file中。程序中分别有三个if语句依次针对出现>,<,|时的情况。我们只要分析出现管道符号的情况。

在分析之前首先弄起出管道符号的作用。比如下面命令:

edsionte@edsionte-laptop:~$ ls -l / | wc -c
1513

管道符号前后分别是两个shell命令,前命令的输出作为后命令的输入。

如果检测出命令行中含有管道符号,即how=have_pipe。然后将命令行中两个shell命令分离出来,前命令依然存于arg数组中,而后命令存于argnext数组中。实现命令如下:

if(how==have_pipe)
	{
		for(i=0;arg[i]!=NULL;i++)
		{
			if(strncmp(arg[i],"|",1)==0)
			{
				arg[i]=NULL;
				int j;
				for(j=i+1;arg[j]!=NULL;j++)
				{
					argnext[j-(i+1)]=arg[j];
				}
				argnext[j-(i+1)]=arg[j];
				break;
			}
		}
	}

接下来就要用到进程控制的内容了。我们fork一个新的进程,让它去执行命令行参数中的命令。当命令行中不包含管道符号时,只需fork一个进程,比如ls -l / > a;我们只需fork一个新进程,让其(if(pid==0))执行ls命令即可;否则,我们必须fork两个信进程,前一个进程执行|前的命令,另外一个进程执行|后的命令。至于另个进程如何”通信”,如何让前一个命令的输出称为后者的输入?不着急,后面会详细说明。

1.如果所输命令中不包含<,>,|,how==normal(0);那么调用exe族函数即可让子进程去执行所输的命令。如下:

			if(pid==0)
			{
				if(!(find_command(arg[0])))
				{
					printf("%s: command not found\n",arg[0]);
					exit(0);
				}
				execvp(arg[0],arg);
				exit(0);
			}

find_command函数是在指定目录下去查找命令arg[0]对应的可执行程序。

2.如果命令行中包括输出重定向符号>,how==out_redirect(1);那么需在上述代码中exe函数前添加下面的代码:

				fd=open(file,O_RDWR|O_CREAT|O_TRUNC,0644);
				dup2(fd,1);//make the file as a standard output stream
				execvp(arg[0],arg);//execute the command

前面我们已经提到file中存储的是输出重定向(或输入重定向)的文件名,因此首先应以可读可写方式打开一个文件,并且档要打开的文件存在时,会清空源文件数据,这些我们在前面的博文中都有解释。我们知道标准输出输入的文件描述符为:1,0,利用dup2函数就可以将fd,1同时指向file文件,实现将标准输出重定向到我们刚已打开的文件。

3.如果命令行中包含输入重定向符号<,how==out_redirect(2);那么添加如下代码:

				fd=open(file,O_RDONLY);
				dup2(fd,0);//make the file as a standard output stream
				execvp(arg[0],arg);//execute the command

这里只涉及到读取文件,因此以只读方式打开文件。

4.如果命令行中包含管道符号,how==have_pipe(3);如同我们在上面所述的,子进程还需再fork一个进程(暂且叫它子子进程,child_child_process)。首先让子子进程去执行管道符号前面的shell命令,并将输出结果暂存于tempfile文件中;让子进程去执行管道符号后面的shell命令,并读取tempfile文件,将其作为第二个命令的输入。这样,子进程和子子进程实现了管道”通信”。具体代码实现其实是上述2和3的结合,具体如下:

			if(pid==0)//child_process
			{
				int pid2;
				int status2;
				int fd2;

				if((pid2=fork())<\0)
				{
					printf("fork2 error\n");
					return;
				}
				else if(pid2==0)//child_child_process
				{
					if(!(find_command(arg[0])))
					{
						printf("%s: command not found\n",arg[0]);
						exit(0);
					}
					fd2=open("/tmp/tempfile",O_WRONLY|O_CREAT|O_TRUNC,0644);
					dup2(fd2,1);
					execvp(arg[0],arg);
					exit(0);
				}

				if(waitpid(pid2,&status2,0)==-1)//waiting for child_child_process
				{
					printf("wait for child_child process error\n");
				}

				if(!(find_command(argnext[0])))
				{
					printf("%s:command not found\n",argnext[0]);
					exit(0);
				}

				fd2=open("/tmp/tempfile",O_RDONLY);
				dup2(fd2,0);
				execvp(argnext[0],argnext);

				if(remove("/tmp/tempfile"))
				{
					printf("remove error\n");
				}
				exit(0);
			}

上出1~4处代码需要一个switch语句来组织,根据how参数来选择相应代码,进行执行。另外,由于上述代码中设计两次fork进程,因此要弄清楚父子进程的关系,其次还需了解代码4中waitpid函数的作用。由于子子进程和子进程同组,因此子进程等待子子进程结束。

同样,父进程需要等待子进程执行完毕。但是如果命令行中包含后台运行父&,那么附近成可直接返回,不用等待子进程。这些功能的实现代码如下:

	if(background==1)
	{
		printf("process id %d\n",pid);
	}

	if(waitpid(pid,&status,0)==-1)
	{
		printf("wait for child process error\n");
	}

至此,我们分析完了do_cmd函数的功能,完整代码可以参考这里。

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key