Linux进程编程基础介绍

Linux系统是一个多进程的系统,它的进程之间具有并行性、互不干扰等特点。也就是说,每个进程都是一个独立的运行单位,拥有各自的权利和责任。其中,各个进程都运行在独立的虚拟地址空间,因此,即使一个进程发生异常,它也不会影响到系统中的其他进程。

Linux进程是一个具有独立功能的程序关于某个数据集合的一次可以并发执行的运行活动,是处于活动状态的计算机程序。进程作为构成系统的基本细胞,不仅是系统内部独立运行的实体,而且是独立竞争资源的基本实体。

Linux进程是一个程序的一次执行的过程,同时也是资源分配的最小单元。它和程序是有本质区别的,程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念;进程是一个动态的概念,它是程序执行的过程,包括了动态创建、调度和消亡的整个过程。它是程序执行和资源管理的最小单位。

进程是程序的执行过程,根据它的生命周期可以划分成3种状态:(1)、执行态:该进程正在运行,即进程正在占用CPU;(2)、就绪态:进程已经具备执行的一切条件,正在等待分配CPU的处理时间片;(3)、等待态(阻塞态):进程不能使用CPU,若等待事件发生(等待的资源分配到)则可将其唤醒。

导致进程终止的三种情况:正常终止、异常终止、外部干扰。

终止进程的主要操作过程如下;(1)、找到指定进程的PCB;(2)、终止该进程的运行;(3)、回收该进程所占用的全部资源;(4)、终止其所有子孙进程,回收它们所占用的全部资源;(5)、将被终止进程的PCB从原来队列中摘走。

进程阻塞的过程如下:(1)、立即停止当前进程的执行;(2)、现行进程的CPU现场保存;(3)、现行状态由“运行”改为“阻塞”;(4)、转到进程调度程序。

Linux 系统的进程状态模型的各种状态:用户状态、内核状态、内存中就绪、内存中睡眠、就绪且换出、睡眠且换出、被抢先、创建状态、僵死状态。

进程的上下文是由用户级上下文、寄存器上下文以及系统级上下文组成。主要内容是该进程用户空间内容、寄存器内容以及与该进程有关的内核数据结构。当系统收到一个中断、执行系统调用或内核做上下文切换时,就会保存进程的上下文。一个进程是在它的上下文中运行的,若要调度进程,就要进行上下文切换。

在Linux系统中,用户创建一个进程的唯一方法就是使用系统调用fork。

Linux系统调用exit,是进程用来终止执行时调用的。进程发出该调用,内核就会释放该进程所占的资源,释放进程上下文所占的内存空间,保留进程表项,将进程表项中纪录进程状态的字段设为僵死状态。内核在进程收到不可捕捉的信号时,会从内核内部调用exit,使得进程退出。父进程通过wait得到其子进程的进程表项中记录的计时数据,并释放进程表项。最后,内核使得进程1(init 进程,init进程是系统所有进程的起点,它的进程号是1。)接收终止执行的进程的所有子进程。如果有子进程僵死,就向init 进程发出一个SIGCHLD 的软中断信号。

一个进程通过调用wait来与它的子进程同步,如果发出调用的进程没有子进程则返回一个错误,如果找到一个僵死的子进程就取子进程的PID及退出时提供给父进程的参数。如果有子进程,但没有僵死的子进程,发出调用的进程就睡眠在一个可中断的级别上,直到收到一个子进程僵死(SIGCLD)的信号或其他信号。

进程控制的一个主要内容就是对其他程序引用。该功能是通过系统调用exec来实现的,该调用将一个可执行的程序文件读入,代替发出调用的进程执行。内核读入程序文件的正文,清除原先进程的数据区,清除原先用户软中断信号处理函数的地址,当exec调用返回时,进程执行新的正文。

Linux系统是一个分时系统,内核给每个进程分一个时间片,该进程的时间片用完就会调度另一个进程执行。

进程调度分成两个部分,一个是调度的时机,即什么时候调度;一个是调度的算法,即如何调度和调度哪个进程。

fork函数启动一个新的进程,这个进程几乎是当前进程的一个拷贝:子进程和父进程使用相同的代码段;子进程复制父进程的堆栈段和数据段。这样,父进程的所有数据都可以留给子进程,但是,子进程一旦开始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不再共享任何数据了。它们再要交互信息时,只有通过进程间通信来实现。

对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零。在操作系统中,我们用ps函数就可以看到不同的进程号,对父进程而言,它的进程号是由比它更低层的系统调用赋予的,而对于子进程而言,它的进程号即是fork函数对父进程的返回值。

实际执行fork时,物理空间上两个进程的数据段和堆栈段都还是共享着的,当有一个进程写了某个数据时,这时两个进程之间的数据才有了区别,系统就将有区别的"页"从物理上也分开。系统在空间上的开销就可以达到最小。

系统调用exec是用来执行一个可执行文件来代替当前进程的执行映像。需要注意的是,该调用并没有生成新的进程,而是在原有进程的基础上,替换原有进程的正文,调用前后是同一个进程,进程号PID不变。但执行的程序变了(执行的指令序列改变了)。

系统调用exec和fork经常结合使用,父进程fork一个子进程,在子进程中调用exec来替换子进程的执行映像,并发的执行一些操作。

系统调用exit的功能是终止发出调用的进程。

系统调用wait的功能是发出调用的进程只要有子进程,就睡眠直到它们中的一个终止为止。

函数调用sleep可以用来使进程挂起指定的秒数。

获得进程相关的ID:与进程相关的ID有,(1)、真正用户标识号(UID):该标识号负责标识运行进程的用户;(2)、有效用户标识号(EUID):该标识号负责标识以什么用户身份来给新创建的进程赋所有权、检查文件的存取权限和检查通过系统调用kill向进程发送软中断信号的许可权限;(3)、真正用户组标识号(GID):负责标识运行进程的用户所属的组ID;(4)、有效用户组标识号(EGID):用来标识目前进程所属的用户组。可能因为执行文件设置set-gid位而与gid不同;(5)、进程标识号(PID):用来标识进程;(6)、进程组标识号(process group ID):一个进程可以属于某个进程组。可以发送信号给一组进程。注意,它不同与gid。

如果要获得进程的用户标识号,用getuid调用。调用geteuid是用来获得进程的有效用户标识号。

如果要获得运行进程的用户组ID,使用getgid调用来获得真正的用户组ID,用getegid获得有效的用户组ID。

如果要获得进程的ID,使用getpid调用;要获得进程的父进程的ID,使用getppid调用。

如果要获得进程所属组的ID,使用getpgrp调用;若要获得指定PID进程所属组的ID用getpgid调用。

调用setuid为当前发出调用的进程设置真正和有效用户ID。参数uid是新的用户标识号(该标识号应该在/etc/passwd文件中存在)。调用setgid设置当前发出调用的进程的真正、有效用户组ID。该调用允许进程指定进程的用户组ID为参数gid,如果进程的有效用户ID不是超级用户,该参数gid必须等于真正用户组ID、有效用户组ID中的一个。

调用setpgrp用来将发出调用的进程的进程组ID设置成与该进程的PID相等。注意,以后由这个进程派生的子进程都拥有该进程组ID(除非修改子进程的进程组ID)。调用setpgid用来将进程号为参数pid的进程的进程组ID设定为参数pgid。如果参数pid为0,则修改发出调用进程的进程组ID。

chdir是用来将进程的当前工作目录改为由参数指定的目录。

系统调用chroot用来改变发出调用进程的根(“/”)目录。

系统调用nice用来改变进程的优先权。

所谓僵尸进程,是指使用fork后,子进程先于父进程结束,但是因为父子进程间依然有关系,那么子进程实际上不会真正意义上终结,如果查看当前进程表,会发现该进程依然存在。僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。

系统对一个用户可以同时运行的进程数是有限制的,对超级用户没有该限制,但也不能超过进程表的最大表项的数目

Linux下一个进程在内存里有三部的数据,就是"代码段"、"堆栈段"和"数据段"。这三个部分也是构成一个完整的执行序列的必要的部分。"代码段",顾名思义,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用相同的代码段。"堆栈段"存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。而数据段则存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc 之类的函数取得的空间)。系统如果同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据段。

test_fork1.cpp:

//fork函数的使用
//输出结果的顺序和进程调度的顺序有关
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>

extern int errno;

int main()
{
	char buf[100];
	pid_t cld_pid;
	int fd;
	int status;

	if ((fd = open("temp", O_CREAT|O_TRUNC|O_RDWR,S_IRWXU)) == -1) {
		printf("open error %d\n",errno);
		exit(1);
	}

	strcpy(buf, "This is parent process write\n");

	if ((cld_pid = fork()) == 0) { /* 这里是子进程执行的代码 */
		strcpy(buf, "This is child process write\n");
		printf("This is child process\n");
		printf("My PID(child) is %d\n", getpid()); /*打印出本进程的ID*/
		printf("My parent PID is %d\n", getppid()); /*打印出父进程的ID*/
		write(fd, buf, strlen(buf));
		close(fd);
		exit(0);
	} else { /* 这里是父进程执行的代码 */
		printf("This is parent process\n");
		printf("My PID(parent) is %d\n",getpid());/*打印出本进程的ID*/
		printf("My child PID is %d\n", cld_pid);/*打印出子进程的ID*/
		write(fd, buf, strlen(buf));
		close(fd);
	}
	
	//父子进程是彼此相互独立运行的,所以要想让父进程等待子进程,只需使用wait()系统调用。
	wait(&status); /* 如果此处没有这一句会如何?*/
	
	return 0;
}
test_fork2.cpp:

//fork的使用
//屏幕上交替出现子进程与父进程各打印出的一千条信息
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>

int main()
{
	int i;
	if (fork() == 0)	{
		/* 子进程程序 */
		for (i = 1; i <1000; i ++)
			printf("This is child process\n");
	} else {
		/* 父进程程序*/
		for (i = 1; i <1000; i ++)
		printf("This is parent process\n");
	}
	
	return 0;
}
test_fork3.cpp:

//waitpid的使用
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main()
{
	pid_t pc, pr;
	pc=fork();
	if (pc<0) /* 如果fork 出错 */
		printf("Error occured on forking.\n");
	else if (pc == 0) {/* 如果是子进程 */
		sleep(10);/* 睡眠10 秒 */
		//exit(0);
		return 0;
	}
	
	/* 如果是父进程 */
	do {
		pr = waitpid(pc, NULL, WNOHANG); /* 使用了WNOHANG 参数,waitpid 不会在这里等待 */
		
		if (pr == 0) {/* 如果没有收集到子进程 */
			printf("No child exited\n");
			sleep(1);
		}
	} while (pr == 0); /* 没有收集到子进程,就回去继续尝试 */
	
	if (pr == pc)
		printf("successfully get child %d\n", pr);
	else
		printf("some error occured\n");
	
	return 0;
}
test_fork4.cpp:

//fork的使用
//此代码来自:http://www.linuxidc.com/Linux/2013-06/85903p6.htm
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>

int main()
{
	pid_t child1, child2, child;
	/*先创建子进程1*/
	child1 = fork();
	/*子进程1的出错处理*/
	if (child1 == -1) {
		printf("Child1 fork error\n");
		exit(1);  /*异常退出*/
	} else if(child1 == 0) { /*在子进程1中调用execlp()函数*/
		printf("I am child1 and I execute ‘ls -l‘\n");
		if (execlp("ls", "ls", "-l", NULL) < 0) {
			printf("Child1 execlp error\n");
		}
	} else {/*在父进程中再创建进程2,然后等待两个子进程的退出*/
		child2 = fork();
		/*子进程2的出错处理*/
		if (child2 == -1) {
			printf("Child2 fork error\n");
			exit(1);
		} else if (child2 == 0) {/*在子进程2中使其暂停5s*/
			printf("I am child2. I will sleep for 5 seconds!\n");
			sleep(5);
			printf("I am child2. I have awaked and I will exit!\n");
 			exit(0);
		}

		printf("I am father progress\n");
		child = waitpid(child1, NULL, 0);/*阻塞式等待*/
		
		if (child == child1) 
			printf("I am father progress. I get child1 exit code:%d\n", child);
		else
			printf("Error occured!\n");

		do {
			child = waitpid(child2, NULL, WNOHANG);/*非阻塞式等待*/
			if (child == 0) {
				printf("I am father progress. The child2 progress has not exited!\n");
				sleep(1);
			}
		} while (child == 0);

		if (child == child2)
			printf("I am father progress. I get child2 exit code:%d\n",child);
		else
			printf("Erroe occured!\n");
	}
	
	exit(0);
}

管道和信号是进程间通信的两种机制。操作系统中的每一个管道有两个文件描述符,一个文件描述符用来读,另一个用来写。信号是一个软件中断,主要用于进程间异步事件通知与进程控制。

进程间的通信类型有6种:(1)、管道(pipe)和有名管道(FIFO);(2)、信号(signal);(3)、共享内存;(4)、消息队列;(5)、信号量;(6)、套接字(socket)。

进程间通信目的有5种:(1)、数据传输:一个进程需要将它的数据发送给另一个进程;(2)、共享数据:多个进程想要共享数据,一个进程对共享数据进行修改后,别的进程可以立刻看到;(3)、通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程);(4)、资源共享:多个进程之间共享同样的资源,为了做到这一点,需要内核提供锁和同步机制;(5)、进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

管道是Linux中最常用的进程间通信的IPC机制。使用管道时,一个进程的输出可成为另外一个进程的输入。当输入/输出的数据量特别大时,管道这种IPC机制非常有用。

在Linux中,通过将两个file结构指向同一个临时的VFS索引节点,而两个VFS索引节点又指向同一个物理页而实现管道。

管道允许在进程之间按先进先出的方式传送数据,管道也能使进程同步执行。管道传统的实现方法是通过文件系统作为存储数据的地方。有两种类型的管道:一种是无名管道,简称为管道;另一种是有名管道,也称为FIFO。进程使用系统调用open来打开有名管道,使用系统调用pipe来建立无名管道。使用无名管道通讯的进程,必须是发出pipe调用的进程及其子进程。使用有名管道通讯的进程没有上述限制。

系统调用dup是用来复制一个文件描述符,也就是将进程u区的文件描述符表中的一项复制一份,使得这两项同时指向系统文件表的同一表项。

pipe管道:若要创建一个简单的管道,可以使用系统调用pipe(),它接收一个参数,也就是一个包括两个整数的数组。如果系统调用成功,此数组将包括管道使用的两个文件描述符,一个为读端,一个为写端。pipe管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;只能用于父子进程或者兄弟进程之间。管道主要用于父子进程间通信。实际上,通常先创建一个管道,再通过fork函数创建一个子进程,其实pipe的两个文件描述符指向相同的内存空间,只不过filedes[1]有写权限,filedes[0]有读权限。

由于进程的文件描述符0指向标准输入(键盘),1指向标准输出(屏幕),2指向标准错误(屏幕),所以进程可用的文件描述符从3开始。进程是通过对文件描述符的操作从而实现对文件描述符所指向文件的操作。

标准流管道:管道的操作也支持文件流模式,这种管道称为标准流管道。标准流管道通过popen()创建一个管道popen()会调用fork()产生一个子进程,执行一个shell以运行命令来开启一个进程,并把执行结果写入管道中,然后返回一个文件指针。程序通过文件指针可读取管道中的内容。使用popen()创建的标准流管道,需要用pclose()进行关闭。

命名管道(FIFO):和一般的管道基本相同,但也有一些显著的不同,(1)、命名管道是在文件系统中作为一个特殊的设备文件而存在的;(2)、不同祖先的进程之间可以通过命名管道共享数据;(3)、当共享命名管道的进程执行完所有的I/O操作以后,命名管道将继续保存在文件系统中,以便以后使用;(4)、普通管道只能由父子兄弟等相关进程使用,它们共同的祖先进程创建了管道。但是,通过命名管道,不相关的进程也能交换数据;(5)、一旦已经用mkfifo函数创建了一个FIFO,就可用open打开它。实际上,一般的文件I/O函数(close、read、write、unlink等)都可用于FIFO。

test_pipe1.cpp:

//pipe管道的使用
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h> 
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

void look_into_pipe() 
{
	int n;
	int fd[2];
	char line[1024];
	struct stat buf;
	
	if (pipe(fd) < 0) { /*创建管道*/
		printf("pipe error.\n");
		return;
	}
	
	fstat(fd[0], &buf);
	if (S_ISFIFO(buf.st_mode)) { /*S_ISFIFO为测试此文件类型是否为管道文件*/
		printf("fd[0]: FIFO file type.\n");
	}
	printf("fd[0]: inode=%d\n", buf.st_ino);
	
	fstat(fd[1], &buf);
	if (S_ISFIFO(buf.st_mode)) {
		printf("fd[1]: FIFO file type.\n");
	}
	printf("fd[1]: inode=%d\n", buf.st_ino);
	
	write(fd[1], "hello world.\n", 12);
	n = read(fd[0], line, 512 );
	write(STDOUT_FILENO, line, n);
	
	n = write(fd[0], "HELLO WORLD.\n", 12); /*0端只允许读,1端才允许写,这样做是为了测试*/
	if (-1 == n)
		printf("\nwrite error\n") ;
}

int main()
{
	look_into_pipe() ;
}
test_pipe2.cpp:

//pipe管道的使用
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(){
	int pipe_fd[2];
	pid_t pid;
	char buf_r[100];
	char *p_wbuf;
	int r_num;
	memset(buf_r, 0, sizeof(buf_r));
	
	if (pipe(pipe_fd) < 0){ //创建管道
		perror("pipe create error\n");
		return -1;
	}
	
	if ((pid = fork()) == 0){//表示在子进程中
		//关闭管道写描述符,进行管道读操作
		printf("child pipe1=%d; pipe2=%d\n", pipe_fd[0], pipe_fd[1]) ;
		close(pipe_fd[1]);
		//管道描述符中读取
		sleep(2);
		
		if ((r_num = read(pipe_fd[0], buf_r, 100)) > 0) {
			printf("%d numbers read from the pipe, data is %s\n", r_num, buf_r);
		}
		
		close(pipe_fd[0]);
		exit(0);
	} else if (pid > 0) {//表示在父进程中,父进程写
		//关闭管道读描述符,进行管道写操作
		printf("parent pipe1=%d; pipe2=%d\n", pipe_fd[0], pipe_fd[1]) ;
		close(pipe_fd[0]);
		
		if (write(pipe_fd[1], "Hello", 5) != -1)
			printf("parent write1 success!\n");
		if (write(pipe_fd[1], " Pipe", 5) != 1)
			printf("parent write2 success!\n");
		
		close(pipe_fd[1]);
		sleep(3);
		//waitpid()与wait()功能类似,都是用户主进程等待子进程结束或中断
		waitpid(pid, NULL, 0);
		exit(0);
	} else {
		perror("fork error");
		exit(-1);
	}
	
	return 0;
}
test_pipe3.cpp:

//标准流管道的使用
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#define BUFSIZE 1024

int main()
{
	FILE *fp;
	char *cmd = "ps -ef";
	char buf[BUFSIZE];
	buf[BUFSIZE] = ‘\0‘;
	
	if ((fp=popen(cmd, "r")) == NULL)
		perror("popen");
	
	while ((fgets(buf, BUFSIZE, fp)) != NULL)
		printf("%s", buf);
	
	pclose(fp);
	exit(0);
}
test_pipe4.cpp:

#命名管道FIFO的使用,创建命名管道并写入数据
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#define FIFO "/tmp/fifo"

int main()
{
	char buffer[80];
	int fd;
	int n;
	int ret;
	char info[80];
	
	unlink(FIFO); /*若存在该管道文件,则进行删除*/
	ret = mkfifo(FIFO, 0600); /*0600表明只有该用户进程有读写权限*/
	if (ret) {
		perror("mkfifo error");
		return -1;
	}
	
	memset(info, 0x00, sizeof(info));
	strcpy(info, "happy new year!");
	fd = open(FIFO, O_WRONLY);
	n=write(fd, info, strlen(info));
	if (n < 0) {
		perror("write error") ;
		return -1 ;
	}
	
	close(fd);
	
	return 0 ;
}
test_pipe5.cpp:

//命名管道FIFO的使用,从命名管道中读取数据
//此测试用例,与test_pipe4一起使用
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#define FIFO "/tmp/fifo"

int main()
{
	char buffer[80];
	int fd;
	int n ;
	char info[80] ;
	fd= open(FIFO, O_RDONLY);
	n = read(fd, buffer, 80);
	
	if (n < 0) {
		perror("read error") ;
		return -1 ;
	}
	
	printf("buffer=%s\n", buffer);
	close(fd);
	return 0 ;
}

信号是进程间通信机制中唯一的异步通信机制,可以看做是异步通知,通知接收信号的进程有哪些事情发生了。信号同时又是一种软件中断,当某进程接收到信号时,会中止当前程序的执行,去处理信号的注册函数,然后回到断点程序继续往下执行。

进程能对每一个信号设置独立的处理方式:它能忽略该信号,也能设置相应的信号处理程序(称为捕捉),或对信号什么也不做,信号发生的时候执行系统的默认动作。

所有的信号中,有两个信号(SIGSTOP和SIGKILL)是特别的,它们既不能被捕捉,也不能被忽略,也不能被阻塞,这个特性确保了系统管理员在所有时候内都能用暂停信号和杀死信号结束某个进程。

不可靠信号是指信号值小于32的信号。可靠信号是指后来添加的新信号(信号值位于32及64之间)。信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。

现在在Linux系统中,不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号的丢失,而后者不会。

内核为进程产生信号,来说明不同的事件,这些事件就是信号源。主要的信号源有:(1)、异常:进程运行过程中出现的异常;(2)、其它进程:一个进程可以向另外一个或一组进程发送信号;(3)、终端中断:Ctrl+C等;(4)、作业控制:前台、后台进程的管理;(5)、分配额:CPU超时或文件大小突破限制;(6)、通知:通知进程某事件发生,如I/O就绪等;(7)、报警:计时器到期。

信号的三种操作方式:忽略此信号、捕捉喜欢、执行系统的默认动作。

信号的5种默认动作:异常终止(abort)、退出(exit)、忽略(ignore)、停止(stop)、继续(continue)。

阻塞信号允许信号被发送给进程,但不进行处理,需要等到阻塞解除后再处理。而忽略信号是进程根本不接收该信号,所有被忽略的信号都被简单丢弃。

kill函数可以向有用户权限的任何进程发送信号,通常用kill函数来结束进程。与kill函数不同的是,raise函数只向进程自身发送信号。使用alarm函数可以设置一个时间值(闹钟时间),在将来的某个时刻该时间值超过时发送信号。pause函数使调用进程挂起直至捕捉到一个信号。

信号是与一定的进程相联系的。也就是说,一个进程可以决定在进程中对哪些信号进行什么样的处理。

signal函数,有两个形参,分别代表需要处理的信号编号值和处理信号函数的指针。它主要是用于前32种非实时信号的处理,不支持信号的传递信息。

sigaction函数用来查询和设置信号处理方式,它是用来替换早期的signal函数。

信号集用来描述一类信号的集合,Linux所支持的信号可以全部或部分的出现在信号集中。信号集操作函数最常用的地方就是用于信号屏蔽。

信号量与其他进程间通信的方式不大相同,它主要提供对进程间共享资源访问控制机制,相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,从而实现多个进程对某些共享资源的互斥访问;同时,进程也可以修改该标志。信号量除了用于访问控制外,还可用于进程同步。

test_signal1.cpp:

//信号的使用
#include <unistd.h>
#include <signal.h>
#include <stdio.h>

typedef void (*signal_handler)(int);

void signal_handler_fun(int signal_num) /*信号处理函数*/
{ 
	printf("catch signal %d\n", signal_num);
}

int main()
{
	int i;
	int time ;
	signal_handler p_signal = signal_handler_fun;
	signal(SIGALRM, p_signal); /*注册SIGALRM信号处理方式*/
	//alarm()用来设置信号SIGALRM在经过参数seconds指定的秒数后传送给目前的进程
	alarm(3);
	for (i=1; i<5; i++) {
		printf("sleep %d ...\n", i);
		sleep(1);
	}
	
	alarm(3);
	sleep(2);
	time=alarm(0); /*取消SIGALRM信号,返回剩余秒数*/
	printf("time=%d\n", time);
	
	for (i=1; i<3; i++) {
		printf("sleep %d ...\n", i);
		sleep(1);
	}
	
	return 0 ;
}
test_signal2.cpp:

//信号的使用:父进程发信号给子进程
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
	pid_t pid;
	int status;
	pid = fork() ;
	if (0 == pid) {
		printf("Hi I am child process!\n");
		sleep(10);
	} else if (pid > 0) {
		printf("send signal to child process (%d) \n", pid);
		sleep(1);
		//kill函数是将信号发送给指定的pid进程
		/*发送SIGABRT信号给子进程,此信号引起接收进程异常终止*/
		kill(pid ,SIGABRT);
		/*等待子进程返回终止信息*/
		wait(&status);
		
		if(WIFSIGNALED(status))
			printf("child process receive signal %d\n", WTERMSIG(status));
	} else {
		perror("fork error") ;
		return -1 ;
	}
	
	return 0 ;
}
test_signal3.cpp:
//信号的使用
//运行时,需要按:Ctrl+C或Ctrl+#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>

/*自定义信号处理函数*/
void my_func(int sign_no)
{
	if (sign_no == SIGINT)
		printf("I have get SIGINT\n");
	else if (sign_no == SIGQUIT)
		printf("I have get SIGQUIT\n");
}

int main()
{
	printf("Waiting for signal SIGINT or SIGQUIT \n ");
	/*发出相应的信号,并跳转到信号处理函数处*/
	signal(SIGINT, my_func);
	signal(SIGQUIT, my_func);
	
	pause();
	pause();
	exit(0);
}
test_signal4.cpp:

//信号的使用:sigaction函数
//此测试程序有段错误
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>

void new_op(int, siginfo_t *, void *);

int main(int argc, char** argv)
{
	struct sigaction act;
	int sig;
	sig = atoi(argv[1]);
	sigemptyset(&act.sa_mask);
	act.sa_flags = SA_SIGINFO;
	act.sa_sigaction = new_op;
	
	if (sigaction(sig, &act, NULL) < 0) {
		perror("install sigal error");
		return -1 ;
	}
	
	while(1) {
		sleep(2);
		printf("wait for the signal\n");
	}
	
	return 0 ;
}

void new_op(int signum, siginfo_t *info, void *myact)
{
	printf("receive signal %d\n", signum);
	sleep(5);
}
test_signal5.cpp:
//信号集函数的使用,需要Ctrl+C和Ctrl+\的参与
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

/*自定义的信号处理函数*/
#if 0
void my_funcnew(int signum, siginfo_t *info, void *myact);
#endif

void my_func(int signum)
{
	printf("If you want to quit, please try SIGQUIT\n");
}

int main()
{
	sigset_t set, pendset;
	struct sigaction action1, action2;
	
	/*设置信号处理方式*/
	sigemptyset(&action1.sa_mask);
	
#if 0 /*信号新的安装机制*/
	action1.sa_flags = SA_SIGINFO;
	action1.sa_sigaction = my_funcnew;
#endif
	/*信号旧的安装机制*/
	action1.sa_flags = 0;
	action1.sa_handler = my_func;
	sigaction(SIGINT, &action1, NULL);
	
	/*初始化信号集为空*/
	if (sigemptyset(&set) < 0) {
		perror("sigemptyset");
		return -1 ;
	}
	/*将相应的信号加入信号集*/
	if (sigaddset(&set, SIGQUIT) < 0) {
		perror("sigaddset");
		return -1 ;
	}
	if (sigaddset(&set, SIGINT) < 0) {
		perror("sigaddset");
		return -1 ;
	}
	
	/*设置信号集屏蔽字*/
	if (sigprocmask(SIG_BLOCK, &set, NULL) < 0) {
		perror("sigprocmask");
		return -1 ;
	} else {
		printf("blocked\n");
	}
	
	/*测试信号是否加入该信号集*/
	if (sigismember(&set, SIGINT)) {
		printf("SIGINT in set\n") ;
	}
	
	sleep(30);
	/*测试未决信号*/
	if (sigpending(&pendset) <0) {
		perror("get pending mask error");
	}
	if (sigismember(&pendset, SIGINT)) {
		printf("signal SIGINT is pending\n");
	}
	
	sleep(30) ;
	if (sigprocmask(SIG_UNBLOCK, &set,NULL) < 0) {
		perror("sigprocmask");
		return -1 ;
	} else {
		printf("unblock\n");
	}
	
	while(1) {
		sleep(1) ;
	}
	
	return 0 ;
}

共享内存区域是被多个进程共享的一部分物理内存。如果多个进程都把该内存区域映射到自己的虚拟地址空间,则这些进程就都可以直接访问该共享内存区域,从而可以通过该区域进行通信。共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。这块共享虚拟内存的页面,出现在每一个共享该页面的进程的页表中。

注:以上内容及测试代码整理自网络。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。