Linux系统编程之进程线程

Linux系统编程之进程线程

本文主要介绍:

进程线程

进程和线程的基本概念

创建进程方式

进程间通信方式

创建线程方式

线程间同步方式

进程线程学习第一天

一、进程和程序

进程:运行着的程序,也是程序的一次执行过程,进程是在内存中运行。进程在执行过程中分配了一定的资源。

程序:是静态的,可以理解成a.out 存在于磁盘中。

注意:进程是程序执行和资源管理的最小单位。

调度算法: 先进先出 / 短进程优先 / 高优先级优先 / 时间片轮转

二、进程分类

交互进程:由shell控制和运行的,和终端关联的。交互进程可以在前台运行,也可以在后台运行。

前台进程:占有终端的进程,其他的称之为后台进程

后台进程:不占用终端的进程

守护进程:在后台运行,他一般是linux启动时执行,系统关闭才结束 批处理进程:不属于某个终端,他被提交到一个队列中以便顺序执

孤儿进程:父进程产生了子进程,但父进程结束,子进程未结束的情况,这种进程会移交给init进程(也叫1号进程),init进程会成为孤儿进程的父进程。

前后台进程转换
ctrl+c 终止进程 结束进程 回到shell
ctrl+z 挂起,把前台进程转换成后台进程,回到shell ,进程没结束
jobs 查看当前在后台执行的进程的进程pid (命令 jobs -l)
fg + n 其中的n是jobs的序列号 不是pid的值,在后台暂停,程序没结束。(fg 1)
& 在可执行程序后加去地址符号,则进程默认转换成后台进程(./a.out &) 和当前终端没关系 没法用ctrl+c 和ctrl+z 来结束和挂起(需要补充)
bg N 将前台进程转换成后台进程运行。 (bg n)

二、进程状态

 新建状态:应用程序被创建时的状态,没有进入到就绪队列中。
  就绪状态:进行已经具备运行条件,等待系统分配处理器运行(放入到就绪队列中)
  运行状态:进程正在运行
  阻塞状态:又称之为等待状态或者睡眠状态。进程在等待一个信号或者系统资源
  停止:也叫死亡状态,进程完成任务后正常结束。
 进程的三要素:
       每个进程都有进程id,是唯一的标志
       每个进程都有pcb,内核中秒数进程的主要数据 一个结构体
       每个进程都会进行内存映射。     

二、进程的命令

 ps: 列出活跃的进程
 ps -au  列出所有进程  但进程都和终端有关系
 ps -aux  列出所有进程,包括有关系和终端没关系的进程。
 top  动态显示进程信息,等价于任务栏管理器
 top -d  n秒   每隔n秒刷新一次信息。
 pstree 打印进程的树状信息,关系信息。
 ps -ef  标准的格式显示系统的进程信息
 kill  发送信号给进程
 列出所有信号   kill -l
 给进程发信号  kill -信号  PID  
 1) SIGHUP  终端关闭会发送               进程结束
 2) SIGINT   ctrl + c 终止当前进程      进程结束 
 3) SIGQUIT  ctrl + \  停止当前进程     进程结束
 9) SIGKILL    杀死进程                       进程结束		
12) SIGUSR2   用户自定信号        
14) SIGALRM   闹钟信号
19) SIGSTOP	 进程暂停
20) SIGTSTP   ctrl+z  挂起进程        转换成后台进程

三、进程的API函数

1.获取进程ID

 函数原型  pid_t getpid(void);
 头文件: #include <sys/types.h>         #include <unistd.h>
  返回值: 当前进程的进程id号

   函数原型:pid_t getppid(void);
  作用: 获取当前进程的父进程id号
  练习:把当前进程的进程id和父进程的进程id打印在屏幕上

2.创建子进程 (fork和vfork的区别)

2.1 fork( ),函数原型pid_t fork(void);创建子进程,克隆了父进程的一切特征(也克隆了父进程的内存空间)父子进程之间不受影响,

父进程改变某一个变量,子进程不改变。返回值有2个,返回给父进程的是子进程的PID,返回给子进程的是0,如果失败返回-1;

2.2 vfork( ) 换上原型 pid_t vfork(void);用来创建子进程,也是返回两个值,父进程是返回子进程的pid 子进程返回0,但是子进程不会复制父进程的内存空间,

是以共享的方式访问父进程的内存,如果父进程的内存修改,子进程同时修改。vfok中是优先子进程执行,子进程结束后再执行父进程。

2.3 clone( ) 是linux内核创建进程的方式,创建进程和线程都使用的clone函数,开发中尽量避免使用,因为不利于移植。

练习: 父子进程执行不同的代码
 #include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main()
{
printf("danny帅\n");
pid_t pd = fork();
//子进程执行pid == 0中的代码
if(pd == 0)  {    while (1) {    printf("我是子进程\n");  }}
//父进程执行else中的代码
else {  while (1) {printf("----我是父进程--------\n");   }}
printf("pid = %d\n",pd);
printf("好帅\n");
//从fork开始的代码,在父进程和子进程中都有一份
return 0;
}

写操作拷贝技术:fork创建新进程,但不会产生父进程的副本。当子进程需要修改父进程内容的时候,才会拷贝需要修改的内存空间。父进程同样。
用vfork创建一个子进程,公用变量x 父子进程分别打印。

四、exec系列函数:

作用:在一个进程中执行另外一个进程,例如在a.out进程中执行b.out
   #include <unistd.h>
   extern char **environ;    全局变量,已经定义好了,直接声明后使用,字符串的首地址,环境变量。

4.2 函数原型:int execl(const char *path, const char arg, … / (char *) NULL */);

         函数作用:列举的方式传参
         函数参数:path 可执行程序的位置
                          arg   参数  需要执行的命令  可变长。
         返回值: 失败返回-1  成功返回0错误信息存在errno中

4.2 函数原型:int execlp(const char *file, const char arg, … / (char *) NULL */);

        作用:优先到PATH路径中查找,其他功能和execl一样。

   int execle(const char *path, const char *arg, ...   /*, (char *) NULL, char * const envp[] */);
   int execv(const char *path, char *const argv[]);
   int execvp(const char *file, char *const argv[]);
   int execvpe(const char *file, char *const argv[],  char *const envp[]);

作业: 自行学习四个函数,并且写测试代码。

进程线程学习第二天

一、exec系列函数:

  作用:在一个进程中执行另外一个进程,例如在a.out进程中执行b.out
  特点: exec 执行后他取缔了原来进程的数据段,代码段,堆栈,执行完成后,原来调用的进程内容除了进程号外,其他内容全部替换。
   使用: 如果当前进程想执行另外一个进程,但自己没有其他事情可做,自己结束执行exec中的进程。 
  #include <unistd.h>
   extern char **environ;    全局变量,已经定义好了,直接声明后使用,字符串的首地址,环境变量。

4.2 函数原型:int execl(const char *path, const char arg, … / (char *) NULL */);

         函数作用:列举的方式传参
         函数参数:path 可执行程序的位置
                          arg   参数  需要执行的命令  可变长。
         返回值: 失败返回-1  成功返回0错误信息存在errno中
         案   例:execl("/bin/ls" ,"ls","-l",NULL);

4.2 函数原型:int execlp(const char *file, const char arg, … / (char *) NULL */);

        作      用:优先到PATH路径中查找,其他功能和execl一样。
        参      数:   file   文件名   (会在path路径中查找)
                           arg后的参数和execl一样

     int execle(const char *path, const char *arg, ...   /*, (char *) NULL, char * const envp[] */);
     作     用:  在envp中查找执行程序和命令

    int execv(const char *path, char *const argv[]);
     作     用: 在数组中寻找执行命令

    int execvp(const char *file, char *const argv[]);
     作    用: 执行文件在file中查找但其他命令在数组中查找

    int execvpe(const char *file, char *const argv[],  char *const envp[]);
     作    用: 在path中查找执行文件,其他命令在数组和环境变量中查找。

  总结:  l   列举的方式   list
              v  数组的方式传参   数组也要NULL结束
              p  可执行程序名再PATH路径中查找
              e  环境变量传参

二、退出进程

  进程的终止:第一种 自愿终止,main函数return 
                     第二种:被迫终止, 调用exit系列函数。
                                                    接受信号也会终止
   原理:当进程结束时,内核通知父进程,在父进程处理前,这个子进程是僵尸进程,占用的各种资源被回收,进程描述符任然存在。
     #include <stdlib.h>

函数原型: void exit(int status);

     作用:退出进程
     参数: status 状态码,给到父进程
     特点: 做善后处理。刷新缓冲区,释放放缓冲区,退出进程,调用了_exit

     #include <unistd.h>

函数原型:void _exit(int status);

     作用:退出进程
     参数: status 状态码,给到父进程
     特点: 立马退出,直接退出、
  面试题:请讲下 exit()和_exit的区别:_exit()执行后会立即返回给内核,而exit()会做一些善后处理,然后将控制权交给内核。

三、进程堵塞

 #include <sys/types.h>
   #include <sys/wait.h>

函数原型:pid_t wait(int *status);

   作   用:     等待子进程结束,堵塞函数。只要有进程结束则立马返回,不管是哪个进程。
   参   数:    status  子进程退出时候的状态码
                    如果不关心子进程的退出码 则直接传入NULL
   返回值: 如果成功返回子进程的id 失败返回-1; errno
                  在等待所有进程,只要有一个进程结束  wait就会返回。
    
    WEXITSTATUS(status)
     状态信息需要通过当前宏的修改才能使用,不可以直接使用wait的状态信息。

 练  习: 请创建3个子进程,保存进程id号,然后父进程等待所有子进程结束后打印出结束后的子进程id。

函数原型: pid_t waitpid(pid_t pid, int *status, int options);

   作用: 等待某一个进程结束,或者某几个进程结束。
   参数: pid 需要等待的进程id
             status  状态码 是子进程中exit的传出值
             options   是0表示堵塞。   WNOHANG 是非堵塞(不等待)
    pid的可能值如下:
     < -1  等待进程组id为pid绝对值的组下任意一个子进程id.

     -1     等待任意的子进程,等价于wait

     0      等待任意的进程  这个进程组的id是等于调用的进程id.

    > 0   等待进程id是pid的子进程结束。.

   进程组: 当运行某一个可执行程序,创建一个进程。同时会创建一个进程组,这个进程组的id就是当前进程的id,如果这个父进程创建了子进程,
	这些子进程都有自己的pid,他们都属于父进程的组id中,他们属于同一个组。

 练习: 请创建三级进程  父进程  子进程  孙子进程 在子进程中等待孙子进程  在父进程中等待子进程。等子进程结束 则父进程也一起结束。

四、守护进程

定义:守护进程也叫后台进程,通常独立于中断,通常是在linux启动的时候运行。

 终端:是系统和用户交互的界面,从当前终端产生的进程都依附于当前终端。如果当前中断关闭,对应的前台进程会自动关闭,但是守护进程突破此现值,

守护进程的步骤:

  第一步:创建子进程,父进程退出
              if(fork()  > 0 ){  exit(0);}
   第二步:在子进程中创建新的会话。(会话中 各种进程组)
            setsid    用来创建一个新的会话,并且当前进程成为新会话的组长。
   第三步:设置文件的权限掩码。增加守护进程的灵活性
            umask(0);
    第四步:更改当前目录
     chdir 改变当前目录。  一般情况下修改成根目录或者/temp 为守护进程的当前目录
再将父进程的三个默认打开的文件重定向  dup2();
    第五步: 关闭文件描述符。
          原因:新建的子进程会继承父进程的所有打开的文件描述符。(fork)
                   在完成会话后,为了防止资源的浪费。关闭不用的文件。
                     close 函数 或者fclose
         getdtabsize 可以获取最大的文件描述符。  sysconf()

五、系统日志和 main函数善后

  main函数善后   void (*function)(void)
  #include <stdlib.h>
   函数原型:int atexit(void (*function)(void));
   函数作用:main函数接受后需要做的时期,可以调用的函数。
   函数参数: (*function)  函数指针,返回值是void 参数是void的函数
   
  #include <stdlib.h>

   int on_exit(void (*function)(int , void *), void *arg);
   函数作用:main函数接受后需要做的时期,可以调用的函数。
   函数参数:(*function) 函数指针,返回值是void 参数是void的函数
                 
 系统日志:
  使用原因:系统日志文件
  打开系统日志文件
  #include <syslog.h>

   void openlog(const char *ident, int option, int facility);
   参数: ident    提示字符串
              option:LOG_PID    把消息加入到进程id中
              int facility : LOG_DAEMON  守护进程。 
             
   void syslog(int priority, const char *format, ...);
   作用:写日志,
             priority 优先级   LOG_INFO  普通日志
                                       LOG_ERR   错误消息
             需要写的内容
    
   void closelog(void);
    作用:关闭日志文件
    
 作业:把当前进程的进程id 写入到系统日志文件中,然后创建2个子进程,分别写5条日志到系统日志中。主进程等待所有子进程结束。后关闭日志日志文件。

进程线程学习第三天

多进程通信
1.早期: 无名管道 有名管道 信号
IPC : 共享内存 信号量 信息队列
网络: socket通信

管道通信:

2.无名管道

是一个文件,系统维护的一个文件,内核中。
特点: 只能用于亲缘关系的进程之间(父子  子孙..)
           半双工的通信方式,同一个时间只可以由一个方向发给另外一个方向
           有固定的读写端
           管道可以看成是特殊的文件,对于他的读写可以用read和write函数
           基于文件描述符的通信方式,当一个管道建立,有两个描述符
           fd[0]用于读文件    fd[1]用来写文件

函数原型: int pipe(int pipefd[2]); 【重】

头文件 : #include <unistd.h>
作用: 创建一个无名管道
参数: fd的数组,fd【0】,fd[1].
返回值: 如果成功返回0 失败返回-1.错误信息存入errno

注意点: 有写管道,就一定有读管道,否则会炸裂。
在写端存在的情况下:
管道有数据: read返回实际读取的数据字节数。
管道无数据: read是处于堵塞状态,直到管道有数据才解除堵塞
如果有读数据,写端如果不存在则会一直read堵塞,如果写端存在则不会出现。

3.有名管道

 为了解决不相关的进程之间的通信,也是一个文件。

函数原型:int mkfifo(const char *pathname, mode_t mode); 【重】

   头  文  件:     #include <sys/types.h>       #include <sys/stat.h>
   作      用: 创建有名管道,管道名字叫pathname
   参     数:  pathname,   路径 也叫管道名
                    mode         创建管道文件的模式  0666
    返回值: 如果成功返回0  失败返回-1

写一个进程,把屏幕上输入的内容写入管道,第二个进程读出来。

二、信号

理解: 进程通信的方式,他不传递数据,只传递通知。信号可以理解成中断,异步通信方式, 信号可以在用户空间和内核空间之间进行交互。
信号处理方式:
忽略信号:对信号不做任何处理,但有两个信号不可以忽略,SIGKILL SIGSTOP
捕捉信号:定义信号处理函数,当信号发生时,执行对应的函数
缺省操作:linux对每种信号都做了默认操作。

信号命令: kill -命令  pid
注意:编号为1到31的信号是传统的unix支持的信号,是不可靠信号(非实时),不存储,不放入信号队列。信号容易丢失,不排队速度快
        32~63 信号,是扩展信号,叫做可靠信号,存入消息队列,不会丢失。相对来说速度不够快。
 信号处理函数:

   函数原型:int kill(pid_t pid, int sig);  【重】
   头文件:   #include <sys/types.h>         #include <signal.h>
   参  数: pid 需要发送哪个进程的id,   sig 是需要发送的信号
                > 0   发送sig信号给pid的进程(一个进程)
                0      发送给正在调用的进程组所在进程下的所有id
                -1   发送sig信号给所有进程(除了1号init进程)
                <-1 给进程组为绝对值pid的组发送sig信号。
   返回值:如果成功返回0 如果失败返回-1  错误信息存入errno

  #include <signal.h>

   函数原型: int raise(int sig);
   作       用: 给当前进程发送信号,相当于 kill(getpid(),sig)
   参数:    需要放的信号内容
    返回值: 如果成功返回 0,失败返回-1,错误信息errno

作业:用有名管道写一个服务器和客户端
客户端:父进程每一秒产生一个子进程,每个子进程往有名管道写一个自己pid,以及当前的系统时间,
服务器:从无名管道中读取数据并且打印到屏幕上。

闹钟信号:

三、设置闹钟函数:

  函数原型:unsigned int alarm(unsigned int seconds);  【重】
  头文件: #include <unistd.h>
  作用:在参数seconds秒后发送一个闹钟给自己,信号名称:SIGALRM---14
  返回值: 如果先前没有设置闹钟,成功返回0,失败返回-1
                如果之前设置了闹钟,返回之前设置的闹钟剩余秒数,
  注意: 一个进程只可以设置一个闹钟,不可以多个,当第二次设置闹钟的时候,不会成功,只会返回第一个闹钟剩余的时间。等价于得到闹钟的剩余秒数。

堵塞当前进程
函数原型: int pause(void);
头文件:#include <unistd.h>
作用: 让当前进程处于堵塞状态,等待接受一个信号(终止信号或者引起调用信号处理函数的信号)
如果是非终止信号或者是不需要捕获的信号,则不会解除堵塞。

pause和wait的区别:

前者是等待另外的进程发送sigkill信号,而wait是父进程等待子进程的结束。

信号处理函数:

函数原型:sighandler_t signal(int signum, sighandler_t handler); 【重】

   回调函数:  typedef void (*sighandler_t)(int);
   头文件:      #include <signal.h>
   参数:       signum  信号的值,需要处理什么信号
                    handler 参数:
                   如果想忽略信号(SIG_IGN)    1
                   如果想默认操作(SIG_DEL)0
                   对信号自定义处理的回调函数地址

  返回值:返回先前处理函数的指针,如果没有先前的处理函数则返回自己。如果失败返回SIG_ERR  (-1)
    
   练习:请自定义处理ctrl+z 信号。  (我是ctrl+z)

 SIGUSR1
  练习: 写一个代码,让子进程每隔5秒给父进程发送一个消息 ,父进程得到消息后,在屏幕上打印一句话。  (day03/09childContFather.c)

四、共享内存:

定义: 共享内存是最高效的进程间通信方式。进程之间可以直接读写内存。不需要其他的数据拷贝。一般情况下,共享内存是不安全的,
在写的过程中是可以立马读的,所以数据有可能不完整。为了解决这个问题,需要用到锁机制。
 流程:  【重】
  1.创建或者打开共享内存  shmget函数
  2.映射共享内存    shmat 函数
  3.内存的读写。 使用指针读写。
   4.删除共享内存    shmdt    (shmctl) 

函数原型:int shmget(key_t key, size_t size, int shmflg); 【重】

  头   文  件:#include <sys/ipc.h>         #include <sys/shm.h>  
  作        用 : 创建或者打开共享内存。
  参          数:key 是内核识别ipc的一个标志。
                       两种获取办法: ftok获取   第二种方式:系统自动给与  IPC_PRIVATE
                      size 共享内存的大小,以字节为单位,如果是0则是获取共享内存。
                       shmflg:权限, IPC_CREAT|0666   如果不存在则创建,如果存在则打开  
 返  回  值: 返回key相关的共享内存的id,如果失败返回-1,如果key相同则id也相同。

函数原型:key_t ftok(const char *pathname, int proj_id);

   头  文  件:#include <sys/types.h>           #include <sys/ipc.h>
   作       用:根据路径名和id产生一个唯一的pic的key值。
   参       数:pathname  一个路径,相同的路径和相同的id产生的key值相同
                    id     工程的id,自定义的,任意8位都行
   返  回  值:如果成功返回 key值,如果出错返回-1

函数原型:void *shmat(int shmid, const void *shmaddr, int shmflg); 【重】

   头  文  件:#include <sys/types.h>           #include <sys/shm.h>
   作       用:将申请到的共享内存空间映射到4G的虚拟内存的某一个位置。
   参       数:shmid,共享内存的id 是shmget函数的返回值
                    shmaddr  需要映射的地址,一般情况下是NULL,系统自动分配
                    shmflg  标志,一般情况下写0 表示可读可写。
   返  回  值:如果成功返回 映射的首地址,如果出错返回-1

函数原型:int shmdt(const void *shmaddr);

   函数作用:讲共享内存从映射中删除。
   函数参数:shmaddr 是shmat的返回值,也是映射的地址。
   返回值:  如果成功返回0 失败返回-1

    #include <sys/ipc.h>
   #include <sys/shm.h>

函数原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf); 【重】

   函数参数:shmid  是共享内存的id  是shmget的返回值
                    cmd   是命令,需要控制共享内存的命令
                              IPC_STAT  获取共享内存的信息  存入到buf中
                              IPC_SET   设置共享内存的信息。
                              IPC_RMID  删除共享内存。注意和shmdt的区别
   函数作用:  可以控制共享内存
   返回值:    如果成功返回0 失败返回-1   

  struct shmid_ds {
           struct ipc_perm shm_perm;    /* 操作权限,是三种ipc的共有的结构体 */
           size_t          shm_segsz;   /* 共享内存的大小,字节数*/
           time_t          shm_atime;   /* 最后加载的时间 */
           time_t          shm_dtime;   /* 最后离开的时间 */
           time_t          shm_ctime;   /* 最后修改时间 */
           pid_t           shm_cpid;    /* 创建共享内存的进程id */
           pid_t           shm_lpid;    /* 在该段 shmat的进程id */
           shmatt_t        shm_nattch;  /* 使用该共享内存的进程数目*/
           ...
       };

#include <sys/mman.h>
自行查询: void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);

进程线程学习第四天

消息队列 (生产者消费者模型)

消息队列:消息队列是ipc系列的一种对象,一种进程间通信的方式
每个消息队列都有他的id唯一标志。
用户可以在消息队列中添加消息和读取消息(收发数据)

编程步骤:

               1、得到key值  ftok
               2、得到消息队列的id, msgget
               3、数据的收发   msgsnd   msgrcv
               4、删除消息队列 msgctl

函数原型: int msgget(key_t key, int msgflg);

  头 文  件:       #include <sys/types.h>       #include <sys/ipc.h>       #include <sys/msg.h>
  函数作用:创建消息队列并且返回消息队列的id值。
  函数参数:  key值,ftok的返回值,IPC_PRIVATE
                    msgflg  IPC_CREAT |0666

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

函数原型:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

   函数作用: 往消息队列发送数据  (和write有点相似)
   函数参数: msgid   是消息队列的id值
                     msgp   需要发送数据的缓存  (需要有自己固定的结构体)
                     struct msgbuf {
                                  long mtype;       /* 大于0的消息类型 */
                                 char mtext[1];    /* 需要发送的数据,正文内容 */
                      };

                    msgsz   发送数据的大小 ,实际发送的正文内容的大小
                    msgflg   如果是0则表示堵塞,发送完毕后再返回,如果不堵塞IPC_NOWAIT
   返回值: 如果成功返回0  失败返回-1

函数原型: ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

   函数作用: 接受消息队列中数据  (和read有点相似)
   函数参数: msgid   是消息队列的id值
                     msgp   需要接受数据的缓存
                    msgsz   发送数据的大小
                    msgtyp  消息的类型(消息的范围)
                          0    消息队列中的第一个消息。
                         >0  接受消息队列中第一个msgtyp类型的消息
                         <0  接受消息队列中 不大于msgtyp  类型的绝对值的最小类型的第一个消息。
                    msgflg   如果是0则表示堵塞,发送完毕后再返回,如果不堵塞IPC_NOWAIT


 #include <sys/types.h>
   #include <sys/ipc.h>
   #include <sys/msg.h>

函数原型: int msgctl(int msqid, int cmd, struct msqid_ds *buf);

  函数参数: msqid,   消息队列的id值
                     cmd,  需要控制消息队列的命令
                    IPC_SET    设置消息队列的属性
                    IPC_STAT  获取消息队列的状态
                    IPC_RMID 删除消息队列
  函数作用: 控制消息队列(关闭 获取 设置)
  返 回 值: 如果成功返回0  失败返回-1

信号量:也叫信号灯。他是进程之间或者线程之间的同步机制。(临界资源的锁)

编码步骤:

0、或者key值,ftok
1、打开或者创建信号量   semget       (semaphore)
2、信号量的初始化(semctl) 不一定需要
3、p、v操作  semop()
4、删除信号量   semctl

函数原型:int semget(key_t key, int nsems, int semflg);

   头  文 件:  #include <sys/types.h>       #include <sys/ipc.h>       #include <sys/sem.h>
   函数作用:创建或者打开信号量
   函数参数:key 是ipic的key值,第一种ftok   IPC_PRIVATE
                    nsems  信号量的个数 如1 ,2
                     semflg     IPC_CREAT   | 0666;
   返回值:  如果成功返回信号量的id  失败返回-1

注意: p操作把当前进程的运行状态设置为堵塞状态,直到另外一个进程唤醒。申请一个空的资源(信号量减一)如果申请成功直接退出,
如果失败,进程堵塞,一直等待,直到有进程v操作为止。
v操作负责把一个堵塞的进程唤醒(释放资源)(信号量加一)

函数原型:int semop(int semid, struct sembuf *sops, size_t nsops);

   函数参数:semid 信号量的id  semget函数的返回值  
              struct sembuf {            
                    unsigned short sem_num;  /* 信号的第几个。信号量的编号 */
                    short          sem_op;   /* 信号量的pv操作,如果是-1则是p操作,如果是v则1*/
                    short          sem_flg;  /* 标志位 默认为0 */
                  };
               nsops : 需要操作的信号量的个数
   函数作用: 给信号量做pv操作,p则信号量减一  v是信号量加1
  返回值:成功返回信号量的标识符  失败返回-1
  头文件:  #include <sys/types.h>       #include <sys/ipc.h>       #include <sys/sem.h>

函数原型: int semctl(int semid, int semnum, int cmd, …);

   函数作用: 删除 修改  初始化 信号量  做信号量的操作
   函数参数: semid    信号量的id 是semget函数的返回值
                    cmd   操作信号的命令
                    IPC_RMID   删除信号量
                    SETVAL   设置信号量的值
                    GETVAL  获取信号量的值
                    semnum   信号量的编号。使用单个信号量的时候 默认是0
                   第四个参数:
                    union semun {   //当前联合需要自行定义
                             int              val;    /* 设置信号量的时候使用 */
                             struct semid_ds *buf;    /*IPC_STAT, IPC_SET的时候需要的buf结构体指针 */
                             unsigned short  *array;  /* 设置多个信号量的时候 数组指针 */
                             struct seminfo  *__buf;  /* IPC_INFO  */
       };

     返回值: 如果是GETVAL 成功返回信号量的值,失败返回-1
                   如果是SETVAL 成功返回0  失败返回-1

线程创线程同步

线程和进程的区别:

 进程: 独占4g的内存空间
             linux内核 对每个进程都要pcb   task_struct 保存
             每个进程之间都参与内核的调用,彼此之间不受影响。
             进程开销大(时间片轮转,开销和加载)
  线程: 
           线程是cpu的最小调度单位。
           同一个进程可以有多个线程,但都共享同一个地址空间。
            也称之为轻量级的进程(linux没办法区分进程和线程)
   特点: 大大提高了任务的切换效率。
   使用进程的哪些资源:
         可执行的指令。(代码)
         静态数据(全局变量,常量,静态变量);
         进程中的文件描述符
         当前的工作目录
         用户id  用户组id
 线程的唯一标志:PCB
         线程id
         堆栈
         优先级
         线程的状态和属性

编码的步骤

 1. 创建线程
  2.线程的分离
  3.线程的处理
  4.线程的退出

进程线程学习第五天

线程创建

#include <pthread.h>
函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
函数参数:thread 线程对象,线程的id值
attr 线程的属性 NULL
start_routine 线程处理函数,当创建线程后,自动跳转到线程处理函数中
arg 传递给线程回调函数的参数 如果没有参数NULL
返 回 值:如果成功返回0 失败返回-1
函数作用:创建线程
注意点: 在编译的过程中加 -pthread

线程退出

   函数原型:void pthread_exit(void *retval);
   函数参数:retval 退出码,可以被主线程获取到,pthread_join
   函数作用:退出线程
   函数返回值: 无

加入线程

#include <pthread.h>
函数原型:int pthread_join(pthread_t thread, void **retval);
函数作用:主线程等待子线程结束,释放线程描述符
函数参数:thread 线程的id
retval 二级指针,接受线程exit的退出码。(一般情况下是一个一级指针的地址,传入地址)
返回值:成功返回0 失败返回错误errno

线程的分离:

  当线程分离后,主线程不需要等待子线程结束,不需要join也会释放子线程pcb,理解成线程的托管。分离后的线程由1号线程,也就是init线程托管。
  编码步骤:(创建前分离)

1线程初始化

函数原型:int pthread_attr_init(pthread_attr_t *attr);

     作用: 对线程进行初始化,获得状态信息。通过(pthread_attr_t 获得 
     函数参数:attr  传出参数,传入结构体地址,会得到状态信息
      返回值: 成功返回0  失败返回错误码。

2.设置线程分离(获取分离状态)

     函数原型:int pthread_attr_setdetachstate (pthread_attr_t *__attr,int __detachstate)
     函数作用:用来设置线程分离
     函数参数: attr 从init函数的第一个参数来。
                       __detachstate  设置或者获取分离状态
                        PTHREAD_CREATE_DETACHED    分离            
                        PTHREAD_CREATE_JOINABLE      
     返回值:成功返回0  失败返回错误码

函数原型:int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

      函数作用:获取线程的分离状态
      函数参数:  attr 从init函数的第一个参数来。
                       __detachstate  获取分离状态,是传出参数。
      返回值:成功返回0  失败返回错误码
         
  
   创建后分离,填入线程id后 ,此线程就变成了分离线程

函数原型:int pthread_detach(pthread_t thread);

   头文件:  #include <pthread.h>
   函数作用: 对线程进行创建后分离。

互斥锁:多个进程同时访问临界资源,则可以给临界资源加锁,得到锁的线程执行,没抢到占锁的则等待。

步骤:

  1.先对互斥锁初始化
   2.加锁操作
   3.数据处理
   4.解锁操作
   5.销毁互斥锁

函数原型:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

   函数作用: 对互斥锁进行初始化
   函数参数:mutex 互斥锁对象 或者说互斥锁id
                   mutexattr 属性 NULL
    返回值:如果成功返回0 失败返回错误码 errno

函数原型:int pthread_mutex_lock(pthread_mutex_t *mutex);

    函数参数: mutex   是init的第一个参数
    作用: 对互斥锁加锁。其他线程没法在加锁。
    返回值:如果成功返回0 失败返回错误码 errno

函数原型:int pthread_mutex_trylock(pthread_mutex_t *mutex);

    作用:尝试枷锁,如果没有锁则直接返回,不等待
    返回值:如果成功返回0 失败返回错误码 errno

函数原型:int pthread_mutex_unlock(pthread_mutex_t *mutex);

   作用: 对互斥锁解锁操作
   函数参数:  mutex   是init的第一个参数
    返回值:如果成功返回0 失败返回错误码 errno

函数原型:int pthread_mutex_destroy(pthread_mutex_t *mutex);

    作       用: 对互斥锁解锁操作
    函数参数:  mutex   是init的第一个参数
    返  回 值:如果成功返回0 失败返回错误码 errno

信号量:

  步骤:
  1.信号量的初始化
  2.pv操作
  3.销毁信号量


   #include <semaphore.h>

函数原型:int sem_init(sem_t *sem, int pshared, unsigned int value);

   函数参数:sem 信号量的对象(传入地址,函数中会修改指针指向的空间值)
                    pshared   如果是0 表示线程   如果是非0表示进程。(可以处理进程和线程)
                    value  信号量的初值。
   函数作用:初始化信号量
   返  回 值:成功返回0 失败返回-1

     #include <semaphore.h>

函数原型:int sem_wait(sem_t *sem);

    函数参数:sem    信号量对象
    函数作用: 是信号量的p操作,信号量-1,占用资源
    返回值: 如果成功返回0  失败-1

          #include <semaphore.h>

函数原型:int sem_post(sem_t *sem);

    函数参数: sem   信号量对象   sem_init的第一个参数
   返回值:如果成功返回0  失败-1
  函数作用: 对信号量进行v操作,是否资源,信号量+1

版权声明:本文为qq_52531759原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
THE END
< <上一篇
下一篇>>