当前位置: 首页 > 编程日记 > 正文

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);elseprintf("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);elseprintf("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);elseprintf("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 1024int 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);
#endifvoid 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 ;
}

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

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

相关文章:

关于互联网技术基层绩效管理的一些思考

起因是一篇内部的文章&#xff0c;那记录也就留在内部吧&#xff0c;磨炼了的价值观在自己心里就好。 类似的还有 1. 罗振宇不发年终奖&#xff1a;https://xueqiu.com/7118120763/119669075 2. 有赞白鸦强行一波996&#xff1a;https://baijiahao.baidu.com/s?id1623959680…

波纹管 编织管

为什么80%的码农都做不了架构师&#xff1f;>>> 波纹管 编织管 http://wenku.baidu.com/view/4272a9feaef8941ea76e057e.html 转载于:https://my.oschina.net/tadcat/blog/151049

Git基础(常用命令)介绍

版本控制是一种记录若干文件内容变化,以便将来查阅特定版本修订情况的系统. 关于版本控制分为三种&#xff1a;本地版本控制系统&#xff0c;如rcs&#xff1b;集中化的版本控制系统&#xff0c;如CVS、SVN&#xff1b;分布式版本控制系统&#xff0c;如Git。 Git基础要点 G…

MIT开发新加密货币,用户所需数据比比特币减少99%

MIT的研究人员开发了一种新的加密货币&#xff0c;大大减少了用户加入网络和验证交易所需的数据&#xff0c;与当今流行的加密货币相比&#xff0c;最高可达99%。这意味着网络更具扩展性。 像比特币之类流行的加密货币都是构建于区块链上的网络&#xff0c;而区块链是按照一系列…

深入了解AI加速芯片的定制数据流架构与编译器 | 公开课

随着人工智能时代的来临&#xff0c;业内对于更高效率算力的需求也越来越紧迫&#xff0c;而传统的 CPU 计算能力弱&#xff0c;只适合软件编程&#xff0c;并不适合应用于人工神经网络算法的自主迭代运算。为了满足支撑深度学习的大规模并行计算的需求&#xff0c;人工智能芯片…

《GPU高性能编程CUDA实战》中代码整理

CUDA架构专门为GPU计算设计了一种全新的模块&#xff0c;目的是减轻早期GPU计算中存在的一些限制&#xff0c;而正是这些限制使得之前的GPU在通用计算中没有得到广泛的应用。使用CUDA C来编写代码的前提条件包括&#xff1a;(1)、支持CUDA的图形处理器&#xff0c;即由NVIDIA推…

​50年来最具影响力的十大编程语言!

作者 | javinpaul译者 | 馨怡责编 | 屠敏出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;【导语】“适者生存”的自然法则在应用竞争激烈的编程语言界同样适用&#xff0c;而在数百种编程语言中&#xff0c;相对而言&#xff0c;哪些最具影响力&#xff1f;哪些才是…

【基础篇】DatePickerDialog日期控件的基本使用(一)

项目步骤&#xff1a; 1.首先在Main.xml布局文件中添加一个Button标签&#xff0c;用来点击显示日期控件&#xff0c;Main.xml内容如下&#xff1a; <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android" xmlns:tools"http://sch…

PoPo数据可视化第9期

PoPo数据可视化 聚焦于Web数据可视化与可视化交互领域&#xff0c;发现可视化领域有意思的内容。不想错过可视化领域的精彩内容, 就快快关注吧 :)2018 in the Ito Design Lab&#xff08;视频内容请关注微信公众号浏览&#xff09;1900~2018年城市温度异常变化可视化Temperatur…

面向可解释的NLP:北大、哈工大等提出文本分类的生成性解释框架

作者 | Hui Liu, Qingyu Yin, William Yang Wang 译者 | Rachel编辑 | Jane出品 | AI科技大本营&#xff08;ID: rgznai100&#xff09;【导语】北大、哈工大和加州大学圣巴巴拉分校在 ACL 2019 的一篇论文中联合提出了一个全新的生成性解释框架&#xff0c;该框架能够对分类策…

pyramid参数

2019独角兽企业重金招聘Python工程师标准>>> 普通参数permission: 该view的访问权限&#xff0c;这个后续会具体介绍。attr: Pyramid默认调用的是view类的__call__函数&#xff0c;如果需要指定调用其他方法&#xff0c;通过attr指定。如attrindex。renderer: 指定构…

Linux下常用的C/C++开源Socket库

1. Linux Socket Programming In C : http://tldp.org/LDP/LG/issue74/tougher.html 2. ACE: http://www.cs.wustl.edu/~schmidt/ACE.html ACE采用ACE_OS适配层屏蔽各种不同的、复杂繁琐的操作系统API。 ACE是一个大型的中间件产品&#xff0c;代码20万行左右&…

前端技术选型的遗憾和经验教训

我是Max&#xff0c;Spectrum的技术联合创始人。Spectrum 是一个面向大型在线社区的开源聊天应用程序&#xff0c;最近被GitHub收购。我们是一个三人团队&#xff0c;主要拥有前端和设计背景&#xff0c;我们在这个项目上工作了近两年时间。 事后看来&#xff0c;以下是我做出的…

时间序列的建模新思路:清华、李飞飞团队等提出强记忆力E3D-LSTM网络

作者 | Yunbo Wang,、Lu Jiang、 Ming-Hsuan Yang、Li-Jia Li、Mingsheng Long、Li Fei-Fei译者 | 凯隐编辑 | Jane出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09;【导读】如何对时间序列进行时空建模及特征抽取&#xff0c;是RGB视频预测分类&#xff0…

了解node.js

转载自http://debuggable.com/posts/understanding-node-js:4bd98440-45e4-4a9a-8ef7-0f7ecbdd56cb 当我向人们介绍node.js时&#xff0c;通常会得到两种反应&#xff0c;一种人马上就能了解&#xff0c;另一种则是非常困惑。 如果你是第二种人&#xff0c;请看一下我对node的解…

VS2013中Image Watch插件的使用(OpenCV)

之前在vs2010中写OpenCV程序时经常用NativeViewer&#xff0c;安装此插件后&#xff0c;在调试代码时&#xff0c;对于cv::Mat变量&#xff0c;CV_TYPE类型为CV_8UC1或CV_8UC3&#xff0c;可以随时查看显示结果。其操作步骤为&#xff1a;1. 从http://sourceforge.net/p…

【spring boot2】第8篇:spring boot 中的 servlet 容器及如何使用war包部署

嵌入式 servlet 容器 在 spring boot 之前的web开发&#xff0c;我们都是把我们的应用部署到 Tomcat 等servelt容器&#xff0c;这些容器一般都会在我们的应用服务器上安装好环境&#xff0c;但是 spring boot 中并不需要外部应用服务器安装这些servlet容器&#xff0c;spring …

让织梦内容页arclist标签的当前文章标题加亮显示

很多人在用织梦做站的时候&#xff0c;会用到在当前栏目页面&#xff0c;给当前栏目标题使用指定样式如标题加亮&#xff0c;或者放个背景图。这是一个很常用和实用的功能&#xff0c;比如在导航页面&#xff0c;标识当前在浏览哪个栏目。如下图&#xff1a; 但是有些时候&…

RHEL6入门系列之九,常用命令2

今天还是继续来学习Linux的基本命令。4、touch命令——建立空文件touch命令用于建立空文件。[rootlocalhost ~]# mkdir /root/test ‘创建目录/root/test[rootlocalhost ~]# touch /root/test/test1.txt ‘在目录/root/test中创建空文件test1.txt[rootlocalhos…

为什么华为200万招聘AI博士,马斯克却推出脑机接口对抗AI?

作者 | 伍杏玲来源 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;7 月&#xff0c;华为一则薪资通知刷爆朋友圈&#xff1a;华为给8位博士应届生给予 89.6 万至 201 万的年薪。其中薪资最高的两位博士均研究人工智能相关专业。7 月还有一件大事&#xff1a;马斯克发布…

Artistic Style在windows下的使用(C/C++)

ArtisticStyle是一个开源的源代码格式化工具。主页地址为&#xff1a;http://astyle.sourceforge.net/&#xff0c;它可以应用在C、C、Objective-C、C#、Java等程序语言中。http://astyle.sourceforge.net/astyle.html中为对使用它的详细介绍。从http://sourceforge.net/projec…

ESXi主机与网络中其他主机的网咯数据包捕获

1、tcpdump-uw -i vmk0 -s 1514 host x.x.x.x 指定捕获与某台主机间的网络数据包2、tcpdump -i vmk0 -s 1514 port not 22 and port not 53 在捕获的数据包中&#xff0c;过滤掉指定端口的数据包3、tcpdump-uw -i vmk0 -s 1514 -w traffic.pcap 捕获的数据包保存成PCAP文件&…

Windows下批处理文件(.bat)的使用

批处理(Batch)&#xff0c;就是进行批量的处理&#xff0c;英文译文BATCH&#xff0c;批处理文件后缀BAT就取的前三个字母&#xff0c;通常被认为是一种简化的脚本语言&#xff0c;它应用于DOS和Windows系统中。批处理文件是扩展名为.bat或.cmd的文本文件&#xff0c;包含一条或…

金融业加速智能化,解析360金融AI基础架构和应用

传统金融信贷业务中&#xff0c;催收、客服及电销人员占比超过 60%&#xff0c;人员素质参差不齐的现状造成了管理成本过高的问题&#xff0c;由此衍生的客户体验差&#xff0c;也成为困扰金融业的一大通病。 8 月 15 日&#xff0c;在 360金融 AI 媒体开放日上&#xff0c;360…

正则:匹配一个汉字姓名

//汉字姓名正则var reg/^[\u4e00-\u9fa5]{2,}(\.[\u4e00-\u9fa5])?$/console.log(reg.test(张卫健.爱新觉罗))console.log(reg.test(兔子)) 复制代码

NLP命名实体识别开源实战教程 | 深度应用

作者 | 小宋是呢来源 | CSDN博客近几年来&#xff0c;基于神经网络的深度学习方法在计算机视觉、语音识别等领域取得了巨大成功&#xff0c;另外在自然语言处理领域也取得了不少进展。在NLP的关键性基础任务—命名实体识别&#xff08;Named Entity Recognition&#xff0c;NER…

poj 2063完全背包

题意&#xff1a;给出总资金和投资年份 &#xff0c;n个股票 给出股票价格和其一年的利润。问如何选择能获得最大利润。 思路&#xff1a;股票可以重复选择&#xff0c;完全背包问题&#xff0c;完全背包也是从01背包衍生而行的&#xff0c;其主要区别在于中间那层循环的次序不…

UTF-8 CPP的使用

UTF-8 CPP是一个简单、小巧、轻量级、跨平台的UTF-8编码字符串库。下面对其使用方法进行简单的介绍&#xff1a;1. 从http://sourceforge.net/projects/utfcpp/下载最新的utf8_v2_3_4.zip源码&#xff0c;将其解压缩&#xff1b;2. 新建一个vs2013 控制台工程TestUTF…

一行js代码识别Selenium+Webdriver及其应对方案

有不少朋友在开发爬虫的过程中喜欢使用Selenium Chromedriver&#xff0c;以为这样就能做到不被网站的反爬虫机制发现。 先不说淘宝这种基于用户行为的反爬虫策略&#xff0c;仅仅是一个普通的小网站&#xff0c;使用一行Javascript代码&#xff0c;就能轻轻松松识别你是否使用…

Android系统移植与调试之-------如何修改Android设备添加重启、飞行模式、静音模式等功能(一)...

1、首先先来看一下修改前后的效果对比图 修改之后的图片 确认重启界面 具体的修改内容在下一篇中具体介绍。 Android系统移植与调试之------->如何修改Android设备添加重启、飞行模式、静音模式等功能&#xff08;二&#xff09; 作者&#xff1a;欧阳鹏 欢迎转载&#xf…