基于i.mx的linux系统编程---第六章

1. Linux进程的讲解

程序

静态文件

进程

运行着的实体

查看进程之间的关系

pstree

操作系统如何区分进程

PID:进程的身份证(每个进程都会有自己的进程ID号)

2. 创建一个新进程

fork函数

头文件:

#include <unistd.h>

函数原型:

pid_t fork(void);

返回值:
成功 :0或其他正整数
失败:-1

fork函数特性
  • 执行fork函数之后,fork函数会返回两次
  • 在父亲进程中返回时,返回值为0
  • 在新进程返回时,返回值为进程的pid
    fork函数叫做复制一个进程更加贴切
fork函数要点总结

在执行fork函数之前,操作系统只有一个进程,fork函数之前的代码只会被执行一次。
在执行fork函数之后,操作系统有两个几乎一样的进程(复制和原来一摸一样的进程),fork函数之后的代码会被执行两次。

3. 子进程的偷梁换柱

当使用fork函数创建进程的时候,由于子进程与父进程是一样的代码数据,可以使用exex函数实现把子进程运行的代码改变

exec函数族

常用后缀:

l:代表以列表形式传参(list)
v:代表以矢量数组形式传参(vector)
p:代表使用环境变量Path来寻找指定执行文件
e:代表用户提供自定义的环境变量

头文件:

#include <unistd.h>

函数原型:

int execl(const char *path, const char *arg, ...)
int execlp(const char *file, const char *arg, ...)
int execv(const char *path, char *const argv[])
int execve(const char *path, char *const argv[],char *const envp[])

返回值:
成功:不返回
失败:-1

要点总结
  • l后缀和v后缀必须两者选其一来使用
  • p后缀和e后缀是可选的,可用可不用
  • 组合后缀的相关函数还有很多,可自己进一步了解
    exce函数有可能执行失败,需要预防
  • 新程序的文件路径出错
  • 传参或者是自定义环境变量时,没有加NULL
  • 新程序没有执行权限
exec函数使用实例
#include <unistd.h>

int main(int argc,char **argv )
{
   pid_t  new_id = fork();  //创建新的子线程
   
   if (pid_t ==-1 )  //创建新的进程失败
   {
          printf(“fork error:%d\r\n”, pid_t);
          return -1;
   }
   if (pid_t == 0 )   // 子进程
   {
     execl("/bin/ls","ls","-l",null); //就是把子进程运行的代码换成了bin下的ls指令
     printf(“error!\n”);
     return -2;
   }
  return 0;
}
//最终输出结果就是  ls -l  的结果

4. 退出进程

正常退出:
  • 从main函数return
  • 调用exit()函数终止
  • 调用_exit()函数终止
exit和_exit退出函数

头文件:

#include <unistd.h>
#include <stdlib.h>

原型:

void _exit(int status);  //当执行这句代码的时候,会直接把进程的虚拟内存释放与相关的进程控制块也会被释放
void exit(int status);   //会首先检查进程中的文件缓冲区的数据是否为空,不为空的话,就先处理IO缓存区的数据,再去退出进程

返回值:
不返回

程序实例
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc ,char ** argv)
{
  pid_t  result = fork();
  if(result  == -1 )
  {
    printf("fork error\r\n");
  }
  esle if( result == 0)  //当fork()函数输出的结果是0的时候,就是子线程
  {
     printf(“son”);
     _exit(0);    //使用_exit();函数,直接退出,:由于printf(),会首先把数据写入缓冲区,但是_exit()会直接退出,因此printf会得不到执行
  }
  if (result ==1 )
  {
    printf("parent");
    exit(0); //使用exit()函数,在退出进程的时候,会把缓冲区的数据提前处理,因此可以输出数据
  }
}

5.父进程等待子进程的退出

wait函数

头文件

#include <sys/wait.h>

函数原型

pid_t wait(int *status)

返回值

  • 成功:退出的子进程的pid
  • 失败:-1
处理子进程退出状态值的宏
  • WIFEXITED(status) :如果子进程正常退出,则该宏为真(就是值为1)
  • WEXITSTATUS(status):如果子进程正常退出,则该宏获取子进程的退出值
小技巧:可以使用man fork(或者execl等函数),就可以找到需要依赖的头文件
下面是程序实例
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
#include <unsited.h>

int main(int argc,char **argv)
{
  pid_t result;
  int status=0;
  result = fork();//创建新的子线程
  if(result ==-1)
  {
  printf("fork() error %d\r\n",result);
  return -1;
  }
  if(result   =0)
  {
   printf("son\r\n");
   exit(125);
  }
    if(result  > 0 )
  {
       wait(&status);  //等待子进程的退出,阻塞了父线程,只有子线程退出,才会得到进入就绪态
	   printf("parent%d:\r\n",status);  //此时就是输出子进程退出时候传出的值
	   if(WIFEXITED(status)  ==1 )
	   {
	      printf("status:%d\r\n", WEXITSTATUS(status));
	   }
  }
  return 0;
}

//输出结果:son 
                                 status:125

6. 进程的几个状态(生老病死)

在这里插入图片描述
进程:是资源分配的基本单位,进程是由进程控制块(PCB)与程序段与数据段组成,有自己的虚拟内存空间(每个进程都有自己的虚拟空间,这些地址都是从相同的地址开始的,主要是靠内存管理单元完成的):线程是比较小的一个执行单元,没有自己独立的内存空间,同一个进程里面的线程共享进程的所有资源,线程是CPU调度的基本单位

进程组的作用:对相同类型的进程进行管理

进程组的诞生在shell里面直接执行一个应用程序,对于大部分进程来说,自己就是进程组的首进程。进程组只有一个进程
,如果进程调用了fork函数,那么父子进程同属一个进程组,父进程为首进程,在shell中通过管道执行连接起来的应用程序,两个程序同属一个进程组,第一个程序为进程组的首进程
进程组id:pgid,由首进程pid决定

会话
作用:管理进程组
会话的诞生 调用setsid()函数,新建一个会话,应用程序作为会话的第一个进程,称为会话首进程,用户在终端正确登录之后,启动shell时linux系统会创建一个新的会话,shell进程作为会话首进程
会话id:会话首进程id,SID

前台进程组
shell进程启动时,默认是前台进程组的首进程。
前台进程组的首进程会占用会话所关联的终端来运行,shell启动其他应用程序时,其他程序成为首进程

后台进程组
后台进程中的程序是不会占用终端
在shell进程里启动程序时,加上&符号可以指定程序运行在后台进程组里面

ctrl+z:也能够把正在运行在前台进程组的程序放到后台进程组来运行,就可以使终端得到CPU的运行权

jobs:查看有哪些后台进程组 比如 [20] sleep 200,他的jobs id就是200
fg+job id可以把后台进程组切换为前台进程组

终端

  • 物理终端
    - 串口终端
    - lcd终端
  • 伪终端
    - ssh远程连接产生的终端(SSH是一个软件)
    - 桌面系统启动的终端
  • 虚拟终端
    linux内核自带的,ctrl+alt+f0~f6可以打开7个虚拟终端

7.守护进程

守护进程的概念:一般会话用来管理前后台进程组,而且会话一般关联着一个终端,当终端被关闭的之后,会话中的所有进程都会被关闭。守护进程是不会受终端的影响,就算终端被退出,也可以在后台执行。
创建守护进程的过程:
(1)使用fork()函数创建一个子进程,父进程直接使用exit()函数退出
(2)使用setsid()函数创建一个新的会话,摆脱当前终端的影响。
(3)使用chrdir()函数改变守护进程的工作目录,改为“/”根目录
(4)使用umask()函数,重设文件权限的掩码–修改文件的运行权限(补充,使用touch 文件,创建的文件默认权限是666,只读文件,使用sudo chmod 777 文件名,增加文件的权限)
(5)关闭不需要的文件描述符:0,1,2:标准输入、标准输出、标准出错
vim diamon.c #创建守护源文件
程序实例

下面是创建守护进程的代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/type.h>
#include<sys/wait.h>
#include<sys/stat.h>

int main (void)
{
	 pid_t pid;
	 int fd,len,i,num;
	 char *buf="the dameon is running\r\n";
	len=srtlen(buf);

    /* 创建新的进程 */
	pid = fork();
	if(pid <0 )
	{
		  printf("fork() error\r\n");
		  return -1;
	}
	if(pid >0 )
	{
      /*退出父进程 */
	  exit(0);  

      /*//创建新的会话,摆脱当前终端的影响 */
      setsid();  

     /*//改变当前的工作目录 */
      chdir();  
     
     /*/重设文件权限的掩码*/
      umask();  
      for(i=0;i<3;i++)
      {
       /* //关闭标准输入、标准输出与标准出错*/
      	close(i); 
      }
	while(1)  //此时就是一个守护进程,结束当前shell终端的时候,此守护进程也不会结束
	{
	    /*//创建并打开了一个log文件,权限设置成了666,有读写的权限 */
		fd=open("/var/log/dameon.log",O_CREAT|O_WRONLY|O_APPEND,0666);  
		write(fd,buf,ssizeof(buf));
		close(fd);
		sleep(5);
	}
	}
}

8. ps命令详解

有两个相关指令:
aux
axjf

  • 下面对他们参数的详解
    • a:显示一个终端所有的进程
    • u:显示进程的归属用户及内存使用情况
    • x:显示没有关联控制终端的进程
    • j:显示进程归属的进程组id、会话id、父进程id
    • f:以ascii形式显示出进程的层次关系
当执行了PS aux指令
  • user:进程是哪个用户产生的
  • pid:进程的身份证号码
  • %cpu:表示进程占用了cpu计算能力的百分比
  • %mem:表示进程占用了系统内存的百分比
  • vsz:进程使用的虚拟内存大小
  • rss:进程使用的物理内存大小
  • tty:表示进程关联的终端
  • stat:表示进程当前状态
    • D:表示进程处于不可唤醒的状态 ,通常用于IO情况
    • -R:表示正在得到cpu运行
    • -s:表示处于睡眠状态(也叫阻塞态)
    • -T:表示暂停态
    • -X:表示死掉的状态
    • -Z:表示僵尸状态
  • start:表示进程的启动时间
  • time:记录进程的运行时间
  • command:表示进程执行的具体程序
当执行了ps axjf指令
  • ppid:表示进程的父进程id
  • pid:进程的身份证号码
  • pgid:进程所在进程组的id
  • sid:进程所在会话的id
  • tty:表示进程关联的终端
  • tpgid:值为-1,表示进程为守护进程
  • stat:表示进程当前状态
  • uid:启动进程的用户id
  • time:记录进程的运行时间
  • command:表示进程的层次关系
总结

当关注进程本身:使用ps aux | grep “sleep” #就是在所有进程中查找叫sleep的进程
当关注进程之间的关系(比如进程组,后台进程组等等):使用ps axjf

9.僵尸进程与托孤进程

进程正常退出过程
  • 子进程调用exit(status),子进程进入僵尸态(此时虚拟内存与进程控制块还未被回收)
  • 父进程调用wait(&status),子进程的虚拟内存会被操作系统回收,他的资源也会被回收
僵尸进程产生过程

子进程退出后,父进程没有调用wait()处理子进程的身后事,子进程就一直处于僵尸态,就变化成僵尸进程。

#include<stdio.h>
#include <stdlib.h>
#include<unisted.h>

int main(void)
{
  int status=0;
  pid_t pid = fork();
  if(pid < 0 )
  {
    printf("fork() error%d\r\n",pid);
    return -1;
  }
  if(pid == 0 )
  {
    printf("son exiting\r\n");
    exit(124);
  }
  else{
  /*本来父亲进程要执行wait指令,把子进程从僵尸态变成死亡态,但是关闭了,子进程就成为了僵尸进程*/
  //wait(&status);
   while(1);
  }
}
托孤进程
  • 概念:就是父进程要比子进程先退出,Linux就会主动把子进程托孤给1号进程(init进程),这就是托孤进程。

9. 进程间通讯(IPC)

进程间通讯发生的情况
  • 数据传输:多个进程之间的相互数据交换
  • 资源共享:就是进程之间资源共享
  • 事件通知:一个进程通知另外进程
  • 进程控制:比如进程同步与进程互斥
Linux下的进程通讯(ipc)

有很多标准:主要学习posix ipc(IEEE美国电器学会指定的标准)

  • 消息队列
  • 信号量
  • 共享内存
无名管道
  • pipe函数
    ·头文件

     #include <unistd.h>
    

    ·函数原型

    int pipe(int pipefd[2]);
    
  • 返回值
    ·成功:0
    ·失败:-1

  • 特点

    • 特殊文件(没有名字),无法使用open,但是可以使用close。
    • 只能通过子进程继承文件描述符的形式来使用—只能通过父子进程,子进程继承了父进程的文件描述符fd后才能完成通讯
    • write和read操作可能会阻塞进程
    • 所有文件描述符被关闭之后,无名管道被销毁
  • 使用过程

    • 父进程调用pipe()函数创建无名管道
    • 再利用fork()函数创建子进程
    • 利用文件系统调用close()关闭无用端口
    • write/read读写端口
    • close读写端口
#include<unistd.h>
#include<string.h>
#include<stdio.h>
#include<errno.h>

#define MAX_DATA_LEN 256
int main(void)
{
 pid_t pid;
 int pipe_fd[2];
 int status;
 char buf[MAX_DATA_LEN]char const data[]="pipe test\r\n";
 int read_read,ral_write;
 if( (pid= fork()) <0 )
 {
    printf("fork() error :%d\r\n",pid);
   return -1;
 }
 
  if(pid ==0 )
  {
	  /*子进程关闭文件描述符1*/
	   close(pipe_fd[1]);
	  /* 子进程读取管道的内容*/
	  if((real_read = read(pipe_fd[0],buf,MAX_DATA_LEN)) > 0 )
	  {
	   	printf("%d bytes read from the pipe is %s\r\n",real_read,buf);
	  }
	  /*关闭pipe_fd文件描述符0*/
	  close(pipe_fd[0]);
	  exit(25);
  }
  else if( pid >0 )
  {
  /*关闭文件描述符0*/
   close(pipe_fd[0]);
   /*父进程向管道写入数据*/
   if((real_write = write(pipe_fd[1],data,sizeof(data))) >0)
   {
    printf("write %d bytes %s\r\n",real_write,data);
   }
   
   /*等待子进程结束,就是把僵尸态变成结束态,此时wait收到数据为25*/
   wait(&status);
   /*退出父进程*/
   exit(0);
  }
  
}
有名管道
  • 有名管道的特点
    - 无名管道通讯只能发生在父子进程等有关系的进程,而有名管道可以用在任何进程之间的通讯
    - 有文件名,可以使用open函数打开
    - 任意进程间数据传输
    - write和read操作可能会阻塞进程:当使用read函数的时候,如果有名管道中不存在数据的时候,就会阻塞进程(或者使用write函数的时 候,如果有名管道中放满了数据,不能存放现有的数据,就会被阻塞,直到其他进程使用read函数把数据读出来)。
    - write具有"原子性":一次性执行,不会一部分一部分写数据

  • 有名管道mkfifo函数简介

  • 头文件:
    #include <sys/types.h> #include <sys/state.h>

  • 函数原型:

      ```
      int mkfifo(const char *filename,mode_t mode)
      ```
    
  • 返回值:
    - 成功:0
    - 失败:-1

  • 使用步骤
    - 第一个进程mkfifo创建有名管道
    - open有名管道,write/read数据
    - close有名管道
    - 第二个进程open有名管道,read/write数据
    - close有名管道
    ///程序实例

//这是fifo_write.c文件,向有名管道写入数据
#include<sys/types.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>

#define MY_FIFO "/tmp/myfifo"
/*定义最大操作buf文件的长度*/
#define MAX_BUFF_LEN 4096  

int main (int argc,char ** argv)
{
 int fd;
 char buf[MAX_BUFF_LEN ];
 if(argc <1 )
 {
	  printf("get datd errpr \r\n");
	  exit(1);
 }
 strcpy(buf,argv[1]);
fd = read(MY_FIFO ,O_WRONLY);
if(fd ==-1 )
{
 printf("open fifo fail\r\n");
 exit(2);
}
if((nwrite = write(fd,buf,MAX_BUFF_LEN )) >0 )
{
 printf("write %d bytes,%s\r\n",nwrite,buf);
}
close(fd);
exit(0);
return 0;
}

///这是fifi_read.c文件,向有名管道读取数据,当有名管道中没有数据的时候会被阻塞
#include<sys/types.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>

#define MY_FIFO "/tmp/myfifo"
/*定义最大操作buf文件的长度*/
#define MAX_BUFF_LEN 4096  

int main(void)
{
  ///下面是伪代码
  mkfifo(MYFIFO,0666);//创建有名管道文件 666权限
  fd = open(MYFIFO,O_RDONLY);
  while(1)
  {
	   memset(buf,0,sizeof(buf));
	   if(nread = read(fd,buf,MAX_BUFF_LEN ) >0)
	   {
	    printf("read from fifo %d bytes,%s",nread,buf);
	   }
  }
  close(fd);
  exit(0);
  
}

10.信号简介

信号的基本概念
  • 软件模拟中断,进程接受信号后做出相应响应:也有信号处理函数signal();
  • 查看linu下的信号:kill -l(杀死进程)
  • Linux一共有64个信号,他们使用宏定义的 1-64(可以使用阿拉伯数字代替相关的信号表示)
怎么产生信号?
  • 硬件

    • 执行非法指令
    • 访问非法内存
    • 驱动程序
  • 软件

    • 控制台:
      - ctrl+c:中断信号
      - ctrl+|:退出信号
      - ctrl+z:停止信号
    • kill命令 :使用方法:kill 进程ID号
    • 在自己的程序调用kill()函数
信号的处理方式;
  • 忽略:进程当信号从来没有发生过
  • 捕获:进程会调用相应的处理函数,进行相应的处理
  • 默认:使用系统默认处理方式来处理信号
常用信号分析
信号名信号编号产生原因默认处理方式
SIGHUP1关闭终端终止
SIGINT2ctrl+c终止
SIGQUIT3ctrl+\终止+转储
SIGABRT6abort()终止+转储
SIGPE8算术错误终止
SIGKILL9kill -9 pid终止,不可捕获/忽略
SIGUSR110自定义忽略
SIGSEGV11段错误终止+转储
SIGUSR212自定义忽略
SIGALRM14alarm() 定时器终止
SIGTERM15kill pid终止
SIGCHLD17(子)状态变化忽略
SIGTOP19ctrl+z暂停,不可捕获/忽略
  • pkill命令:与kill命令相似,但是只需要使用pkill 进程名
处理与发送信号
  • signal函数
  • 函数功能:信号相应函数,当设置了相应信号的时候,如果信号发生了,就会去执行这个函数(有点类似中断服务子程序)
  • 头文件:
    include<signal.h>
    
  • 函数原型:
    typedef void (*sighandler_t)(int); //定义了函数指针
    
    sighandler_t signal(int signum,sighandler_t handler);
    
  • 参数:
    • signum: 要设置的信号
    • handler:
      • SIG_IGN:忽略
      • SIG_DFL:默认
      • void (*sighandler_t)(int):自定义
  • 返回值:
    • 成功:上一次设置的handler
    • 失败:SIG_ERR
下面是Linux 信号的测试伪代码
#include <stdio.h>
#include <signal.h>
#include<stdlib.h>

void signal_hanel(int sig )  /此时传进来的参数就是发生的信号种类(1-64{
 if(sig == SIGINT )
 {
 //把SIGINT设置成默认模式
  signal(SIGINT,SIG_DFL);
 }
}

int main(int argc,char ** argv)
{
/*设置了SIGINT的signal函数,当信号SIGNAL发生的时候,就会调用执行signal函数*/
 signal(SIGINT,signal_hanel);
 while(1)
 {
  printf("please inter ctrl +c\r\n");
  sleep(10);
 }
 exit(1);
}
/伪代码分析,当代码执行的时候,会一直提示please inter ctrl +c, 首先如果按键输入ctrl+c,就会先执行signal函数,再去执行signal_hanel函数,设置SIGINT为默认触发模式,再次执行ctrl+c,就会结束当前进程
对signal函数的进一步理解
#include<all.h>

void signal_handle(int sig)
{
  printf("hello,this is signal\r\n");
}
int main(int argc,char **argv)
{
/*这个是信号SIGINT相应函数,如果发生了信号SIGINT,就会执行这个函数*/
 signal(SIGINT,signal_handle);//
 while(1);
 exit(null);
}
///如果再终端一直执行ctrl+c,就会一直打印出hello,this is signal
  • kill函数
  • 函数功能:向某个进程发送一个信号
    • 比如:sudo kill -9 12540 #向12540进程发送信号9(SIGKILL)
  • 头文件:
#include <sys/types.h>
#include <signal.h>
  • 原型函数:
    int kill(pid_t pid,int sig);
    
  • 参数:
    • pid:进程id
    • sig:要发送的信号
  • 返回值:
    • 成功:0
    • 失败:-1
  • raise函数
  • 函数功能:直接在程序中使用,直接调用某个信号
  • 头文件:
    #include <signal>
    
  • 原型函数:
    int raise(int sig);
    
  • 参数:
    • sig:发送信号
  • 返回值:
    • 成功:0
    • 失败:非0
//下面是对kill函数与raise函数使用详解
#include<all.h>

int main(int argc, char **argv)
{
  pid_t pid;
  if((pid = fork()) <0)
  {
   printf("fork error\r\n");
  }
  if(pid ==0 )
  {
    printf("子进程 stop\r\n");
  /* 子进程主动阻塞,进入睡眠态*/
    rasie(SIGSTOP);    
    /*下面代码将不会执行*/
    exit(125);
  }
  else{
  printf("This is parent runing\r\n");
  ///父进程得到的pid是子进程的PID
  sleep(10);
  if((ret = kill(pid,SIGKILL))== 0)
  {
   printf("kill %d\r\n",pid);
  }
  /*父进程把子进程从僵尸态变为结束态*/
  wait(null);
  exit(0);
  }
}
//程序分析:首先父进程会主动进入睡眠态3s,子进程调用raise(SIGSTOP)直接进入睡眠态,当父进程进入运行时,调用kill函数执行SIGKILL,杀死子进程

11. 信号集处理函数

屏蔽信号集
  • 屏蔽信号的概念:屏蔽某些信号,这些信号就不会被相应,当连续产生某个信号,可能导致信号得不到及时的处理,就会把信号屏蔽,而被屏蔽的信号就会被放入未处理信号集(非实时信号就只会保留一个,也就是他的堆栈空间就只有1,其他的实时信号就会全部被保留)
    ,而实现修改对某个信号不屏蔽,可以使用以下两个方式:
    • 手动:使用信号集相关API,去设置相关信号
    • 自动
未处理信号集

信号如果被屏蔽,则记录在未处理信号集中

  • 非实时信号(1~31),不排队,只留一个(其他信号都会被屏蔽)
  • 实时信号(34~64),排队,保留全部
信号集相关API
  • signal_t 就是信号集的数据类型
  • int sigemptyset(sigset_t *set);
    • 将信号集合初始化为0
  • int sigfillset(sigset_t *set);
    • 将信号集合初始化为1
  • int sigaddset(sigset_t *set,int signum);
    • 将信号集合某一位设置为1
  • int sigdelset(sigset_t *set,int signum);
    • 将信号集合某一位设置为0
  • //上面四个函数就是自己定义一个64位的信号集,然后再调用下面这个函数就可以实现把定义好的信号集添加到系统的信号集
  • int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
    • 使用设置好的信号集去修改信号屏蔽集
    • 参数how:
      • SIG_BLOCK:屏蔽某个信号(屏蔽集 | set)
      • SIG_UNBLOCK:打开某个信号(屏蔽集 & (~set))
      • SIG_SETMASK:屏蔽集 = set
    • 参数oldset:保存就的屏蔽集的值,NULL表示不保存
///下面把SIGINT进行解除对它的屏蔽
#include <all.h>

void signal_handle(int sig)
{
 /*自己定义一个sig信号集*/
  sigset_t set;

 /*把自定义的信号集清零*/
  sigemptyset(&set);
/*然后再把SIGINT位置一,以便解除对SIGINT的屏蔽*/
sigaddset(&set,SIGINT);
/*把自定义的信号集加到屏蔽信号集*/
sigprocmask(SIG_UNBLOCK,&set,NULL);

printf("hello\r\n");
sleep(5);
printf("world\r\n");
}
int main(int argc,char **argv)
{
/*信号SIGINT的响应函数*/
  signal(SIGINT,signal_handle);
  while(1);
  printf("main end\r\n");
}
/*程序分析:如果没有进行解除SIGINT的屏蔽,如果一直给SIGINT信号,它就会被屏蔽,这里解除了对他的屏蔽,就会一直执行*/

12.消息队列 system_V

system-V ipc特点
  • 独立于进程:不像管道通讯那样,必须建立文件描述符fd,当进程结束的时候,管道通讯就会被结束
  • 没有文件名和文件描述符
  • IPC对象具有key和ID
消息队列用法:过程
  • 定义一个唯一key(使用ftok()函数)
  • 构造消息对象(使用msgget()函数)
  • 发送特定类型消息(使用msgsnd()函数)
  • 接受特定类型消息(msgrcv)
  • 删除消息队列(msgctl)
ftok函数

功能:创建一个消息队列,并获取这个消息队列的key

函数原型:

key_t ftok(const char *path,int proj_id)

参数:

  • path:一个合法路径(可以是文件夹与文件)
  • proj_id:一个整数
    返回值:
    成功:合法键值

失败:-1

msgget函数

功能:获取消息队列ID

函数原型:

int msgget(key_t key,int msgflg)

参数:

  • key:消息队列的键值
  • msgflg:
    • IPC_CREAT:如果消息队列不存在,则创建
    • mode:访问权限
      返回值:
      成功:该消息队列的ID
      失败:-1
msgsnd函数

功能:发送消息到消息队列

函数原型:

int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg);

参数:

  • msqid:消息队列ID

  • msgp:消息缓存区

    • struct msgbuf

      {

      ​ long mtype; //消息标识

      ​ char mtext[1]; //消息内容

      }

  • msgsz:消息正文的字节数

  • msgflg:

    • IPC_NOWAIT:非阻塞发送
    • 0:阻塞发送

返回值:

成功:0

失败:-1

msgrcv函数

功能:从消息队列读取消息

函数原型:

ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg)

参数:

  • msqid:消息队列ID

  • msgp:消息缓存区

  • msgsz:消息正文的字节数

  • msgtyp:要接受消息的标识

  • msgflg:

    • IPC_NOWAIT:非阻塞读取
    • MSG_NOERROR:截断消息
    • 0:阻塞读取

返回值:

成功:0

失败:-1

msgctl函数

功能:设置或获取消息队列的相关属性

函数原型:

int msgctl(int msgqid,int cmd,struct maqid_ds *buf)
  • msgqid:消息队列的ID

  • cmd

    • IPC_STAT:获取消息队列的属性信息
    • IPC_SET:设置消息队列的属性
    • IPC_RMID:删除消息队列
  • buf:相关结构体缓冲区

///这是消息发送message.c,发送消息的函数
#include<all.h>

#define MAX_BUF_LEN 512

typedef struct message
{
  long msg_type; //消息中的类别,可以自定义消息发送的类别信息
  char buf[MAX_BUF_LEN]}message;

int main(int argc,char **argv)
{
/*定义消息的ID与key*/
 int msg_id;
 key_t key;
 message msg;
/* 建立消息的key值,第一个参数就是路径,第二个参数是自己随意设置的*/
 if((key = ftok("tmp",11)) == -1)
 {
   printf("ftok error\r\n");
   exit(2);
 }
 /*建立消息的id值,如果消息队列不存在*/
 if((msg_id = msgget(key,IPC_CREAT|0666)) == -1)
 {
     printf("msgget error\r\n");
     exit(2);
 }
 printf("creat msg:%d\n",msg_id);
 /*开始向消息队列写入数据*/
 while(1)
 {
		  printf("please inter some msaage\n");
		  if(fgets(msg.buf,MAX_BUF_LEN,stdin) == null)  // fgets(str, 7, stdin);  /*从输入流stdin即输入缓冲区中读取7个字符到字符数组str中*/
		  {
		     printf("message posted");
		     exit(2);
		  }
		  msg.msg_type = getid();
		  if((msgsnd(msg_id,&msg,strlen(msg.buf),0)) <0 )
		  {
		     printf("message posted");
		     exit(1);
		  }
		  if(strncmp(msg.buf,"quit",4) == 0)
		  {
		  break;
		  }
 }
 exit(0);
}

消息接收类似,就只需要建立key与ID就行了,然后就阻塞读取消息
whilestrncmp(msg.buf,"quit",4) != 0{
 msgrcv(msg_id,(void *)&msg,buffer_size,0,0); //阻塞读取
 printf("read from message %d:bytes %s\n",msg.type,msg,buf);
}
exit(0);

13.信号量 system_V

本质

计数器

作用
  • 主要作用:(1)实现进程的同步作用,把进程按照一定的顺序进行执行,(2)实现互斥量:保护互斥资源,在同一时刻,只有一个进程在访问互斥资源。
  • 保护共享资源
  • 互斥
  • 同步
信号量用法:步凑
  • 定义一个唯一key(ftok)
  • 构造一个信号量(semget)
  • 初始化信号量(semctl SETVA)
  • 对信号量进行P/V操作(semop)
  • 删除信号量(semctl RMID)
semget函数

功能:获取信号量的ID,创建一个信号量
函数原型:

int semget(key_t key,int nsems,int semflg)

参数:

  • key:信号量键值
  • nsems:信号量数量
  • semflg:
  • IPC_CREATE:信号量不存在则创建
  • mode:信号量的权限
    返回值:
    成功:信号量ID
    失败:-1
semctl函数

功能:获取或设置信号量的相关属性
函数原型:

int semctl(int semid,int semnum,int cmd,union semun arg)

参数:

  • semid:信号量ID

  • semnum:信号量编号,也就是有几个信号量(信号量的编号是从零开始的)

  • cmd:

    • IPC_STAT:获取信号量的属性信息
    • IPC_SET:设置信号量的属性
    • IPC_RMID:删除信号量
    • IPC_SETVAL:设置信号量的值
  • arg:

    union semun
    {
    int val;
    struct semid_ds *buf;
    }

返回值:

成功:由cmd类型决定

失败:-1

semop函数

函数原型:

int semop(int semid,struct sembuf *sops,size_t nsops)

参数:

  • semid:信号量ID

  • sops:信号量操作结构体数组
    struct sembuf
    {

    ​ short sem_num; //信号量编号

    ​ short sem_op;//信号量P/V操作

    ​ short sem_flg; //信号量行为,SEM_UNDO

    }

    • nsops:信号量数量
      返回值:
      成功:0
使用信号量
#include <stdio.h>
#include <stdlib.h>
#include <all.h>

/*定义semctl函数所需要的结构体变量*/
union semun
{
   int val;
   struct semid_ds *buf;
};
/*定义semop操作所需要的结构体变量*/
struct sembuf 
{
  short sem_num; //需要操作信号量的编号
  short sem_op;//表示信号量P/V操作
  short sem_flg;//表示信号量的行为  一般设置成SEM_UNDO,表四系统自动回收生意的信号量
};

/*初始化信号量
sem_id:初始化信号量的id值
init_value:初始化信号量的值(也就是初始化信号量的个数)
*/
int init_sem(int sem_id,int init_value)
{
	 union semun sem_union;
	 sem_union.val = init_value;
	 
	 if((semctl(sim_id,0,IPC_SETVAL,sem_union)) == -1)
	 {
	  printf("sem init fail:\r\n");
	  return -1;
	 }

    return 0;
}

/*
删除信号量
sem_id:信号量的id号
*/
int del_sem(int sem_id)
{
  union semun sem_union;
  if(semctl(sem_id,0,IPC_RMID,sem_union) == -1)
  {
    printf("del_sem fail\r\n");
    return -1;
  }
  return 0;
}
/*信号的P操作

*/
int sem_p(int sem_id)
{
	  struct sembuf sops;
	  sops.sem_num =0;  //当个信号量的编号应该为零
	  sops.sem_op = -1; //表示P操作,每次减少一个
	  sops.sem_flg  = SEM_UNDO; // 表示自动释放系统中剩余的信号量
	  
	 if(semop(sem_id,&sops,1) == -1)
	 {
	    printf("semop_fail");
	 }
	 return 0;
 }
/*
对信号进程V操作
sem_id:信号量的id号
*/
int sem_V(int sem_id)
{
 	  struct sembuf sops;
	  sops.sem_num =0;  //当个信号量的编号应该为零
	  sops.sem_op = 1; //表示V操作,每次编号为0的信号量都会自动增加一
	  sops.sem_flg  = SEM_UNDO; // 表示自动释放系统中剩余的信号量
	  if(semop(sem_id,&sops,1) == -1)
	 {
	    printf("semop_fail");
	 }
	 return 0;
}

int main(int argc,char **argv)
{
  pid_t result;
  int sem_id;
  sem_id = semget((key_t)6666,1,0666| IPC_CREAT);  /*创建一个信号量*/
  init_sem(sem_id,0); /*初始化sem,初始化的信号量的值为0*/
  result = fork();
  if(result == -1)
  {
   printf("fork reeor");
   exit(1);
  }
  if(result ==0 )
  {
   printf("son begin sleep for some time\r\n");
   sleep(3);
   printf("son V the sem");
   sem_V(sem_id);
  }
  else
  {
    sem_p(sem_id);  /*由于初始信号量的值为0,p操作就会阻塞父进程,直到子进程进行V操作信号量过后,才会P成功*/
    printf("parent succes P sem\r\n");
    sem_v(sem_id);
    
    del_sem(sem_id);  /*删除信号量*/
  }
}

14.system-V 共享内存

作用

高效率传输大量数据(管道通信,消息通信只是使用数据量不大的时候,在大数据量传输的时候,采用共享内存)

共享内存用法
  • 定义一个唯一key(ftok):可以使用ftok,也可以(key_t)6666,自定义一个值
  • 构造一个共享内存对象(shmget)
  • 共享内存映射(shmat)
  • 解除共享内存映射(shmdt) :其中shm 就是share memary
  • 删除共享内存(shmctl RMID)
shmget函数

功能:获取共享内存对象的ID(也就是创建一个共享内存区域)

函数原型:

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

参数:

  • key:共享对象键值
  • nsems:共享内存大小
  • shmflg:
  • IPC_CREATE:共享内存不存在则创建
  • mode:共享内存的权限

返回值:
成功:共享内存ID
失败:-1

shmat函数

功能:映射共享内存
函数原型:

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

参数:

  • shmid:共享内存ID
  • shmaddr:映射地址,当参数设置为NULL为自动分配
  • shmflg:
    • SHM_RDONLY:只读方式映射
    • 0:可读可写

返回值:
成功:共享内存首地址
失败:-1

shmdt函数

功能:解除共享内存映射

函数原型:

int shmdt(const void *shmaddr)

参数:

shmaddr:映射地址

返回值:

成功:0

失败:-1

shmctl函数

功能:获取或设置共享内存的相关属性
函数原型:

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

参数:

  • shmid:共享内存ID

  • cmd:

    • IPC_STAT:获取共享内存的属性信息
    • IPC_SET:设置共享内存的属性
    • IPC_RMID:删除共享内存
  • buf:属性缓冲区
    返回值:
    成功:由cmd类型决定
    失败:-1

下面是对共享内存进行介绍
#include <stdio.h>
#include <stdlib.h>
#include <all.h>

#define SHM_LEN 1024 //定义共享内存大小

/*定义semctl函数所需要的结构体变量*/
union semun
{
   int val;
   struct semid_ds *buf;
};
/*定义semop操作所需要的结构体变量*/
struct sembuf 
{
  short sem_num; //需要操作信号量的编号
  short sem_op;//表示信号量P/V操作
  short sem_flg;//表示信号量的行为  一般设置成SEM_UNDO,表四系统自动回收生意的信号量
};

/*初始化信号量
sem_id:初始化信号量的id值
init_value:初始化信号量的值(也就是初始化信号量的个数)
*/
int init_sem(int sem_id,int init_value)
{
	 union semun sem_union;
	 sem_union.val = init_value;
	 
	 if((semctl(sim_id,0,IPC_SETVAL,sem_union)) == -1)
	 {
	  printf("sem init fail:\r\n");
	  return -1;
	 }

    return 0;
}

/*
删除信号量
sem_id:信号量的id号
*/
int del_sem(int sem_id)
{
  union semun sem_union;
  if(semctl(sem_id,0,IPC_RMID,sem_union) == -1)
  {
    printf("del_sem fail\r\n");
    return -1;
  }
  return 0;
}
/*信号的P操作

*/
int sem_p(int sem_id)
{
	  struct sembuf sops;
	  sops.sem_num =0;  //当个信号量的编号应该为零
	  sops.sem_op = -1; //表示P操作,每次减少一个
	  sops.sem_flg  = SEM_UNDO; // 表示自动释放系统中剩余的信号量
	  
	 if(semop(sem_id,&sops,1) == -1)
	 {
	    printf("semop_fail");
	 }
	 return 0;
 }
/*
对信号进程V操作
sem_id:信号量的id号
*/
int sem_V(int sem_id)
{
 	  struct sembuf sops;
	  sops.sem_num =0;  //当个信号量的编号应该为零
	  sops.sem_op = 1; //表示V操作,每次编号为0的信号量都会自动增加一
	  sops.sem_flg  = SEM_UNDO; // 表示自动释放系统中剩余的信号量
	  if(semop(sem_id,&sops,1) == -1)
	 {
	    printf("semop_fail");
	 }
	 return 0;
}

int main(int argc,char **argv)
{
  pid_t result;
  int sem_id;
  int shm_id;
  char * addr;
  char *buf= "hello my linux\r\n";
  
  sem_id = semget((key_t)6666,1,0666| IPC_CREAT);  /*创建一个信号量*/
  init_sem(sem_id,0); /*初始化sem,初始化的信号量的值为0*/

  shm_id = shmget((key_t)7777,SHM_LEN,0666| IPC_CREAT );//创建共享内存

  result = fork(); /*创建子进程*/
  if(result == -1)
  {
   printf("fork reeor");
   exit(1);
  }
  if(result ==0 )  /*这是子进程*/
  {
   printf("son begin sleep for some time\r\n");
   sleep(3);
   
   addr = shmat(shm_id,NULL,0); /*映射共享内存*/
   memcpy(addr,buf,sizeof(buf));
   
   printf("son V the sem");
   sem_V(sem_id);  /*子进程对信号量进行V操作*/
   
   exit(125);
  }
  else
  {
    sem_p(sem_id);  /*由于初始信号量的值为0,p操作就会阻塞父进程,直到子进程进行V操作信号量过后,才会P成功*/
 
    addr = shmat(shm_id,NULL,0); /*映射共享内存*/
    printf("share memory :%s",addr);
    shmdt(shm_id); /*解除共享内存*/
    shmctl(shm_id,IPC_RMID,NULL);  /*删除共享内存*/
    
    printf("parent succes P sem\r\n");
    sem_v(sem_id);
    del_sem(sem_id);  /*删除信号量*/
    
    wait(&status);
    exit(1);
  }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值