前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux内核编程--进程控制,线程控制,锁机制

Linux内核编程--进程控制,线程控制,锁机制

作者头像
Coder-ZZ
发布2022-05-09 21:43:42
1.1K0
发布2022-05-09 21:43:42
举报
文章被收录于专栏:C/C++进阶专栏C/C++进阶专栏

一,进程控制

进程标识

每个进程都有一个非负整型表示的唯一进程ID。进程ID是可复用的,当一个进程终止后,其进程ID也会被其他进程使用。

除了进程ID,每个进程还有一些其他标识符可以获取。

代码语言:javascript
复制
#include <unistd.h>
pid_t getpid(void);    --返回调用进程的进程ID
pid_t getppid(void);   --返回调用进程的父进程ID
uid_t getuid(void);    --返回调用进程的实际用户ID
uid_t geteuid(void);   --返回调用进程的有效用户ID
gid_t getgid(void);    --返回调用进程的实际组ID
gid_t getegid(void);   --返回调用进程的有效组ID

*上面这些get类函数,也有与之对应的set函数,比如 setuid()、setgid()等。

控制进程的常见函数

1.创建新进程(子进程)--fork/vfork

代码语言:javascript
复制
#include <unistd.h>
pid_t fork(void);

返回:若成功,子进程返回0,父进程返回子进程ID。若失败,返回-1

fork函数被调用一次,但是有两次返回:子进程返回0,父进程返回子进程的进程ID。

子进程可以调用getppid()函数获得父进程的进程ID。

由于子进程是父进程的副本,所以子进程可以获得父进程的数据空间,堆和栈的副本。

代码语言:javascript
复制
#include <unistd.h>
pid_t vfork(void);

返回:若成功,子进程返回0,父进程返回子进程ID。若失败,返回-1。

vfork与fork的区别是,vfork并不将父进程的地址空间完全复制到子进程中。

vfork创建子进程后,子进程先运行,子进程调用exec或exit后,父进程再开始被调度运行。

2.挂起进程--wait/waitpid/waitid

代码语言:javascript
复制
#include <sys/wait.h>
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);

--pid参数取值:
pid==-1,等待任一子进程,此时的waitpid()与wait()等效
pid>0,等待“进程ID==pid”的子进程
pid==0,等待“组ID==调用进程组ID”的子进程
pid<-1,等待“组ID==pid的绝对值”的子进程

返回:若成功,返回进程ID。若失败,返回0或-1。

父进程调用wait/waitpid后会被挂起,直到子进程终止运行并将退出状态返回给父进程。

父进程也可以通过wait/waitpid来检验子进程是否已经运行结束。

waitpid与wait的区别:

1.wait返回任一终止子进程的状态,waitpid可以指定一个特定的进程进行等待。

2.waitpid提供了wait的非阻塞版本,可以不阻塞地获取一个子进程的状态。

3.waitpid可以通过WUNTRACED和WCONTINUED选项支持作业控制。

3.终止进程--exit

代码语言:javascript
复制
#include <stdlib.h>
void exit(int status)

--status: 进程退出码,主要返回给父进程

无返回

4.在进程中启动另一个进程并退出--exec函数族

代码语言:javascript
复制
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

返回:若成功,不做返回。若失败,返回-1。

第一个参数,file和path的区别: 当参数为path时,传入的数据为路径名;当参数为file时,传入的数据为可执行文件名。

l:表示通过逐个列举的方式传入参数(execl、execle、execlp)

v:表示通过构造指针数组的方式传入参数(execv、execve、execvp)

e:可传递新进程环境变量(execle、execve)

p:可执行文件查找方式为文件名(execlp、execvp)

exec可以根据指定的文件名或目录名找到可执行文件,并取代原进程的数据段、代码段和堆栈。exec执行完以后,原进程的内容除了进程ID外,其他全部被新的进程替换了

5.进程中运行shell指令--system

代码语言:javascript
复制
#include <stdlib.h>

int system(const char *cmdstring)

system调用fork产生子进程, 由子进程来执行shell脚本cmdstring, 此命令执行完后返回到原调用的父进程。

system函数在系统中的实现:system函数执行时,会调用fork、execve、waitpid等函数。

代码语言:javascript
复制
int system(const char * cmdstring)
{
    pid_t pid;
    int status;
    if(cmdstring == NULL)
    {
        return (1); 
    }
    if((pid = fork())<0)
    {
        status = -1; 
    }
    else if(pid == 0)
    {
        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
        _exit(127); //exec只在执行失败时才返回原有的进程  
    }
    else
    {
        while(waitpid(pid, &status, 0) < 0)
        {
            if(errno != EINTR)
            {
                status = -1;
                break;
            }
        }
    }
    return status; //如果waitpid成功,则返回子进程的退出状态
}

如果fork()、exec()和waitpid()都执行成功,system()返回shell的终止状态。

代码样例:

代码语言:javascript
复制
#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>

using namespace std;

#define NUM_THREADS 5

void *wait(void *t) {
   int i;
   long tid;
   tid = (long)t;

   sleep(1);
   cout << "Sleeping in thread " << endl;
   cout << "Thread with id : " << tid << "  ...exiting " << endl;
   pthread_exit(NULL);
}

int main () {
   int rc;
   int i;
   pthread_t threads[NUM_THREADS];
   pthread_attr_t attr;
   void *status;
   // Initialize and set thread joinable
   pthread_attr_init(&attr);
   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

   for( i = 0; i < NUM_THREADS; i++ ) {
      cout << "main() : creating thread, " << i << endl;
      rc = pthread_create(&threads[i], &attr, wait, (void *)i );
      if (rc) {
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }
   // free attribute and wait for the other threads
   pthread_attr_destroy(&attr);
   for( i = 0; i < NUM_THREADS; i++ ) {
      rc = pthread_join(threads[i], &status);
      if (rc) {
         cout << "Error:unable to join," << rc << endl;
         exit(-1);
      }
      cout << "Main: completed thread id :" << i ;
      cout << "  exiting with status :" << status << endl;
   }

   cout << "Main: program exiting." << endl;
   pthread_exit(NULL);
}

二,线程控制:

同一个进程的所有线程都可以访问该进程的组成部件,如文件描述符,内存和进程中的代码。

正由于同一进程内的所有线程共享相同的内存,所以线程之间同步信息很方便,不需要使用IPC机制。

线程控制的编码实现:

POSIX版的线程模块--pthread

存放线程属性的结构体--pthread_attr_t

代码语言:javascript
复制
typedef struct
{
       __detachstate;   --两种状态:detachable和joinable
       __schedpolicy;   --线程的调度策略
       __schedparam;    --线程的调度优先级
       __inheritsched;  --线程的继承策略
       __scope;         --线程的作用域
       ......
} pthread_attr_t;

1.线程ID的获取和比较--pthread_self/pthread_equal

正如每个进程都有一个进程ID,每个线程也有对应的线程ID--线程标识。进程ID的数据类型是pid_t,线程ID的数据类型是pthread_t。

比较两个线程ID是否相等的函数--pthread_equal:

代码语言:javascript
复制
#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2)

返回:若相等,返回非0数值。否则,返回0。

获得线程自身的线程ID的函数--pthread_self:

代码语言:javascript
复制
#include <pthread.h>
pthread_t pthread_self(void)

返回:调用线程的线程ID

2.创建线程--pthread_create

代码语言:javascript
复制
#include <pthread.h>
int pthread_create(pthread_t *tid,
                   const pthread_attr_t *attr,
                   void * (*start_routine)(void *),
                   void *arg);

void * (*start_routine)--指向要线程化的函数的指针

返回:若成功,返回0。否则,返回错误编号

进程中的主线程由exec执行后创建,其余的线程由pthread_create创建。

如果新线程创建成功,其线程ID可以通过tid指针返回。

pthread_attr_t类型的attr指针,指向了线程的属性,例如线程优先级,栈的大小等等。如果需要使用默认属性,可以把attr参数置为空指针。

当线程内封装了一个函数func时,arg参数用来给该函数传参。

3.线程终止--pthread_exit/pthread_join/pthread_cancel

如果进程中的任意线程调用了exit、_Exit或者_exit,那么整个进程就会终止。

有三种方式可以让单个线程退出的时候不终止整个进程:

(1)线程被启动线程的函数返回时,返回值是线程的退出码(pthread_create的第三个参数)

(2)线程被同一进程中的其他线程取消(pthread_cancel)

(3)线程调用pthread_exit

代码语言:javascript
复制
#include <pthread.h>
void pthread_exit(void *status);

不向调用者返回

status是void*类型的指针,这种指针可以指向任何类型的值, status所指向的数据将作为线程退出时的返回值。

pthread_exit相当于执行了“线程退出+return返回"。

代码语言:javascript
复制
#include <pthread.h>
int pthread_join(pthread_t tid, void **status);

返回:若成功,返回0。否则,返回错误编号

类似于wait/waitpid将进程挂起,以等待它的子进程结束。线程场景中可以使用pthread_join将一个线程挂起以等待另一个线程结束并获得它的退出状态。

被等待的线程的退出状态将存放到status指针指向的位置。

只有线程未分离时(pthread_detach),才可以使用pthread_join从另一个线程获得线程id和返回值。

代码语言:javascript
复制
#include <pthread.h>
int pthread_cancel(pthread_t tid);

返回:若成功,返回0。否则,返回错误编号

线程成功接收到其他线程发来的Cancel信号,不代表它一定会终止运行。

如果线程接收Cancel信号后终止运行,相当于调用了“pthread_exit(PTHREAD_CANCELED);”

4.分离线程--pthread_detach

代码语言:javascript
复制
#include <pthread.h>
int pthread_detach(pthread_t tid);

返回:若成功,返回0。否则,返回错误编号

该函数主要用来清理线程终止后残留的资源占用。

被分离后的线程,其终止时,线程的存储资源会被立即收回。

该函数会将线程从主线程的分支中分离出去,当线程结束后,它的退出状态不由其它线程通过pthread_join获取,而是由该线程主动释放。

代码样例:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>

#ifndef NUM_THREADS
#define NUM_THREADS 8
#endif

void *printHello(void *threadid) {
    long tid;
    tid = (long)threadid;
    printf("Hello from thread %ld, pthread ID - %lu\n", tid, pthread_self());
    return NULL;
}

int main(int argc, char const *argv[]) {
    pthread_t threads[NUM_THREADS];
    int rc;
    long t;

    for (t = 0; t < NUM_THREADS; t++) {
        rc = pthread_create(&threads[t], NULL, printHello, (void *)t);
        if (rc) {
            printf("ERORR; return code from pthread_create() is %d\n", rc);
            exit(EXIT_FAILURE);
        }
    }

    int ret;
    for (t = 0; t < NUM_THREADS; t++) {
        void *retval;
        ret = pthread_join(threads[t], &retval);
        if (retval == PTHREAD_CANCELED)
            printf("The thread was canceled - ");
        else
            printf("Returned value %d - ", (int)retval);
    }
    pthread_exit(NULL);
}

三,锁机制

1.互斥锁

互斥锁可以确保同一时间只有一个线程能访问指定数据。

互斥锁的用法:在访问共享资源前对资源加锁,访问完成后对资源解锁。

一个线程对共享资源加互斥锁以后,其他试图对共享资源加互斥锁的线程就会阻塞,直到当前资源的锁被释放。

有了互斥锁以后,每次只有一个线程可以访问共享资源。

数据类型:

pthread_mutex_t(使用之前,必须初始化)

常用函数:

初始化互斥锁--pthread_mutex_init

销毁互斥锁--pthread_mutex_destroy

加上互斥锁--pthread_mutex_lock

解除互斥锁--pthread_mutex_unlock

有时间限制的互斥锁

--pthread_mutex_timedlock

加上互斥锁(非阻塞模式)

--pthread_mutex_trylock

代码语言:javascript
复制
#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *mutex,
                       const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
代码语言:javascript
复制
#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex)

*带有"try"关键词的函数,表示一次尝试不成功就报错返回然后再试一次,代表着非阻塞模式。

2.读写锁

读写锁有三种状态:

1.读模式下的加锁状态

2.写模式下的加锁状态

3.不加锁状态

写模式一次只能允许一个线程加锁,但是读模式可以允许多个线程同时加锁。

写模式被加锁后,其他试图给写模式或读模式加锁的线程都会被阻塞。

读模式被加锁后,其他试图给写模式加锁的线程会被阻塞,给读模式加锁的线程会得到访问权。

读模式被加锁后,这时有一个线程试图对写模式加锁,其后到来的读模式加锁线程会被阻塞,这样可以避免读模式锁被长期占用。

当对一个共享资源的读次数远大于写次数时,可以考虑使用读写锁。

写模式和互斥锁很像,一次只有一个线程可以访问。

数据类型:

pthread_rwlock_t

常用函数:

初始化读写锁--pthread_rwlock_init

销毁读写锁--pthread_rwlock_destroy

读模式加锁--pthread_rwlock_rdlock

写模式加锁--pthread_rwlock_wrlock

解锁--pthread_rwlock_unlock

有时间限制的读锁--pthread_rwlock_timedrdlock

有时间限制的写锁--pthread_rwlock_timedwrlock

代码语言:javascript
复制
#include <pthread.h>    

int pthread_rwlock_init(pthread_rwlock_t *rwlock,
                        const pthread_rwlockattr_t *attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER;
代码语言:javascript
复制
#include <pthread.h>

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

3.自旋锁

自旋锁和互斥锁的区别是,自旋锁机制在获得锁之前会一直忙等,而不会让线程因为阻塞而休眠。

如果一个共享资源被加锁的时间很短,更推荐使用自旋锁。

数据类型:

pthread_spinlock_t

常用函数:

初始化自旋锁--pthread_spin_init

销毁自旋锁--pthread_spin_destroy

自旋锁加锁--pthread_spin_lock

pthread_spin_trylock

自旋锁解锁--pthread_spin_unlock

代码语言:javascript
复制
#include <pthread.h>

int pthread_spin_init(pthread_spinlock_t *lock int pshared);
int pthread_spin_destroy(pthread_spinlock_t *lock);       
代码语言:javascript
复制
#include <pthread.h>

int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);

4.条件变量

条件变量搭配互斥锁使用,互斥锁提供互斥机制,条件变量提供信号机制。

条件变量被用来阻塞一个线程,当条件不满足时,线程解除互斥锁并等待条件成立。当条件成立的时候,线程重新给互斥锁加锁。

条件变量相当于给互斥锁加了if-else,if条件满足时才允许加锁。

数据类型:

pthread_cond_t

常用函数:

初始化条件变量--pthread_cond_init

销毁条件变量--pthread_cond_destroy

用条件变量阻塞当前线程,直到条件成立

--pthread_cond_wait

pthread_cond_timedwait

解除线程的阻塞状态

--pthread_cond_signal

pthread_cond_broadcast

(此处的signal,不是操作系统中的“SIGXXX"信号)

代码语言:javascript
复制
#include <pthread.h>

int pthread_cond_init(pthread_cond_t *cond,
                      const pthread_condattr_t *attr);
int pthread_cond_destroy(pthread_cond_t *cond);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
代码语言:javascript
复制
#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *cond,
                      pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond,
                           pthread_mutex_t *mutex,
                           const struct timespec *abstime);
代码语言:javascript
复制
#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

代码样例:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUM_THREADS 5

typedef struct _thread_data_t {
    int tid;
    double stuff;
} thread_data_t;
/* shared data between threads */
double shared_x;
pthread_mutex_t lock_x;
void *thr_func(void *arg) {
    thread_data_t *data = (thread_data_t *)arg;

    printf("hello from thr_func, thread id: %d\n", data->tid);
    /* get mutex before modifying and printing shared_x */
    pthread_mutex_lock(&lock_x);
      shared_x += data->stuff;
      printf("x = %f\n", shared_x);
    pthread_mutex_unlock(&lock_x);

    pthread_exit(NULL);
}
int main(int argc, char **argv) {
    pthread_t thr[NUM_THREADS];
    int i, rc;
    /* create a thread_data_t argument array */
    thread_data_t thr_data[NUM_THREADS];

    /* initialize shared data */
    shared_x = 0;

    /* initialize pthread mutex protecting "shared_x" */
    pthread_mutex_init(&lock_x, NULL);

    /* create threads */
    for (i = 0; i < NUM_THREADS; ++i) {
      thr_data[i].tid = i;
      thr_data[i].stuff = (i + 1) * NUM_THREADS;
      if ((rc = pthread_create(&thr[i], NULL, thr_func, &thr_data[i]))) {
        fprintf(stderr, "error: pthread_create, rc: %d\n", rc);
        return EXIT_FAILURE;
      }
    }
    /* block until all threads complete */
    for (i = 0; i < NUM_THREADS; ++i) {
      pthread_join(thr[i], NULL);
    }

    return EXIT_SUCCESS;
}

*本次代码分享不提供运行结果,建议使用GDB工具进行调试分析,收获会更大

进程原语和线程原语的比较:

参考教程:

《UNIX环境高级编程 第3版》

《UNIX网络编程 卷1:套接字联网API 第3版》

《UNIX网络编程 卷2:进程间通信 第2版》

http://www.csc.villanova.edu/

https://randu.org/tutorials/threads/

https://www.tutorialspoint.com/cplusplus/cpp_multithreading.htm

https://www.delftstack.com/howto/c/pthread_join-return-value-in-c/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员与背包客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档