嵌入式Linux C编程 05

6.1 Linux进程概述linux

  • 交互进程:
  • 批处理进程:
  • 守护进程:

内核把进程存放在任务队列(task list)的双向循环链表中,其中链表的每一项都是类型为task_struct,称为进程描述符的结构,定义在<include/linux/sched.h>中。数组

task_struct结构中最重要的两个域:state 和 pid异步

1)进程状态函数

  • 运行(TASK_RUNNING):包括就绪和运行,指进程随时能够被调度运行或正在运行
  • 可中断(TASK_INTERRUPTIBLE):进程中止运行,直到知足继续运行条件
  • 不可中断(TASK_UNINTERRUPTIBLE):中止运行,知足条件,也不会立刻被激活。
  • 僵死(TASK_ZOMBIE):进程运行结束,等待父进程销毁
  • 中止(TASK_STOPPED):进程中止运行,当进程收到SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU等信号,就会中止

2)任务标识post

内核经过惟一的进程标识值PID来识别每一个进程。PID是一个非负数,实际是一个短整型数据,最大32768。能够查看/proc/sys/kernel/pid_max来肯定。spa

进程的建立、执行和终止操作系统

fork() 和 exec()线程

fork()经过复制当前进程的内容建立一个子进程,子进程与父进程的区别仅仅在于不一样的PID、PPID和其余一些资源。指针

写时复制技术。code

exec()函数负责读取可执行文件并将其载入地址空间开始运行。

6.1.二、进程的调度

优先级在0 ~ MAX_PRIO-1之间,数值越低优先级越高。

实时程序的优先级范围在0 ~ MAX_RT_PRIO-1,通常进程的优先级在MAX_RT_PRIO ~ MAX_PRIO-1之间。

内核中默认配置:优先级0 ~ 139,实时进程占用0 ~ 99,通常进程100 ~ 139

6.二、Linux进程控制相关API

一、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函数。

6.三、进程间通讯

System V IPC单机,Socket

  • Unix进程间通讯(IPC)方式包括管道、FIFO、信号
  • System V进程间通讯(IPC)包括System V消息队列、System V信号灯、System V共享内存区
  • Posix 进程间通讯(IPC)包括Posix消息队列、Posix信号灯、Posix共享内存区

Linux中使用较多的进程间通讯方式:

  1. 管道(Pipe)及有名管道(named pipe)。管道可用于有亲缘关系进程间的通讯;有名管道除具备管道的功能外,还容许无亲缘关系进程之间进行通讯。
  2. 信号(Signal)是在软件层次上对中断机制的一种模拟。
  3. 消息队列是消息的连接表,包括Posix消息队列和System V消息队列。它克服了前两种通讯方式种信息量有限的缺点,具备写权限的进程能够向消息队列中按照必定的规则添加新消息;对消息队列有读权限的进程则能够从消息队列中读取消息。
  4. 共享内存,是一种最高效的进程间通讯方式。多个进程访问同一块内存空间。须要依靠某种同步机制,入互斥锁和信号量等。
  5. 信号量,主要做为进程间和同一进程中不一样线程之间的同步手段。
  6. 套接字(Socket)一种更为通用的进程间通讯机制,可用于不一样主机之间的进程间通讯。

6.3.一、管道通讯

一、无名管道(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.二、信号通讯

信号是在软件层次上对中断机制的一种模拟。

信号是异步的。惟一的异步通讯方式。

两个来源:硬件来源和软件来源。

响应:

  1. 忽略信息:可是SIGKILL和SIGSTOP不能忽略
  2. 捕捉信息:定义信号处理函数
  3. 执行默认操做:

信号的处理包括信号的发送、捕获以及信号的处理,

  • 发送信号的函数:kill、raise
  • 设置信号处理的函数:signal
  • 其余相关的函数:alarm、pause

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);

6.3.三、共享内存

共享内存容许多个进程共享一个给定的内存区域。数据不须要在多个进程之间复制,因此是最高效的一种通讯方式。

使用共享内存的关键在于如何在多个进程之间对一给定的存储区进行同步访问。

一般信号量被用来实现对共享内存的访问。

实现步骤:

  1. 建立共享内存,shmget函数。从内存中得到一段共享内存区域。
  2. 映射共享内存,把这段建立的内存映射到具体的进程空间去。shmat函数。
  3. 撤销映射的操做。函数为shmdt
  4. 删除共享内存对象,函数为shmctl
#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);

6.3.四、消息队列

消息队列由内核维护。

  1. 建立或打开消息队列。使用函数msgget,这里建立的消息队列的数量会受到系统消息队列数量的限制。
  2. 添加消息队列。使用函数msgsnd,它把消息添加到已打开的消息队列末尾。
  3. 读取消息队列。使用函数msgrcv,它把消息从消息队列中取走,与FIFO不一样的是,这里能够指定取走某一类型的消息。
  4. 控制消息队列。使用函数msgctl,能够完成多项功能。
#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);

6.4 线程相关API

pthread线程库,遵循POSIX规范,具备很好的可移植性。

一、线程建立和退出

  1. 建立线程。指定线程所要执行的函数,使用的函数是pthread_create。
  2. 调用相关线程函数。在线程建立以后,就开始运行相关的线程函数。
  3. 线程退出。在线程调用函数运行完以后,该线程也就自动结束了。另外一种退出线程的方法是使用函数pthread_exit,这是线程的主动行为。
  4. 线程资源回收。因为一个进程中的多个线程是共享数据段的,所以一般在线程退出以后,退出线程所占用的资源并不会随着线程的终止而获得释放。pthread_join函数,pthread_join能够拥有将当前主线程挂起,等待子线程的结束。调用它的线程将一直阻塞直到指定的线程结束为止。
#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中线程同步的方法主要有互斥锁和信号量。

  • 互斥锁初始化:pthread_mutex_init
  • 互斥锁上锁:pthread_mutex_lock
  • 互斥锁判断上锁:pthread_mutex_trylocks
  • 互斥锁解锁:pthread_mutex_unlock
  • 删除互斥锁:pthread_mutex_destroy
#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的无名信号量,主要用于线程间的互斥同步。

  • sem_init用于建立一个信号量,并能初始化它的值。
  • sem_wait 和 sem_trywait 至关于P操做,他们都能将信号量的值减一,两种区别在于若信号量等于0时,sem_wait将会阻塞线程,而sem_trywait则会当即返回。
  • sem_post 至关于V操做,它将信号量的值加一同时发出信号唤醒等待的线程。
  • sem_getvalue用于获得信号量的值
  • sem_destroy用于删除信号量。
#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
相关文章
相关标签/搜索