cha38.编写安全的特权程序

38.1

用一个普通的非特权用户登录系统,创建一个可执行文件(或复制一个既有文件如/bin/sleep),然后启用该文件的set-user-ID权限位(chmod u+s)。尝试修改这个文件(如cat >>fle)。当使用(ls -l)时文件的权限会发生什么情况呢?为何会发生这种情况?

现象:saved-user-id不见了
原因:通过下面的例子,猜测open时会清除set-usr-id标志位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//
// Created by root on 8/1/23.
//

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

static int exitcode = 0;

#define ERR(str...) do { fprintf(stderr, "%s:%d\n%s:", __FILE__, __LINE__, strerror(errno)); fprintf(stderr, str); fprintf(stderr, "\n"); exit(exitcode++); } while(0)

int main(int argc, char **argv) {
if(argc < 3) {
ERR("Usage: %s src dst [content..]", argv[0]);
}
char *file1 = argv[1];
char *file2 = argv[2];
int fd1 = open(file1, O_RDONLY);
if(fd1 == -1) {
ERR("file to open %s", file1);
}
struct stat stat1;
if(fstat(fd1, &stat1) == -1) {
ERR("fstat(fd1, &stat1)");
}
fprintf(stderr, "F_GETFL:%o\n", stat1.st_mode);
int fd2 = open(file2, O_WRONLY | O_CREAT, stat1.st_mode);
if(fd2 == -1) {
ERR("fail to open %s", file2);
}

char buffer[4096] = {0};
ssize_t readsize = 0;
while ((readsize = read(fd1, buffer, 4096)) > 0) {
ssize_t writesize = write(fd2, buffer, readsize);
if(writesize != readsize) {
ERR("fail to write %s", file2);
}
}
if(readsize < 0) {
ERR("fail to read %s", file1);
}
for(int i = 3; i < argc; i++) {
size_t len = strlen(argv[i]);
if(write(fd2, argv[i], len) != len) {
ERR("fail to write %s", file2);
}
}
return 0;
}

38.2

编写一个与 sudo(8)程序类似的 set-user-ID-root 程序。这个程序应该像下面这样接收命令行选项和参数:

阅读更多

cha37.DAEMON

37.1

编写一个使用syslog(3)的程序(与logger(1)类似)来将任意的消息写入到系统日志
文件中。程序应该接收包含如记录到日志中的消息的命令行参数,同时应该允许指
定消息的level。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//
// Created by root on 7/24/23.
//

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <sys/stat.h>
#include <limits.h>
#include <sys/resource.h>
#include <syslog.h>

volatile int init = 0;

void restart(int sig) {
if(init) {
closelog();
printf("restart\n");
}
openlog("meow", LOG_CONS|LOG_NDELAY|LOG_PID, LOG_USER);
init = 1;
}

#define min(x, y) ((x) < (y) ? (x) : (y))

#define CHECK_WITH_RET(x, ret, format, ...) \
do { \
if(!(x)) { \
fprintf(stderr, format, __VA_ARGS__); \
ret \
} \
} while(0)

#define CHECK(x) CHECK_WITH_RET(x, return -1;, "%s:%d\nerror: %s\nunmet condition:\"%s\"\n", __FILE__, __LINE__,strerror(errno), #x)
#define CONDITION(x) CHECK_WITH_RET(x, exit(2);, "%s:%d\nunmet condition:\"%s\"\n", __FILE__, __LINE__, #x)
#define CHECKMAIN(x) CHECK_WITH_RET(x, exit(1);, "%s:%d\nerror: %s\nunmet condition:\"%s\"\n", __FILE__, __LINE__,strerror(errno), #x)

int makeDaemon() {
pid_t pid;
switch ((pid = fork())) {
case -1:
CHECK(0);
case 0:
break;
default:
printf("pid:%ld\n", (long)pid);
_exit(0);
}
CHECK(setsid() != -1);
CHECK(umask(0) != -1);
CHECK(chroot("/") != -1);
int nullfd = open("/dev/null", O_RDWR);
CHECK(nullfd != -1);
CHECK(dup2(nullfd, STDIN_FILENO) != -1);
CHECK(dup2(nullfd, STDOUT_FILENO) != -1);
CHECK(dup2(nullfd, STDERR_FILENO) != -1);
CHECK(close(nullfd) != -1);
long fdmax = sysconf(_SC_OPEN_MAX);
struct rlimit rlimit;
CHECK(getrlimit(RLIMIT_NOFILE, &rlimit) != -1);
for(int fdi = 0; fdi < min(fdmax, rlimit.rlim_cur); fdi++) {
close(fdi);
}
CHECK(signal(SIGHUP, restart) != SIG_ERR);
return 0;
}

struct logmesg {
int level;
char *str;
};

int main(int argc, char **argv) {
printf("pid:%ld\n", (long)getpid());
CHECKMAIN(makeDaemon() != -1);
printf("pid:%ld\n", (long)getpid());
restart(SIGHUP);
CHECKMAIN(signal(SIGHUP, restart) != SIG_ERR);
CONDITION(argc >= 3);
struct logmesg *data = malloc(sizeof(struct logmesg));
data->level = atoi(argv[1]);
CONDITION(data->level >= 0 && data->level <= 7);
int strlen = 0;
for(char **argvi = &argv[2]; *argvi; argvi++) {
strlen += snprintf(NULL, 0, "%s %s", (char *)NULL, *argvi);
}
char *str = malloc(strlen + 1);

for(char **argvi = &argv[2]; *argvi; argvi++) {
strlen += sprintf(str, "%s %s",str, *argvi);
}
data->str = str+1;
syslog(data->level, "%s", data->str);
printf("syslog: %s\n", data->str);
closelog();
}

不及丢为什么,syslog.conf中配置的东西打印不出来

2023年8月23日更新

wsl下syslog确实不行,换一个环境,安装syslog后,重新允许,可以正常打印log

阅读更多

cha36.进程资源

36.1

编写一个程序使用getrusage0 RUSAGE CHILDREN标记获取wait调用所等待的
子进程相关的信息。(让程序创建一个子进程并使子进程消耗一些 CPU 时间,接着
让父进程在调用wait0前后都调用getrusage0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
//
// Created by root on 7/18/23.
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/times.h>
#include <unistd.h>
#include <limits.h>
#include <sys/resource.h>

#define CHECK(x) \
do { \
if(!(x)) { \
fprintf(stderr, "%s:%d\nerror: %s\n", __FILE__, __LINE__,strerror(errno)); \
exit(0); \
} \
} while(0)



#define rcputime(rusage, xtime) rusage.xtime.tv_sec + rusage.xtime.tv_usec/1000000.0
int main(int argc, char **argv) {
long tic = sysconf(_SC_CLK_TCK);
long tottm = 0;
for(int i = 1; i < argc; i++) {
char *end = NULL;
long sec = strtol(argv[i], &end, 10);
tottm += sec;
CHECK(end != argv[i] && errno == 0);
if(!fork()) {
struct tms tms, start;
CHECK(times(&start) != (clock_t) -1);
CHECK(times(&tms) != (clock_t) -1);
for(; (tms.tms_utime + tms.tms_stime)/tic - (start.tms_utime + start.tms_stime)/tic <= sec;) {
CHECK(times(&tms) != (clock_t) -1);
}
exit(0);
}
}
printf("total time:%ld\n", tottm);
struct rusage rusage;
CHECK(getrusage(RUSAGE_CHILDREN, &rusage) != -1);
printf("child use:\n\tcpu time:%.2lfs (user)\n\tcpu time:%.2lfs (system)\ntotal:%.2lfs\n", rcputime(rusage, ru_utime), rcputime(rusage, ru_stime), rcputime(rusage, ru_utime) + rcputime(rusage, ru_stime));
for(;;)
if(waitpid(-1, NULL, 0) == -1 && errno == 10) break;
CHECK(getrusage(RUSAGE_CHILDREN, &rusage) != -1);
printf(""
"child use:\n"
"\tcpu time:\n"
"\t\tcpu time:%.2lfs (user)\n"
"\t\tcpu time:%.2lfs (system)\n"
"\ttotal:%.2lfs\n"
"\tMax resident set:%ldKB\n"
"\tIntegral shared text mem:%ldkB/s\n"
"\tIntegral shared data mem:%ldkB/s\n"
"\tIntegral shared stack mem:%ldkB/s\n"
"\tsoft page fault:%ld\n"
"\thard page fault:%ld\n"
"\tswaps out of physical mem:%ld\n"
"\tfile input block:%ld\n"
"\tfile output block:%ld\n"
"\tIPC msg send:%ld\n"
"\tIPC msg recv:%ld\n"
"\tsignal recv:%ld\n"
"\tvoluntary context switch:%ld\n"
"\tinvoluntary context switch:%ld\n",
rcputime(rusage, ru_utime),
rcputime(rusage, ru_stime),
rcputime(rusage, ru_utime) + rcputime(rusage, ru_stime),
rusage.ru_maxrss,
rusage.ru_ixrss,
rusage.ru_idrss,
rusage.ru_isrss,
rusage.ru_minflt,
rusage.ru_majflt,
rusage.ru_nswap,
rusage.ru_inblock,
rusage.ru_oublock,
rusage.ru_msgsnd,
rusage.ru_msgrcv,
rusage.ru_nsignals,
rusage.ru_nvcsw,
rusage.ru_nivcsw);
}

36.2

编写一个程序来执行一个命令,接着显示其当前的资源使用。这个程序与 time(1)
命令的功能类似,因此可以像下面这样使用这个程序:

1
$ ./rusage command arg...
阅读更多

cha35.进程优先级和调度

读书笔记

nice

nice值形象的说是一个进程的友好(nice)程度,越nice(nice值数值越大的)的进程越礼让,多个任务争用cpu的时候越礼让。(本书中高nice值表示nice值数值更低,更不nice

  • 取值范围为(-20(不友好,高优先级)-19(友好,低优先级))
  • fork、exec时继承nice值

RLIMIT_NICE

资源限制,特权进程最高可以将nice值提升到20-RLIMIT_NICE

阅读更多

cha34.进程组、会话和作业控制

读书笔记

34.1

假设一个父进程执行了下面的步骤。
这个应用程序设计可能会碰到什么问题?考虑 shell 管道。
如何避免此类问题的发生?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

void handler(int sig){
printf("%d, received %d(%s)\n", getpid(), sig, strsignal(sig));
}

int main() {
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGUSR1);
signal(SIGUSR1, handler);
for(int i = 0; i < 10; i++) {
pid_t pid;
if((pid = fork()) == 0) {
pause();
return 0;
} else {
printf("child-%d: %d\n", i, pid);
}
}
signal(SIGUSR1, SIG_IGN);
killpg(getpgrp(), SIGUSR1);
waitpid(-1, NULL, 0);
}
  • 假设不发送SIGUSER时
    • 当不使管道时,可以看到有小于等于10个子进程显示收到SIGUSER1
    • 如果使用管道,会发现child-%d: %d被重复输出了多次,这是由于stdout被重定向后变成了全缓冲,fork的子进程退出后fflush()时会将fork前未flush的数据打印出来。
  • 假设执行该命令./practice34.1 | cat,输出会是User defined signal 1,即cat与程序同属一个进程组,cat也会收到该信号,导致无法正常执行(非预期内)

避免

阅读更多

cha33.线程:更多细节

33.1

编写程序以便证明:作为函数sigpending()的返回值,同一个进程中的的不同线程可以拥有不同的 pending信号。可以使用函数pthread_kill(分别发送不同的信号给阻塞这些信号的两个不同的线程,接着调用sigpending()方法并显示这些pending信号的信息。(可能会发现程序清单20-4中函数的作用。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
//
// Created by root on 7/2/23.
//
#define _GNU_SOURCE
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

#define CHECK(x) do { if(!(x)) { fprintf(stderr, "ERROR: %s\nfile=%s, line=%d\n", strerror(errno), __FILE__, __LINE__); exit(1); } } while(0)
#define THREAD_CNT 5
pthread_mutex_t sig_mutex;
pthread_cond_t sig_cond;
int thread_ready_count;

void handler(int sig, siginfo_t *info, void *context) {
printf("recv sig = %d: %s, from = %d\n", sig, strsignal(sig), info->si_value.sival_int);
}

pthread_t tid2pthread[THREAD_CNT];

int tid2sig(int tid) {
return SIGRTMAX - 1 - tid;
}

void *fun_thread(void *arg) {
int tid = *(int *)arg;
free(arg);
sigset_t set;
CHECK(sigemptyset(&set) == 0);
CHECK(sigaddset(&set, tid2sig(tid)) == 0);
CHECK(pthread_sigmask(SIG_SETMASK, &set, NULL) == 0);
CHECK(sigaction(tid2sig(tid), &(struct sigaction) {
.sa_sigaction =handler,
.sa_flags = SA_SIGINFO,
.sa_mask = 0
}, NULL) == 0);
printf("Thread-%d, sigaction %d\n", tid, tid2sig(tid));
CHECK(pthread_mutex_lock(&sig_mutex) == 0);
thread_ready_count++;
tid2pthread[tid] = pthread_self();
CHECK(pthread_mutex_unlock(&sig_mutex) == 0);
CHECK(pthread_cond_broadcast(&sig_cond) == 0);

CHECK(pthread_mutex_lock(&sig_mutex) == 0);
while(thread_ready_count < THREAD_CNT){
CHECK(pthread_cond_wait(&sig_cond, &sig_mutex) == 0);
}
CHECK(pthread_mutex_unlock(&sig_mutex) == 0);
int sig = tid2sig((tid + 1) % THREAD_CNT);
printf("Thread-%d send %d(%s) to %d\n", tid, sig, strsignal(sig), (tid + 1) % THREAD_CNT);
CHECK(pthread_sigqueue(tid2pthread[(tid + 1) % THREAD_CNT], sig, (union sigval) {
.sival_int=tid
}) == 0);
do {
printf("Thread-%d sig not pending %d\n", tid, tid2sig(tid));
sleep(1);
CHECK(sigemptyset(&set) == 0);
CHECK(sigpending(&set) == 0);
} while(sigismember(&set, tid2sig(tid)) == 0);
printf("Thread-%d sig pending %d\n", tid, tid2sig(tid));
CHECK(sigemptyset(&set) == 0);
CHECK(pthread_sigmask(SIG_SETMASK, &set, NULL) == 0);
CHECK(sigpending(&set) == 0);
sig = -1;
while(sig != tid2sig(tid)) CHECK(sigwait(&set, &sig) == 0);
printf("Thread-%d exit\n", tid);
return NULL;
}

int main(int argc, char *argv[]) {
CHECK(pthread_mutex_init(&sig_mutex, NULL) == 0);
CHECK(pthread_cond_init(&sig_cond, NULL) == 0);
thread_ready_count = 0;
for(int i = 0; i < THREAD_CNT; i++) {
pthread_t thread;
int *data = malloc(sizeof(int));
*data = i;
CHECK(pthread_create(&thread, NULL, fun_thread, data) == 0);
}
pthread_exit(NULL);
}

33.2

假设一个线程使用fork()创建了一个子进程。当子进程终止时,可以保证由此产生的SIGCHLD信号一定会发送给调用fork()的线程吗(可以用进程中的其他线程做对比)?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//
// Created by root on 7/2/23.
//
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>

#define CHECK(x) do { if(!(x)) { fprintf(stderr, "ERROR: %s\nfile=%s, line=%d\n", strerror(errno), __FILE__, __LINE__); exit(1); } } while(0)

sigset_t set;

void handler(int sig, siginfo_t *info, void *context) {
printf("recv sig = %d: %s, my tid is %ld\n", sig, strsignal(sig), pthread_self());
}

void *fn(void *arg) {
CHECK(sigaction(SIGCHLD, &(struct sigaction) {
.sa_mask = 0,
.sa_flags = SA_SIGINFO,
.sa_sigaction=handler
}, NULL) == 0);
if(*(int *)arg) {
printf("I am %ld, I fork\n", pthread_self());
if(!fork()) {
exit(0);
}
// wait(NULL);
} else {
printf("I am %ld, I sleep\n", pthread_self());
sleep(10);
}
free(arg);
return NULL;
}

int main() {
CHECK(sigemptyset(&set) == 0);
CHECK(sigaddset(&set, SIGCHLD) == 0);
CHECK(sigaction(SIGCHLD, &(struct sigaction) {
.sa_mask = 0,
.sa_flags = SA_SIGINFO,
.sa_sigaction=handler
}, NULL) == 0);
pthread_t tid;
int ok = 0;
CHECK(pthread_create(&tid, NULL, fn, memcpy(malloc(sizeof(int)), &ok, sizeof(int))) == 0);
ok = 1;
CHECK(pthread_create(&tid, NULL, fn, memcpy(malloc(sizeof(int)), &ok, sizeof(int))) == 0);
pthread_exit(NULL);
}
阅读更多

cha32.线程取消

读书笔记

带参宏

从作用域的角度上看,带参宏和函数的区别:

  • 函数有新的函数栈,而带参宏没有,因而带参宏在不同作用域调用时,会受到作用域的影响。如在宏A中的定义的变量,也会影响后续。
  • 如果一对作用相反的带参宏,如本节的pthread_cleanup_pushpthread_cleanup_pop,很多实现都是使用带参宏,那么这两个宏调用时必须属于同一个代码块

?,保证push的作用域包含pop不就好了吗

线程取消

阅读更多

cha31.线程安全和每线程存储

读书笔记

  • pthread提供了一种所有线程只执行一次(用于所有线程只初始化一次)的方法pthread_once(pthread_once_t*, void (*)(void));
  • pthread提供了每线程存储,即每个线程有独立与其他线程的存储,使用k-v存储
    • int pthread_key_create(pthread_key_t *, void (*)(void *));每个线程都存储一份,第一个参数为一个全局变量的指针,第二个参数为线程终止时自动调用的析构函数
    • int pthread_setspecific(pthread_key_t, const void *)指定key所对应的内存区域,进程终止时会将第二个参数送入析构函数
    • void * pthread_getspecific(pthread_key_t)获取key所对应的内存区域

31.1

实现pthread_once

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//
// Created by root on 6/29/23.
//

#include <pthread.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>

void init() {
printf("init\n");
}
typedef struct {
pthread_mutex_t mutex;
bool call;
} _pthread_once_t;
_pthread_once_t once = {
.mutex=PTHREAD_MUTEX_DEFAULT,
.call=false
};
int _pthread_once(_pthread_once_t *once_ctrl, void (*init)(void)) {
bool ok;
if(pthread_mutex_lock(&once_ctrl->mutex) == 0) {
ok = !once_ctrl->call;
once_ctrl->call = true;
pthread_mutex_unlock(&once_ctrl->mutex);
} else {
return -1;
}
if(ok) {
init();
}
return 0;
}

void *fun(void *arg) {
printf("Thread-%d start\n", *(int*)arg);
if(_pthread_once(&once, init) == 0){
printf("Thread-%d _pthread_once Success\n", *(int*)arg);
}
free(arg);
return NULL;
}

int main() {
pthread_t t;
for(int i = 1; i <= 100; i++)
pthread_create(&t, NULL, fun, memcpy(malloc(sizeof(int)), &i, sizeof(int)));
pthread_exit(NULL);
}

31.2

阅读更多

cha30.线程:线程同步

读书笔记

条件变量

一般搭配一个条件和一个互斥量使用

经典的使用方式如下

  • 对于需要在某某条件下运行的线程,先对mutex加锁,以读取condition
    • 若满足,则在预期状态下执行后续操作,结束后对mutex解锁
    • 若不满足,则pthread_cond_wait
      • 此时该函数会先解锁mutex(允许其他进程获取mutex以修改状态)
      • 陷入阻塞,直到其他线程调用pthread_cond_signalpthread_cond_broadcast唤醒
      • 唤醒后,获取mutex锁,以检查条件是否满足,满足则执行后续,不满足继续调用wait阻塞(故此处需要用while
  • 对于可以改变某某条件的线程
    • 获取mutex
    • 改变condition
    • signal/broadcast
    • unlock(unlock与上一步顺序可调换)
1
2
3
4
5
6
7
8
9
10
11
12
// one thread
pthread_mutex_lock(&mutex);
while(!condition)
pthread_cond_wait(&cond, &mutex);
/* condition matched, execute task */
pthread_mutex_unlock(&mutex);

// other thread
pthread_mutex_lock(&mutex);
condition = xxxx; // change condition
pthread_cond_signal(&cond); // or pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex); // 以上两行顺序任意,
阅读更多

cha29.线程:介绍

29.1

若一线程执行了如下代码,可能会产生什么结果?
pthread_join(pthread_self(),NULL);
在 Linux上编写一个程序,观察一下实际会发生什么情况。假设代码中有一变量 tid,其中包含了某个线程ID,在自身发起pthread_join(tid, NULL)调用时,要避免造成与上述语句相同的后果,该线程应采取何种措施?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#define CHECK(x) do { \
if(!(x)) { \
fwritef(STDERR_FILENO, "error: %s\n", strerror(errno)); \
pthread_exit(NULL); \
} \
} while(0)
void *thread1_fun(void * args) {
sleep(1);
return NULL;
}

void *thread_fun(void * args) {
void *ret;
printf("thread: before,join-pthread_self()\n");
CHECK(pthread_join(pthread_self(), &ret) == 0);
printf("thread: after,join-pthread_self()\n");
return NULL;
}

int main() {
pthread_t thread;
CHECK(pthread_create(&thread, NULL, thread_fun, NULL) == 0);
CHECK(pthread_detach(thread) == 0);
printf("main: pthread_created.\n");
sleep(10);
return 0;
}

结果

pthread_join返回值非0,出现错误,errno表示SUCCESS

为啥是success呢,errno不是一个线程一份吗,还是这个函数不算系统调用??可能是他调用了waitpid,但是出错位置不在waitpid

阅读更多