Bootstrap

linux高级编程实验

目录

文件管理(一)

文件管理(二)

文件管理(三)

文件管理(四)

文件管理(五)

文件管理(六)

进程(一)

进程(二)

进程(三)

进程(四)

进程(五)

管道(一)

管道(二)

管道(三)

进程间的通信(一)

进程间的通信(二)

进程间的通信(三)

进程间的通信(四)

进程间的通信(五)

进程间的通信(六)

进程间的通信(七)

进程间的通信(八)

进程间的通信(九)

线程实验(一)


文件管理(一)

编写代码,完成以下功能:
1.创建文件file1,写入字符串“abcdefghijklmn”;
2.创建文件file2,写入字符串“ABCDEFGHIJKLMN”;
3.读取file1中的内容,写入file2,使file2中的字符串内容为“abcdefghijklmn ABCDEFGHIJKLMN”

 

利用Linux进行C程序开发,首先需要了解程序要求,理清思路。

按照要求,我们需要用open创建两个文件file1和file2.利用write将字符串“abcdefghijklmn”写入file1中,同理创建file2,将“ABCDEFGHIJKLMN”写入file2;最后结合lseek函数,将file中的字符串读出,写入file2;
 

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
int main()
{
	int fd1,fd2;
	char str[14];
	fd1 = open("file1",O_RDWR|O_CREAT,S_IRWXU);
	
	if(fd1 < 0)
	  perror("open");
	
	write(fd1,"abcdefghijklmn",14);
	lseek(fd1,0,SEEK_SET);
 
	fd2 = open("file2",O_RDWR|O_CREAT,S_IRWXU);
	
	if(fd2 < 0)
	  perror("open");
	lseek(fd2,14,SEEK_END);
	write(fd2,"ABCDEFGHIJKLMN",14);
 
	read(fd1,str,14);
	lseek(fd2,0,SEEK_SET);
	write(fd2,str,14);
 
	close(fd1);
	close(fd2);
 
	system("cat file2");
        printf("\n");
	system("rm -f file1 file2");
 
	return 0;
}

通过阅读代码我们可以发现,在创建file2之后,我用了lseek函数,并且将偏移量设为14,这样做的目的是增大file2的文件大小,否则会产生文件覆盖,写入的小写字符串将大写字符串覆盖,导致实验失败。

利用lseek函数”扩充“文件时,应格外注意一点:lseek函数”扩充“文件后,并不能直接使得文件大小改变,需要在下一个写操作之后才能使文件变大。即第23句和第24句的位置不能互换,否则文件内容会发生覆盖。

system函数内的语句会被终端执行。

代码执行结果:

实验完成。
 

文件管理(二)

编写代码,完成以下功能:
1.创建新文件,该文件具有用户读写权限。
2.采用dup/dup2/fcntl复制一个新的文件描述符,通过新文件描述符向文件写入“class_name”字符串;
3.通过原有的文件描述符读取文件中的内容,并且打印显示;

由题目可知,我们需要创建一个文件file,然后用dup/dup2/fcntl中的任意一种方式,复制file的文件描述符,通过新的文件描述符将自己的班级名写入file中,最后通过原来的文件描述符读取文件信息。

通过对文件描述符的相关知识的了解,我们知道一个文件描述符只能对应一个文件,而多个文件描述符可以指向同一个文件。
 

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
	int fd,fd1;
	char *str = "yidongyiban";
 
	fd = open("file",O_CREAT|O_RDWR);
	if(fd < 0)
	  perror("open");
 
	fd1 = dup(fd);
	if(fd1 < 0)
	  perror("dup");
 
	write(fd1,str,strlen(str));
	lseek(fd,0,SEEK_SET);
 
	char buf[12];
	read(fd,buf,12);
	printf("The buf is:%s\n",buf);
 
	close(fd);
	close(fd1);
 
	system("rm -f file");
	return 0;
}

通过dup函数,能够复制原文件的文件描述符,函数返回新的文件描述符。

程序运行结果如下:

实验完成

文件管理(三)

编写程序实现以下功能:
1.输入文件名称,能够判断文件类型,判断实际用户对该文件具有哪些存取权限;
2.要求打印出文件类型信息,inode节点编号,链接数目,用户id,组id,文件大小信息;
3.修改文件的权限为当前用户读写,组内用户读写,组外用户无权限。

首先,在编写程序之前,我们需要了解如何给程序输入文件名称。利用scanf函数当然能够实现,但是在C语言中,还有一个更加方便的方法能够为程序传递所需参数。

在以前的学习中,我们可能认为main函数是没有参数的,因为main的写法是int main(),但是main函数是有且只有两个参数的。

C语言规定main函数的参数为argc和argv。其中,argc代表参数的个数,必须为int型;argv[]储存了函数的参数,必须为字符串类型的指针数组。因此,main函数的函数头可以写为:main(int argc,char *argv[])或main(int argc,char **argv)。

其中,argv[0]中储存的是程序的全名,argv[1]为第一个参数,argv[2]为第二个参数......
接下来,一个简单的程序能帮助理解:

#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
	for(int i = 0; i < argc; i++)
	{
		printf("The argv[%d] is:%s\n ",i,argv[i]);		
	}
	return 0;
}

程序运行结果:

通过这个简单的程序,能够很清楚的看出argc和argv[]的含义

我们可以通过argv[]文件名传递到程序

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
int main(int argc,char *argv[])
{
	if(argc < 2)
	{
		printf("Please input the filename!\n");
		return 0;
	}
 
	struct stat statbuf;
	int i;
	for(i = 1;i < argc;i++)
	{
		if(lstat(argv[i],&statbuf) < 0)
			perror("lstat");
 
		char *buf;
	
		if(S_ISREG(statbuf.st_mode))
			buf = "Regular file!";
		else if(S_ISDIR(statbuf.st_mode))
			buf = "Directory file!";
		else if(S_ISCHR(statbuf.st_mode))
			buf = "Char file!";
		else
			buf = "Other file!";
		printf("The %s is:%s\n",argv[i],buf);
 
		printf("The %s mode is:%d\n",argv[i],statbuf.st_mode);
		printf("The %s inode is:%d\n",argv[i],statbuf.st_ino);
		printf("The %s uid is:%d\n",argv[i],statbuf.st_uid);
		printf("The %s gid is:%d\n",argv[i],statbuf.st_gid);
		printf("The %s size is:%d\n",argv[i],statbuf.st_size);
		printf("The %s link num is:%d\n",argv[i],statbuf.st_nlink);
	}
 
	for(i = 1;i < argc;i++)
	{
		if(access(argv[i],R_OK))
		  printf("The user can read the %s\n",argv[1]);
		else if(access(argv[i],W_OK))
		  printf("The user can write the %s\n",argv[1]);
		else if(access(argv[i],X_OK))
		  printf("The user can read and write the %s\n",argv[1]);
 
	}
 
	for(i = 1;i < argc;i++)
	{
		if(chmod(argv[i],0660) < 0)
			perror("chmod");
		else
			printf("The file.mode chmod successful!\n");
	}
 
	return 0;
}

可能有人注意到了我在修改文件权限时用到了0660这样的一串数字,那么它的含义是什么呢?

 
0xxxx是文件权限的另一种表示方法,一般Linux文件或目录权限分为三个,当前用户,组内用户和组外用户。每个都有三个权限rwx,即读,写,执行权限。
权限的表示方法有两种,一是直观法,即直接用rwx表示,另外一种是二进制数值法,如:644,755等。读是4,写是2,执行是1,三个相加得7,以此类推,如果是6,则表示读,写,没有执行权限。

当运行程序但并没有传入参数时,程序退出并提示输入文件名

程序运行结果:

实验完成
 

文件管理(四)

编写程序实现以下功能:
1.新建文件,设置文件权限屏蔽字为0;
2.建立该文件的硬链接文件,打印硬链接文件的inode节点号和文件大小;
3.建立该文件的软链接文件,打印软链接文件的inode节点号和文件大小;打印软链接文件中的内容;
4.打印源文件的inode节点号,文件大小和链接数目;
5.调用unLink对源文件进行操作,打印源文件链接数目;

在进行程序编写之前,我们应当了解文件的软链接和硬链接的含义及两者的区别。

1.软链接,以路径的形式存在。类似于Windows操作系统中的快捷方式
2.软链接可以 跨文件系统 ,硬链接不可以
3.软链接可以对一个不存在的文件名进行链接
4.软链接可以对目录进行链接

软链接文件存放的内容是原文件的路径名。

硬链接:硬链接文件是对原文件的文件名

Linux下的每个文件都具有各自的权限位,用户要想对文件进行某种操作,就必须具有相应权限。Linux下的文件权限位如下表所示:
st_mode     含义
S_IRUSR     当前用户读(0400)
S_IWUSR     当前用户写(0200)
S_IXUSR     当前用户执行(0100)
S_IRWXU                           当前用户读、写、执行(0700)
S_IRGRP     组内用户读(0040)
S_IWGRP     组内用户写(0020)
S_IXGRP     组内用户执行(0010)
S_IRWXG     组内用户读、写、执行(0070)
S_IROTH     组外用户读(0004)
S_IWOTH     组外用户写(0002)
S_IXOTH               组外用户执行(0001)
S_IRWXO             组外用户读、写、执行(0007)

当用户创建一个文件时,需要为新的文件指定一个权限字,在实际应用中,系统有时不希望用户设置所有的权限。因此可以通过umask函数设置权限屏蔽字,直接将不需要用户干预的文件权限进行屏蔽,这样即使用户设置了相关文件权限位,系统也会将其忽略,仍然将其设为0;
 

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
	umask(0);
	struct stat statbuf;
	int fd = open("file",O_CREAT|O_RDWR);
	if(fd < 0)
	  perror("open");
 
	char *str = "hello world";
	if(write(fd,str,strlen(str)) < 0)
	  perror("write");
	
	link("./file","./hard_link");
 
	
	if(lstat("hard_link",&statbuf) < 0)
	  perror("lstat");
 
	printf("The hard_link's inode is: %d\n",statbuf.st_ino);
	printf("The hard_link's size is: %d\n",statbuf.st_size);
 
	symlink("file","sort_link");
 
	if(lstat("sort_link",&statbuf) < 0)
        perror("lstat");
 
	printf("The sort_link's inode is: %d\n",statbuf.st_ino);
        printf("The sort_link's size is: %d\n",statbuf.st_size);
	
	char buf[4];
	readlink("sort_link",buf,4);
	printf("The sort_link is: %s\n",buf);
 
	if(lstat("file",&statbuf) < 0)
	  perror("lstat");
 
	printf("The file's inode is: %d\n",statbuf.st_ino);
	printf("The file's size is: %d\n",statbuf.st_size);
	printf("The frist linknum is: %d\n",statbuf.st_nlink);
 
	unlink("file");
 
	if(lstat("file",&statbuf) < 0)
	  perror("lstat");
	printf("The second linknum is: %d\n",statbuf.st_nlink);
 
	close(fd);
	return 0;
}

程序运行结果:

我们可以看到,在第二次利用lstat获取file的信息时,提示没有file,这正是进行ulink的结果。

ulink函数的作用:删除目录相并减少一个连接数,如果链接数为0并且没有任何进程打开该文件,该文件内容才能被真正删除,但是若又进程打开了该文件,则文件暂时不删除直到所有打开该文件的进程都结束时文件才能被删除。

因此,运行结果中显示的第二个linknum仍然是第一个的值。

实验成功
 

文件管理(五)

1.新建/home/user目录;
2.把当前工作路径移至/home/user目录;
3.打印当前工作路径;

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
	char str[128];
 
	if(getcwd(str,128) < 0)
	  perror("getwcd");
	else
	  printf("The workdir is:%s\n",str);
 
	if(mkdir("/home/user",0666) < 0)
	  perror("mkdir");
	else
	  printf("The dir create successfully!\n");
	
	if(chdir("/home/user") < 0)
	  perror("chdir");
	else
	{
		getcwd(str,128);
		printf("The workdir is:%s\n",str);
	}
 
	rmdir("/home/user");
	return 0;
}

虽然程序已经编写完毕,但是我们在运行程序时却会遇到问题:

如图所示,在运行程序时,系统提示权限不足,那么就无法创建"/home/user"。那么我们应该怎样才能成功执行呢?

我们可以先切换到root用执行该程序,就能成功创建。

实验完成

文件管理(六)

编写程序完成以下功能:
1.递归遍历/home目录,打印出所有文件和子目录名称及节点号。
2.判断文件类型,如果是子目录,继续进行递归遍历,直到遍历完所有子目录为止。

所谓递归,就函数自己调用自己,直到完成函数终止条件,跳出递归函数。

要想递归遍历/home目录,就必须确定合适的迭代条件。既然要遍历/home目录,那就要将所有的文件夹都访问到。因此,只要是/home目录下的文件夹,都要被访问,因此,我们以打开文件的结果作为迭代条件,只要打开的是目录文件,就能够进入递归函数。不仅如此,要想打开正确的目录,就必须获得目录的路径,因此,我们会用到spritnf函数来储存文件路径。

spritf函数的作用和printf的作用相似,只不过printf是将字符串输出到屏幕,而sprintf函数是将字符串输出到字符串中。

函数示例如下
 

#include <stdio.h>
#include <stdlib.h>
int main()
{
	char *str = "hello";
	char *buf = "world";
	char s[128];
 
	sprintf(s,"%s%s",str,buf);
 
	printf("The s is: %s\n",s);
	
	return 0;
}

程序运行结果为:

利用sprintf函数,我们就能将文件的绝对路径写入字符串中,通过字符串打开文件

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
 
void show(char *path)
{
	DIR *dir;
	char str[128];
	struct dirent *dirp;
	struct stat statbuf;
 
	dir = opendir(path);
 
	if(dir)
	{
		while((dirp = readdir(dir)) != NULL)
		{	
			sprintf(str,"%s/%s",path,dirp->d_name);
			if(lstat(str,&statbuf) < 0)
			  perror("lstat");
	
			if(dirp->d_name[0] == '.')
			  continue;
 
			if(S_ISDIR(statbuf.st_mode))
			{
				show(str);
 
				printf("The dirent's name is: %s\n",dirp->d_name);
				printf("The dirent's inode is: %d\n",dirp->d_ino);
			}
			else
			{
				printf("The file's name is: %s\n",dirp->d_name);
				printf("The file's inode is: %d\n",dirp->d_ino);
			}
		}
	}
	else
		perror("opendir");
 
	closedir(dir);
}
 
int main()
{
	show("/home");
	return 0;
}

实验完成

进程(一)

编写代码,实现以下功能:

  • 打印当前所有环境变量的值;
  • 添加新的环境变量NEWENV=first;
  • 修改环境变量NEWENV的值为second;
  • 打印环境变量NEWENV的值。

extern是全局变量声明。
只要声明全局变量就默认 前面加extern(程序员可以不加,但编译器默认加上)

当函数定义里有extern关键字,则表示该函数的定义可能在其它的源文件中。

程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
extern char **environ;
 
int main()
{
	char **env = environ;
	while(*env)
	{
		printf("The env is: %s\n",*env);
		env++;
	}
 
	putenv("NEWENV=first");
	
	char *str;
	str = getenv("NEWENV");
	printf("The NEWENV is: %s\n",str);
 
	if(setenv("NEWENV","second",1) < 0)
	  perror("setenv");
	
	str = getenv("NEWENV");
	printf("The NEWENV is: %s\n",str);
 
	return 0;
}

实验完成

进程(二)

编写代码实现以下功能:

  • 打印字符串“hello world!”
  • 在打印字符串“hello world!”前调用三次fork,分析打印结果。

我们在编写程序之前,我们先分析一下,应该会打印出几个“hello world”。

首先我们要了解fork函数的工作机制。首先,调用一次fork函数会返回两个返回值,父进程会返回子进程的pid,子进程会返回0。

父子进程都会执行fork函数下的第一条语句。

父子进程的执行顺序是不确定的,取决于操作系统的调度。

综上所诉,调用三次fork数后,将会打印八个“hello world”。

程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
 
	fork();
	fork();
	fork();
 
	printf("hello world!!!\n");
	return 0;
}

程序运行结果如下:

实验完成

进程(三)

创建子进程
在子进程中打开文件file1,写入自己的“班级_姓名_学号”,
父进程读取file1中的内容,并且打印显示。
在父进程中获取已经结束的子进程的状态信息,打印该信息,并且打印结束的子进程的进程号

 

由实验二我们了解到调用fork函数后,父进程会返回子进程的pid,子进程则会返回0。因此,在调用fork函数后,只要加上一个判断语句,就能确定是在父进程还是子进程了。

要想获取到已经结束的子进程的状态信息,需要我们用到wait函数并结合相关宏将子进程的退出状态打印。

wait函数的执行过程如下:

程序如下:
 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
int main()
{
	int fd,pid;
	fd = open("file",O_CREAT|O_RDWR,S_IRWXU);
	if(fd< 0)
	  perror("open");
 
	pid = fork();
 
	if(pid  == 0)
	{
		printf("This is the child!\n");
		char str[128] = "yidongyiban_wangguangjie_1715925494";
		
		if(write(fd,str,128) < 0)
		  perror("write");
 
		exit(5);
	}
	else
	{
		printf("This is the father!\n");
		
		char buf[128];
		int n,status;
		if(read(fd,buf,128) < 0)
		  perror("read");
		printf("The buf is: %s\n",buf);
 
		if(wait(&status) < 0)
		  perror("perror");
 
		if(WIFEXITED(status))
		  n = WEXITSTATUS(status);
		else
		  printf("wait error!\n");
 
		printf("The child's pid is: %d\n",pid);
		printf("The child exit status is: %d\n",n);
	}
	return 0;
}

其中,WIFEXITED宏用来检测子进程是否正常结束,成功则返回非零值,否则为0;

WEXITSTATUS宏则会返回子进程的结束状态,例如:子进程通过exit(5)退出,则它的返回值就是5。

程序运行结果:

实验完成

进程(四)

编写程序实现以下功能:
1,在父进程中定义变量n,在子进程中对变量n进行++操作;并且打印变量n的值,打印子进程pid;
2,在父进程中打印变量n的值,并且打印父进程pid。
3,要求分别用fork和vfork创建子进程。

在进行程序编写之前,我们要明白vfork和fork的区别。

1.vfork产生的子进程和父进程共享代码段和数据段,而fork产生的子进程是拷贝父进程的数据段和代码段。

2.vfork产生的子进程的一定比父进程先运行,而fork产生的子进程和父进程的执行顺序不一定,由操作系统的调度决定。

3.vfork产生的子进程调用exit或exec族函数之后才能对父进程进行调度,如果在此之前子进程的运行依赖父进程的进一步执行结果,则会照成死锁。

程序结果分析:假设变量n的值为1,在vfork函数中,父进程打印的n值是2,而fork函数中的父进程打印的n值是1。

fork.c程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
 
int main()
{
	int n = 1;
	if(fork() == 0)
	{
		printf("This is child,the pid is%d\n",getpid());
		printf("The n is: %d\n",++n);
	}
	else
	{
		printf("This is father,the pid is%d\n",getpid());
		printf("The n is: %d\n",n);
	}
 
	return 0;
}

fork.c程序运行结果:

vfork.c程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
	int n = 1;
	pid_t pid;
	pid = vfork();
	if(pid < 0)
	  perror("vfork");
	else if(pid == 0)
	{
		printf("This is child,the child's pid is: %d\n",getpid());
		printf("The n is: %d\n",++n);
		exit(0);
	}
	else
	{
		printf("This is father,the father's pid is: %d\n",getpid());
		printf("The n is: %d\n",n);
	}
	return 0;
}

vfork.c程序运行结果:

通过两个程序的运行结果我们可以发现,之前的推理是正确的。

注意,在试验中的n的自加必须用++n。因为如果用n++,两个父进程输出的n值都会是1。

实验完成

进程(五)

创建子进程一,在子进程中递归打印/home目录中的内容(用exec系列函数调用第一次实验中的代码完成此功能);
子进程结束的时候完成以下功能:
打印字符串“Child process exited!”
打印子进程标识符,打印父进程标识符。
创建子进程二, 打印子进程运行环境中环境变量“USER”的值,通过exec系列中的某个函数设置子进程”USER”环境变量值为“zhangsan”,并且让该子进程完成以下命令:“ls-li /home”.

在程序编写之前,我们应该对exec族函数的功能进行一定的了解。

exec族函数的作用是在进程中,通过文件名找到可执行文件(二进制文件或脚本文件)并对其进行调用。一个进程一旦调用exec族的函数,它本身就已经结束了。系统会将原来进程的代码段更新,并废弃其数据段和堆栈段。唯一没有被改变的就是该进程的pid,因此,对于操作系统来说,进程并没有结束,只是更换了新的程序。

于此同时,我们要清楚如何在进程结束时将字符串打印。利用atexit登记函数,可以实现题目要求。

atexit的作用是程序可以在进程结束时完成对进程资源的清理工作,通过登记自定义函数来实现清理工作。注意:先登记的后执行,即登记顺序和执行顺序是相反的。同时,_exit的调用不能触发atexit登记的函数。

atexit函数实例如下:
 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
void test1(void )
{
	printf("This is the test1!\n");
}
 
void test2(void)
{
	printf("This is the test2!\n");
}
 
int main()
{
	if(fork() == 0)
	{
		printf("This is the child!\n");
		atexit(test2);
		atexit(test1);
		exit(1);
	}
	else
	{
		printf("This is father!\n");
	}
	return 0;
}

程序运行结果如下:

通过程序运行结果我们发现,函数的执行顺序确实是和登记顺序相反的。

要想让子进程二执行“ls -li /home”,则可以利用system函数执行。

程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
void fun(void)
{
	printf("~~~~~~~~~~~~~~~~~~~~~~\n");
	printf("Child process exited!!!\n");
	printf("The child's pid is: %d\n",getpid());
	printf("The father's pid is %d\n",getppid());
	printf("~~~~~~~~~~~~~~~~~~~~~~\n");
}
		
int main()
{
	pid_t pid;
	pid = vfork();
 
	if(pid <0)
	  perror("vfork");
	else if(pid == 0)
	{
		printf("This is the child1 !!!\n");
		atexit(fun);
 
		if((execl("/home/wang/test/file/test6/test","test",NULL)) < 0)
		{
		  perror("execl");
		  exit(0);
		}
	}
	else
	{
		printf("This is the father !!!\n");
		if(vfork() == 0)
		{
			printf("This is the child2 !!!\n");
			printf("The child2's father's pid is: %d\n",getppid());
			char * env[] = {"USER=zhangsan",NULL};
			char *p;
			
			p = getenv("USER");
			if(p)
			{
				printf("The user is: %s\n",p);
			}
 
			system("ls -li /home");
			if((execle("/bin/env","env",NULL,env)) < 0)
			  perror("execle");
			
			exit(1);
		}
	}
	return 0;
}

在我的Fedora中运行程序发现,只用vfork函数创建的子进程中才能成功调用atexit函数,而fork函数创建的子进程下不能实现对atexit函数的调用,但是在我的ubuntu系统中却能正常运行。

程序运行结果: 

实验完成

管道(一)

编写程序实现以下功能:
利用匿名管道实现父子进程间通信,要求父进程发送字符串“hello child”给子进程;
子进程收到父进程发送的数据后,给父进程回复“hello farther”
父子进程通信完毕,父进程依次打印子进程的退出状态以及子进程的pid。

管道作为较为原始的进程间的通信方式,在进程间的通信中被广泛使用。管道可分为有名管道和匿名管道,两者有相似之处又有一定的区别。

匿名管道的特点如下:

1.匿名管道是半双工的,数据只能朝一个放向流动,因此要实现两个进程的通信,就必须建立两个匿名管道;

2.匿名管道只能在管道尾部写入数据,从管道头部读取数据;fd[0]用于进程读取数据,fd[1]用于进程写入数据;

3.匿名管道不具备存储数据的能力,数据一旦被读取,其它进程将不能从该管道中读取数据;

4.匿名管道只能在有血缘关系的进程间通信;如父子进程和兄弟进程。

程序如下:
 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
 
int main()
{
	int fd1[2],fd2[2];
	pipe(fd1);
	pipe(fd2);
	
	int pid;
	pid = fork();
 
	if(pid < 0)
		perror("fork");
	else if(pid == 0)
	{
		close(fd1[0]);
		close(fd2[1]);
		char str[12];
		printf("This is the child!\n");
		
		if(read(fd2[0],str,12) > 0)
		{
			printf("Received the news: %s\n",str);
			if(write(fd1[1],"hello father",12) < 0)
			  perror("write");
		}
		else
			perror("read");
 
		exit(5);
	}
	else
	{
		int status;
		printf("This is the father!\n");
		
		close(fd1[1]);
		close(fd2[0]);
 
		char buf[24] = "hello child";
		if(write(fd2[1],buf,12) < 0)
		  perror("write");
		else
		{
			printf("Send news successful!\n");
		}
		
		wait(&status);
		if(WIFEXITED(status))
		{
			printf("The child's pid is: %d\n",pid);
			printf("The child's exited status is: %d\n",WEXITSTATUS(status));
		}
	}
 
	return 0;
}

程序结果:

管道(二)

编写程序实现以下功能:
利用匿名管道实现兄弟进程间通信,要求兄进程发送字符串“This is elder brother ,pid is (兄进程进程号)”给弟进程;
弟进程收到兄进程发送的数据后,给兄进程回复“This is younger brother ,pid is(弟进程进程号)”;

 

我们都知道利用fork函数能创建一个子进程,但是如何利用fork函数创建兄弟进程呢?

我们可以利用fork函数先建立一个子进程,在子进程中,将要发送的信息写入管道,然后再在父进程中再次调用fork函数,那么父进程里创建的子进程就是先前创建的进程的弟进程。我们可以让子进程将自己的父进程的pid打印,验证两进程是否是兄弟进程。

程序如下:
 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
int main()
{
	int fd1[2],fd2[2];
	pipe(fd1);
	pipe(fd2);
 
	int pid;
	pid = fork();
	if(pid == 0)
	{
		printf("This is the elder brother!\n");
		printf("The elder's father's pid is: %d\n",getppid());
 
		close(fd1[1]);
		close(fd2[0]);
		char str1[64],str2[64];
		sprintf(str1,"This is the elder brother,pid is %d",getpid());
		
		if(write(fd2[1],str1,64) < 0)
		  perror("write");
		
		if(read(fd1[0],str2,64) < 0)
		  perror("read");
		else
		  printf("The news from younger is: %s\n",str2);
 
	}
	else
	{
		if(fork() == 0)
		{
			printf("This is the younger brother!\n");
			printf("The younger's father's pid is: %d\n",getppid());
 
			close(fd1[0]);
			close(fd2[1]);
			char buf1[64],buf2[64];
			if(read(fd2[0],buf1,64) > 0)
			{
				printf("The news form elder is: %s\n",buf1);
				sprintf(buf2,"This is the younger brother,pid is %d",getpid());
 
				if(write(fd1[1],buf2,64) < 0)
					perror("write");
			}
			else
			  perror("read");
		}
	}
}

运行结果如下: 

通过实验结果我们可以发现,两个进程的父进程并不相同。但是通过在ubuntu的环境下,测试成功。

管道(三)

编写程序实现以下功能:

  • 利用有名管道文件实现进程间通信
  • 要求写进程向有名管道文件写入10次“hello world”
  • 读进程读取有名管道文件中的内容,并依次打印。

有名管道的特点:

1.有名管道支持读写操作,并且存在于文件系统中

2.能够使用使用read和write直接对有名管道进行操作。

3.有名管道是双向管道。

4.可以用于任意进程间的通信,不像匿名管道具有局限性。

我们能够像操作一个文件一样操作有名管道。

实验代码:
 

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
int main()
{
	int pid,fd;
	if(mkfifo("fifotest",0666) < 0)
	  perror("mkfifo");
 
	pid = fork();
	
	if(pid < 0)
	  perror("fork");
	else if(pid == 0)
	{
		printf("This is the write process!\n");
		int fd = open("fifotest",0666);
 
		for(int i = 0; i < 10;i++)
		{
			if(write(fd,"hello world",12) < 0)
				perror("write");
			sleep(1);		
		}
		close(fd);
	}
	else
	{
		char str[128];
		printf("This is the read process!\n");
		int fd1 = open("fifotest",0666);
 
		for(int i = 0;i < 10;i++)
		{
			if(read(fd1,str,128) < 0)
			  perror("read");
			else
			  printf("%s\n",str);
		}
		system("rm -f fifotest");
	}
}

实验结果:

进程间的通信(一)

编写程序实现下列功能:

  • 进程A向进程B发送SIGUSR1信号;
  • 进程B收到信号后,打印字符串“receive SIGUSR1”;
  • 要求用kill函数和signal函数实现以上功能;

信号是比较复杂的通信方式,用于通知进程中某种事件的发生。除了进程间的通信之外,进程还能发送信号给进程本身;每种信号类型都有对应信号处理程序。大多数的信号的系统默认操作是结束进程,当然,进程同样可以向系统请求采取某些代替的操作。

例如:忽略信号、恢复信号的默认操作和执行一个预先设定的信号处理函数。

信号的本质是在软件层次上对进程的中断机制的一种模拟。在原理上,一个进程收到某种信号和处理器收到中断请求是一样的。

信号是所有的进程间的通信机制中唯一一个异步通信机制,可以看作是异步通知。

信号的生命周期如下:

接下来我们通过signal和kill两种方式分别实现进程间的通信。

signal.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
void fun(int sig)
{
	if(sig == SIGUSR1)
		printf("Received SIGUSR1!\n");
}
 
int main()
{
	printf("This is A process,mypid is: %d\n",getpid());
	signal(SIGUSR1,fun);
	pause();
	return 0;	
}

此运行程序的方式和以往有些许不同,运行结果如下:

因为程序中有pause()语句,那么程序运行到此就会停下知道有信号发送给此进程。

然后新建一个终端,在终端输入kill -SIGUSR 11344,那么第二个进程就会发送SIGUSR1信号给pid为11344的进程,也就是进程A。之后程序输出字符串,进程结束。

接下来利用kill函数完成实验。

kill.c
 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
void fun(int sig)
{
	if(sig == SIGUSR1)
	  printf("Reseived SIGUSR1!\n");
}
int main()
{
	int pid;
	
	if(signal(SIGUSR1,fun) < 0)
		perror("signal");
	
	pid = fork();
	
	if(pid < 0)
	  perror("fork");
	else if(pid == 0)
	{
		printf("This is B process!\n");
 
		sleep(2);
	}
	else
	{
		printf("This is A process!\n");
		if(kill(pid,SIGUSR1) < 0)
		  perror("kill");
		return 0;
	}
}

程序运行结果:

进程间的通信(二)

编写程序实现以下功能:

  • 进程A向进程B发送SIGUSR1信号;
  • 进程B收到信号后,打印字符串“receive SIGUSR1”;
  • 要求用sigqueue函数和sigaction函数实现以上功能;

sigaction和signal两个函数都是信号安装函数,但是他们两个之间还是有一定的区别的。

signal不能传递除信号之外的信息,而sigaction能够传递额外的信息;同时,sigaction能够设置进程的掩码,并且能够阻塞进程。

sigqueue函数只要针对实时信号,并且支持传递的信号附带参数,常常和sigaction函数配合使用。

sigaction.c
 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
void fun(int sig)
{
	if(sig == SIGUSR1)
	  printf("Received SIGUSR1!\n");
}
 
int main()
{
	printf("This is the receive process!\n");
	printf("The process pid is: %d\n",getpid());
 
	struct sigaction act,oldact;
 
	act.sa_handler = fun;
	act.sa_flags = 0;
 
	sigaction(SIGUSR1,&act,&oldact);
 
	pause();
	return 0;
}

igaction.c的操作方式与之前相似。

程序运行结果:

sigqueue.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
void handler(int sig,siginfo_t* p,void* q)
{
	if(sig == SIGUSR1)
	  printf("Received SIGUSR1!\n");
}
 
int main()
{
	union sigval mysigval;
	struct sigaction act;
 
	int pid;
	pid = fork();
 
	if(pid < 0)
		perror("fork");
	else if(pid == 0)
	{
		printf("This is the received process!\n");
        act.sa_sigaction = handler;
        act.sa_flags = SA_SIGINFO;
 
		if(sigaction(SIGUSR1,&act,NULL) < 0)
			perror("sigaction");
 
		while(1);
	}
	else
	{
		printf("This is the send process!\n");
		sleep(1);
		if(sigqueue(pid,SIGUSR1,mysigval) < 0)
		  perror("sigqueue");
	}
	return 0;
}

运行结果如下:

进程间的通信(三)

编写程序完成以下功能:

  • 调用setitimer函数分别触发SIGALRM信号,SIGVTALRM信号,SIGPROF信号 ;(可以由多进程分别触发每个信号)
  • 编写信号安装函数,在该函数内部能判断接受到的是什么信号,并把信号打印出来。

setitimer函数的作用是提供精确的定时功能。通过改变settitime函数的第一个参数就能够改变函数触发的信号。

程序如下:
 

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/types.h>
void fun(int sig)
{
	if(sig == SIGALRM)
	  printf("Received the SIGALRM!\n");
	else if(sig == SIGVTALRM)
	  printf("Receive the SIGVTALRM!\n");
	else if(sig == SIGPROF)
	  printf("Receive the SIGPROf!\n");
}
 
int main()
{
	if(signal(SIGALRM,fun) < 0)
		perror("signal");
	if(signal(SIGVTALRM,fun) < 0)
	    perror("signal");
	if(signal(SIGPROF,fun) < 0)
	    perror("signal");
 
	struct itimerval new_value1,new_value2,new_value3;
	
	new_value1.it_value.tv_sec = 1;
	new_value1.it_value.tv_usec = 0;
	new_value1.it_interval.tv_sec = 2;
	new_value1.it_interval.tv_usec = 0;
 
	setitimer(ITIMER_REAL,&new_value1,NULL);
	
	new_value2.it_value.tv_sec = 1;
	new_value2.it_value.tv_usec = 0;
	new_value2.it_interval.tv_sec = 2;
	new_value2.it_interval.tv_usec = 0;
 
	setitimer(ITIMER_VIRTUAL,&new_value2,NULL);
 
	new_value3.it_value.tv_sec = 1;
	new_value3.it_value.tv_usec = 0;
	new_value3.it_interval.tv_sec = 2;
	new_value3.it_interval.tv_usec = 0;
 
	setitimer(ITIMER_PROF,&new_value3,NULL);
 
 
	while(1);
	return 0;
}

程序运行结果:

进程间的通信(四)

编写程序完成以下要求:

  • 进程A向进程B发送信号;
  • 进程B收到进程A发送的信号后,打印出发送信号进程的pid,uid以及信号值。

通过对之前实验的了解,我们知道sigqueue函数能够向进程发送除信号之外的其它更多的信息。因此,我决定应用四个queue函数将发送进程的pid,uid跟信号一起发送给接受进程。

程序如下:
 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
void handler(int sig,siginfo_t* act,void *p)
{
	printf("The sender's pid is: %d\n",act->si_pid);
	printf("The sender's uid is: %d\n",act->si_uid);
	printf("The sig is: %d\n",sig);
}
 
int main()
{
	int pid;
	union sigval mysigval;
	pid = fork();
	if(pid < 0)
	  perror("fork");
	else if(pid == 0)
	{
		printf("This is the received process!\n");
		struct sigaction act;
		act.sa_sigaction = handler;
		act.sa_flags = SA_SIGINFO;
		if(sigaction(SIGUSR1,&act,NULL) < 0)
		  perror("sigaction");
 
		while(1);
	}
	else
	{
		printf("This is the send process!\n");
		sleep(1);
		printf("The sender's pid is: %d\n",getpid());
		if(sigqueue(pid,SIGUSR1,mysigval) < 0)
		  perror("sigqueue");
	}
	return 0;
}

程序运行结果:

进程间的通信(五)

编写程序完成以下功能:

  • 进程A向进程B发送信号,该信号的附带信息为一个值为20的整数;
  • 进程B完成接收信号的功能,并且打印出信号名称以及随着信号一起发送过来的整形变量值。

信号发送进程通过sigqueue函数能够将更多的信息发送给信号接受进程。

程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void handler(int sig,siginfo_t* info,void *p)
{
	printf("The num is: %d\n",info->si_value.sival_int);
}
 
int main()
{
	int pid;
	struct sigaction act;
	act.sa_sigaction = handler;
	act.sa_flags = SA_SIGINFO;
 
	pid = fork();
	if(pid < 0)
	  perror("fork");
	else if(pid == 0)
	{
		printf("This is the receive process!\n");
		if(sigaction(SIGUSR1,&act,NULL) < 0)
		  perror("sigaction");
 
		while(1);
	}
	else
	{
		printf("This is the send process!\n");
		union sigval mysigval;
		mysigval.sival_int = 20;
		
		sleep(1);
		
		if(sigqueue(pid,SIGUSR1,mysigval) < 0)
			perror("sigqueue");
	}
	return 0;
}

程序运行结果:

进程间的通信(六)

编写程序完成下列要求:

  • 进程A向进程B发送信号,该信号的附带信息为一个字符串“Hello world”;
  • 进程B完成接收信号的功能,并且打印出信号名称以及随着信号一起发送过来的字符串值。

和上一个实验类似,发送进程利用sigqueue函数能够将更多的信息发送给接受进程。

程序如下:
 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
void handler(int sig,siginfo_t* info,void *p)
{
	printf("The str is: %s\n",info->si_value.sival_ptr);
}
 
int main()
{
	int pid;
	struct sigaction act;
	act.sa_sigaction = handler;
	act.sa_flags = SA_SIGINFO;
 
	pid = fork();
	if(pid < 0)
	  perror("fork");
	else if(pid == 0)
	{
		printf("This is the receive process!\n");
		if(sigaction(SIGUSR1,&act,NULL) < 0)
		  perror("sigaction");
 
		while(1);
	}
	else
	{
		printf("This is the send process!\n");
		union sigval mysigval;
		mysigval.sival_ptr = "hello world";
		
		sleep(1);
		
		if(sigqueue(pid,SIGUSR1,mysigval) < 0)
			perror("sigqueue");
	}
	return 0;
}

程序运行结果:

进程间的通信(七)

编写程序完成以下功能:

1.创建共享内存,写进程通过键盘不断向内存写入“hello world”;
如果结束写操作,则通过键盘输入“end”
2.读进程从共享内存读取数据,并打印。直到读到“end”为止。

共享内存就是多个进程同时访问一个逻辑内存区域,共享内存是两个不相关的进程传递数据的重要方式。进程将同一段物理内存连接到他们自己的地址空间之后,所有连接的进程都能访问这块内存。如果一个进程对这段内存进行更改,所做的更改将影响更改之后访问这段内存的进程。需要注意的是,共享内存并没有设置同步机制,也就是说,在上一个进程对内存进行更改操作完成之后 ,并没有机制阻止下一个进程对这段内存的更改。因此,我们需要利用其它的机制对共享内存来同步进程对共享内存的访问。例如:信号量。

因为是直接对内存进行操作,省去了数据传输这一步骤,因此共享内存的速度最快。

程序如下:

shmread.c
 

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/shm.h>
#define MAXSIZE 1024
 
struct shm{
	int write;    //记录读进程是否已经将内容读取
	char buffer[MAXSIZE];
};
 
int main()
{
	int shmid;
	struct shm *share;
	void *shmptr = NULL;
	
	if((shmid = shmget(0X44,MAXSIZE,0666|IPC_CREAT)) < 0)
		perror("shmget");
	if((shmptr = shmat(shmid,0,0)) == (void *)-1)
		perror("shmat");
 
	printf("This is the read process!!!\n");
	share = (struct shm *)shmptr;
	while(1)
	{
		if(share->write != 0)
		{
			if(!strncmp(share->buffer,"end",3) == 0)
			{
				printf("%s",share->buffer);
				share->write = 0;
			}
			else
				break;
		}
	}
 
	if(shmdt(shmptr) < 0)
		perror("shmdt");
 
	exit(0);
}

shmwrite.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/shm.h>
#define MAXSIZE 1024
struct shm{
	int write;        //记录读进程是否已经将内容读取
	char buffer[MAXSIZE];
};
 
int main()
{
	int shmid;
	void *shmptr = NULL;
	char str[MAXSIZE];    //存储输入的内容
	struct shm *share;
 
	if((shmid = shmget(0X44,MAXSIZE,0666|IPC_CREAT)) < 0)
		perror("shmget");
	if((shmptr = shmat(shmid,0,0)) == (void *)-1)
		perror("shmat");
 
	printf("This is the write process!!!\n");
	share = (struct shm *)shmptr;
	while(1)
	{
		if(share->write == 1)
		{
			sleep(1);
			printf("Waiting the read process!!!\n");
		}
 
		printf("please input hello world!!!\n");
		fgets(str,MAXSIZE,stdin);
		sprintf(share->buffer,"%s",str);
		share->write = 1;		
 
		if(strncmp(str,"end",3) == 0)
			break;
		sleep(1);
	}
	if(shmdt(shmptr) < 0)
		perror("shmdt");
	exit(0);
}

程序运行截图:

进程间的通信(八)

编写程序完成以下要求:

1.进程A向消息队列发送消息“hello world”
2.进程B从消息队列读取消息,并打印
3.进程C向消息队列发送"自己的姓名"
4.进程D从消息队列中取出姓名字符串,并打印

 

消息队列也叫报文队列,是一个消息的链表。可以把消息看作是一个记录,具有特定的格式以及优先级。对消息队列具有写权限的进程可以按照一定的规则向消息队列中添加消息,而对消息队列具有写权限的进程可以从消息队列中读走消息。和管道相似的是,消息一旦从消息队列中被读走,则消息队列中便不在存在此条消息。

IPC消息队列的缺省最大数为16;

每个消息缺省最大值为8192字节;

队列中的最大值缺省为16384字节;

每个消息队列都有其对应的属性信息,存储在struct_msqid_ds结构体中。

每个消息队列都有一个对应的id,标识消息队列的唯一性。

程序如下:
 

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <sys/msg.h>
#include <sys/types.h>
struct msg{
	char msg_str[128];
};
 
int main()
{
	int qid;
	struct msg mymsg;
	if(qid = msgget(0x66,0666|IPC_CREAT) < 0)
	  perror("msgget");
 
	int pid;
	pid = fork();
	if(pid < 0)
	  perror("fork");
	else if(pid == 0)
	{
		printf("This is A process!\n");
		sprintf(mymsg.msg_str,"hello world");
		
		if(msgsnd(qid,&mymsg,128,0) < 0)
		  perror("msgsnd");
	}
	else
	{
		if(fork() == 0)
		{
			printf("This is B process!\n");
			if(msgrcv(qid,&mymsg,128,0,0) < 0)
			  perror("msgrcv");
 
			printf("The msg is: %s\n",mymsg.msg_str);
		}
		else if(fork() == 0)
		{
			printf("This is the C process!\n");
			sprintf(mymsg.msg_str,"wangguagnjie");
 
			if(msgsnd(qid,&mymsg,128,0) < 0)
			  perror("msgsnd");
		}
		else
		{
			printf("This is D process!\n");
			if(msgrcv(qid,&mymsg,128,0,0) < 0)
			  perror("msgrcv");
 
			printf("The msg is: %s\n",mymsg.msg_str);
		}
	}
	return 0;
}

该程序利用fork函数创建了四个进程,分别完成发送和接受的任务。

程序运行结果如下:

进程间的通信(九)

编写程序完成以下功能:
1.创建含有2个信号量的集合,第一个信号量的初始值是1,第二个信号量的初始值是3,对第一个信号量做加法操作,对第二个信号量做减法操作。
2.在另外一个进程中判断信号量集合中两个信号量的值,当两个信号量在值相等在时候,打印“hello”

 

在linux系统中,有一种资源在同一时间只能由一个进程进行访问,这种资源叫做临界资源。信号量的作用是保护临界资源。那么,信号量是如何保护的呢?

我们都知道,一个数据同时由两个或以上的进程进程进行访问的时候,有可能会发生数据污染,为了保护临界资源,信号量应运而生。信号量是一个整数,通过这个整数来实现同步与互斥机制。比如,我们将一个临界资源的信号量值设为1,当一个进程想要访问这一临界资源时,首先获取该资源的信号量,如果信号量值为1,则访问该资源并将信号量值减一;当下一个进程访问资源时,发现信号量值为0,则进程的状态变为阻塞,知道该资源被上一个进程释放,信号量重新变为1时,该进程才能对这个临界资源进行访问。

信号量分为两种:只有0和1两个值的信号量叫做二值信号量;

能取任意非负整数的信号量称为通用信号量。

程序如下:

test1.c
 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/types.h>
int main()
{
	key_t key;
	key = ftok(".",3);
 
	int sem_id;
	int sems = 2;
 
	sem_id = semget(key,sems,IPC_CREAT);
	if(sem_id < 0)
	  perror("semget");
 
	typedef union semnu{
		int value;
		struct semid_ds *buf;
		unsigned short int *array;
		struct seminfo *__buf;
	}sem;
 
	sem sem_0, sem_1;
	sem_0.value = 1;
        sem_1.value = 3;
	
	semctl( sem_id, 0, SETVAL, sem_0 );
	semctl( sem_id, 1, SETVAL, sem_1 );
 
	struct sembuf buf_p;
        buf_p.sem_num = 0;
        buf_p.sem_op = 1;
        buf_p.sem_flg = (IPC_NOWAIT | SEM_UNDO);
        int semop_value;
        semop_value = semop(sem_id, &buf_p, 1);
        if( semop_value<0 )
        {
            perror("semop(p) error:");
            exit(1);
        }
	
	struct sembuf buf_v;
        buf_v.sem_num = 1;
        buf_v.sem_op = -1;
        buf_v.sem_flg = (IPC_NOWAIT|SEM_UNDO);
        semop_value = semop(sem_id, &buf_v, 1);
        if( semop_value<0 )
        {
            perror("semop(v) error:");
            exit(1);
	}
 
	int sem0_val,sem1_val;
	sem0_val = semctl(sem_id,0,GETVAL);
	sem1_val = semctl(sem_id,1,GETVAL);
 
	printf("The sem0_val is: %d\n",sem0_val);
	printf("The sem1_val is: %d\n",sem1_val);
 
	return 0;
}

test.2

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/types.h>
int main()
{
	key_t key;
	key = ftok(".",3);
 
	int sem_id;
	int sems = 2;
 
	sem_id = semget(key,sems,IPC_CREAT);
	if(sem_id < 0)
	  perror("semget");
 
	typedef union semnu{
		int value;
		struct semid_ds *buf;
		unsigned short int *array;
		struct seminfo *__buf;
	}sem;
 
	int sem0_val,sem1_val;
	sem0_val = semctl(sem_id,0,GETVAL);
	sem1_val = semctl(sem_id,1,GETVAL);
	
	if(sem0_val == sem1_val)
	  printf("hello\n");
 
	return 0;
}

请注意程序的运行顺序(运行test之后才能运行test1),否则将无法得到预期结果。

程序运行截图如下:

线程实验(一)

编程实现以下功能:
主线程实现以下功能:
    定义全局变量key;
    创建两个线程;
    如果线程正常结束,得到线程的结束状态值,并打印;
线程一完成以下操作:
    设置全局变量key的值为字符串“hello world”;
    打印字符串“当前线程ID:key值”;
    接收到线程二发送的取消请求信号后退出;
    结束的时候打印字符串“thread1 ,exited!:key值”;
线程二完成以下操作:
    设置key值为6;
    给线程一发送取消请求信号;
    结束的时候打印字符串“thread2,exited!:key值”;

线程是在一个程序内部能被操作系统调度并并发运行的任务。

线程有自己的运行线索,能够完成指定任务;

线程自己一般不拥有系统资源,只拥有少量在运行中必不可少的资源(程序计数器,一组寄存器,栈,线程信号掩码,局部线程变量和线程私有数据);

与同属于一个进程的其它线程共享进程拥有的所有资源;

可以相互协作完成进程所要完成的任务。

线程优点如下:

1.节俭。2.进程间方便的信号通信方式。

在此次实验中需要用到的知识点包括:线程取消函数和线程清理函数以及线程私有数据相关知识。

整个实验流程主要为:

1.主函数创建两个线程,在两个线程中,分别注册线程清理函数pthread_cleanup()函数,在函数中打印题目要求的字符串;

2.在线程1中,打印题目要求的字符串;

3.在线程2中利用pthread_cancle函数向线程1发送线程取消函数;

4.创建线程私有数据key,在两个线程中分别为他们赋值为:“hello world”和“6”。

程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <pthread.h>
pthread_key_t key;
 
void cleanup(void *arg)
{
	printf("%s\n",(char *)arg);
}
 
void *child_thread1(void *arg)
{
	char *str = "Hello World";
	printf("The child_thread1 run!\n");
	printf("The thread id is: %d\n",syscall(SYS_gettid));
	if(pthread_setspecific(key,str) < 0)
	  perror("pthread_setspecific");
 
	char *get_key = (char *)pthread_getspecific(key);
	printf("The thread1's key is: %s\n",get_key);
 
	pthread_cleanup_push(cleanup,"Thread1,exited!");
	pthread_cleanup_pop(1);
}
 
void *child_thread2(void *arg)
{
	int num = 6;
	printf("The child_thread2 run!\n");
 
	if(pthread_cancel((pthread_t)arg) < 0)
	  perror("pthread_cancle");
 
	if(pthread_setspecific(key,(void *)num) < 0)
	  perror("pthread_setspecific");
	int *get_key = (int *)pthread_getspecific(key);
 
	printf("The thread2's key is: %d\n",get_key);
	pthread_cleanup_push(cleanup,"Thread2,exited!");
    pthread_cleanup_pop(1);
}
 
void *thread(void *arg)
{
	pthread_t tid1,tid2;
	void *tret1,*tret2;
 
	printf("This is the main pthread!\n");
 
	if(pthread_key_create(&key,NULL) < 0)
	  perror("phtread_key_create");
	if(pthread_create(&tid1,NULL,(void *)child_thread1,NULL) < 0)
	  perror("pthread_create");
	
	pthread_join(tid1,&tret1);
	printf("The pthread1 exited is: %d\n",(long)tret1);
 
	if(pthread_create(&tid2,NULL,(void *)child_thread2,&tid1) < 0)
	  perror("pthread_create");
 
	pthread_join(tid2,&tret2);
	printf("The pthread2 exited is: %d\n",(long)tret2);
}
 
int main()
{
	pthread_t id;
	if(pthread_create(&id,NULL,(void *)thread,NULL) < 0)
	  perror("pthread_create");
 
	sleep(1);
	return 0;
}

程序编译会报类型转换的警告,不影响运行结果!

程序运行结果如下:

;