Linux进程和线程

目录

 

进程

概念

 进程创建fork--系统调用

 进程ID

 僵尸进程

 孤儿进程

 进程退出

 等待子进程

 exec函数族(接管一个进程的所有资源)

 守护进程

 精灵进程示例(将一句话写到日记文件里)

进程间通信

 管道

      无名管道

      创建无名管道

    管道和exec函数

  有名管道

   管道的读写

 管道的弊端

 信号

  信号响应

   1、缺省动作

   2、忽略

​ 消息队列MSG

  什么是消息队列

消息队列使用方法

  使用步骤

 共享内存段SHM

  什么是共享内存

  共享内存的使用步骤

 信号量SEM

线程

1、概念

 2、创建一条新的线程

 3、线程属性函数

     线程属性变量的使用步骤

     设置线程的分离属性

 4、线程的退出

5、 取消一条线程

 5、线程资源回收

6、 线程互斥锁

(1)互斥锁安装帮助文档

(2)初始化互斥锁

(3) 销毁

(4)尝试锁

(5)上锁

(6)解锁

 7、POSIX信号量

    A、 POSIX有名信号量

 B、 POSIX匿名信号量

  C、使用情况

8、线程条件变量


进程

概念

  进程:动态概念(运行中程序)
  程序:静态(一些指令集合)程序文件(可以移动)
  软件:文档加程序
  进程:进程是系统分配资源的最小单位
  进程控制(进程状态)动态--生命周期  创建--调度--销毁


 进程创建fork--系统调用

  pid_t fork(void)
  返回值
   <0创建失败
   ==0现在所处的进程是子进程
   >0 现在所处的进程是父进程(子进程的id号)
  fork的两种用法
   (1)一个父进程希望复制自己,使父,子进程同时执行不同代码段。这在网络服务进程中是常见的--父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则等待下一个服务请求到达
   (2)一个进程执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec
 

每一次fork都会产生一个子进程,而wait(),相当于递归一样,等待最后一个子进程死后才去打印它上一个进程:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


int main(int argc, char **argv)
{

	pid_t x;
	int i;
	
	for(i=0; i<10; i++)
	{

	 	x = fork();

		if(x > 0)
		   break;
		
		if(x == 0)
		   continue;
	}
	wait(NULL);
	printf("PID %d    PPID %d\n", getpid(), getppid());
	return 0;
}


 vfork---先运行子进程,(子进程退出后)运行父进程

 注意:一定要在子进程里要用exit()来退出子进程,不然会进入无限的循环。
  fork()与vfork()的区别~~~:>>  

主要为两点:  

(1)执行次序:fork():对父子进程的调度室由调度器决定的;        

          vfork():是先调用子进程,等子进程的exit(1)被调用后,再调用父进程;  

(2)对数据段的影响:fork():父子进程不共享一段地址空间,修改子进程,父进程的内容并不会受影响。            

 vfork():在子进程调用exit之前,它在父进程的空间中运行,也就是说会更改父进程的数据段、 栈和堆。。即共享代码区和数据区,且地址和内容都是一样的。


 进程ID

  每一个进程都有一个非负数整型表示的唯一ID。
  getpid()--获取当前进程的id号
  getppid()--获取当前进程的父进程id
  getuid()--调用进程用户的实际ID
  geteuid()--调用进程的有效用户ID
  getgid()---调用进程实际组ID
  getegid()--调用进程的有效组ID


 僵尸进程

  父进程还运行子进程已经退出(但是父进程没有回收子进程资源)(编程时候必须避免)
  父进程回收子进程资源  在父进程中调用wait
  pid_t wait(int *status)


 孤儿进程

  父进程已经退出子进程还在运行(最后都会把init进程作为父进程)
  操作过程:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正在终止的子进程,如果是,则将该进程的父进程ID更改为1(init的进程ID)


 进程退出


   正常退出(程序运行完毕)
  调用exit退出 (会做善后处理,关闭文件,清空缓存)
   void exit(int status);会关闭所有IO流,进行冲洗
  调用_exit退出 (直接退出) void _exit(int status);
  status:子进程的退出值
  无返回值


 等待子进程


  进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出。如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。


   pid_t wait(int *status);  pid_t waitpid(pid_t pid, int *status, int options);
          WNOHANG:非阻塞等待
          WCONTINUED:报告任意一个从暂停态出来且从未报告的子进程的状态
          WUNTRQCED:报告任意一个当前处于暂停态且从未报告的子进程的状态


  子进程的结束状态值会由参数status返回,而子进程的进程PID也会一起返回,如果不在意结束状态,则参数status可以设置为NULL。如果执行成功则返回进程PID,如果失败则返回-1


  waipid:如果想让父进程周期性检查某个特定的子进程是否已经终止,可以使用waitpid(child_pid,(int *)0,WNOHANG)


 exec函数族(接管一个进程的所有资源)

int execl(const char *path, const char *arg, ...);  后面变参必须以NULL结束(类似于main函数参数)
	execl("/bin/ls", "ls","-a", "-l", NULL);   ls -a -l 必须把要运行的程序路径加到PATH环境变中
	
	int  execlp(const char *file, const char *arg, ...);   
	execl("ls", "ls","-a", "-l", NULL);   ls -a -l 
	
	int execle(const char *path, const char *arg,..., char * const envp[]);
	char *envp[] = {"MM=/home/gec", "/usr/local", NULL};
	execle("/home/gec/myshare/main", "main", NULL, envp);

	int execv(const char *path, char *const argv[]);  
	char *argv[] = {"ls","-a", "-l", NULL};
	execv("/bin/ls", argv);
	
	int execvp(const char *file, char *const argv[]);
	int execvpe(const char *file, char *const argv[],char *const envp[]);

	system("/home/gec/main")---类似于函数调用

 exec函数族成功执行后,原有的程序代码都将被指定的文件或脚本覆盖,,后面的代码无法运行,也无法返回


 守护进程

  是指在后台运行且不与终端相连街的一种进程(也称之为精灵进程或后台进程)


 精灵进程示例(将一句话写到日记文件里)


  daemon.h

#ifndef __DAEMON_H
#define __DAEMON_H

#include <stdio.h>
#include <syslog.h>

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

int daemon_init(void);

#endif

daemon.c

#include "daemon.h" 

int daemon_init(void)
{
	pid_t pid;
	int fd0, fd1, fd2, max_fd,i;	

	// 1
	if((pid = fork()) < 0) 
	{
		perror("fork faild!");
		exit(1);
	}
	else if(pid != 0)//让父进程能出
	{
		exit(0);
	}

	// 2
	signal(SIGHUP, SIG_IGN);//忽略终端被关闭的信号

	// 3
	if(setsid() < 0) //创建一个新的会话
	{
		exit(1);
	}

	// 4
	if((pid = fork()) < 0)
	{
		perror("fork faild!");
		exit(1);
	}
	else if(pid != 0)
		exit(0);

	// 5
	setpgrp(); //设置孙子进程的进程组,彻底脱离会话

	// 6  关闭看有的文件描述符,默认是1024个,但没有那么多打开,那么close就没反应的
	max_fd = sysconf(_SC_OPEN_MAX);//获取文件打开的最大个数
	for (i = max_fd; i>=0; i--)
	{
		close(i);//关闭文件
	}

	// 7 ; 0:表示不会影响你设置的权限,当你是umask(0777)的话,你要设置的文件权限都会被减为0,没有权限的意思
	umask(0);

	// 8 修改守护进程的当前工作路径,让它挂载到跟目录不会被卸载
	chdir("/");


	//Initialize the log file. 打开日记文件
	openlog("daemon_test", LOG_CONS | LOG_PID, LOG_DAEMON);

	return 0;
}


  main.c

#include <unistd.h>
#include "daemon.h"

int main(void)
{
	daemon_init();	 //初始化一个精灵进程

	while(1)
	{
		syslog(LOG_DAEMON, "I am a daemonAAA!"); //将这句话写到日记文件里
		sleep(2); //2S写一次,但日记里只会出现一次,下面会告诉你重复了多少次
	}

	return 0;
}



进程间通信

进程间通信  管道, 信号, 消息队列,信号量,内存共享


 管道

      无名管道

         无名管道--只使用于亲属进程(父子进程)
          PIPE的特征:
                    1、没有名字,因此无法使用open() (只能在一个进程中被创建出来,通过继承方式将它的文件描述符传递给子进程,这也是                   PIPE只能用于亲缘进程的原因)
                   2、 只能用于亲缘进程间的通信(父子进程,兄弟进程,祖孙进程)
                   3、 半双工工作方式:读写端分开
                   4、 写入操作不具有原子性,因此只能用于一对一的简单通信情型
                   5、 不能使用lseek()来定位(因为它的数据不像普通文件那样按块的方式存放在如硬盘,Flash等块设备上)


      创建无名管道


       int pipe(int pipefd[2]);
      从pipefd[1]写入,从pipefd[0]读出
 代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int fd[2];//用于存放PIPE的两个文件描述符
    int ret = pipe(fd);
    if(ret < 0)
    {
         perror("create pipe failed \n");
         return -1;
    }
    pid_t pid = fork();
    if(pid < 0)
    {
        perror("create fork failed\n");
    }
    if(pid == 0)
    {
      write(fd[1],"hello world",12);
    }
    if(pid > 0)
    {
      char buf[12] = {0};
      read(fd[0],buf,12);
      printf("%s\n",buf);
    }
       close(fd[0]);
       close(fd[1]);
	return 0;
}


    管道和exec函数


     pipe2.c
   

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
    int processed;
    int fd[2];
    const char data[] = "123";
    char buf[BUFSIZ+1];
    memset(buf,'\0',sizeof(buf));
    int ret = pipe(fd);
    if(ret < 0)
    {
       perror("create pipe failed\n");
       return -1;
    }
    pid_t pid = fork();
    if(pid < 0)
    {
      perror("create fork failed\n");
      return -1;
    }
    if(pid == 0)
    {
      //把读取管道数据的文件描述符保存到一个缓存区中,
      //该缓存区的内容将构成pipe3程序的一个参数
      sprintf(buf,"%d",fd[0]);
       //(char *)0的作用是终止被调用程序的参数列表
      (void)execl("pipe3","pipe3",buf,(char *)0);
      exit(EXIT_FAILURE);
    }
    if(pid > 0)
    {
     processed = write(fd[1],data,strlen(data));
     printf("%d --wrote %d bytes\n",getpid(),processed);
    }
	return 0;
}


     pipe3.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char **argv)
{
    int processed;
    char buf[BUFSIZ+1];
    int descriptor;
    memset(buf,'\0',sizeof(buf));
    sscanf(argv[1],"%d",&descriptor);
    processed = read(descriptor,buf,BUFSIZ);
    printf("%d --read %d bytes: %s\n",getpid(),processed,buf);
	return 0;
}


  有名管道

   有一个实实在在的管道文件-
   FIFO的特征
    1、有名字,存储于普通文件系统之中
    2、任何具有相应权限的进程都可以使用open()来获取FIFO的文件描述符(程序不能以O_RDWR模式打开FIFO文件进行读写操作)
    3、跟普通文件一样,使统一的read()/write()来读/写
    4、跟普通文件不同,不能使用lseek()来定位,原因同PIPE
    5、具有写入原子性,支持多写者同时进行写操作而数据不会被践踏
    6、 First in First out 最先被写入FIFO的数据,最先被读出
   创建管道 mkfifo 文件名

int mkfifo(const char *pathname, mode_t mode);

   管道的读写


    读操作read
        有写者===》有数据===》 正常读取
                   ===》无数据===》 阻塞等待

     无写者===》有数据===》 正常读取

                ===》无数据===》立即返回

       
    写操作write


     有读者===》缓存未满===》正常写入
                         缓存已满===》阻塞等待
       
     无读者===》缓存未满===》立即收到SIGPIPE
               ===》 缓存已满===》 立即收到SIGPIPE
     
             
       


   FIFO间通信代码
    fifoA.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#define FIFOPATH "/tmp/fifo"


int main(int argc, char **argv)
{
	int ret = mkfifo(FIFOPATH, 00777);
	if(ret < 0 && errno != EEXIST)
	{
		perror("create fail");
		return -1;
	}
	
	//
	int fd = open(FIFOPATH, O_RDWR);
	if(fd < 0)
	{
		perror("open fifo fail");
		return -1;
	}

	char buf[32] = {0};
	while(1)
	{
		scanf("%s", buf);
		ret = write(fd, buf, strlen(buf)+1);
		if(ret <= 0)
		{
			perror("write fail");
			break;
		}
		if(strcmp(buf, "quit") == 0) break;

	}
	close(fd);
	return 0;
}


    fifoB.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#define FIFOPATH "/tmp/fifo"


int main(int argc, char **argv)
{
	int ret = mkfifo(FIFOPATH, 00777);
	if(ret < 0 && errno != EEXIST)
	{
		perror("create fail");
		return -1;
	}
	
	//
	int fd = open(FIFOPATH, O_RDWR);
	if(fd < 0)
	{
		perror("open fifo fail");
		return -1;
	}

	char buf[32] = {0};
	while(1)
	{
		ret = read(fd, buf, 32);
		if(ret <= 0)
		{
			perror("write fail");
			break;
		}
		if(strcmp(buf, "quit") == 0) break;
		printf("wwww%s\n", buf);

	}
	close(fd);
	return 0;
}


  popen()和pclose()
    FILE *popen(const char *command, const char *type);
         popen函数允许一个程序将另一个程序作为新进程来启动,并可以传递数据给它或者通过它接收数据

   参数
     command:字符串是要运行的程序名和相应的参数
     type:
              “r”:被调用的程序输出就可以被调用程序使用,调用程序利用popen函数返回的FILE*文件流指针,就可以通过常用的stdio库函数        (如fread)来读取被调用程序的输出
             “w”:调用程序就可以用fwrite嗲用向被调用程序发送数据,而被调用程序就可以在自己的标准输入上读取这些数据,被调用的程             序 通常不会意识到自己正从另一个进程读取数据,它只是在标准输入流上读取数据,然后做出相应的操作
    int pclose(FILE *stream);


   读取外部程序的输出:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
       FILE *read_fp;
       char buffer[BUFSIZ+1];
       int chars_read;
       memset(buffer,'\0',sizeof(buffer));
       read_fp = popen("ls -l","r");
       if(read_fp != NULL)
       {
        chars_read = fread(buffer,sizeof(char),BUFSIZ,read_fp);
	 if(chars_read > 0)
	  {
             printf("output was:-\n%s\n",buffer); 
          }
       pclose(read_fp);
       exit(EXIT_SUCCESS);
       }
	return 0;
}

  将读出送往外部程序:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
       FILE *write_fp;
       char buffer[BUFSIZ+1];
       sprintf(buffer,"Once upon a time,threr was...\n");
       write_fp = popen("od -c","w");
       if(write_fp != NULL)
       {
         fwrite(buffer,sizeof(char),strlen(buffer),write_fp);
         pclose(write_fp);
       exit(EXIT_SUCCESS);
       }
	return 0;
}


 管道的弊端

无法在管道中读取一个指定的数据,因为这些数据没有做任何标记,读者进程只能按次序地逐个读取


 信号

 发送和接受信号
       kill()和signal()
            发送信号kill:


    信号拦截signal
           1、 sighandler_t signal(int signum, sighandler_t handler);(拦截的信号, 拦截后要做的事情)
           2、 typedef void (*sighandler_t)(int);
   

(升级板)sigqueue()和sigaction()

 


     struct sigaction的结构体

  struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };


代码:

void ouch(int sig)
{
  printf("ouch %d",sig);
}
int main(int argc, char **argv)
{
    struct sigaction act;
    act.sa_handler = ouch;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGINT,&act,NULL);
    while(1)
    {
      printf("hello world\n");
      sleep(1);
    }
	return 0;
}


    sigaction.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <stdbool.h>
#include <strings.h>

void f(int sig, siginfo_t *info, void *arg)
{
	printf("sig: %d\n", sig); // SIGINT(2)
	printf("int: %d\n", info->si_int); // a.sival_int
	printf("from %d\n", info->si_pid);
}

int main(void)
{
	pid_t x = fork();

	if(x > 0) // 父进程
	{
		struct sigaction act;
		bzero(&act, sizeof(act));

		act.sa_sigaction = f;
		act.sa_flags = SA_SIGINFO; // 我要用扩展版的响应函数

		// 向内核申请,将来收到SIGINT信号的时候,按照act结构体中
		// 描述的情况来处理。NULL表示不需要内核返回该信号原来的处理模式
		sigaction(SIGINT, &act, NULL); // signal()的扩展版

		pause();
	}

	if(x == 0) // 子进程
	{
		union sigval a;
		a.sival_int = 100;

		printf("child PID: %d\n", getpid());

		sleep(1);

		// 给父进程发送信号SIGINT,顺便还发了a这个值
		sigqueue(getppid(), SIGINT, a); // kill()的扩展版
	}
}


  信号响应


   1、缺省动作

p1:int main(int argc, char **argv)
{
    printf("my PID %d\n",getpid());
    pause();
	return 0;
}
p2:int main(int argc, char **argv)
{
      pid_t pid;
      scanf("%d",&pid);
      kill(pid,SIGINT);	
	return 0;
}
通过p2发送信号将p1杀死


   2、忽略

p1:int main(int argc, char **argv)
{
    printf("my PID %d\n",getpid());
    signal(SIGINT,SIG_IGN);
    pause();
	return 0;
}
p2:int main(int argc, char **argv)
{
      pid_t pid;
      scanf("%d",&pid);
      kill(pid,SIGINT);	
	return 0;
}
p2向p1发送杀死信号,没有反应,因为p1已经忽略该信号 SIGKILL和SIGSTOP都无法忽略

3、捕捉

p1:void f(int sig)
{
  printf("catch sig:%d\n",sig);
}
int main(int argc, char **argv)
{
    printf("my PID %d\n",getpid());
    signal(SIGINT,f);
    pause();
	return 0;
}
p2:int main(int argc, char **argv)
{
      pid_t pid;
      scanf("%d",&pid);
      kill(pid,SIGINT);	
	return 0;
}
f()函数由内核调用,函数预先告诉内核,等接受到SIGINT信号时,调用f()函数


   4、阻塞


  代码

void catch(int sig)
{
  fprintf(stderr,"%d catch SIGQUIT\n",getpid());
}
int main(int argc, char **argv)
{
    signal(SIGQUIT,catch);
    sigset_t sig;
    sigemptyset(&sig);
    sigaddset(&sig,SIGQUIT);

    sigprocmask(SIG_BLOCK,&sig,NULL);
    pid_t pid;
    pid = fork();
    if(pid == 0)
    {
       fprintf(stderr,"child %d\n",getpid());
       int i = 10;
       while(i>0)
       {
        
       printf("%d\n",i--);
        sleep(1);
       }
         sigprocmask(SIG_UNBLOCK,&sig,NULL);
        while(1)
	pause();
     }
    if(pid > 0)
    {
         fprintf(stderr,"parent %d\n",getpid());
          sigprocmask(SIG_UNBLOCK,&sig,NULL);
           while(1)
	   pause();
    }
	return 0;
}

因为SIGQUIT被阻塞了,子进程接收到SIGQUIT是没到反应,等到10秒后解除阻塞才有反应,而父进程一开始就已经解除阻塞

 其他

 查看或删除系统中的IPC对象



 获取一个当前未用的IPC的key



 消息队列MSG

  什么是消息队列

      消息队列提供一种带有数据标识的特殊管道,使用一段被写入的数据都变成带标识的消息,读取该段消息的进程只要指定这个标识就可以正确的读取,而不受到其他消息的干扰

消息队列使用方法

(1)发送者


  a、 获取消息队列的ID int msgget(key_t key, int msgflg);



   b、 将数据放入一个附带有标识的特殊的结构体,发送给消息队列


   
   (2)接收者
      A、 获取消息队列的ID


  
    B、将指定标识的消息读出


 
  (3  )设置或获取消息队列的相关属性
              1、int msgctl(int msqid, int cmd, struct msqid_ds *buf);
              2、msqid:消息队列ID
              3、cmd:
                   IPC_STAT:获取该MSG的信息,存储在结构体msqid_ds中
                   IPC_SET:设置该MSG的信息,储存在结构体msqid_ds中
                   IPC_RMID:立即删除该MSG,并唤醒所有阻塞在该MSG上的进程,同时忽略第三个参数
                   IPC_INFO:获取关于当前系统中MSG的限制值信息
                   MSG_INFO:获取关于当前系统中MSG的相关资源消耗信息
                   MSG_STAT:同IPC_STAT,但msgid为该消息队列的内核中记录所有信息的数组的下标,因此通过迭代所有的下标可以获得系统                中所有消息队列的相关信息
              4、buf:相关信息结构提的缓冲区
              5、IPC_STAT获取的属性信息被存放在一下结构体中

 struct msqid_ds {
              struct ipc_perm msg_perm;     /* Ownership and permissions */
               time_t          msg_stime;    /* Time of last msgsnd(2) */
               time_t          msg_rtime;    /* Time of last msgrcv(2) */
               time_t          msg_ctime;    /* Time of last change */
               unsigned long   __msg_cbytes; /* Current number of bytes in
                                                queue (nonstandard) */
               msgqnum_t       msg_qnum;     /* Current number of messages
                                                in queue */
               msglen_t        msg_qbytes;   /* Maximum number of bytes
                                                allowed in queue */
               pid_t           msg_lspid;    /* PID of last msgsnd(2) */
               pid_t           msg_lrpid;    /* PID of last msgrcv(2) */
           };

   6、其中,权限相关的信息用如下结构体来表示 

 struct ipc_perm {
               key_t          __key;       /* Key supplied to msgget(2) */
               uid_t          uid;         /* Effective UID of owner */
               gid_t          gid;         /* Effective GID of owner */
               uid_t          cuid;        /* Effective UID of creator */
               gid_t          cgid;        /* Effective GID of creator */
               unsigned short mode;        /* Permissions */
               unsigned short __seq;       /* Sequence number */
           };


   (4)删除消息队列 msgctl(msgid,IPC_RMID,NULL);


  使用步骤

   1.设定一个key值
   2.在内核空间中申请队列(id)
   3.写数据
   4.读数据
   5.销毁


 共享内存段SHM


  什么是共享内存

     多个进程可以把一段内存映射到自己的进程空间,以此来实现数据的共享以及传输,这也是所有进程间通信方式中最快的一种

  共享内存的使用步骤


   1、获取共享内存对象的ID
           int shmget(key_t key, size_t size, int shmflg);
           key:共享内存的键值
           size:共享内存的尺寸(PAGE_SIZE的整数倍)
           shmflg:
                 IPC_CREAT:如果key对应的共享内存不存在,则创建
                 IPC_EXCL:如果该key对应的共享内存已存在,则报错
                 SHM_HUGETLB:使用大页面在分配共享内存
                 SHM_NORESERVE:不在交换分区中为这块共享内存保留空间
                 mode:共享内存的访问权限(八进制,如0644)
          返回值
              成功:该内存的ID
              失败:-1
        备注:如果key指定为IPC_PRVATE,则会自动产生一个随机未用的新键值
   2、将共享内存映射至本进程虚拟内存空间的某个区域
        void *shmat(int shmid, const void *shmaddr, int shmflg);
        int shmdt(const void *shmaddr);解除映射 shmaddr共享内存的首地址
        shmid:共享内存ID
        shmaddr:
              (1)如果为NULL,则系统会自动选择一个合适的虚拟内存空间地址去映射共享内存
              (2)如果不为NULL,则系统会根据shmaddr来选择一个合适的内存区域
        shmflg:
                SHM_RDONLY以只读的方式映射共享内存
                SHM_REMAP:重新映射,此时shmaddr不能为NULL
                SHM_RND:自动选择比shmaddr小的最大页对齐地址
                可用0,表示对共享内存可读可写
        第二参数被设置,那么第三个参数设置为SHM_RND,
        返回值
              成功:共享内存的首地址
              失败:-1
   3、当不在使用是,解除映射关系
   4、当没有进程再需要这块共享内存时,删除它
         int shmctl(int shmid, int cmd, struct shmid_ds *buf);
         shmid:共享内存ID
         cmd:
              IPC_STAT:获取属性信息,放到buf中
              IPC_SET:设置属性信息为buf指向的内容
              IPC_RMID:将共享内存标记为“立即被删除”状态
              IPC_INFO:获取关于共享内存的限制值信息
              SHM_INFO:获取系统为共享内存消耗的资源信息
              SHM_STAT:同IPC_STAT,但shmid为该SHM在内核中记录所有SHM信息的数组下标,因此通过迭代所有下标可以获得系统中所有           SHM的相关信息
              SHM_LOCK:禁止系统将该SHM交换至swap分区
              SHM_UNLOCK:允许系统将该SHM交换至swap分区
        返回值
             成功:
                IPC_INFO:内核中记录所有SHM信息的数组的下标最大值
                SHM_INFO:内核中记录所有SHM信息的数组的下标最大值
                SHM_STAT:x下标值为shmid的SHM的ID
            失败:-1
     IPC_STAT获得的属性信息被存放在以下结构体中:

   struct shmid_ds {
               struct ipc_perm shm_perm;    /* Ownership and permissions */
               size_t          shm_segsz;   /* Size of segment (bytes) */
               time_t          shm_atime;   /* Last attach time */
               time_t          shm_dtime;   /* Last detach time */
               time_t          shm_ctime;   /* Last change time */
               pid_t           shm_cpid;    /* PID of creator */
               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
               shmatt_t        shm_nattch;  /* No. of current attaches */
               ...
           };

 其中权限信息结构体:

struct ipc_perm {
               key_t          __key;    /* Key supplied to shmget(2) */
               uid_t          uid;      /* Effective UID of owner */
               gid_t          gid;      /* Effective GID of owner */
               uid_t          cuid;     /* Effective UID of creator */
               gid_t          cgid;     /* Effective GID of creator */
               unsigned short mode;     /* Permissions + SHM_DEST and
                                           SHM_LOCKED flags */
               unsigned short __seq;    /* Sequence number */
           };


   5、可以通过shmctl获取或设置共享内存的相关属性
             int shmctl(int shmid, int cmd, struct shmid_ds *buf);
             shmid:共享内存的ID
             cmd:要采取的动作
                    常用的值
                    IPC_STAT:获取属性信息,放到buf中

 struct shmid_ds {
               struct ipc_perm shm_perm;    /* Ownership and permissions */
               size_t          shm_segsz;   /* Size of segment (bytes) */
               time_t          shm_atime;   /* Last attach time */
               time_t          shm_dtime;   /* Last detach time */
               time_t          shm_ctime;   /* Last change time */
               pid_t           shm_cpid;    /* PID of creator */
               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
               shmatt_t        shm_nattch;  /* No. of current attaches */
               ...
           };


         IPC_SET:设置属性为buf指向的内容
         IPC_RMID:删除共享内存段
         buf:属性信息结构体指针


 代码:
   shmA.c
 

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv)
{
	//1.获取key值
	key_t key = ftok("/", 100);
	printf("key = %d\n", key);

	//2.获取共享内存id
	int shmid = shmget(key, 1024, IPC_CREAT|0777);
	if(shmid < 0)
	{
		perror("get id fail");
		return -1;
	}
	
	//3.映射--链接
	char *p = shmat(shmid, NULL, 0);
	memset(p, 0, 1024);	
	memcpy(p, "hello haha", 11);
	getchar();

	//4.释放映射
	shmdt(p);

	//5.获取共内存状态
 	struct shmid_ds shmbuf;
	int ret = shmctl(shmid, IPC_STAT, &shmbuf);
	if(ret < 0)
	{
		perror("获取状态失败");
	}
	//当映射该SHM的进程个数为0时,删除共享内存
	if(shmbuf.shm_nattch == 0)
	{
		shmctl(shmid, IPC_RMID, NULL);	
	}
	return 0;
}


   shmB.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv)
{
	//1.获取key值
	key_t key = ftok("/", 100);
	printf("key = %d\n", key);

	//2.获取共享内存id
	int shmid = shmget(key, 1024, IPC_CREAT|0777);
	if(shmid < 0)
	{
		perror("get id fail");
		return -1;
	}
	
	//3.映射--链接
	char *p = shmat(shmid, NULL, 0);
	if(p == NULL)
	{
		perror("shmat fail");
	}
	printf("p=%s", p);
	getchar();
}


 信号量SEM

1、概念

        信号量的P,V操作最核心的特征是:它们是原子性的,也就是说对信号量元素的值的增加和减少,系统保证CUP的电气特性级别上不可分割,这跟整型数据的加减法有本质的区别
2、 获取信号量的ID
    int semget(key_t key, int nsems, int semflg);
   key:信号量的键值
   nsems:信号量的个数
   semflg:
    IPC_CREAT:如果key对应的信号量不存在,则创建
    IPC_EXCL:如果key对应的信号量存在,则报错
    mode:信号量的访问权限
3、对信号量进行P/V操作,或等零操作
      int semop(int semid, struct sembuf *sops, unsigned nsops);
     semid:信号量的ID(是由semget返回的信号量标识符)
     sops:信号量操作结构体数组

 struct sembuf
{
  unsigned short sem_num;  /* 信号量的元素序号(数组下标)(除非需要使用一组信号量,否则它的取值一般为0) */
  short          sem_op;   /* 操作元素(通常只会用到两个值,一个是-1,也就是P操作,它等待信号量变为可用,一个是+1,也就是V操作,它发送信号表示信号量现在已可用) */
  short          sem_flg;  /* 操作选项 */
}

     nsops:结构体数组元素个数
 4、 获取或设置信号量的相关信息
   int semctl(int semid, int semnum, int cmd, ...);
   semid:信号量ID
   semmum:信号量元素序号(当需要用到成组的信号量是,就需要用到这个数,它一般取值为0,表示这是第一个也是唯一一个信号量)
   cmd:将要采取的动作
    两个常用的值
    SETVAL:用来把信号量初始化为一个已知的值,这是通过union semun中的val成员设置,其作用是咋信号量第一次使用之前对它进行设置

 union semun {
               int              val;    /* Value for SETVAL */
               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short  *array;  /* Array for GETALL, SETALL */
               struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
           };

    IPC_RMID:用于删除一个已经无需继续使用的信号量标识符


线程

1、概念

线程是系统调度的最小单位。

进程资源(进程间数据独立)  线程资源(一个进程中的多个子线程是共用进程资源)(数据段, 堆)线程有自己的独立栈空间


 2、创建一条新的线程

  int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
  thread:新线程的TID 用pthread_t ID名来创建
  attr:线程属性(线程属性如果为NULL,则会创建一个标准属性的线程,)
  start_routine:线程例程(线程例程指的是如果线程创建成功,那么该线程会立即去执行的函数)
  arg:线程的例程参数
  返回值
     成功:0
     失败:errno
  代码

struct student
{
   char name[20];
   int id;
   int phone;
};
void * function(void *arg)
{
   struct student *al = ((struct student *)arg);
   while(1)
   {
       printf("name =%s id = %d phone = %d\n",al->name,al->id,al->phone);
       sleep(1);
   }
}
int main(int argc, char **argv)
{
	pthread_t pthread;
	
	struct student arg;
	strcpy(arg.name,"wangyisi");
	arg.id = 883;
	arg.phone = 123456;
	
	int ret = pthread_create(&pthread,NULL,function,(void *)&arg);
	if(ret !=0)
	{
             perror("pthread_ceate fail\n");
	     exit(1);
	}
       while(1)
       {
          printf("wait----\n");
	  sleep(1);
       }
	return 0;
}


 3、线程属性函数


     线程属性变量的使用步骤

       (1)定义线程属性变量,并使用pthread_attr_init初始化
                int pthread_attr_init(pthread_attr_t *attr);
       (2)使用pthread_attr_setXXX来设置相关的属性
       (3)使用该线程属性变量创建相应的线程
       (4)使用pthread_attr_destroy()销毁该线程属性变量
               int pthread_attr_destroy(pthread_attr_t *attr);


     设置线程的分离属性

          1、一条线程如果可接合,意味着这条线程在退出时不会自动释放自身资源,而会成为僵尸进程,同时意味着该线程退出值可以              被 其他线程取代,因此,如果不需要某条线程的退出值,那最好将线程设置为分离状态以保证该线程不会成为僵尸线程
          2、pthread_detach(id);
          3、pthread_attr_setdetachstate
               参数
                  attr:线程属性变量
                  detachstate
                           PTHREAD_CREATE_DETACHED:分离
                           PTHREAD_CREATE_JOINABLE:接合

  代码 

#include <stdio.h>
#include <pthread.h>

void *routine(void *arg)
{
	pthread_exit(NULL);
}

int main(int argc, char **argv)
{

	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr,
			PTHREAD_CREATE_DETACHED);

	pthread_t tid;
	int i=0;
	for(i=0; i<10; i++)
	{
		pthread_create(&tid, &attr, routine, NULL);
	}

	pause();
	return 0;
}


 4、线程的退出

   void pthread_exit(void *retval);
  retval:线程退出值


5、 取消一条线程

    (1)int pthread_cancel(pthread_t thread);
             给指定的线程发送一条取消请求
  (2)thread:线程TID
  (3)设置取消状态
           int pthread_setcancelstate(int state, int *oldstate);  int pthread_setcanceltype(int type, int *oldtype);
          参数
              state新的取消状态
                    PTHREAD_CANCEL_ENABLE:使能取消请求
                    PTHREAD_CANCEL_DISABLE:关闭取消请求
             oldstate:旧的取消请求
             type:新的取消类型
                    PTHREAD_CANCEL_DEFERRED:延时响应
                    PTHREAD_CANCEL_ASYNCHRONOUS:立即响应
            oldtype:旧的取消类型


 5、线程资源回收

  int pthread_join(pthread_t thread, void **retval);
  thread:线程TID
  retval:储存线程退出值的内存的指针
  该函数要注意的几点
           如果线程退出是没有退出值,retval可以指定为NULL
           pthread_join()指定的线程如果还在运行,那么它将会阻塞等待
  代码1
 

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
void * function(void *arg)
{
  int  i = 0;
  while(i < 10)
  {
       printf("%d \n",i++);
       sleep(1);
  }
}
int main(int argc, char **argv)
{
    pthread_t pthread;
    int res = pthread_create(&pthread,NULL,function,NULL);
     if(res != 0)
     {
      perror("create failed\n"); 
      exit(1);
     }
     pthread_join(pthread,NULL);
    printf("over\n");
     return 0;
}


  代码2(将线程里的值返回到线程)  

#include <stdio.h>
#include <pthread.h>

void *routine(void *arg)
{
	char *s = (char *)arg;
	printf("argument: %s", s);

	sleep(1);
	pthread_exit("Bye-Bye!\n");
}

int main(int argc, char **argv)
{
	pthread_t tid;
	pthread_create(&tid, NULL, routine, (void *)"testing string\n");

	void *p;
	pthread_join(tid, &p);
	
	printf("exit value: %s", (char *)p);
	return 0;
}


6、 线程互斥锁

(1)互斥锁安装帮助文档

   sudo apt-get install manpsges
   sudo apt-get install manpsges manpsges-dev manpages-posix-dev

(2)初始化互斥锁

   int pthread_mutex_init (pthread_mutex_t *__mutex,const pthread_mutexattr_t *__mutexattr)

(3) 销毁

 int pthread_mutex_destroy (pthread_mutex_t *__mutex)

(4)尝试锁

   int pthread_mutex_trylock (pthread_mutex_t *__mutex)

(5)上锁

   int pthread_mutex_lock (pthread_mutex_t *__mutex)

(6)解锁

   int pthread_mutex_unlock (pthread_mutex_t *__mutex)

(7)代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
pthread_mutex_t m;

void * function(void *arg)
{
  char *msg = (char *)arg;
  pthread_mutex_lock(&m);
  while(*msg != '\0')
  {
    fprintf(stderr,"%c",*msg);
    usleep(100);
    msg++;
  }
  pthread_mutex_unlock(&m);
  pthread_exit(NULL);
}
int main(int argc, char **argv)
{
      pthread_mutex_init(&m,NULL);
      pthread_t t1,t2;
      pthread_create(&t1,NULL,function,"AAAAAAAAAAAAAAAAAAAAA");
      pthread_create(&t2,NULL,function,"BBBBBBBBBBBBBBBBBBBBB");

      pthread_exit(NULL);

	return 0;
}


 7、POSIX信号量

    A、 POSIX有名信号量

          POSIX有名信号量使用步骤
          (1)使用sem_open()来创建或打开一个有名信号量
                 sem_t *sem_open(const char *name, int oflag);  sem_t *sem_open(const char *name, int oflag,  mode_t mode, unsigned int               value);
          参数
             name:信号量的名字,必须翼正斜杠“/”开头
             oflag:
                  O_CREATE:如果该名字对应的信号量不存在,则创建
                  O_EXCL:如果该名字对应的信号量已存在,则报错
             mode:八进制读写权限,0666
             value:初始化
    (2)使用sem_wait()和sem_post()来分别进行P操作和V操作
                int sem_wait(sem_t *sem);将信号量的值减1
                int sem_post(sem_t *sem);将信号量的值加1
    (3)使用sem_close()来关闭它
             int sem_close(sem_t *sem);
             sem:信号量指针
    (4)使用sem_unlink()来删除它,并释放系统资源
              int sem_unlink(const char *name);
              name:信号量名字


 B、 POSIX匿名信号量


 使用步骤
    (1)在这些线程都能访问到的区域定义这种变量(如全局变量)类型搜索sem_t
    (2)在任何线程使用它之前,用sem_init()初始化它
             int sem_init(sem_t *sem, int pshared, unsigned int value);
             参数
                 sem:信号量指针
                 pshared:该信号量的作用范围:0为线程间,非0为进程间
                 value:初始值
    (3)使用sem_wait()/sem_trywait()和sem_post()来分别进行P操作和V操作
      (4)不需要时,使用sem_destroy()来销毁
           int sem_destroy(sem_t *sem);
   代码
 

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <stdlib.h>
sem_t space,data;

void * function(void *arg)
{
  char *buf = (char *)arg;
   while(1)
   {
    sem_wait(&data);//等待有数据,就往里面读
    printf("bytes:%d\n",strlen(buf));
    sem_post(&space);//读完后空间就加1
   }
}
int main(int argc, char **argv)
{
   sem_init(&space,0,1);
   sem_init(&data,0,0);
    
   char buf[32]; 
   pthread_t tid;
   pthread_create(&tid,NULL,function,(void *)buf);

   while(1)
   {
     sem_wait(&space);//等待有空间就往内存写数据
     bzero(buf,32);
     fgets(buf,32,stdin);
     sem_post(&data);//数据就加1
   }
	return 0;
}


  C、使用情况

           进程间通信用到同步用到有名信号量,线程间通信同步用到匿名信号量
 

8、线程条件变量


  (1)初始化条件变量
   pthread_cond_init(pthread_cond_t *restrict,const pthread_condaddr_t *restrict attr)
   cond :条件变量 可以用pthread_cond_t+名
   attr:条件变量的属性,一般始终为0
  (2)销毁条件变量
   pthread_cond_destroy(pthread_cond_t *cond)
   cond :条件变量 可以用pthread_cond_t+名
  (3)等待条件资源
   pthread_cond_wait(pthread_cond_t *restrict,pthread_mutex_t *restrict mutex)
   cond :条件变量 可以用pthread_cond_t+名
   murex:互斥锁
 (4) 发送信号资源
   pthread_cond_signal(pthread_cond_t *cond)
  广播唤醒信号(唤醒所有等待线程)
   pthread_cond_broadcast(pthread_cond_t *cond)
 

猜你喜欢

转载自blog.csdn.net/qq_43158393/article/details/85401622