6.1 Linux进程概述linux
内核把进程存放在任务队列(task list)的双向循环链表中,其中链表的每一项都是类型为task_struct,称为进程描述符的结构,定义在<include/linux/sched.h>中。数组
task_struct结构中最重要的两个域:state 和 pid异步
1)进程状态函数
2)任务标识post
内核经过惟一的进程标识值PID来识别每一个进程。PID是一个非负数,实际是一个短整型数据,最大32768。能够查看/proc/sys/kernel/pid_max来肯定。spa
进程的建立、执行和终止操作系统
fork() 和 exec()线程
fork()经过复制当前进程的内容建立一个子进程,子进程与父进程的区别仅仅在于不一样的PID、PPID和其余一些资源。指针
写时复制技术。code
exec()函数负责读取可执行文件并将其载入地址空间开始运行。
优先级在0 ~ MAX_PRIO-1之间,数值越低优先级越高。
实时程序的优先级范围在0 ~ MAX_RT_PRIO-1,通常进程的优先级在MAX_RT_PRIO ~ MAX_PRIO-1之间。
内核中默认配置:优先级0 ~ 139,实时进程占用0 ~ 99,通常进程100 ~ 139
一、fork函数
执行一次,返回两个值。建立一个新进程,称为子进程。
父进程返回子进程的进程号,子进程返回0.
#include <sys/types.h> // 提供类型pid_t的定义 #include <unistd.h> pid_t for(void); // 0:子进程 // 子进程ID(大于0的整数):父进程 // -1:出错
int result = fork(); if (result == -1) { exit(-1); } else if (result ==0) { /*子进程相关语句*/ } else { /*父进程相关语句*/ }
二、exec函数族
取代原调用进程的数据段、代码段和堆栈段。
#include <unistd.h> int execl(const char *path, const char *arg, ...); int execv(const char *path, char * const argv[]); int execle(const char *path, const char *arg, ..., char *const envp[]); int execve(const char *path, char *const argv[], char *const envp[]); int execlp(const char *file, const char *arg, ...); int execvp(const char *file, char * const argv[]); // 出错:-1
前4个完整路径,后2个文件名,从PATH查找。
l(list):逐个列举,char * arg
v(vector):指针数组,*const argv[]
必须以NULL结尾。
三、exit 和 _exit
exit检查进程中文件的打开状况,把文件缓冲区中的内容写回文件。
_exit直接将进程关闭,缓冲区中的数据会丢失。
exit: #include <stdlib.h> _exit: #include <unistd.h> void exit/_exit(int status); // 0 标识正常
四、wait 和 waitpid
wait函数可使父进程阻塞。直到任意一个子进程结束或者父进程接到了一个指定的信号为止。若是该父进程没有子进程或者他的子进程已经结束,则wait函数会当即返回。
wait是waitpid的一个特例。
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options); // 成功:子进程的进程号 或 0(调用成功但子进程还未退出) // 失败:-1
status若为空,则不保存子进程的退出状态。
五、避免僵死进程实例
当一个进程已经终止,可是其父进程还没有对其进行回收的进程被称为僵死进程。
为了不,一种办法是父进程调用wait/waitpid等待子进程结束,可是在子进程结束前父进程会一直阻塞,不能作任何事情。
另外一种更好的方法就是调用两次fork函数。
System V IPC单机,Socket
Linux中使用较多的进程间通讯方式:
一、无名管道(PIPE)
半双工、具备固定的读端和写端。
一种特殊的文件,存在于内存中。
二、有名管道(FIFO)
可以使互不相关的两个进程实现彼此通讯。
能够经过路径名指出,而且在文件系统中是可见的。
FIFO严格地遵循先进先出规则,对管道及FIFO的读老是从开始处返回数据,对它们的写则把数据添加到末尾。
有名管道的建立
mkfifo函数,相似于open操做,能够指定管道的路径和读/写权限。
“mknod 管道名 p”命令来建立有名管道。
管道建立成功后,就可使用open、read、write函数了。与普通文件不一样的是阻塞问题。
普通文件不阻塞,管道中有阻塞的可能。O_NONBLOCK
#include <sys/types.h> #include <sys/state.h> int mkfifo(const char *filename, mode_t mode); // 成功:0 // 出错:-1 mkfifo("myfifo", 0666); mkfifo("myfifo", O_RDWR | O_NONBLOCK); // mkfifo仅建立了管道,并无打开管道。
6.3.二、信号通讯
信号是在软件层次上对中断机制的一种模拟。
信号是异步的。惟一的异步通讯方式。
两个来源:硬件来源和软件来源。
响应:
信号的处理包括信号的发送、捕获以及信号的处理,
kill 和 raise
raise函数只容许进程向自身发送信号。
#include <signal.h> #include <sys/types.h> int kill(pid_t pid, int sig); int raise(int sig); // 成功:0 // 出错:-1 raise(SIGSTOP); kill(pis, SIGKILL);
alarm 和 pause
设置一个定时器,到时间时发送SIGALARM信号。一个进程只能有一个闹钟时间,新值代替以前的值。
pause函数是用于将调用进程睡眠直到捕捉到信号为止。一般能够用来让进程等待信号到达。
#include <unistd.h> unsigned int alarm(unsigned int seconds) int pause(void); // 成功:上一个时钟的剩余时间,不然返回0 // 出错:-1,而且把error值设为EINTR ret = alarm(5); pause();
signal
使用signal函数时,只须要把处理的信号和处理函数列出便可。主要用于前32种非实时信号的处理,不支持信号传递信息。
#include <signal.h> void (*signal(int signum, /*指定信号*/ void (*handler))(int)))(int) /*对信号的处理*/ // 函数原型 typedef void func(int); func *signal(int, func *); // 成功:之前的信号处理配置 // 出错:-1
void my_func(int signo) { ... } signal(SIGINT, my_func); signal(SIGQUIT, my_func);
共享内存容许多个进程共享一个给定的内存区域。数据不须要在多个进程之间复制,因此是最高效的一种通讯方式。
使用共享内存的关键在于如何在多个进程之间对一给定的存储区进行同步访问。
一般信号量被用来实现对共享内存的访问。
实现步骤:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, /*IPC_PRIVATE*/ int size, /*共享内存区大小*/ int shmflg); /*通open函数的权限位,也能够用8进制表示法*/ // 成功:共享内存段标识符 // 出错:-1 void *shmat(int shmid, /*要映射的共享内存区标识符*/ const void *shmaddr, /*将共享内存映射到指定地址(0:由操做系统决定映射地址)*/ int shmflg); /*SHM_RDONLY:共享内存只读。默认0,可读写*/ // 成功:被映射后的地址 // 出错:-1 int shmdt(const void *shmaddr); /*被映射的共享内存的地址*/ // 成功:0 // 出错:-1 int shmctl(int shmid, /*共享内存的标识符*/ int cmd, /*要执行的操做的命令字*/ struct shmid_ds *buf); /*获取/设置共享内存属性的结构体的地址*/ // 成功:0 // 出错:-1
/*建立共享内存*/ int shmid = shmget(IPC_PRIVATE, BUFSZ, 0666); /*映射共享内存*/ char *shmaddr = (char *)shmat(shmid, 0, 0); /*查看共享内存对象*/ system("ipcs -m"); /*取消共享内存映射*/ shmdt(shmaddr); /*删除共享内存*/ shmctl(shmid, IPC_RMID, NULL);
消息队列由内核维护。
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key, /*返回新的或已有队列的队列ID、IPC_PRIVATE*/ int flag); // 成功:消息队列ID // 出错:-1 int msgsnd(int msqid, /*消息队列的队列ID*/ const void *prt, /*指向消息结构的指针*/ size_t size, /*消息的大小,不包括消息类型*/ int flag); /*IPC_NOWAIT:若消息并无当即发送而调用进程会当即执行返回。0:阻塞直到消息发送完成为止*/ // prt消息的结构用户本身定义,第一个成员的类型必须为long struct msgbuf { long mtype; /*消息类型*/ char mtext[LEN]; /*消息正文*/ ... } // 成功:0 // 出错:-1 int msgrcv(int msqid, /*消息队列的队列ID*/ const void *msgp, /*接收消息缓冲区*/ int size, /*消息的字节数*/ long msgtype, /*接收的消息类型*/ int flag); /*标志位*/ // 成功:接收到的消息的字节数 // 失败:-1 int msgctl(int msqid, int cmd, struct msqid_ds *buf); /*消息队列缓冲区*/ // 成功:0 // 失败:-1
typedef struct { long mtype; ... } MSG; MSG msg; /*生成消息队列须要的key*/ key_t key = ftok(".", 'q'); /*建立消息队列*/ int msqid = msgget(key, IPC_CREATE|0666); /*添加消息到消息队列*/ msgsnd(msqid, &msg, sizeof(msg)-sizeof(long), 0); /*读取消息队列中第一个消息*/ msgrcv(msqid, &msg, sizeof(msg)-sizeof(long), 0, 0); /*从系统内核中移走消息队列*/ msgctl(msqid, IPC_RMID, NULL);
pthread线程库,遵循POSIX规范,具备很好的可移植性。
一、线程建立和退出
#include <pthread.h> int pthread_create(pthread_t *thread, /*线程对象*/ pthread_attr_t *attr, /*线程属性设置,默认为NULL,可使用其余函数来设置*/ void *(*start_routine)(void *), /*线程执行的函数*/ void *arg); /*传递给start_routine的参数*/ // 成功:0 // 出错:-1 void pthread_exit(void *retval); /*线程的返回值,可由其余函数如pthread_join来检测获取*/ int pthread_join(pthread_t thread, void ** thread_return); /*用户定义的指针,用来存放被子线程的返回值(不为NULL时)*/ // 成功:0 // 出错:-1
二、mutex线程访问控制
线程共享进程的资源和地址空间。POSIX中线程同步的方法主要有互斥锁和信号量。
#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *mutex, /*指向互斥锁对象的指针*/ const pthread_mutexattr_t *mutexattr); /*互斥锁的属性*/ int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); int pthread_mutex_destroy(pthread_mutex_t *mutex); // 成功:0 // 出错:-1
mutexattr 若是为NULL表示按默认属性建立互斥锁。
三、信号量线程控制
信号量也就是操做系统中的PV原语。信号量本质上是一个非负的整数计数器,被用来控制对公共资源的访问。
PV原语是对整数计数器信号量sem的操做。一次P操做使sem减一,一次V操做使sem加一。进程(或线程)根据信号量的值来判断是否对公共资源具备访问权限。
当信号量sem的值大于0时,该进程(或线程)具备公共资源的访问权限;相反,当信号量sem的值等于0时,该进程(或线程)就将阻塞直到信号量sem的值大于0为止。
PV原语主要用于进程或线程间的同步和互斥这两种典型状况。若用于互斥,几个进程(或线程)每每只设置一个信号量sem。
当信号量用于同步操做时,每每会设置多个信号量,并安排不一样的初始值来实现他们之间的顺序执行。
Linux实现了POSIX的无名信号量,主要用于线程间的互斥同步。
#include <semaphore.h> int sem_init(sem_t *sem, /*信号量*/ int pshated, /*决定信号量可否在几个进程间共享。因为目前Linux尚未实现进程间共享信号量,因此这个值只可以取0*/ unsigend int value); /*信号量初始化值*/
#include <pthread.h> int sem_wait(sem_t *sem); int sem_trywait(sem_t *sem); int sem_post(sem_t *sem); int sem_getvalue(sem_t *sem); int sem_destroy(sem_t *sem); // 成功:0 // 出错:-1